1 // This is CodeMirror (http://codemirror.net), a code editor
2 // implemented in JavaScript on top of the browser's DOM.
4 // You can find some technical background for some of the code below
5 // at http://marijnhaverbeke.nl/blog/#cm-internals .
8 if (typeof exports
== "object" && typeof module
== "object") // CommonJS
9 module
.exports
= mod();
10 else if (typeof define
== "function" && define
.amd
) // AMD
11 return define([], mod
);
12 else // Plain browser env
13 this.CodeMirror
= mod();
19 // Kludges for bugs and behavior differences that can't be feature
20 // detected are enabled based on userAgent etc sniffing.
22 var gecko
= /gecko\/\d/i.test(navigator
.userAgent
);
23 // ie_uptoN means Internet Explorer version N or lower
24 var ie_upto10
= /MSIE \d/.test(navigator
.userAgent
);
25 var ie_upto7
= ie_upto10
&& (document
.documentMode
== null || document
.documentMode
< 8);
26 var ie_upto8
= ie_upto10
&& (document
.documentMode
== null || document
.documentMode
< 9);
27 var ie_upto9
= ie_upto10
&& (document
.documentMode
== null || document
.documentMode
< 10);
28 var ie_11up
= /Trident\/([7-9]|\d{2,})\./.test(navigator
.userAgent
);
29 var ie
= ie_upto10
|| ie_11up
;
30 var webkit
= /WebKit\//.test(navigator
.userAgent
);
31 var qtwebkit
= webkit
&& /Qt\/\d+\.\d+/.test(navigator
.userAgent
);
32 var chrome
= /Chrome\//.test(navigator
.userAgent
);
33 var presto
= /Opera\//.test(navigator
.userAgent
);
34 var safari
= /Apple Computer/.test(navigator
.vendor
);
35 var khtml
= /KHTML\//.test(navigator
.userAgent
);
36 var mac_geLion
= /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator
.userAgent
);
37 var mac_geMountainLion
= /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator
.userAgent
);
38 var phantom
= /PhantomJS/.test(navigator
.userAgent
);
40 var ios
= /AppleWebKit/.test(navigator
.userAgent
) && /Mobile\/\w+/.test(navigator
.userAgent
);
41 // This is woefully incomplete. Suggestions for alternative methods welcome.
42 var mobile
= ios
|| /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator
.userAgent
);
43 var mac
= ios
|| /Mac/.test(navigator
.platform
);
44 var windows
= /win/i.test(navigator
.platform
);
46 var presto_version
= presto
&& navigator
.userAgent
.match(/Version\/(\d*\.\d*)/);
47 if (presto_version
) presto_version
= Number(presto_version
[1]);
48 if (presto_version
&& presto_version
>= 15) { presto
= false; webkit
= true; }
49 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
50 var flipCtrlCmd
= mac
&& (qtwebkit
|| presto
&& (presto_version
== null || presto_version
< 12.11));
51 var captureRightClick
= gecko
|| (ie
&& !ie_upto8
);
53 // Optimize some code when these features are not used.
54 var sawReadOnlySpans
= false, sawCollapsedSpans
= false;
58 // A CodeMirror instance represents an editor. This is the object
59 // that user code is usually dealing with.
61 function CodeMirror(place
, options
) {
62 if (!(this instanceof CodeMirror
)) return new CodeMirror(place
, options
);
64 this.options
= options
= options
|| {};
65 // Determine effective options based on given values and defaults.
66 for (var opt
in defaults
) if (!options
.hasOwnProperty(opt
))
67 options
[opt
] = defaults
[opt
];
68 setGuttersForLineNumbers(options
);
70 var doc
= options
.value
;
71 if (typeof doc
== "string") doc
= new Doc(doc
, options
.mode
);
74 var display
= this.display
= new Display(place
, doc
);
75 display
.wrapper
.CodeMirror
= this;
78 if (options
.lineWrapping
)
79 this.display
.wrapper
.className
+= " CodeMirror-wrap";
80 if (options
.autofocus
&& !mobile
) focusInput(this);
83 keyMaps
: [], // stores maps added by addKeyMap
84 overlays
: [], // highlighting overlays, as added by addOverlay
85 modeGen
: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
86 overwrite
: false, focused
: false,
87 suppressEdits
: false, // used to disable editing during key handlers when in readOnly mode
88 pasteIncoming
: false, cutIncoming
: false, // help recognize paste/cut edits in readInput
90 highlight
: new Delayed() // stores highlight worker timeout
93 // Override magic textarea content restore that IE sometimes does
94 // on our hidden textarea on reload
95 if (ie_upto10
) setTimeout(bind(resetInput
, this, true), 20);
97 registerEventHandlers(this);
100 runInOp(this, function() {
101 cm
.curOp
.forceUpdate
= true;
104 if ((options
.autofocus
&& !mobile
) || activeElt() == display
.input
)
105 setTimeout(bind(onFocus
, cm
), 20);
109 for (var opt
in optionHandlers
) if (optionHandlers
.hasOwnProperty(opt
))
110 optionHandlers
[opt
](cm
, options
[opt
], Init
);
111 for (var i
= 0; i
< initHooks
.length
; ++i
) initHooks
[i
](cm
);
115 // DISPLAY CONSTRUCTOR
117 // The display handles the DOM integration, both for input reading
118 // and content drawing. It holds references to DOM nodes and
119 // display-related state.
121 function Display(place
, doc
) {
124 // The semihidden textarea that is focused when the editor is
125 // focused, and receives input.
126 var input
= d
.input
= elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
127 // The textarea is kept positioned near the cursor to prevent the
128 // fact that it'll be scrolled into view on input from scrolling
129 // our fake cursor out of view. On webkit, when wrap=off, paste is
130 // very slow. So make the area wide instead.
131 if (webkit
) input
.style
.width
= "1000px";
132 else input
.setAttribute("wrap", "off");
133 // If border: 0; -- iOS fails to open keyboard (issue #1287)
134 if (ios
) input
.style
.border
= "1px solid black";
135 input
.setAttribute("autocorrect", "off"); input
.setAttribute("autocapitalize", "off"); input
.setAttribute("spellcheck", "false");
137 // Wraps and hides input textarea
138 d
.inputDiv
= elt("div", [input
], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
139 // The fake scrollbar elements.
140 d
.scrollbarH
= elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
141 d
.scrollbarV
= elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
142 // Covers bottom-right square when both scrollbars are present.
143 d
.scrollbarFiller
= elt("div", null, "CodeMirror-scrollbar-filler");
144 // Covers bottom of gutter when coverGutterNextToScrollbar is on
145 // and h scrollbar is present.
146 d
.gutterFiller
= elt("div", null, "CodeMirror-gutter-filler");
147 // Will contain the actual code, positioned to cover the viewport.
148 d
.lineDiv
= elt("div", null, "CodeMirror-code");
149 // Elements are added to these to represent selection and cursors.
150 d
.selectionDiv
= elt("div", null, null, "position: relative; z-index: 1");
151 d
.cursorDiv
= elt("div", null, "CodeMirror-cursors");
152 // A visibility: hidden element used to find the size of things.
153 d
.measure
= elt("div", null, "CodeMirror-measure");
154 // When lines outside of the viewport are measured, they are drawn in this.
155 d
.lineMeasure
= elt("div", null, "CodeMirror-measure");
156 // Wraps everything that needs to exist inside the vertically-padded coordinate system
157 d
.lineSpace
= elt("div", [d
.measure
, d
.lineMeasure
, d
.selectionDiv
, d
.cursorDiv
, d
.lineDiv
],
158 null, "position: relative; outline: none");
159 // Moved around its parent to cover visible view.
160 d
.mover
= elt("div", [elt("div", [d
.lineSpace
], "CodeMirror-lines")], null, "position: relative");
161 // Set to the height of the document, allowing scrolling.
162 d
.sizer
= elt("div", [d
.mover
], "CodeMirror-sizer");
163 // Behavior of elts with overflow: auto and padding is
164 // inconsistent across browsers. This is used to ensure the
165 // scrollable area is big enough.
166 d
.heightForcer
= elt("div", null, null, "position: absolute; height: " + scrollerCutOff
+ "px; width: 1px;");
167 // Will contain the gutters, if any.
168 d
.gutters
= elt("div", null, "CodeMirror-gutters");
170 // Actual scrollable element.
171 d
.scroller
= elt("div", [d
.sizer
, d
.heightForcer
, d
.gutters
], "CodeMirror-scroll");
172 d
.scroller
.setAttribute("tabIndex", "-1");
173 // The element in which the editor lives.
174 d
.wrapper
= elt("div", [d
.inputDiv
, d
.scrollbarH
, d
.scrollbarV
,
175 d
.scrollbarFiller
, d
.gutterFiller
, d
.scroller
], "CodeMirror");
177 // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
178 if (ie_upto7
) { d
.gutters
.style
.zIndex
= -1; d
.scroller
.style
.paddingRight
= 0; }
179 // Needed to hide big blue blinking cursor on Mobile Safari
180 if (ios
) input
.style
.width
= "0px";
181 if (!webkit
) d
.scroller
.draggable
= true;
182 // Needed to handle Tab key in KHTML
183 if (khtml
) { d
.inputDiv
.style
.height
= "1px"; d
.inputDiv
.style
.position
= "absolute"; }
184 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
185 if (ie_upto7
) d
.scrollbarH
.style
.minHeight
= d
.scrollbarV
.style
.minWidth
= "18px";
187 if (place
.appendChild
) place
.appendChild(d
.wrapper
);
188 else place(d
.wrapper
);
190 // Current rendered range (may be bigger than the view window).
191 d
.viewFrom
= d
.viewTo
= doc
.first
;
192 // Information about the rendered lines.
194 // Holds info about a single rendered line when it was rendered
195 // for measurement, while not in view.
196 d
.externalMeasured
= null;
197 // Empty space (in pixels) above the view
200 d
.updateLineNumbers
= null;
202 // Used to only resize the line number gutter when necessary (when
203 // the amount of lines crosses a boundary that makes its width change)
204 d
.lineNumWidth
= d
.lineNumInnerWidth
= d
.lineNumChars
= null;
205 // See readInput and resetInput
207 // Set to true when a non-horizontal-scrolling line widget is
208 // added. As an optimization, line widget aligning is skipped when
210 d
.alignWidgets
= false;
211 // Flag that indicates whether we expect input to appear real soon
212 // now (after some event like 'keypress' or 'input') and are
213 // polling intensively.
214 d
.pollingFast
= false;
215 // Self-resetting timeout for the poller
216 d
.poll
= new Delayed();
218 d
.cachedCharWidth
= d
.cachedTextHeight
= d
.cachedPaddingH
= null;
220 // Tracks when resetInput has punted to just putting a short
221 // string into the textarea instead of the full selection.
222 d
.inaccurateSelection
= false;
224 // Tracks the maximum line length so that the horizontal scrollbar
225 // can be kept static when scrolling.
228 d
.maxLineChanged
= false;
230 // Used for measuring wheel scrolling granularity
231 d
.wheelDX
= d
.wheelDY
= d
.wheelStartX
= d
.wheelStartY
= null;
233 // True when shift is held down.
239 // Used to get the editor into a consistent state again when options change.
241 function loadMode(cm
) {
242 cm
.doc
.mode
= CodeMirror
.getMode(cm
.options
, cm
.doc
.modeOption
);
246 function resetModeState(cm
) {
247 cm
.doc
.iter(function(line
) {
248 if (line
.stateAfter
) line
.stateAfter
= null;
249 if (line
.styles
) line
.styles
= null;
251 cm
.doc
.frontier
= cm
.doc
.first
;
252 startWorker(cm
, 100);
254 if (cm
.curOp
) regChange(cm
);
257 function wrappingChanged(cm
) {
258 if (cm
.options
.lineWrapping
) {
259 cm
.display
.wrapper
.className
+= " CodeMirror-wrap";
260 cm
.display
.sizer
.style
.minWidth
= "";
262 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(" CodeMirror-wrap", "");
265 estimateLineHeights(cm
);
268 setTimeout(function(){updateScrollbars(cm
);}, 100);
271 // Returns a function that estimates the height of a line, to use as
272 // first approximation until the line becomes visible (and is thus
273 // properly measurable).
274 function estimateHeight(cm
) {
275 var th
= textHeight(cm
.display
), wrapping
= cm
.options
.lineWrapping
;
276 var perLine
= wrapping
&& Math
.max(5, cm
.display
.scroller
.clientWidth
/ charWidth(cm
.display
) - 3);
277 return function(line
) {
278 if (lineIsHidden(cm
.doc
, line
)) return 0;
280 var widgetsHeight
= 0;
281 if (line
.widgets
) for (var i
= 0; i
< line
.widgets
.length
; i
++) {
282 if (line
.widgets
[i
].height
) widgetsHeight
+= line
.widgets
[i
].height
;
286 return widgetsHeight
+ (Math
.ceil(line
.text
.length
/ perLine
) || 1) * th
;
288 return widgetsHeight
+ th
;
292 function estimateLineHeights(cm
) {
293 var doc
= cm
.doc
, est
= estimateHeight(cm
);
294 doc
.iter(function(line
) {
295 var estHeight
= est(line
);
296 if (estHeight
!= line
.height
) updateLineHeight(line
, estHeight
);
300 function keyMapChanged(cm
) {
301 var map
= keyMap
[cm
.options
.keyMap
], style
= map
.style
;
302 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(/\s*cm-keymap-\S+/g, "") +
303 (style
? " cm-keymap-" + style
: "");
306 function themeChanged(cm
) {
307 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(/\s*cm-s-\S+/g, "") +
308 cm
.options
.theme
.replace(/(^|\s)\s*/g, " cm-s-");
312 function guttersChanged(cm
) {
315 setTimeout(function(){alignHorizontally(cm
);}, 20);
318 // Rebuild the gutter elements, ensure the margin to the left of the
319 // code matches their width.
320 function updateGutters(cm
) {
321 var gutters
= cm
.display
.gutters
, specs
= cm
.options
.gutters
;
322 removeChildren(gutters
);
323 for (var i
= 0; i
< specs
.length
; ++i
) {
324 var gutterClass
= specs
[i
];
325 var gElt
= gutters
.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass
));
326 if (gutterClass
== "CodeMirror-linenumbers") {
327 cm
.display
.lineGutter
= gElt
;
328 gElt
.style
.width
= (cm
.display
.lineNumWidth
|| 1) + "px";
331 gutters
.style
.display
= i
? "" : "none";
332 var width
= gutters
.offsetWidth
;
333 cm
.display
.sizer
.style
.marginLeft
= width
+ "px";
334 if (i
) cm
.display
.scrollbarH
.style
.left
= cm
.options
.fixedGutter
? width
+ "px" : 0;
337 // Compute the character length of a line, taking into account
338 // collapsed ranges (see markText) that might hide parts, and join
339 // other lines onto it.
340 function lineLength(line
) {
341 if (line
.height
== 0) return 0;
342 var len
= line
.text
.length
, merged
, cur
= line
;
343 while (merged
= collapsedSpanAtStart(cur
)) {
344 var found
= merged
.find(0, true);
345 cur
= found
.from.line
;
346 len
+= found
.from.ch
- found
.to
.ch
;
349 while (merged
= collapsedSpanAtEnd(cur
)) {
350 var found
= merged
.find(0, true);
351 len
-= cur
.text
.length
- found
.from.ch
;
353 len
+= cur
.text
.length
- found
.to
.ch
;
358 // Find the longest line in the document.
359 function findMaxLine(cm
) {
360 var d
= cm
.display
, doc
= cm
.doc
;
361 d
.maxLine
= getLine(doc
, doc
.first
);
362 d
.maxLineLength
= lineLength(d
.maxLine
);
363 d
.maxLineChanged
= true;
364 doc
.iter(function(line
) {
365 var len
= lineLength(line
);
366 if (len
> d
.maxLineLength
) {
367 d
.maxLineLength
= len
;
373 // Make sure the gutters options contains the element
374 // "CodeMirror-linenumbers" when the lineNumbers option is true.
375 function setGuttersForLineNumbers(options
) {
376 var found
= indexOf(options
.gutters
, "CodeMirror-linenumbers");
377 if (found
== -1 && options
.lineNumbers
) {
378 options
.gutters
= options
.gutters
.concat(["CodeMirror-linenumbers"]);
379 } else if (found
> -1 && !options
.lineNumbers
) {
380 options
.gutters
= options
.gutters
.slice(0);
381 options
.gutters
.splice(found
, 1);
387 // Prepare DOM reads needed to update the scrollbars. Done in one
388 // shot to minimize update/measure roundtrips.
389 function measureForScrollbars(cm
) {
390 var scroll
= cm
.display
.scroller
;
392 clientHeight
: scroll
.clientHeight
,
393 barHeight
: cm
.display
.scrollbarV
.clientHeight
,
394 scrollWidth
: scroll
.scrollWidth
, clientWidth
: scroll
.clientWidth
,
395 barWidth
: cm
.display
.scrollbarH
.clientWidth
,
396 docHeight
: Math
.round(cm
.doc
.height
+ paddingVert(cm
.display
))
400 // Re-synchronize the fake scrollbars with the actual size of the
402 function updateScrollbars(cm
, measure
) {
403 if (!measure
) measure
= measureForScrollbars(cm
);
405 var scrollHeight
= measure
.docHeight
+ scrollerCutOff
;
406 var needsH
= measure
.scrollWidth
> measure
.clientWidth
;
407 var needsV
= scrollHeight
> measure
.clientHeight
;
409 d
.scrollbarV
.style
.display
= "block";
410 d
.scrollbarV
.style
.bottom
= needsH
? scrollbarWidth(d
.measure
) + "px" : "0";
411 // A bug in IE8 can cause this value to be negative, so guard it.
412 d
.scrollbarV
.firstChild
.style
.height
=
413 Math
.max(0, scrollHeight
- measure
.clientHeight
+ (measure
.barHeight
|| d
.scrollbarV
.clientHeight
)) + "px";
415 d
.scrollbarV
.style
.display
= "";
416 d
.scrollbarV
.firstChild
.style
.height
= "0";
419 d
.scrollbarH
.style
.display
= "block";
420 d
.scrollbarH
.style
.right
= needsV
? scrollbarWidth(d
.measure
) + "px" : "0";
421 d
.scrollbarH
.firstChild
.style
.width
=
422 (measure
.scrollWidth
- measure
.clientWidth
+ (measure
.barWidth
|| d
.scrollbarH
.clientWidth
)) + "px";
424 d
.scrollbarH
.style
.display
= "";
425 d
.scrollbarH
.firstChild
.style
.width
= "0";
427 if (needsH
&& needsV
) {
428 d
.scrollbarFiller
.style
.display
= "block";
429 d
.scrollbarFiller
.style
.height
= d
.scrollbarFiller
.style
.width
= scrollbarWidth(d
.measure
) + "px";
430 } else d
.scrollbarFiller
.style
.display
= "";
431 if (needsH
&& cm
.options
.coverGutterNextToScrollbar
&& cm
.options
.fixedGutter
) {
432 d
.gutterFiller
.style
.display
= "block";
433 d
.gutterFiller
.style
.height
= scrollbarWidth(d
.measure
) + "px";
434 d
.gutterFiller
.style
.width
= d
.gutters
.offsetWidth
+ "px";
435 } else d
.gutterFiller
.style
.display
= "";
437 if (mac_geLion
&& scrollbarWidth(d
.measure
) === 0) {
438 d
.scrollbarV
.style
.minWidth
= d
.scrollbarH
.style
.minHeight
= mac_geMountainLion
? "18px" : "12px";
439 var barMouseDown = function(e
) {
440 if (e_target(e
) != d
.scrollbarV
&& e_target(e
) != d
.scrollbarH
)
441 operation(cm
, onMouseDown
)(e
);
443 on(d
.scrollbarV
, "mousedown", barMouseDown
);
444 on(d
.scrollbarH
, "mousedown", barMouseDown
);
448 // Compute the lines that are visible in a given viewport (defaults
449 // the the current scroll position). viewPort may contain top,
450 // height, and ensure (see op.scrollToPos) properties.
451 function visibleLines(display
, doc
, viewPort
) {
452 var top
= viewPort
&& viewPort
.top
!= null ? viewPort
.top
: display
.scroller
.scrollTop
;
453 top
= Math
.floor(top
- paddingTop(display
));
454 var bottom
= viewPort
&& viewPort
.bottom
!= null ? viewPort
.bottom
: top
+ display
.wrapper
.clientHeight
;
456 var from = lineAtHeight(doc
, top
), to
= lineAtHeight(doc
, bottom
);
457 // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
458 // forces those lines into the viewport (if possible).
459 if (viewPort
&& viewPort
.ensure
) {
460 var ensureFrom
= viewPort
.ensure
.from.line
, ensureTo
= viewPort
.ensure
.to
.line
;
461 if (ensureFrom
< from)
462 return {from: ensureFrom
,
463 to
: lineAtHeight(doc
, heightAtLine(getLine(doc
, ensureFrom
)) + display
.wrapper
.clientHeight
)};
464 if (Math
.min(ensureTo
, doc
.lastLine()) >= to
)
465 return {from: lineAtHeight(doc
, heightAtLine(getLine(doc
, ensureTo
)) - display
.wrapper
.clientHeight
),
468 return {from: from, to
: to
};
473 // Re-align line numbers and gutter marks to compensate for
474 // horizontal scrolling.
475 function alignHorizontally(cm
) {
476 var display
= cm
.display
, view
= display
.view
;
477 if (!display
.alignWidgets
&& (!display
.gutters
.firstChild
|| !cm
.options
.fixedGutter
)) return;
478 var comp
= compensateForHScroll(display
) - display
.scroller
.scrollLeft
+ cm
.doc
.scrollLeft
;
479 var gutterW
= display
.gutters
.offsetWidth
, left
= comp
+ "px";
480 for (var i
= 0; i
< view
.length
; i
++) if (!view
[i
].hidden
) {
481 if (cm
.options
.fixedGutter
&& view
[i
].gutter
)
482 view
[i
].gutter
.style
.left
= left
;
483 var align
= view
[i
].alignable
;
484 if (align
) for (var j
= 0; j
< align
.length
; j
++)
485 align
[j
].style
.left
= left
;
487 if (cm
.options
.fixedGutter
)
488 display
.gutters
.style
.left
= (comp
+ gutterW
) + "px";
491 // Used to ensure that the line number gutter is still the right
492 // size for the current document size. Returns true when an update
494 function maybeUpdateLineNumberWidth(cm
) {
495 if (!cm
.options
.lineNumbers
) return false;
496 var doc
= cm
.doc
, last
= lineNumberFor(cm
.options
, doc
.first
+ doc
.size
- 1), display
= cm
.display
;
497 if (last
.length
!= display
.lineNumChars
) {
498 var test
= display
.measure
.appendChild(elt("div", [elt("div", last
)],
499 "CodeMirror-linenumber CodeMirror-gutter-elt"));
500 var innerW
= test
.firstChild
.offsetWidth
, padding
= test
.offsetWidth
- innerW
;
501 display
.lineGutter
.style
.width
= "";
502 display
.lineNumInnerWidth
= Math
.max(innerW
, display
.lineGutter
.offsetWidth
- padding
);
503 display
.lineNumWidth
= display
.lineNumInnerWidth
+ padding
;
504 display
.lineNumChars
= display
.lineNumInnerWidth
? last
.length
: -1;
505 display
.lineGutter
.style
.width
= display
.lineNumWidth
+ "px";
506 var width
= display
.gutters
.offsetWidth
;
507 display
.scrollbarH
.style
.left
= cm
.options
.fixedGutter
? width
+ "px" : 0;
508 display
.sizer
.style
.marginLeft
= width
+ "px";
514 function lineNumberFor(options
, i
) {
515 return String(options
.lineNumberFormatter(i
+ options
.firstLineNumber
));
518 // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
519 // but using getBoundingClientRect to get a sub-pixel-accurate
521 function compensateForHScroll(display
) {
522 return display
.scroller
.getBoundingClientRect().left
- display
.sizer
.getBoundingClientRect().left
;
527 // Updates the display, selection, and scrollbars, using the
528 // information in display.view to find out which nodes are no longer
529 // up-to-date. Tries to bail out early when no changes are needed,
530 // unless forced is true.
531 // Returns true if an actual update happened, false otherwise.
532 function updateDisplay(cm
, viewPort
, forced
) {
533 var oldFrom
= cm
.display
.viewFrom
, oldTo
= cm
.display
.viewTo
, updated
;
534 var visible
= visibleLines(cm
.display
, cm
.doc
, viewPort
);
535 for (var first
= true;; first
= false) {
536 var oldWidth
= cm
.display
.scroller
.clientWidth
;
537 if (!updateDisplayInner(cm
, visible
, forced
)) break;
540 // If the max line changed since it was last measured, measure it,
541 // and ensure the document's width matches it.
542 if (cm
.display
.maxLineChanged
&& !cm
.options
.lineWrapping
)
543 adjustContentWidth(cm
);
545 var barMeasure
= measureForScrollbars(cm
);
547 setDocumentHeight(cm
, barMeasure
);
548 updateScrollbars(cm
, barMeasure
);
549 if (first
&& cm
.options
.lineWrapping
&& oldWidth
!= cm
.display
.scroller
.clientWidth
) {
555 // Clip forced viewport to actual scrollable area.
556 if (viewPort
&& viewPort
.top
!= null)
557 viewPort
= {top
: Math
.min(barMeasure
.docHeight
- scrollerCutOff
- barMeasure
.clientHeight
, viewPort
.top
)};
558 // Updated line heights might result in the drawn area not
559 // actually covering the viewport. Keep looping until it does.
560 visible
= visibleLines(cm
.display
, cm
.doc
, viewPort
);
561 if (visible
.from >= cm
.display
.viewFrom
&& visible
.to
<= cm
.display
.viewTo
)
565 cm
.display
.updateLineNumbers
= null;
567 signalLater(cm
, "update", cm
);
568 if (cm
.display
.viewFrom
!= oldFrom
|| cm
.display
.viewTo
!= oldTo
)
569 signalLater(cm
, "viewportChange", cm
, cm
.display
.viewFrom
, cm
.display
.viewTo
);
574 // Does the actual updating of the line display. Bails out
575 // (returning false) when there is nothing to be done and forced is
577 function updateDisplayInner(cm
, visible
, forced
) {
578 var display
= cm
.display
, doc
= cm
.doc
;
579 if (!display
.wrapper
.offsetWidth
) {
584 // Bail out if the visible area is already rendered and nothing changed.
585 if (!forced
&& visible
.from >= display
.viewFrom
&& visible
.to
<= display
.viewTo
&&
586 countDirtyView(cm
) == 0)
589 if (maybeUpdateLineNumberWidth(cm
))
591 var dims
= getDimensions(cm
);
593 // Compute a suitable new viewport (from & to)
594 var end
= doc
.first
+ doc
.size
;
595 var from = Math
.max(visible
.from - cm
.options
.viewportMargin
, doc
.first
);
596 var to
= Math
.min(end
, visible
.to
+ cm
.options
.viewportMargin
);
597 if (display
.viewFrom
< from && from - display
.viewFrom
< 20) from = Math
.max(doc
.first
, display
.viewFrom
);
598 if (display
.viewTo
> to
&& display
.viewTo
- to
< 20) to
= Math
.min(end
, display
.viewTo
);
599 if (sawCollapsedSpans
) {
600 from = visualLineNo(cm
.doc
, from);
601 to
= visualLineEndNo(cm
.doc
, to
);
604 var different
= from != display
.viewFrom
|| to
!= display
.viewTo
||
605 display
.lastSizeC
!= display
.wrapper
.clientHeight
;
606 adjustView(cm
, from, to
);
608 display
.viewOffset
= heightAtLine(getLine(cm
.doc
, display
.viewFrom
));
609 // Position the mover div to align with the current scroll position
610 cm
.display
.mover
.style
.top
= display
.viewOffset
+ "px";
612 var toUpdate
= countDirtyView(cm
);
613 if (!different
&& toUpdate
== 0 && !forced
) return;
615 // For big changes, we hide the enclosing element during the
616 // update, since that speeds up the operations on most browsers.
617 var focused
= activeElt();
618 if (toUpdate
> 4) display
.lineDiv
.style
.display
= "none";
619 patchDisplay(cm
, display
.updateLineNumbers
, dims
);
620 if (toUpdate
> 4) display
.lineDiv
.style
.display
= "";
621 // There might have been a widget with a focused element that got
622 // hidden or updated, if so re-focus it.
623 if (focused
&& activeElt() != focused
&& focused
.offsetHeight
) focused
.focus();
625 // Prevent selection and cursors from interfering with the scroll
627 removeChildren(display
.cursorDiv
);
628 removeChildren(display
.selectionDiv
);
631 display
.lastSizeC
= display
.wrapper
.clientHeight
;
632 startWorker(cm
, 400);
635 updateHeightsInViewport(cm
);
640 function adjustContentWidth(cm
) {
641 var display
= cm
.display
;
642 var width
= measureChar(cm
, display
.maxLine
, display
.maxLine
.text
.length
).left
;
643 display
.maxLineChanged
= false;
644 var minWidth
= Math
.max(0, width
+ 3);
645 var maxScrollLeft
= Math
.max(0, display
.sizer
.offsetLeft
+ minWidth
+ scrollerCutOff
- display
.scroller
.clientWidth
);
646 display
.sizer
.style
.minWidth
= minWidth
+ "px";
647 if (maxScrollLeft
< cm
.doc
.scrollLeft
)
648 setScrollLeft(cm
, Math
.min(display
.scroller
.scrollLeft
, maxScrollLeft
), true);
651 function setDocumentHeight(cm
, measure
) {
652 cm
.display
.sizer
.style
.minHeight
= cm
.display
.heightForcer
.style
.top
= measure
.docHeight
+ "px";
653 cm
.display
.gutters
.style
.height
= Math
.max(measure
.docHeight
, measure
.clientHeight
- scrollerCutOff
) + "px";
656 // Read the actual heights of the rendered lines, and update their
657 // stored heights to match.
658 function updateHeightsInViewport(cm
) {
659 var display
= cm
.display
;
660 var prevBottom
= display
.lineDiv
.offsetTop
;
661 for (var i
= 0; i
< display
.view
.length
; i
++) {
662 var cur
= display
.view
[i
], height
;
663 if (cur
.hidden
) continue;
665 var bot
= cur
.node
.offsetTop
+ cur
.node
.offsetHeight
;
666 height
= bot
- prevBottom
;
669 var box
= cur
.node
.getBoundingClientRect();
670 height
= box
.bottom
- box
.top
;
672 var diff
= cur
.line
.height
- height
;
673 if (height
< 2) height
= textHeight(display
);
674 if (diff
> .001 || diff
< -.001) {
675 updateLineHeight(cur
.line
, height
);
676 updateWidgetHeight(cur
.line
);
677 if (cur
.rest
) for (var j
= 0; j
< cur
.rest
.length
; j
++)
678 updateWidgetHeight(cur
.rest
[j
]);
683 // Read and store the height of line widgets associated with the
685 function updateWidgetHeight(line
) {
686 if (line
.widgets
) for (var i
= 0; i
< line
.widgets
.length
; ++i
)
687 line
.widgets
[i
].height
= line
.widgets
[i
].node
.offsetHeight
;
690 // Do a bulk-read of the DOM positions and sizes needed to draw the
691 // view, so that we don't interleave reading and writing to the DOM.
692 function getDimensions(cm
) {
693 var d
= cm
.display
, left
= {}, width
= {};
694 for (var n
= d
.gutters
.firstChild
, i
= 0; n
; n
= n
.nextSibling
, ++i
) {
695 left
[cm
.options
.gutters
[i
]] = n
.offsetLeft
;
696 width
[cm
.options
.gutters
[i
]] = n
.offsetWidth
;
698 return {fixedPos
: compensateForHScroll(d
),
699 gutterTotalWidth
: d
.gutters
.offsetWidth
,
702 wrapperWidth
: d
.wrapper
.clientWidth
};
705 // Sync the actual display DOM structure with display.view, removing
706 // nodes for lines that are no longer in view, and creating the ones
707 // that are not there yet, and updating the ones that are out of
709 function patchDisplay(cm
, updateNumbersFrom
, dims
) {
710 var display
= cm
.display
, lineNumbers
= cm
.options
.lineNumbers
;
711 var container
= display
.lineDiv
, cur
= container
.firstChild
;
714 var next
= node
.nextSibling
;
715 // Works around a throw-scroll bug in OS X Webkit
716 if (webkit
&& mac
&& cm
.display
.currentWheelTarget
== node
)
717 node
.style
.display
= "none";
719 node
.parentNode
.removeChild(node
);
723 var view
= display
.view
, lineN
= display
.viewFrom
;
724 // Loop over the elements in the view, syncing cur (the DOM nodes
725 // in display.lineDiv) with the view as we go.
726 for (var i
= 0; i
< view
.length
; i
++) {
727 var lineView
= view
[i
];
728 if (lineView
.hidden
) {
729 } else if (!lineView
.node
) { // Not drawn yet
730 var node
= buildLineElement(cm
, lineView
, lineN
, dims
);
731 container
.insertBefore(node
, cur
);
732 } else { // Already drawn
733 while (cur
!= lineView
.node
) cur
= rm(cur
);
734 var updateNumber
= lineNumbers
&& updateNumbersFrom
!= null &&
735 updateNumbersFrom
<= lineN
&& lineView
.lineNumber
;
736 if (lineView
.changes
) {
737 if (indexOf(lineView
.changes
, "gutter") > -1) updateNumber
= false;
738 updateLineForChanges(cm
, lineView
, lineN
, dims
);
741 removeChildren(lineView
.lineNumber
);
742 lineView
.lineNumber
.appendChild(document
.createTextNode(lineNumberFor(cm
.options
, lineN
)));
744 cur
= lineView
.node
.nextSibling
;
746 lineN
+= lineView
.size
;
748 while (cur
) cur
= rm(cur
);
751 // When an aspect of a line changes, a string is added to
752 // lineView.changes. This updates the relevant part of the line's
754 function updateLineForChanges(cm
, lineView
, lineN
, dims
) {
755 for (var j
= 0; j
< lineView
.changes
.length
; j
++) {
756 var type
= lineView
.changes
[j
];
757 if (type
== "text") updateLineText(cm
, lineView
);
758 else if (type
== "gutter") updateLineGutter(cm
, lineView
, lineN
, dims
);
759 else if (type
== "class") updateLineClasses(lineView
);
760 else if (type
== "widget") updateLineWidgets(lineView
, dims
);
762 lineView
.changes
= null;
765 // Lines with gutter elements, widgets or a background class need to
766 // be wrapped, and have the extra elements added to the wrapper div
767 function ensureLineWrapped(lineView
) {
768 if (lineView
.node
== lineView
.text
) {
769 lineView
.node
= elt("div", null, null, "position: relative");
770 if (lineView
.text
.parentNode
)
771 lineView
.text
.parentNode
.replaceChild(lineView
.node
, lineView
.text
);
772 lineView
.node
.appendChild(lineView
.text
);
773 if (ie_upto7
) lineView
.node
.style
.zIndex
= 2;
775 return lineView
.node
;
778 function updateLineBackground(lineView
) {
779 var cls
= lineView
.bgClass
? lineView
.bgClass
+ " " + (lineView
.line
.bgClass
|| "") : lineView
.line
.bgClass
;
780 if (cls
) cls
+= " CodeMirror-linebackground";
781 if (lineView
.background
) {
782 if (cls
) lineView
.background
.className
= cls
;
783 else { lineView
.background
.parentNode
.removeChild(lineView
.background
); lineView
.background
= null; }
785 var wrap
= ensureLineWrapped(lineView
);
786 lineView
.background
= wrap
.insertBefore(elt("div", null, cls
), wrap
.firstChild
);
790 // Wrapper around buildLineContent which will reuse the structure
791 // in display.externalMeasured when possible.
792 function getLineContent(cm
, lineView
) {
793 var ext
= cm
.display
.externalMeasured
;
794 if (ext
&& ext
.line
== lineView
.line
) {
795 cm
.display
.externalMeasured
= null;
796 lineView
.measure
= ext
.measure
;
799 return buildLineContent(cm
, lineView
);
802 // Redraw the line's text. Interacts with the background and text
803 // classes because the mode may output tokens that influence these
805 function updateLineText(cm
, lineView
) {
806 var cls
= lineView
.text
.className
;
807 var built
= getLineContent(cm
, lineView
);
808 if (lineView
.text
== lineView
.node
) lineView
.node
= built
.pre
;
809 lineView
.text
.parentNode
.replaceChild(built
.pre
, lineView
.text
);
810 lineView
.text
= built
.pre
;
811 if (built
.bgClass
!= lineView
.bgClass
|| built
.textClass
!= lineView
.textClass
) {
812 lineView
.bgClass
= built
.bgClass
;
813 lineView
.textClass
= built
.textClass
;
814 updateLineClasses(lineView
);
816 lineView
.text
.className
= cls
;
820 function updateLineClasses(lineView
) {
821 updateLineBackground(lineView
);
822 if (lineView
.line
.wrapClass
)
823 ensureLineWrapped(lineView
).className
= lineView
.line
.wrapClass
;
824 else if (lineView
.node
!= lineView
.text
)
825 lineView
.node
.className
= "";
826 var textClass
= lineView
.textClass
? lineView
.textClass
+ " " + (lineView
.line
.textClass
|| "") : lineView
.line
.textClass
;
827 lineView
.text
.className
= textClass
|| "";
830 function updateLineGutter(cm
, lineView
, lineN
, dims
) {
831 if (lineView
.gutter
) {
832 lineView
.node
.removeChild(lineView
.gutter
);
833 lineView
.gutter
= null;
835 var markers
= lineView
.line
.gutterMarkers
;
836 if (cm
.options
.lineNumbers
|| markers
) {
837 var wrap
= ensureLineWrapped(lineView
);
838 var gutterWrap
= lineView
.gutter
=
839 wrap
.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " +
840 (cm
.options
.fixedGutter
? dims
.fixedPos
: -dims
.gutterTotalWidth
) + "px"),
842 if (cm
.options
.lineNumbers
&& (!markers
|| !markers
["CodeMirror-linenumbers"]))
843 lineView
.lineNumber
= gutterWrap
.appendChild(
844 elt("div", lineNumberFor(cm
.options
, lineN
),
845 "CodeMirror-linenumber CodeMirror-gutter-elt",
846 "left: " + dims
.gutterLeft
["CodeMirror-linenumbers"] + "px; width: "
847 + cm
.display
.lineNumInnerWidth
+ "px"));
848 if (markers
) for (var k
= 0; k
< cm
.options
.gutters
.length
; ++k
) {
849 var id
= cm
.options
.gutters
[k
], found
= markers
.hasOwnProperty(id
) && markers
[id
];
851 gutterWrap
.appendChild(elt("div", [found
], "CodeMirror-gutter-elt", "left: " +
852 dims
.gutterLeft
[id
] + "px; width: " + dims
.gutterWidth
[id
] + "px"));
857 function updateLineWidgets(lineView
, dims
) {
858 if (lineView
.alignable
) lineView
.alignable
= null;
859 for (var node
= lineView
.node
.firstChild
, next
; node
; node
= next
) {
860 var next
= node
.nextSibling
;
861 if (node
.className
== "CodeMirror-linewidget")
862 lineView
.node
.removeChild(node
);
864 insertLineWidgets(lineView
, dims
);
867 // Build a line's DOM representation from scratch
868 function buildLineElement(cm
, lineView
, lineN
, dims
) {
869 var built
= getLineContent(cm
, lineView
);
870 lineView
.text
= lineView
.node
= built
.pre
;
871 if (built
.bgClass
) lineView
.bgClass
= built
.bgClass
;
872 if (built
.textClass
) lineView
.textClass
= built
.textClass
;
874 updateLineClasses(lineView
);
875 updateLineGutter(cm
, lineView
, lineN
, dims
);
876 insertLineWidgets(lineView
, dims
);
877 return lineView
.node
;
880 // A lineView may contain multiple logical lines (when merged by
881 // collapsed spans). The widgets for all of them need to be drawn.
882 function insertLineWidgets(lineView
, dims
) {
883 insertLineWidgetsFor(lineView
.line
, lineView
, dims
, true);
884 if (lineView
.rest
) for (var i
= 0; i
< lineView
.rest
.length
; i
++)
885 insertLineWidgetsFor(lineView
.rest
[i
], lineView
, dims
, false);
888 function insertLineWidgetsFor(line
, lineView
, dims
, allowAbove
) {
889 if (!line
.widgets
) return;
890 var wrap
= ensureLineWrapped(lineView
);
891 for (var i
= 0, ws
= line
.widgets
; i
< ws
.length
; ++i
) {
892 var widget
= ws
[i
], node
= elt("div", [widget
.node
], "CodeMirror-linewidget");
893 if (!widget
.handleMouseEvents
) node
.ignoreEvents
= true;
894 positionLineWidget(widget
, node
, lineView
, dims
);
895 if (allowAbove
&& widget
.above
)
896 wrap
.insertBefore(node
, lineView
.gutter
|| lineView
.text
);
898 wrap
.appendChild(node
);
899 signalLater(widget
, "redraw");
903 function positionLineWidget(widget
, node
, lineView
, dims
) {
904 if (widget
.noHScroll
) {
905 (lineView
.alignable
|| (lineView
.alignable
= [])).push(node
);
906 var width
= dims
.wrapperWidth
;
907 node
.style
.left
= dims
.fixedPos
+ "px";
908 if (!widget
.coverGutter
) {
909 width
-= dims
.gutterTotalWidth
;
910 node
.style
.paddingLeft
= dims
.gutterTotalWidth
+ "px";
912 node
.style
.width
= width
+ "px";
914 if (widget
.coverGutter
) {
915 node
.style
.zIndex
= 5;
916 node
.style
.position
= "relative";
917 if (!widget
.noHScroll
) node
.style
.marginLeft
= -dims
.gutterTotalWidth
+ "px";
923 // A Pos instance represents a position within the text.
924 var Pos
= CodeMirror
.Pos = function(line
, ch
) {
925 if (!(this instanceof Pos
)) return new Pos(line
, ch
);
926 this.line
= line
; this.ch
= ch
;
929 // Compare two positions, return 0 if they are the same, a negative
930 // number when a is less, and a positive number otherwise.
931 var cmp
= CodeMirror
.cmpPos = function(a
, b
) { return a
.line
- b
.line
|| a
.ch
- b
.ch
; };
933 function copyPos(x
) {return Pos(x
.line
, x
.ch
);}
934 function maxPos(a
, b
) { return cmp(a
, b
) < 0 ? b
: a
; }
935 function minPos(a
, b
) { return cmp(a
, b
) < 0 ? a
: b
; }
937 // SELECTION / CURSOR
939 // Selection objects are immutable. A new one is created every time
940 // the selection changes. A selection is one or more non-overlapping
941 // (and non-touching) ranges, sorted, and an integer that indicates
942 // which one is the primary selection (the one that's scrolled into
943 // view, that getCursor returns, etc).
944 function Selection(ranges
, primIndex
) {
945 this.ranges
= ranges
;
946 this.primIndex
= primIndex
;
949 Selection
.prototype = {
950 primary: function() { return this.ranges
[this.primIndex
]; },
951 equals: function(other
) {
952 if (other
== this) return true;
953 if (other
.primIndex
!= this.primIndex
|| other
.ranges
.length
!= this.ranges
.length
) return false;
954 for (var i
= 0; i
< this.ranges
.length
; i
++) {
955 var here
= this.ranges
[i
], there
= other
.ranges
[i
];
956 if (cmp(here
.anchor
, there
.anchor
) != 0 || cmp(here
.head
, there
.head
) != 0) return false;
960 deepCopy: function() {
961 for (var out
= [], i
= 0; i
< this.ranges
.length
; i
++)
962 out
[i
] = new Range(copyPos(this.ranges
[i
].anchor
), copyPos(this.ranges
[i
].head
));
963 return new Selection(out
, this.primIndex
);
965 somethingSelected: function() {
966 for (var i
= 0; i
< this.ranges
.length
; i
++)
967 if (!this.ranges
[i
].empty()) return true;
970 contains: function(pos
, end
) {
972 for (var i
= 0; i
< this.ranges
.length
; i
++) {
973 var range
= this.ranges
[i
];
974 if (cmp(end
, range
.from()) >= 0 && cmp(pos
, range
.to()) <= 0)
981 function Range(anchor
, head
) {
982 this.anchor
= anchor
; this.head
= head
;
986 from: function() { return minPos(this.anchor
, this.head
); },
987 to: function() { return maxPos(this.anchor
, this.head
); },
989 return this.head
.line
== this.anchor
.line
&& this.head
.ch
== this.anchor
.ch
;
993 // Take an unsorted, potentially overlapping set of ranges, and
994 // build a selection out of it. 'Consumes' ranges array (modifying
996 function normalizeSelection(ranges
, primIndex
) {
997 var prim
= ranges
[primIndex
];
998 ranges
.sort(function(a
, b
) { return cmp(a
.from(), b
.from()); });
999 primIndex
= indexOf(ranges
, prim
);
1000 for (var i
= 1; i
< ranges
.length
; i
++) {
1001 var cur
= ranges
[i
], prev
= ranges
[i
- 1];
1002 if (cmp(prev
.to(), cur
.from()) >= 0) {
1003 var from = minPos(prev
.from(), cur
.from()), to
= maxPos(prev
.to(), cur
.to());
1004 var inv
= prev
.empty() ? cur
.from() == cur
.head
: prev
.from() == prev
.head
;
1005 if (i
<= primIndex
) --primIndex
;
1006 ranges
.splice(--i
, 2, new Range(inv
? to
: from, inv
? from : to
));
1009 return new Selection(ranges
, primIndex
);
1012 function simpleSelection(anchor
, head
) {
1013 return new Selection([new Range(anchor
, head
|| anchor
)], 0);
1016 // Most of the external API clips given positions to make sure they
1017 // actually exist within the document.
1018 function clipLine(doc
, n
) {return Math
.max(doc
.first
, Math
.min(n
, doc
.first
+ doc
.size
- 1));}
1019 function clipPos(doc
, pos
) {
1020 if (pos
.line
< doc
.first
) return Pos(doc
.first
, 0);
1021 var last
= doc
.first
+ doc
.size
- 1;
1022 if (pos
.line
> last
) return Pos(last
, getLine(doc
, last
).text
.length
);
1023 return clipToLen(pos
, getLine(doc
, pos
.line
).text
.length
);
1025 function clipToLen(pos
, linelen
) {
1027 if (ch
== null || ch
> linelen
) return Pos(pos
.line
, linelen
);
1028 else if (ch
< 0) return Pos(pos
.line
, 0);
1031 function isLine(doc
, l
) {return l
>= doc
.first
&& l
< doc
.first
+ doc
.size
;}
1032 function clipPosArray(doc
, array
) {
1033 for (var out
= [], i
= 0; i
< array
.length
; i
++) out
[i
] = clipPos(doc
, array
[i
]);
1037 // SELECTION UPDATES
1039 // The 'scroll' parameter given to many of these indicated whether
1040 // the new cursor position should be scrolled into view after
1041 // modifying the selection.
1043 // If shift is held or the extend flag is set, extends a range to
1044 // include a given position (and optionally a second position).
1045 // Otherwise, simply returns the range between the given positions.
1046 // Used for cursor motion and such.
1047 function extendRange(doc
, range
, head
, other
) {
1048 if (doc
.cm
&& doc
.cm
.display
.shift
|| doc
.extend
) {
1049 var anchor
= range
.anchor
;
1051 var posBefore
= cmp(head
, anchor
) < 0;
1052 if (posBefore
!= (cmp(other
, anchor
) < 0)) {
1055 } else if (posBefore
!= (cmp(head
, other
) < 0)) {
1059 return new Range(anchor
, head
);
1061 return new Range(other
|| head
, head
);
1065 // Extend the primary selection range, discard the rest.
1066 function extendSelection(doc
, head
, other
, options
) {
1067 setSelection(doc
, new Selection([extendRange(doc
, doc
.sel
.primary(), head
, other
)], 0), options
);
1070 // Extend all selections (pos is an array of selections with length
1071 // equal the number of selections)
1072 function extendSelections(doc
, heads
, options
) {
1073 for (var out
= [], i
= 0; i
< doc
.sel
.ranges
.length
; i
++)
1074 out
[i
] = extendRange(doc
, doc
.sel
.ranges
[i
], heads
[i
], null);
1075 var newSel
= normalizeSelection(out
, doc
.sel
.primIndex
);
1076 setSelection(doc
, newSel
, options
);
1079 // Updates a single range in the selection.
1080 function replaceOneSelection(doc
, i
, range
, options
) {
1081 var ranges
= doc
.sel
.ranges
.slice(0);
1083 setSelection(doc
, normalizeSelection(ranges
, doc
.sel
.primIndex
), options
);
1086 // Reset the selection to a single range.
1087 function setSimpleSelection(doc
, anchor
, head
, options
) {
1088 setSelection(doc
, simpleSelection(anchor
, head
), options
);
1091 // Give beforeSelectionChange handlers a change to influence a
1092 // selection update.
1093 function filterSelectionChange(doc
, sel
) {
1096 update: function(ranges
) {
1098 for (var i
= 0; i
< ranges
.length
; i
++)
1099 this.ranges
[i
] = new Range(clipPos(doc
, ranges
[i
].anchor
),
1100 clipPos(doc
, ranges
[i
].head
));
1103 signal(doc
, "beforeSelectionChange", doc
, obj
);
1104 if (doc
.cm
) signal(doc
.cm
, "beforeSelectionChange", doc
.cm
, obj
);
1105 if (obj
.ranges
!= sel
.ranges
) return normalizeSelection(obj
.ranges
, obj
.ranges
.length
- 1);
1109 function setSelectionReplaceHistory(doc
, sel
, options
) {
1110 var done
= doc
.history
.done
, last
= lst(done
);
1111 if (last
&& last
.ranges
) {
1112 done
[done
.length
- 1] = sel
;
1113 setSelectionNoUndo(doc
, sel
, options
);
1115 setSelection(doc
, sel
, options
);
1119 // Set a new selection.
1120 function setSelection(doc
, sel
, options
) {
1121 setSelectionNoUndo(doc
, sel
, options
);
1122 addSelectionToHistory(doc
, doc
.sel
, doc
.cm
? doc
.cm
.curOp
.id
: NaN
, options
);
1125 function setSelectionNoUndo(doc
, sel
, options
) {
1126 if (hasHandler(doc
, "beforeSelectionChange") || doc
.cm
&& hasHandler(doc
.cm
, "beforeSelectionChange"))
1127 sel
= filterSelectionChange(doc
, sel
);
1129 var bias
= cmp(sel
.primary().head
, doc
.sel
.primary().head
) < 0 ? -1 : 1;
1130 setSelectionInner(doc
, skipAtomicInSelection(doc
, sel
, bias
, true));
1132 if (!(options
&& options
.scroll
=== false) && doc
.cm
)
1133 ensureCursorVisible(doc
.cm
);
1136 function setSelectionInner(doc
, sel
) {
1137 if (sel
.equals(doc
.sel
)) return;
1142 doc
.cm
.curOp
.updateInput
= doc
.cm
.curOp
.selectionChanged
=
1143 doc
.cm
.curOp
.cursorActivity
= true;
1144 signalLater(doc
, "cursorActivity", doc
);
1147 // Verify that the selection does not partially select any atomic
1149 function reCheckSelection(doc
) {
1150 setSelectionInner(doc
, skipAtomicInSelection(doc
, doc
.sel
, null, false), sel_dontScroll
);
1153 // Return a selection that does not partially select any atomic
1155 function skipAtomicInSelection(doc
, sel
, bias
, mayClear
) {
1157 for (var i
= 0; i
< sel
.ranges
.length
; i
++) {
1158 var range
= sel
.ranges
[i
];
1159 var newAnchor
= skipAtomic(doc
, range
.anchor
, bias
, mayClear
);
1160 var newHead
= skipAtomic(doc
, range
.head
, bias
, mayClear
);
1161 if (out
|| newAnchor
!= range
.anchor
|| newHead
!= range
.head
) {
1162 if (!out
) out
= sel
.ranges
.slice(0, i
);
1163 out
[i
] = new Range(newAnchor
, newHead
);
1166 return out
? normalizeSelection(out
, sel
.primIndex
) : sel
;
1169 // Ensure a given position is not inside an atomic range.
1170 function skipAtomic(doc
, pos
, bias
, mayClear
) {
1171 var flipped
= false, curPos
= pos
;
1172 var dir
= bias
|| 1;
1173 doc
.cantEdit
= false;
1175 var line
= getLine(doc
, curPos
.line
);
1176 if (line
.markedSpans
) {
1177 for (var i
= 0; i
< line
.markedSpans
.length
; ++i
) {
1178 var sp
= line
.markedSpans
[i
], m
= sp
.marker
;
1179 if ((sp
.from == null || (m
.inclusiveLeft
? sp
.from <= curPos
.ch
: sp
.from < curPos
.ch
)) &&
1180 (sp
.to
== null || (m
.inclusiveRight
? sp
.to
>= curPos
.ch
: sp
.to
> curPos
.ch
))) {
1182 signal(m
, "beforeCursorEnter");
1183 if (m
.explicitlyCleared
) {
1184 if (!line
.markedSpans
) break;
1185 else {--i
; continue;}
1188 if (!m
.atomic
) continue;
1189 var newPos
= m
.find(dir
< 0 ? -1 : 1);
1190 if (cmp(newPos
, curPos
) == 0) {
1192 if (newPos
.ch
< 0) {
1193 if (newPos
.line
> doc
.first
) newPos
= clipPos(doc
, Pos(newPos
.line
- 1));
1195 } else if (newPos
.ch
> line
.text
.length
) {
1196 if (newPos
.line
< doc
.first
+ doc
.size
- 1) newPos
= Pos(newPos
.line
+ 1, 0);
1201 // Driven in a corner -- no valid cursor position found at all
1202 // -- try again *with* clearing, if we didn't already
1203 if (!mayClear
) return skipAtomic(doc
, pos
, bias
, true);
1204 // Otherwise, turn off editing until further notice, and return the start of the doc
1205 doc
.cantEdit
= true;
1206 return Pos(doc
.first
, 0);
1208 flipped
= true; newPos
= pos
; dir
= -dir
;
1220 // SELECTION DRAWING
1222 // Redraw the selection and/or cursor
1223 function updateSelection(cm
) {
1224 var display
= cm
.display
, doc
= cm
.doc
;
1225 var curFragment
= document
.createDocumentFragment();
1226 var selFragment
= document
.createDocumentFragment();
1228 for (var i
= 0; i
< doc
.sel
.ranges
.length
; i
++) {
1229 var range
= doc
.sel
.ranges
[i
];
1230 var collapsed
= range
.empty();
1231 if (collapsed
|| cm
.options
.showCursorWhenSelecting
)
1232 updateSelectionCursor(cm
, range
, curFragment
);
1234 updateSelectionRange(cm
, range
, selFragment
);
1237 // Move the hidden textarea near the cursor to prevent scrolling artifacts
1238 if (cm
.options
.moveInputWithCursor
) {
1239 var headPos
= cursorCoords(cm
, doc
.sel
.primary().head
, "div");
1240 var wrapOff
= display
.wrapper
.getBoundingClientRect(), lineOff
= display
.lineDiv
.getBoundingClientRect();
1241 var top
= Math
.max(0, Math
.min(display
.wrapper
.clientHeight
- 10,
1242 headPos
.top
+ lineOff
.top
- wrapOff
.top
));
1243 var left
= Math
.max(0, Math
.min(display
.wrapper
.clientWidth
- 10,
1244 headPos
.left
+ lineOff
.left
- wrapOff
.left
));
1245 display
.inputDiv
.style
.top
= top
+ "px";
1246 display
.inputDiv
.style
.left
= left
+ "px";
1249 removeChildrenAndAdd(display
.cursorDiv
, curFragment
);
1250 removeChildrenAndAdd(display
.selectionDiv
, selFragment
);
1253 // Draws a cursor for the given range
1254 function updateSelectionCursor(cm
, range
, output
) {
1255 var pos
= cursorCoords(cm
, range
.head
, "div");
1257 var cursor
= output
.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
1258 cursor
.style
.left
= pos
.left
+ "px";
1259 cursor
.style
.top
= pos
.top
+ "px";
1260 cursor
.style
.height
= Math
.max(0, pos
.bottom
- pos
.top
) * cm
.options
.cursorHeight
+ "px";
1263 // Secondary cursor, shown when on a 'jump' in bi-directional text
1264 var otherCursor
= output
.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"));
1265 otherCursor
.style
.display
= "";
1266 otherCursor
.style
.left
= pos
.other
.left
+ "px";
1267 otherCursor
.style
.top
= pos
.other
.top
+ "px";
1268 otherCursor
.style
.height
= (pos
.other
.bottom
- pos
.other
.top
) * .85 + "px";
1272 // Draws the given range as a highlighted selection
1273 function updateSelectionRange(cm
, range
, output
) {
1274 var display
= cm
.display
, doc
= cm
.doc
;
1275 var fragment
= document
.createDocumentFragment();
1276 var padding
= paddingH(cm
.display
), leftSide
= padding
.left
, rightSide
= display
.lineSpace
.offsetWidth
- padding
.right
;
1278 function add(left
, top
, width
, bottom
) {
1279 if (top
< 0) top
= 0;
1280 fragment
.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left
+
1281 "px; top: " + top
+ "px; width: " + (width
== null ? rightSide
- left
: width
) +
1282 "px; height: " + (bottom
- top
) + "px"));
1285 function drawForLine(line
, fromArg
, toArg
) {
1286 var lineObj
= getLine(doc
, line
);
1287 var lineLen
= lineObj
.text
.length
;
1289 function coords(ch
, bias
) {
1290 return charCoords(cm
, Pos(line
, ch
), "div", lineObj
, bias
);
1293 iterateBidiSections(getOrder(lineObj
), fromArg
|| 0, toArg
== null ? lineLen
: toArg
, function(from, to
, dir
) {
1294 var leftPos
= coords(from, "left"), rightPos
, left
, right
;
1297 left
= right
= leftPos
.left
;
1299 rightPos
= coords(to
- 1, "right");
1300 if (dir
== "rtl") { var tmp
= leftPos
; leftPos
= rightPos
; rightPos
= tmp
; }
1301 left
= leftPos
.left
;
1302 right
= rightPos
.right
;
1304 if (fromArg
== null && from == 0) left
= leftSide
;
1305 if (rightPos
.top
- leftPos
.top
> 3) { // Different lines, draw top part
1306 add(left
, leftPos
.top
, null, leftPos
.bottom
);
1308 if (leftPos
.bottom
< rightPos
.top
) add(left
, leftPos
.bottom
, null, rightPos
.top
);
1310 if (toArg
== null && to
== lineLen
) right
= rightSide
;
1311 if (!start
|| leftPos
.top
< start
.top
|| leftPos
.top
== start
.top
&& leftPos
.left
< start
.left
)
1313 if (!end
|| rightPos
.bottom
> end
.bottom
|| rightPos
.bottom
== end
.bottom
&& rightPos
.right
> end
.right
)
1315 if (left
< leftSide
+ 1) left
= leftSide
;
1316 add(left
, rightPos
.top
, right
- left
, rightPos
.bottom
);
1318 return {start
: start
, end
: end
};
1321 var sFrom
= range
.from(), sTo
= range
.to();
1322 if (sFrom
.line
== sTo
.line
) {
1323 drawForLine(sFrom
.line
, sFrom
.ch
, sTo
.ch
);
1325 var fromLine
= getLine(doc
, sFrom
.line
), toLine
= getLine(doc
, sTo
.line
);
1326 var singleVLine
= visualLine(fromLine
) == visualLine(toLine
);
1327 var leftEnd
= drawForLine(sFrom
.line
, sFrom
.ch
, singleVLine
? fromLine
.text
.length
+ 1 : null).end
;
1328 var rightStart
= drawForLine(sTo
.line
, singleVLine
? 0 : null, sTo
.ch
).start
;
1330 if (leftEnd
.top
< rightStart
.top
- 2) {
1331 add(leftEnd
.right
, leftEnd
.top
, null, leftEnd
.bottom
);
1332 add(leftSide
, rightStart
.top
, rightStart
.left
, rightStart
.bottom
);
1334 add(leftEnd
.right
, leftEnd
.top
, rightStart
.left
- leftEnd
.right
, leftEnd
.bottom
);
1337 if (leftEnd
.bottom
< rightStart
.top
)
1338 add(leftSide
, leftEnd
.bottom
, null, rightStart
.top
);
1341 output
.appendChild(fragment
);
1345 function restartBlink(cm
) {
1346 if (!cm
.state
.focused
) return;
1347 var display
= cm
.display
;
1348 clearInterval(display
.blinker
);
1350 display
.cursorDiv
.style
.visibility
= "";
1351 if (cm
.options
.cursorBlinkRate
> 0)
1352 display
.blinker
= setInterval(function() {
1353 display
.cursorDiv
.style
.visibility
= (on
= !on
) ? "" : "hidden";
1354 }, cm
.options
.cursorBlinkRate
);
1359 function startWorker(cm
, time
) {
1360 if (cm
.doc
.mode
.startState
&& cm
.doc
.frontier
< cm
.display
.viewTo
)
1361 cm
.state
.highlight
.set(time
, bind(highlightWorker
, cm
));
1364 function highlightWorker(cm
) {
1366 if (doc
.frontier
< doc
.first
) doc
.frontier
= doc
.first
;
1367 if (doc
.frontier
>= cm
.display
.viewTo
) return;
1368 var end
= +new Date
+ cm
.options
.workTime
;
1369 var state
= copyState(doc
.mode
, getStateBefore(cm
, doc
.frontier
));
1371 runInOp(cm
, function() {
1372 doc
.iter(doc
.frontier
, Math
.min(doc
.first
+ doc
.size
, cm
.display
.viewTo
+ 500), function(line
) {
1373 if (doc
.frontier
>= cm
.display
.viewFrom
) { // Visible
1374 var oldStyles
= line
.styles
;
1375 line
.styles
= highlightLine(cm
, line
, state
, true);
1376 var ischange
= !oldStyles
|| oldStyles
.length
!= line
.styles
.length
;
1377 for (var i
= 0; !ischange
&& i
< oldStyles
.length
; ++i
) ischange
= oldStyles
[i
] != line
.styles
[i
];
1378 if (ischange
) regLineChange(cm
, doc
.frontier
, "text");
1379 line
.stateAfter
= copyState(doc
.mode
, state
);
1381 processLine(cm
, line
.text
, state
);
1382 line
.stateAfter
= doc
.frontier
% 5 == 0 ? copyState(doc
.mode
, state
) : null;
1385 if (+new Date
> end
) {
1386 startWorker(cm
, cm
.options
.workDelay
);
1393 // Finds the line to start with when starting a parse. Tries to
1394 // find a line with a stateAfter, so that it can start with a
1395 // valid state. If that fails, it returns the line with the
1396 // smallest indentation, which tends to need the least context to
1398 function findStartLine(cm
, n
, precise
) {
1399 var minindent
, minline
, doc
= cm
.doc
;
1400 var lim
= precise
? -1 : n
- (cm
.doc
.mode
.innerMode
? 1000 : 100);
1401 for (var search
= n
; search
> lim
; --search
) {
1402 if (search
<= doc
.first
) return doc
.first
;
1403 var line
= getLine(doc
, search
- 1);
1404 if (line
.stateAfter
&& (!precise
|| search
<= doc
.frontier
)) return search
;
1405 var indented
= countColumn(line
.text
, null, cm
.options
.tabSize
);
1406 if (minline
== null || minindent
> indented
) {
1407 minline
= search
- 1;
1408 minindent
= indented
;
1414 function getStateBefore(cm
, n
, precise
) {
1415 var doc
= cm
.doc
, display
= cm
.display
;
1416 if (!doc
.mode
.startState
) return true;
1417 var pos
= findStartLine(cm
, n
, precise
), state
= pos
> doc
.first
&& getLine(doc
, pos
-1).stateAfter
;
1418 if (!state
) state
= startState(doc
.mode
);
1419 else state
= copyState(doc
.mode
, state
);
1420 doc
.iter(pos
, n
, function(line
) {
1421 processLine(cm
, line
.text
, state
);
1422 var save
= pos
== n
- 1 || pos
% 5 == 0 || pos
>= display
.viewFrom
&& pos
< display
.viewTo
;
1423 line
.stateAfter
= save
? copyState(doc
.mode
, state
) : null;
1426 if (precise
) doc
.frontier
= pos
;
1430 // POSITION MEASUREMENT
1432 function paddingTop(display
) {return display
.lineSpace
.offsetTop
;}
1433 function paddingVert(display
) {return display
.mover
.offsetHeight
- display
.lineSpace
.offsetHeight
;}
1434 function paddingH(display
) {
1435 if (display
.cachedPaddingH
) return display
.cachedPaddingH
;
1436 var e
= removeChildrenAndAdd(display
.measure
, elt("pre", "x"));
1437 var style
= window
.getComputedStyle
? window
.getComputedStyle(e
) : e
.currentStyle
;
1438 return display
.cachedPaddingH
= {left
: parseInt(style
.paddingLeft
),
1439 right
: parseInt(style
.paddingRight
)};
1442 // Ensure the lineView.wrapping.heights array is populated. This is
1443 // an array of bottom offsets for the lines that make up a drawn
1444 // line. When lineWrapping is on, there might be more than one
1446 function ensureLineHeights(cm
, lineView
, rect
) {
1447 var wrapping
= cm
.options
.lineWrapping
;
1448 var curWidth
= wrapping
&& cm
.display
.scroller
.clientWidth
;
1449 if (!lineView
.measure
.heights
|| wrapping
&& lineView
.measure
.width
!= curWidth
) {
1450 var heights
= lineView
.measure
.heights
= [];
1452 lineView
.measure
.width
= curWidth
;
1453 var rects
= lineView
.text
.firstChild
.getClientRects();
1454 for (var i
= 0; i
< rects
.length
- 1; i
++) {
1455 var cur
= rects
[i
], next
= rects
[i
+ 1];
1456 if (Math
.abs(cur
.bottom
- next
.bottom
) > 2)
1457 heights
.push((cur
.bottom
+ next
.top
) / 2 - rect
.top
);
1460 heights
.push(rect
.bottom
- rect
.top
);
1464 // Find a line map (mapping character offsets to text nodes) and a
1465 // measurement cache for the given line number. (A line view might
1466 // contain multiple lines when collapsed ranges are present.)
1467 function mapFromLineView(lineView
, line
, lineN
) {
1468 if (lineView
.line
== line
)
1469 return {map
: lineView
.measure
.map
, cache
: lineView
.measure
.cache
};
1470 for (var i
= 0; i
< lineView
.rest
.length
; i
++)
1471 if (lineView
.rest
[i
] == line
)
1472 return {map
: lineView
.measure
.maps
[i
], cache
: lineView
.measure
.caches
[i
]};
1473 for (var i
= 0; i
< lineView
.rest
.length
; i
++)
1474 if (lineNo(lineView
.rest
[i
]) > lineN
)
1475 return {map
: lineView
.measure
.maps
[i
], cache
: lineView
.measure
.caches
[i
], before
: true};
1478 // Render a line into the hidden node display.externalMeasured. Used
1479 // when measurement is needed for a line that's not in the viewport.
1480 function updateExternalMeasurement(cm
, line
) {
1481 line
= visualLine(line
);
1482 var lineN
= lineNo(line
);
1483 var view
= cm
.display
.externalMeasured
= new LineView(cm
.doc
, line
, lineN
);
1485 var built
= view
.built
= buildLineContent(cm
, view
);
1486 view
.text
= built
.pre
;
1487 removeChildrenAndAdd(cm
.display
.lineMeasure
, built
.pre
);
1491 // Get a {top, bottom, left, right} box (in line-local coordinates)
1492 // for a given character.
1493 function measureChar(cm
, line
, ch
, bias
) {
1494 return measureCharPrepared(cm
, prepareMeasureForLine(cm
, line
), ch
, bias
);
1497 // Find a line view that corresponds to the given line number.
1498 function findViewForLine(cm
, lineN
) {
1499 if (lineN
>= cm
.display
.viewFrom
&& lineN
< cm
.display
.viewTo
)
1500 return cm
.display
.view
[findViewIndex(cm
, lineN
)];
1501 var ext
= cm
.display
.externalMeasured
;
1502 if (ext
&& lineN
>= ext
.lineN
&& lineN
< ext
.lineN
+ ext
.size
)
1506 // Measurement can be split in two steps, the set-up work that
1507 // applies to the whole line, and the measurement of the actual
1508 // character. Functions like coordsChar, that need to do a lot of
1509 // measurements in a row, can thus ensure that the set-up work is
1511 function prepareMeasureForLine(cm
, line
) {
1512 var lineN
= lineNo(line
);
1513 var view
= findViewForLine(cm
, lineN
);
1514 if (view
&& !view
.text
)
1516 else if (view
&& view
.changes
)
1517 updateLineForChanges(cm
, view
, lineN
, getDimensions(cm
));
1519 view
= updateExternalMeasurement(cm
, line
);
1521 var info
= mapFromLineView(view
, line
, lineN
);
1523 line
: line
, view
: view
, rect
: null,
1524 map
: info
.map
, cache
: info
.cache
, before
: info
.before
,
1529 // Given a prepared measurement object, measures the position of an
1530 // actual character (or fetches it from the cache).
1531 function measureCharPrepared(cm
, prepared
, ch
, bias
) {
1532 if (prepared
.before
) ch
= -1;
1533 var key
= ch
+ (bias
|| ""), found
;
1534 if (prepared
.cache
.hasOwnProperty(key
)) {
1535 found
= prepared
.cache
[key
];
1538 prepared
.rect
= prepared
.view
.text
.getBoundingClientRect();
1539 if (!prepared
.hasHeights
) {
1540 ensureLineHeights(cm
, prepared
.view
, prepared
.rect
);
1541 prepared
.hasHeights
= true;
1543 found
= measureCharInner(cm
, prepared
, ch
, bias
);
1544 if (!found
.bogus
) prepared
.cache
[key
] = found
;
1546 return {left
: found
.left
, right
: found
.right
, top
: found
.top
, bottom
: found
.bottom
};
1549 var nullRect
= {left
: 0, right
: 0, top
: 0, bottom
: 0};
1551 function measureCharInner(cm
, prepared
, ch
, bias
) {
1552 var map
= prepared
.map
;
1554 var node
, start
, end
, collapse
;
1555 // First, search the line map for the text node corresponding to,
1556 // or closest to, the target character.
1557 for (var i
= 0; i
< map
.length
; i
+= 3) {
1558 var mStart
= map
[i
], mEnd
= map
[i
+ 1];
1562 } else if (ch
< mEnd
) {
1563 start
= ch
- mStart
;
1565 } else if (i
== map
.length
- 3 || ch
== mEnd
&& map
[i
+ 3] > ch
) {
1566 end
= mEnd
- mStart
;
1568 if (ch
>= mEnd
) collapse
= "right";
1570 if (start
!= null) {
1572 if (mStart
== mEnd
&& bias
== (node
.insertLeft
? "left" : "right"))
1574 if (bias
== "left" && start
== 0)
1575 while (i
&& map
[i
- 2] == map
[i
- 3] && map
[i
- 1].insertLeft
) {
1576 node
= map
[(i
-= 3) + 2];
1579 if (bias
== "right" && start
== mEnd
- mStart
)
1580 while (i
< map
.length
- 3 && map
[i
+ 3] == map
[i
+ 4] && !map
[i
+ 5].insertLeft
) {
1581 node
= map
[(i
+= 3) + 2];
1589 if (node
.nodeType
== 3) { // If it is a text node, use a range to retrieve the coordinates.
1590 while (start
&& isExtendingChar(prepared
.line
.text
.charAt(mStart
+ start
))) --start
;
1591 while (mStart
+ end
< mEnd
&& isExtendingChar(prepared
.line
.text
.charAt(mStart
+ end
))) ++end
;
1592 if (ie_upto8
&& start
== 0 && end
== mEnd
- mStart
) {
1593 rect
= node
.parentNode
.getBoundingClientRect();
1594 } else if (ie
&& cm
.options
.lineWrapping
) {
1595 var rects
= range(node
, start
, end
).getClientRects();
1597 rect
= rects
[bias
== "right" ? rects
.length
- 1 : 0];
1601 rect
= range(node
, start
, end
).getBoundingClientRect();
1603 } else { // If it is a widget, simply get the box for the whole widget.
1604 if (start
> 0) collapse
= bias
= "right";
1606 if (cm
.options
.lineWrapping
&& (rects
= node
.getClientRects()).length
> 1)
1607 rect
= rects
[bias
== "right" ? rects
.length
- 1 : 0];
1609 rect
= node
.getBoundingClientRect();
1611 if (ie_upto8
&& !start
&& (!rect
|| !rect
.left
&& !rect
.right
)) {
1612 var rSpan
= node
.parentNode
.getClientRects()[0];
1614 rect
= {left
: rSpan
.left
, right
: rSpan
.left
+ charWidth(cm
.display
), top
: rSpan
.top
, bottom
: rSpan
.bottom
};
1619 var top
, bot
= (rect
.bottom
+ rect
.top
) / 2 - prepared
.rect
.top
;
1620 var heights
= prepared
.view
.measure
.heights
;
1621 for (var i
= 0; i
< heights
.length
- 1; i
++)
1622 if (bot
< heights
[i
]) break;
1623 top
= i
? heights
[i
- 1] : 0; bot
= heights
[i
];
1624 var result
= {left
: (collapse
== "right" ? rect
.right
: rect
.left
) - prepared
.rect
.left
,
1625 right
: (collapse
== "left" ? rect
.left
: rect
.right
) - prepared
.rect
.left
,
1626 top
: top
, bottom
: bot
};
1627 if (!rect
.left
&& !rect
.right
) result
.bogus
= true;
1631 function clearLineMeasurementCacheFor(lineView
) {
1632 if (lineView
.measure
) {
1633 lineView
.measure
.cache
= {};
1634 lineView
.measure
.heights
= null;
1635 if (lineView
.rest
) for (var i
= 0; i
< lineView
.rest
.length
; i
++)
1636 lineView
.measure
.caches
[i
] = {};
1640 function clearLineMeasurementCache(cm
) {
1641 cm
.display
.externalMeasure
= null;
1642 removeChildren(cm
.display
.lineMeasure
);
1643 for (var i
= 0; i
< cm
.display
.view
.length
; i
++)
1644 clearLineMeasurementCacheFor(cm
.display
.view
[i
]);
1647 function clearCaches(cm
) {
1648 clearLineMeasurementCache(cm
);
1649 cm
.display
.cachedCharWidth
= cm
.display
.cachedTextHeight
= cm
.display
.cachedPaddingH
= null;
1650 if (!cm
.options
.lineWrapping
) cm
.display
.maxLineChanged
= true;
1651 cm
.display
.lineNumChars
= null;
1654 function pageScrollX() { return window
.pageXOffset
|| (document
.documentElement
|| document
.body
).scrollLeft
; }
1655 function pageScrollY() { return window
.pageYOffset
|| (document
.documentElement
|| document
.body
).scrollTop
; }
1657 // Converts a {top, bottom, left, right} box from line-local
1658 // coordinates into another coordinate system. Context may be one of
1659 // "line", "div" (display.lineDiv), "local"/null (editor), or "page".
1660 function intoCoordSystem(cm
, lineObj
, rect
, context
) {
1661 if (lineObj
.widgets
) for (var i
= 0; i
< lineObj
.widgets
.length
; ++i
) if (lineObj
.widgets
[i
].above
) {
1662 var size
= widgetHeight(lineObj
.widgets
[i
]);
1663 rect
.top
+= size
; rect
.bottom
+= size
;
1665 if (context
== "line") return rect
;
1666 if (!context
) context
= "local";
1667 var yOff
= heightAtLine(lineObj
);
1668 if (context
== "local") yOff
+= paddingTop(cm
.display
);
1669 else yOff
-= cm
.display
.viewOffset
;
1670 if (context
== "page" || context
== "window") {
1671 var lOff
= cm
.display
.lineSpace
.getBoundingClientRect();
1672 yOff
+= lOff
.top
+ (context
== "window" ? 0 : pageScrollY());
1673 var xOff
= lOff
.left
+ (context
== "window" ? 0 : pageScrollX());
1674 rect
.left
+= xOff
; rect
.right
+= xOff
;
1676 rect
.top
+= yOff
; rect
.bottom
+= yOff
;
1680 // Coverts a box from "div" coords to another coordinate system.
1681 // Context may be "window", "page", "div", or "local"/null.
1682 function fromCoordSystem(cm
, coords
, context
) {
1683 if (context
== "div") return coords
;
1684 var left
= coords
.left
, top
= coords
.top
;
1685 // First move into "page" coordinate system
1686 if (context
== "page") {
1687 left
-= pageScrollX();
1688 top
-= pageScrollY();
1689 } else if (context
== "local" || !context
) {
1690 var localBox
= cm
.display
.sizer
.getBoundingClientRect();
1691 left
+= localBox
.left
;
1692 top
+= localBox
.top
;
1695 var lineSpaceBox
= cm
.display
.lineSpace
.getBoundingClientRect();
1696 return {left
: left
- lineSpaceBox
.left
, top
: top
- lineSpaceBox
.top
};
1699 function charCoords(cm
, pos
, context
, lineObj
, bias
) {
1700 if (!lineObj
) lineObj
= getLine(cm
.doc
, pos
.line
);
1701 return intoCoordSystem(cm
, lineObj
, measureChar(cm
, lineObj
, pos
.ch
, bias
), context
);
1704 // Returns a box for a given cursor position, which may have an
1705 // 'other' property containing the position of the secondary cursor
1706 // on a bidi boundary.
1707 function cursorCoords(cm
, pos
, context
, lineObj
, preparedMeasure
) {
1708 lineObj
= lineObj
|| getLine(cm
.doc
, pos
.line
);
1709 if (!preparedMeasure
) preparedMeasure
= prepareMeasureForLine(cm
, lineObj
);
1710 function get(ch
, right
) {
1711 var m
= measureCharPrepared(cm
, preparedMeasure
, ch
, right
? "right" : "left");
1712 if (right
) m
.left
= m
.right
; else m
.right
= m
.left
;
1713 return intoCoordSystem(cm
, lineObj
, m
, context
);
1715 function getBidi(ch
, partPos
) {
1716 var part
= order
[partPos
], right
= part
.level
% 2;
1717 if (ch
== bidiLeft(part
) && partPos
&& part
.level
< order
[partPos
- 1].level
) {
1718 part
= order
[--partPos
];
1719 ch
= bidiRight(part
) - (part
.level
% 2 ? 0 : 1);
1721 } else if (ch
== bidiRight(part
) && partPos
< order
.length
- 1 && part
.level
< order
[partPos
+ 1].level
) {
1722 part
= order
[++partPos
];
1723 ch
= bidiLeft(part
) - part
.level
% 2;
1726 if (right
&& ch
== part
.to
&& ch
> part
.from) return get(ch
- 1);
1727 return get(ch
, right
);
1729 var order
= getOrder(lineObj
), ch
= pos
.ch
;
1730 if (!order
) return get(ch
);
1731 var partPos
= getBidiPartAt(order
, ch
);
1732 var val
= getBidi(ch
, partPos
);
1733 if (bidiOther
!= null) val
.other
= getBidi(ch
, bidiOther
);
1737 // Used to cheaply estimate the coordinates for a position. Used for
1738 // intermediate scroll updates.
1739 function estimateCoords(cm
, pos
) {
1740 var left
= 0, pos
= clipPos(cm
.doc
, pos
);
1741 if (!cm
.options
.lineWrapping
) left
= charWidth(cm
.display
) * pos
.ch
;
1742 var lineObj
= getLine(cm
.doc
, pos
.line
);
1743 var top
= heightAtLine(lineObj
) + paddingTop(cm
.display
);
1744 return {left
: left
, right
: left
, top
: top
, bottom
: top
+ lineObj
.height
};
1747 // Positions returned by coordsChar contain some extra information.
1748 // xRel is the relative x position of the input coordinates compared
1749 // to the found position (so xRel > 0 means the coordinates are to
1750 // the right of the character position, for example). When outside
1751 // is true, that means the coordinates lie outside the line's
1753 function PosWithInfo(line
, ch
, outside
, xRel
) {
1754 var pos
= Pos(line
, ch
);
1756 if (outside
) pos
.outside
= true;
1760 // Compute the character position closest to the given coordinates.
1761 // Input must be lineSpace-local ("div" coordinate system).
1762 function coordsChar(cm
, x
, y
) {
1764 y
+= cm
.display
.viewOffset
;
1765 if (y
< 0) return PosWithInfo(doc
.first
, 0, true, -1);
1766 var lineN
= lineAtHeight(doc
, y
), last
= doc
.first
+ doc
.size
- 1;
1768 return PosWithInfo(doc
.first
+ doc
.size
- 1, getLine(doc
, last
).text
.length
, true, 1);
1771 var lineObj
= getLine(doc
, lineN
);
1773 var found
= coordsCharInner(cm
, lineObj
, lineN
, x
, y
);
1774 var merged
= collapsedSpanAtEnd(lineObj
);
1775 var mergedPos
= merged
&& merged
.find(0, true);
1776 if (merged
&& (found
.ch
> mergedPos
.from.ch
|| found
.ch
== mergedPos
.from.ch
&& found
.xRel
> 0))
1777 lineN
= lineNo(lineObj
= mergedPos
.to
.line
);
1783 function coordsCharInner(cm
, lineObj
, lineNo
, x
, y
) {
1784 var innerOff
= y
- heightAtLine(lineObj
);
1785 var wrongLine
= false, adjust
= 2 * cm
.display
.wrapper
.clientWidth
;
1786 var preparedMeasure
= prepareMeasureForLine(cm
, lineObj
);
1789 var sp
= cursorCoords(cm
, Pos(lineNo
, ch
), "line", lineObj
, preparedMeasure
);
1791 if (innerOff
> sp
.bottom
) return sp
.left
- adjust
;
1792 else if (innerOff
< sp
.top
) return sp
.left
+ adjust
;
1793 else wrongLine
= false;
1797 var bidi
= getOrder(lineObj
), dist
= lineObj
.text
.length
;
1798 var from = lineLeft(lineObj
), to
= lineRight(lineObj
);
1799 var fromX
= getX(from), fromOutside
= wrongLine
, toX
= getX(to
), toOutside
= wrongLine
;
1801 if (x
> toX
) return PosWithInfo(lineNo
, to
, toOutside
, 1);
1802 // Do a binary search between these bounds.
1804 if (bidi
? to
== from || to
== moveVisually(lineObj
, from, 1) : to
- from <= 1) {
1805 var ch
= x
< fromX
|| x
- fromX
<= toX
- x
? from : to
;
1806 var xDiff
= x
- (ch
== from ? fromX
: toX
);
1807 while (isExtendingChar(lineObj
.text
.charAt(ch
))) ++ch
;
1808 var pos
= PosWithInfo(lineNo
, ch
, ch
== from ? fromOutside
: toOutside
,
1809 xDiff
< -1 ? -1 : xDiff
> 1 ? 1 : 0);
1812 var step
= Math
.ceil(dist
/ 2), middle
= from + step
;
1815 for (var i
= 0; i
< step
; ++i
) middle
= moveVisually(lineObj
, middle
, 1);
1817 var middleX
= getX(middle
);
1818 if (middleX
> x
) {to
= middle
; toX
= middleX
; if (toOutside
= wrongLine
) toX
+= 1000; dist
= step
;}
1819 else {from = middle
; fromX
= middleX
; fromOutside
= wrongLine
; dist
-= step
;}
1824 // Compute the default text height.
1825 function textHeight(display
) {
1826 if (display
.cachedTextHeight
!= null) return display
.cachedTextHeight
;
1827 if (measureText
== null) {
1828 measureText
= elt("pre");
1829 // Measure a bunch of lines, for browsers that compute
1830 // fractional heights.
1831 for (var i
= 0; i
< 49; ++i
) {
1832 measureText
.appendChild(document
.createTextNode("x"));
1833 measureText
.appendChild(elt("br"));
1835 measureText
.appendChild(document
.createTextNode("x"));
1837 removeChildrenAndAdd(display
.measure
, measureText
);
1838 var height
= measureText
.offsetHeight
/ 50;
1839 if (height
> 3) display
.cachedTextHeight
= height
;
1840 removeChildren(display
.measure
);
1844 // Compute the default character width.
1845 function charWidth(display
) {
1846 if (display
.cachedCharWidth
!= null) return display
.cachedCharWidth
;
1847 var anchor
= elt("span", "xxxxxxxxxx");
1848 var pre
= elt("pre", [anchor
]);
1849 removeChildrenAndAdd(display
.measure
, pre
);
1850 var rect
= anchor
.getBoundingClientRect(), width
= (rect
.right
- rect
.left
) / 10;
1851 if (width
> 2) display
.cachedCharWidth
= width
;
1857 // Operations are used to wrap a series of changes to the editor
1858 // state in such a way that each change won't have to update the
1859 // cursor and display (which would be awkward, slow, and
1860 // error-prone). Instead, display updates are batched and then all
1861 // combined and executed at once.
1864 // Start a new operation.
1865 function startOperation(cm
) {
1867 viewChanged
: false, // Flag that indicates that lines might need to be redrawn
1868 startHeight
: cm
.doc
.height
, // Used to detect need to update scrollbar
1869 forceUpdate
: false, // Used to force a redraw
1870 updateInput
: null, // Whether to reset the input textarea
1871 typing
: false, // Whether this reset should be careful to leave existing text (for compositing)
1872 changeObjs
: null, // Accumulated changes, for firing change events
1873 cursorActivity
: false, // Whether to fire a cursorActivity event
1874 selectionChanged
: false, // Whether the selection needs to be redrawn
1875 updateMaxLine
: false, // Set when the widest line needs to be determined anew
1876 scrollLeft
: null, scrollTop
: null, // Intermediate scroll position, not pushed to DOM yet
1877 scrollToPos
: null, // Used to scroll to a specific position
1878 id
: ++nextOpId
// Unique ID
1880 if (!delayedCallbackDepth
++) delayedCallbacks
= [];
1883 // Finish an operation, updating the display and signalling delayed events
1884 function endOperation(cm
) {
1885 var op
= cm
.curOp
, doc
= cm
.doc
, display
= cm
.display
;
1888 if (op
.updateMaxLine
) findMaxLine(cm
);
1890 // If it looks like an update might be needed, call updateDisplay
1891 if (op
.viewChanged
|| op
.forceUpdate
|| op
.scrollTop
!= null ||
1892 op
.scrollToPos
&& (op
.scrollToPos
.from.line
< display
.viewFrom
||
1893 op
.scrollToPos
.to
.line
>= display
.viewTo
) ||
1894 display
.maxLineChanged
&& cm
.options
.lineWrapping
) {
1895 var updated
= updateDisplay(cm
, {top
: op
.scrollTop
, ensure
: op
.scrollToPos
}, op
.forceUpdate
);
1896 if (cm
.display
.scroller
.offsetHeight
) cm
.doc
.scrollTop
= cm
.display
.scroller
.scrollTop
;
1898 // If no update was run, but the selection changed, redraw that.
1899 if (!updated
&& op
.selectionChanged
) updateSelection(cm
);
1900 if (!updated
&& op
.startHeight
!= cm
.doc
.height
) updateScrollbars(cm
);
1902 // Propagate the scroll position to the actual DOM scroller
1903 if (op
.scrollTop
!= null && display
.scroller
.scrollTop
!= op
.scrollTop
) {
1904 var top
= Math
.max(0, Math
.min(display
.scroller
.scrollHeight
- display
.scroller
.clientHeight
, op
.scrollTop
));
1905 display
.scroller
.scrollTop
= display
.scrollbarV
.scrollTop
= doc
.scrollTop
= top
;
1907 if (op
.scrollLeft
!= null && display
.scroller
.scrollLeft
!= op
.scrollLeft
) {
1908 var left
= Math
.max(0, Math
.min(display
.scroller
.scrollWidth
- display
.scroller
.clientWidth
, op
.scrollLeft
));
1909 display
.scroller
.scrollLeft
= display
.scrollbarH
.scrollLeft
= doc
.scrollLeft
= left
;
1910 alignHorizontally(cm
);
1912 // If we need to scroll a specific position into view, do so.
1913 if (op
.scrollToPos
) {
1914 var coords
= scrollPosIntoView(cm
, clipPos(cm
.doc
, op
.scrollToPos
.from),
1915 clipPos(cm
.doc
, op
.scrollToPos
.to
), op
.scrollToPos
.margin
);
1916 if (op
.scrollToPos
.isCursor
&& cm
.state
.focused
) maybeScrollWindow(cm
, coords
);
1919 if (op
.selectionChanged
) restartBlink(cm
);
1921 if (cm
.state
.focused
&& op
.updateInput
)
1922 resetInput(cm
, op
.typing
);
1924 // Fire events for markers that are hidden/unidden by editing or
1926 var hidden
= op
.maybeHiddenMarkers
, unhidden
= op
.maybeUnhiddenMarkers
;
1927 if (hidden
) for (var i
= 0; i
< hidden
.length
; ++i
)
1928 if (!hidden
[i
].lines
.length
) signal(hidden
[i
], "hide");
1929 if (unhidden
) for (var i
= 0; i
< unhidden
.length
; ++i
)
1930 if (unhidden
[i
].lines
.length
) signal(unhidden
[i
], "unhide");
1933 if (!--delayedCallbackDepth
) {
1934 delayed
= delayedCallbacks
;
1935 delayedCallbacks
= null;
1937 // Fire change events, and delayed event handlers
1938 if (op
.changeObjs
) {
1939 for (var i
= 0; i
< op
.changeObjs
.length
; i
++)
1940 signal(cm
, "change", cm
, op
.changeObjs
[i
]);
1941 signal(cm
, "changes", cm
, op
.changeObjs
);
1943 if (op
.cursorActivity
) signal(cm
, "cursorActivity", cm
);
1944 if (delayed
) for (var i
= 0; i
< delayed
.length
; ++i
) delayed
[i
]();
1947 // Run the given function in an operation
1948 function runInOp(cm
, f
) {
1949 if (cm
.curOp
) return f();
1952 finally { endOperation(cm
); }
1954 // Wraps a function in an operation. Returns the wrapped function.
1955 function operation(cm
, f
) {
1957 if (cm
.curOp
) return f
.apply(cm
, arguments
);
1959 try { return f
.apply(cm
, arguments
); }
1960 finally { endOperation(cm
); }
1963 // Used to add methods to editor and doc instances, wrapping them in
1965 function methodOp(f
) {
1967 if (this.curOp
) return f
.apply(this, arguments
);
1968 startOperation(this);
1969 try { return f
.apply(this, arguments
); }
1970 finally { endOperation(this); }
1973 function docMethodOp(f
) {
1976 if (!cm
|| cm
.curOp
) return f
.apply(this, arguments
);
1978 try { return f
.apply(this, arguments
); }
1979 finally { endOperation(cm
); }
1985 // These objects are used to represent the visible (currently drawn)
1986 // part of the document. A LineView may correspond to multiple
1987 // logical lines, if those are connected by collapsed ranges.
1988 function LineView(doc
, line
, lineN
) {
1989 // The starting line
1991 // Continuing lines, if any
1992 this.rest
= visualLineContinued(line
);
1993 // Number of logical lines in this visual line
1994 this.size
= this.rest
? lineNo(lst(this.rest
)) - lineN
+ 1 : 1;
1995 this.node
= this.text
= null;
1996 this.hidden
= lineIsHidden(doc
, line
);
1999 // Create a range of LineView objects for the given lines.
2000 function buildViewArray(cm
, from, to
) {
2001 var array
= [], nextPos
;
2002 for (var pos
= from; pos
< to
; pos
= nextPos
) {
2003 var view
= new LineView(cm
.doc
, getLine(cm
.doc
, pos
), pos
);
2004 nextPos
= pos
+ view
.size
;
2010 // Updates the display.view data structure for a given change to the
2011 // document. From and to are in pre-change coordinates. Lendiff is
2012 // the amount of lines added or subtracted by the change. This is
2013 // used for changes that span multiple lines, or change the way
2014 // lines are divided into visual lines. regLineChange (below)
2015 // registers single-line changes.
2016 function regChange(cm
, from, to
, lendiff
) {
2017 if (from == null) from = cm
.doc
.first
;
2018 if (to
== null) to
= cm
.doc
.first
+ cm
.doc
.size
;
2019 if (!lendiff
) lendiff
= 0;
2021 var display
= cm
.display
;
2022 if (lendiff
&& to
< display
.viewTo
&&
2023 (display
.updateLineNumbers
== null || display
.updateLineNumbers
> from))
2024 display
.updateLineNumbers
= from;
2026 cm
.curOp
.viewChanged
= true;
2028 if (from >= display
.viewTo
) { // Change after
2029 if (sawCollapsedSpans
&& visualLineNo(cm
.doc
, from) < display
.viewTo
)
2031 } else if (to
<= display
.viewFrom
) { // Change before
2032 if (sawCollapsedSpans
&& visualLineEndNo(cm
.doc
, to
+ lendiff
) > display
.viewFrom
) {
2035 display
.viewFrom
+= lendiff
;
2036 display
.viewTo
+= lendiff
;
2038 } else if (from <= display
.viewFrom
&& to
>= display
.viewTo
) { // Full overlap
2040 } else if (from <= display
.viewFrom
) { // Top overlap
2041 var cut
= viewCuttingPoint(cm
, to
, to
+ lendiff
, 1);
2043 display
.view
= display
.view
.slice(cut
.index
);
2044 display
.viewFrom
= cut
.lineN
;
2045 display
.viewTo
+= lendiff
;
2049 } else if (to
>= display
.viewTo
) { // Bottom overlap
2050 var cut
= viewCuttingPoint(cm
, from, from, -1);
2052 display
.view
= display
.view
.slice(0, cut
.index
);
2053 display
.viewTo
= cut
.lineN
;
2057 } else { // Gap in the middle
2058 var cutTop
= viewCuttingPoint(cm
, from, from, -1);
2059 var cutBot
= viewCuttingPoint(cm
, to
, to
+ lendiff
, 1);
2060 if (cutTop
&& cutBot
) {
2061 display
.view
= display
.view
.slice(0, cutTop
.index
)
2062 .concat(buildViewArray(cm
, cutTop
.lineN
, cutBot
.lineN
))
2063 .concat(display
.view
.slice(cutBot
.index
));
2064 display
.viewTo
+= lendiff
;
2070 var ext
= display
.externalMeasured
;
2073 ext
.lineN
+= lendiff
;
2074 else if (from < ext
.lineN
+ ext
.size
)
2075 display
.externalMeasured
= null;
2079 // Register a change to a single line. Type must be one of "text",
2080 // "gutter", "class", "widget"
2081 function regLineChange(cm
, line
, type
) {
2082 cm
.curOp
.viewChanged
= true;
2083 var display
= cm
.display
, ext
= cm
.display
.externalMeasured
;
2084 if (ext
&& line
>= ext
.lineN
&& line
< ext
.lineN
+ ext
.size
)
2085 display
.externalMeasured
= null;
2087 if (line
< display
.viewFrom
|| line
>= display
.viewTo
) return;
2088 var lineView
= display
.view
[findViewIndex(cm
, line
)];
2089 if (lineView
.node
== null) return;
2090 var arr
= lineView
.changes
|| (lineView
.changes
= []);
2091 if (indexOf(arr
, type
) == -1) arr
.push(type
);
2095 function resetView(cm
) {
2096 cm
.display
.viewFrom
= cm
.display
.viewTo
= cm
.doc
.first
;
2097 cm
.display
.view
= [];
2098 cm
.display
.viewOffset
= 0;
2101 // Find the view element corresponding to a given line. Return null
2102 // when the line isn't visible.
2103 function findViewIndex(cm
, n
) {
2104 if (n
>= cm
.display
.viewTo
) return null;
2105 n
-= cm
.display
.viewFrom
;
2106 if (n
< 0) return null;
2107 var view
= cm
.display
.view
;
2108 for (var i
= 0; i
< view
.length
; i
++) {
2110 if (n
< 0) return i
;
2114 function viewCuttingPoint(cm
, oldN
, newN
, dir
) {
2115 var index
= findViewIndex(cm
, oldN
), diff
, view
= cm
.display
.view
;
2116 if (!sawCollapsedSpans
) return {index
: index
, lineN
: newN
};
2117 for (var i
= 0, n
= cm
.display
.viewFrom
; i
< index
; i
++)
2121 if (index
== view
.length
- 1) return null;
2122 diff
= (n
+ view
[index
].size
) - oldN
;
2127 oldN
+= diff
; newN
+= diff
;
2129 while (visualLineNo(cm
.doc
, newN
) != newN
) {
2130 if (index
== (dir
< 0 ? 0 : view
.length
- 1)) return null;
2131 newN
+= dir
* view
[index
- (dir
< 0 ? 1 : 0)].size
;
2134 return {index
: index
, lineN
: newN
};
2137 // Force the view to cover a given range, adding empty view element
2138 // or clipping off existing ones as needed.
2139 function adjustView(cm
, from, to
) {
2140 var display
= cm
.display
, view
= display
.view
;
2141 if (view
.length
== 0 || from >= display
.viewTo
|| to
<= display
.viewFrom
) {
2142 display
.view
= buildViewArray(cm
, from, to
);
2143 display
.viewFrom
= from;
2145 if (display
.viewFrom
> from)
2146 display
.view
= buildViewArray(cm
, from, display
.viewFrom
).concat(display
.view
);
2147 else if (display
.viewFrom
< from)
2148 display
.view
= display
.view
.slice(findViewIndex(cm
, from));
2149 display
.viewFrom
= from;
2150 if (display
.viewTo
< to
)
2151 display
.view
= display
.view
.concat(buildViewArray(cm
, display
.viewTo
, to
));
2152 else if (display
.viewTo
> to
)
2153 display
.view
= display
.view
.slice(0, findViewIndex(cm
, to
));
2155 display
.viewTo
= to
;
2158 // Count the number of lines in the view whose DOM representation is
2159 // out of date (or nonexistent).
2160 function countDirtyView(cm
) {
2161 var view
= cm
.display
.view
, dirty
= 0;
2162 for (var i
= 0; i
< view
.length
; i
++) {
2163 var lineView
= view
[i
];
2164 if (!lineView
.hidden
&& (!lineView
.node
|| lineView
.changes
)) ++dirty
;
2171 // Poll for input changes, using the normal rate of polling. This
2172 // runs as long as the editor is focused.
2173 function slowPoll(cm
) {
2174 if (cm
.display
.pollingFast
) return;
2175 cm
.display
.poll
.set(cm
.options
.pollInterval
, function() {
2177 if (cm
.state
.focused
) slowPoll(cm
);
2181 // When an event has just come in that is likely to add or change
2182 // something in the input textarea, we poll faster, to ensure that
2183 // the change appears on the screen quickly.
2184 function fastPoll(cm
) {
2186 cm
.display
.pollingFast
= true;
2188 var changed
= readInput(cm
);
2189 if (!changed
&& !missed
) {missed
= true; cm
.display
.poll
.set(60, p
);}
2190 else {cm
.display
.pollingFast
= false; slowPoll(cm
);}
2192 cm
.display
.poll
.set(20, p
);
2195 // Read input from the textarea, and update the document to match.
2196 // When something is selected, it is present in the textarea, and
2197 // selected (unless it is huge, in which case a placeholder is
2198 // used). When nothing is selected, the cursor sits after previously
2199 // seen text (can be empty), which is stored in prevInput (we must
2200 // not reset the textarea when typing, because that breaks IME).
2201 function readInput(cm
) {
2202 var input
= cm
.display
.input
, prevInput
= cm
.display
.prevInput
, doc
= cm
.doc
;
2203 // Since this is called a *lot*, try to bail out as cheaply as
2204 // possible when it is clear that nothing happened. hasSelection
2205 // will be the case when there is a lot of text in the textarea,
2206 // in which case reading its value would be expensive.
2207 if (!cm
.state
.focused
|| hasSelection(input
) || isReadOnly(cm
) || cm
.options
.disableInput
) return false;
2208 var text
= input
.value
;
2209 // If nothing changed, bail.
2210 if (text
== prevInput
&& !cm
.somethingSelected()) return false;
2211 // Work around nonsensical selection resetting in IE9/10
2212 if (ie
&& !ie_upto8
&& cm
.display
.inputHasSelection
=== text
) {
2217 var withOp
= !cm
.curOp
;
2218 if (withOp
) startOperation(cm
);
2219 cm
.display
.shift
= false;
2221 // Find the part of the input that is actually new
2222 var same
= 0, l
= Math
.min(prevInput
.length
, text
.length
);
2223 while (same
< l
&& prevInput
.charCodeAt(same
) == text
.charCodeAt(same
)) ++same
;
2224 var inserted
= text
.slice(same
), textLines
= splitLines(inserted
);
2226 // When pasing N lines into N selections, insert one line per selection
2227 var multiPaste
= cm
.state
.pasteIncoming
&& textLines
.length
> 1 && doc
.sel
.ranges
.length
== textLines
.length
;
2229 // Normal behavior is to insert the new text into every selection
2230 for (var i
= doc
.sel
.ranges
.length
- 1; i
>= 0; i
--) {
2231 var range
= doc
.sel
.ranges
[i
];
2232 var from = range
.from(), to
= range
.to();
2234 if (same
< prevInput
.length
)
2235 from = Pos(from.line
, from.ch
- (prevInput
.length
- same
));
2237 else if (cm
.state
.overwrite
&& range
.empty() && !cm
.state
.pasteIncoming
)
2238 to
= Pos(to
.line
, Math
.min(getLine(doc
, to
.line
).text
.length
, to
.ch
+ lst(textLines
).length
));
2239 var updateInput
= cm
.curOp
.updateInput
;
2240 var changeEvent
= {from: from, to
: to
, text
: multiPaste
? [textLines
[i
]] : textLines
,
2241 origin
: cm
.state
.pasteIncoming
? "paste" : cm
.state
.cutIncoming
? "cut" : "+input"};
2242 makeChange(cm
.doc
, changeEvent
);
2243 signalLater(cm
, "inputRead", cm
, changeEvent
);
2244 // When an 'electric' character is inserted, immediately trigger a reindent
2245 if (inserted
&& !cm
.state
.pasteIncoming
&& cm
.options
.electricChars
&&
2246 cm
.options
.smartIndent
&& range
.head
.ch
< 100 &&
2247 (!i
|| doc
.sel
.ranges
[i
- 1].head
.line
!= range
.head
.line
)) {
2248 var electric
= cm
.getModeAt(range
.head
).electricChars
;
2249 if (electric
) for (var j
= 0; j
< electric
.length
; j
++)
2250 if (inserted
.indexOf(electric
.charAt(j
)) > -1) {
2251 indentLine(cm
, range
.head
.line
, "smart");
2256 ensureCursorVisible(cm
);
2257 cm
.curOp
.updateInput
= updateInput
;
2258 cm
.curOp
.typing
= true;
2260 // Don't leave long text in the textarea, since it makes further polling slow
2261 if (text
.length
> 1000 || text
.indexOf("\n") > -1) input
.value
= cm
.display
.prevInput
= "";
2262 else cm
.display
.prevInput
= text
;
2263 if (withOp
) endOperation(cm
);
2264 cm
.state
.pasteIncoming
= cm
.state
.cutIncoming
= false;
2268 // Reset the input to correspond to the selection (or to be empty,
2269 // when not typing and nothing is selected)
2270 function resetInput(cm
, typing
) {
2271 var minimal
, selected
, doc
= cm
.doc
;
2272 if (cm
.somethingSelected()) {
2273 cm
.display
.prevInput
= "";
2274 var range
= doc
.sel
.primary();
2275 minimal
= hasCopyEvent
&&
2276 (range
.to().line
- range
.from().line
> 100 || (selected
= cm
.getSelection()).length
> 1000);
2277 var content
= minimal
? "-" : selected
|| cm
.getSelection();
2278 cm
.display
.input
.value
= content
;
2279 if (cm
.state
.focused
) selectInput(cm
.display
.input
);
2280 if (ie
&& !ie_upto8
) cm
.display
.inputHasSelection
= content
;
2281 } else if (!typing
) {
2282 cm
.display
.prevInput
= cm
.display
.input
.value
= "";
2283 if (ie
&& !ie_upto8
) cm
.display
.inputHasSelection
= null;
2285 cm
.display
.inaccurateSelection
= minimal
;
2288 function focusInput(cm
) {
2289 if (cm
.options
.readOnly
!= "nocursor" && (!mobile
|| activeElt() != cm
.display
.input
))
2290 cm
.display
.input
.focus();
2293 function ensureFocus(cm
) {
2294 if (!cm
.state
.focused
) { focusInput(cm
); onFocus(cm
); }
2297 function isReadOnly(cm
) {
2298 return cm
.options
.readOnly
|| cm
.doc
.cantEdit
;
2303 // Attach the necessary event handlers when initializing the editor
2304 function registerEventHandlers(cm
) {
2306 on(d
.scroller
, "mousedown", operation(cm
, onMouseDown
));
2307 // Older IE's will not fire a second mousedown for a double click
2309 on(d
.scroller
, "dblclick", operation(cm
, function(e
) {
2310 if (signalDOMEvent(cm
, e
)) return;
2311 var pos
= posFromMouse(cm
, e
);
2312 if (!pos
|| clickInGutter(cm
, e
) || eventInWidget(cm
.display
, e
)) return;
2313 e_preventDefault(e
);
2314 var word
= findWordAt(cm
.doc
, pos
);
2315 extendSelection(cm
.doc
, word
.anchor
, word
.head
);
2318 on(d
.scroller
, "dblclick", function(e
) { signalDOMEvent(cm
, e
) || e_preventDefault(e
); });
2319 // Prevent normal selection in the editor (we handle our own)
2320 on(d
.lineSpace
, "selectstart", function(e
) {
2321 if (!eventInWidget(d
, e
)) e_preventDefault(e
);
2323 // Some browsers fire contextmenu *after* opening the menu, at
2324 // which point we can't mess with it anymore. Context menu is
2325 // handled in onMouseDown for these browsers.
2326 if (!captureRightClick
) on(d
.scroller
, "contextmenu", function(e
) {onContextMenu(cm
, e
);});
2328 // Sync scrolling between fake scrollbars and real scrollable
2329 // area, ensure viewport is updated when scrolling.
2330 on(d
.scroller
, "scroll", function() {
2331 if (d
.scroller
.clientHeight
) {
2332 setScrollTop(cm
, d
.scroller
.scrollTop
);
2333 setScrollLeft(cm
, d
.scroller
.scrollLeft
, true);
2334 signal(cm
, "scroll", cm
);
2337 on(d
.scrollbarV
, "scroll", function() {
2338 if (d
.scroller
.clientHeight
) setScrollTop(cm
, d
.scrollbarV
.scrollTop
);
2340 on(d
.scrollbarH
, "scroll", function() {
2341 if (d
.scroller
.clientHeight
) setScrollLeft(cm
, d
.scrollbarH
.scrollLeft
);
2344 // Listen to wheel events in order to try and update the viewport on time.
2345 on(d
.scroller
, "mousewheel", function(e
){onScrollWheel(cm
, e
);});
2346 on(d
.scroller
, "DOMMouseScroll", function(e
){onScrollWheel(cm
, e
);});
2348 // Prevent clicks in the scrollbars from killing focus
2349 function reFocus() { if (cm
.state
.focused
) setTimeout(bind(focusInput
, cm
), 0); }
2350 on(d
.scrollbarH
, "mousedown", reFocus
);
2351 on(d
.scrollbarV
, "mousedown", reFocus
);
2352 // Prevent wrapper from ever scrolling
2353 on(d
.wrapper
, "scroll", function() { d
.wrapper
.scrollTop
= d
.wrapper
.scrollLeft
= 0; });
2355 // When the window resizes, we need to refresh active editors.
2357 function onResize() {
2358 if (resizeTimer
== null) resizeTimer
= setTimeout(function() {
2360 // Might be a text scaling operation, clear size caches.
2361 d
.cachedCharWidth
= d
.cachedTextHeight
= d
.cachedPaddingH
= knownScrollbarWidth
= null;
2365 on(window
, "resize", onResize
);
2366 // The above handler holds on to the editor and its data
2367 // structures. Here we poll to unregister it when the editor is no
2368 // longer in the document, so that it can be garbage-collected.
2369 function unregister() {
2370 if (contains(document
.body
, d
.wrapper
)) setTimeout(unregister
, 5000);
2371 else off(window
, "resize", onResize
);
2373 setTimeout(unregister
, 5000);
2375 on(d
.input
, "keyup", operation(cm
, onKeyUp
));
2376 on(d
.input
, "input", function() {
2377 if (ie
&& !ie_upto8
&& cm
.display
.inputHasSelection
) cm
.display
.inputHasSelection
= null;
2380 on(d
.input
, "keydown", operation(cm
, onKeyDown
));
2381 on(d
.input
, "keypress", operation(cm
, onKeyPress
));
2382 on(d
.input
, "focus", bind(onFocus
, cm
));
2383 on(d
.input
, "blur", bind(onBlur
, cm
));
2386 if (!signalDOMEvent(cm
, e
)) e_stop(e
);
2388 if (cm
.options
.dragDrop
) {
2389 on(d
.scroller
, "dragstart", function(e
){onDragStart(cm
, e
);});
2390 on(d
.scroller
, "dragenter", drag_
);
2391 on(d
.scroller
, "dragover", drag_
);
2392 on(d
.scroller
, "drop", operation(cm
, onDrop
));
2394 on(d
.scroller
, "paste", function(e
) {
2395 if (eventInWidget(d
, e
)) return;
2396 cm
.state
.pasteIncoming
= true;
2400 on(d
.input
, "paste", function() {
2401 cm
.state
.pasteIncoming
= true;
2405 function prepareCopy(e
) {
2406 if (d
.inaccurateSelection
) {
2408 d
.inaccurateSelection
= false;
2409 d
.input
.value
= cm
.getSelection();
2410 selectInput(d
.input
);
2412 if (e
.type
== "cut") cm
.state
.cutIncoming
= true;
2414 on(d
.input
, "cut", prepareCopy
);
2415 on(d
.input
, "copy", prepareCopy
);
2417 // Needed to handle Tab key in KHTML
2418 if (khtml
) on(d
.sizer
, "mouseup", function() {
2419 if (activeElt() == d
.input
) d
.input
.blur();
2426 // Return true when the given mouse event happened in a widget
2427 function eventInWidget(display
, e
) {
2428 for (var n
= e_target(e
); n
!= display
.wrapper
; n
= n
.parentNode
) {
2429 if (!n
|| n
.ignoreEvents
|| n
.parentNode
== display
.sizer
&& n
!= display
.mover
) return true;
2433 // Given a mouse event, find the corresponding position. If liberal
2434 // is false, it checks whether a gutter or scrollbar was clicked,
2435 // and returns null if it was. forRect is used by rectangular
2436 // selections, and tries to estimate a character position even for
2437 // coordinates beyond the right of the text.
2438 function posFromMouse(cm
, e
, liberal
, forRect
) {
2439 var display
= cm
.display
;
2441 var target
= e_target(e
);
2442 if (target
== display
.scrollbarH
|| target
== display
.scrollbarV
||
2443 target
== display
.scrollbarFiller
|| target
== display
.gutterFiller
) return null;
2445 var x
, y
, space
= display
.lineSpace
.getBoundingClientRect();
2446 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
2447 try { x
= e
.clientX
- space
.left
; y
= e
.clientY
- space
.top
; }
2448 catch (e
) { return null; }
2449 var coords
= coordsChar(cm
, x
, y
), line
;
2450 if (forRect
&& coords
.xRel
== 1 && (line
= getLine(cm
.doc
, coords
.line
).text
).length
== coords
.ch
) {
2451 var colDiff
= countColumn(line
, line
.length
, cm
.options
.tabSize
) - line
.length
;
2452 coords
= Pos(coords
.line
, Math
.round((x
- paddingH(cm
.display
).left
) / charWidth(cm
.display
)) - colDiff
);
2457 // A mouse down can be a single click, double click, triple click,
2458 // start of selection drag, start of text drag, new cursor
2459 // (ctrl-click), rectangle drag (alt-drag), or xwin
2460 // middle-click-paste. Or it might be a click on something we should
2461 // not interfere with, such as a scrollbar or widget.
2462 function onMouseDown(e
) {
2463 if (signalDOMEvent(this, e
)) return;
2464 var cm
= this, display
= cm
.display
;
2465 display
.shift
= e
.shiftKey
;
2467 if (eventInWidget(display
, e
)) {
2469 // Briefly turn off draggability, to allow widgets to do
2470 // normal dragging things.
2471 display
.scroller
.draggable
= false;
2472 setTimeout(function(){display
.scroller
.draggable
= true;}, 100);
2476 if (clickInGutter(cm
, e
)) return;
2477 var start
= posFromMouse(cm
, e
);
2480 switch (e_button(e
)) {
2483 leftButtonDown(cm
, e
, start
);
2484 else if (e_target(e
) == display
.scroller
)
2485 e_preventDefault(e
);
2488 if (webkit
) cm
.state
.lastMiddleDown
= +new Date
;
2489 if (start
) extendSelection(cm
.doc
, start
);
2490 setTimeout(bind(focusInput
, cm
), 20);
2491 e_preventDefault(e
);
2494 if (captureRightClick
) onContextMenu(cm
, e
);
2499 var lastClick
, lastDoubleClick
;
2500 function leftButtonDown(cm
, e
, start
) {
2501 setTimeout(bind(ensureFocus
, cm
), 0);
2503 var now
= +new Date
, type
;
2504 if (lastDoubleClick
&& lastDoubleClick
.time
> now
- 400 && cmp(lastDoubleClick
.pos
, start
) == 0) {
2506 } else if (lastClick
&& lastClick
.time
> now
- 400 && cmp(lastClick
.pos
, start
) == 0) {
2508 lastDoubleClick
= {time
: now
, pos
: start
};
2511 lastClick
= {time
: now
, pos
: start
};
2514 var sel
= cm
.doc
.sel
, addNew
= mac
? e
.metaKey
: e
.ctrlKey
;
2515 if (cm
.options
.dragDrop
&& dragAndDrop
&& !addNew
&& !isReadOnly(cm
) &&
2516 type
== "single" && sel
.contains(start
) > -1 && sel
.somethingSelected())
2517 leftButtonStartDrag(cm
, e
, start
);
2519 leftButtonSelect(cm
, e
, start
, type
, addNew
);
2522 // Start a text drag. When it ends, see if any dragging actually
2523 // happen, and treat as a click if it didn't.
2524 function leftButtonStartDrag(cm
, e
, start
) {
2525 var display
= cm
.display
;
2526 var dragEnd
= operation(cm
, function(e2
) {
2527 if (webkit
) display
.scroller
.draggable
= false;
2528 cm
.state
.draggingText
= false;
2529 off(document
, "mouseup", dragEnd
);
2530 off(display
.scroller
, "drop", dragEnd
);
2531 if (Math
.abs(e
.clientX
- e2
.clientX
) + Math
.abs(e
.clientY
- e2
.clientY
) < 10) {
2532 e_preventDefault(e2
);
2533 extendSelection(cm
.doc
, start
);
2535 // Work around unexplainable focus problem in IE9 (#2127)
2536 if (ie_upto10
&& !ie_upto8
)
2537 setTimeout(function() {document
.body
.focus(); focusInput(cm
);}, 20);
2540 // Let the drag handler handle this.
2541 if (webkit
) display
.scroller
.draggable
= true;
2542 cm
.state
.draggingText
= dragEnd
;
2543 // IE's approach to draggable
2544 if (display
.scroller
.dragDrop
) display
.scroller
.dragDrop();
2545 on(document
, "mouseup", dragEnd
);
2546 on(display
.scroller
, "drop", dragEnd
);
2549 // Normal selection, as opposed to text dragging.
2550 function leftButtonSelect(cm
, e
, start
, type
, addNew
) {
2551 var display
= cm
.display
, doc
= cm
.doc
;
2552 e_preventDefault(e
);
2554 var ourRange
, ourIndex
, startSel
= doc
.sel
;
2556 ourIndex
= doc
.sel
.contains(start
);
2558 ourRange
= doc
.sel
.ranges
[ourIndex
];
2560 ourRange
= new Range(start
, start
);
2562 ourRange
= doc
.sel
.primary();
2567 if (!addNew
) ourRange
= new Range(start
, start
);
2568 start
= posFromMouse(cm
, e
, true, true);
2570 } else if (type
== "double") {
2571 var word
= findWordAt(doc
, start
);
2572 if (cm
.display
.shift
|| doc
.extend
)
2573 ourRange
= extendRange(doc
, ourRange
, word
.anchor
, word
.head
);
2576 } else if (type
== "triple") {
2577 var line
= new Range(Pos(start
.line
, 0), clipPos(doc
, Pos(start
.line
+ 1, 0)));
2578 if (cm
.display
.shift
|| doc
.extend
)
2579 ourRange
= extendRange(doc
, ourRange
, line
.anchor
, line
.head
);
2583 ourRange
= extendRange(doc
, ourRange
, start
);
2588 setSelection(doc
, new Selection([ourRange
], 0), sel_mouse
);
2589 } else if (ourIndex
> -1) {
2590 replaceOneSelection(doc
, ourIndex
, ourRange
, sel_mouse
);
2592 ourIndex
= doc
.sel
.ranges
.length
;
2593 setSelection(doc
, normalizeSelection(doc
.sel
.ranges
.concat([ourRange
]), ourIndex
),
2594 {scroll
: false, origin
: "*mouse"});
2597 var lastPos
= start
;
2598 function extendTo(pos
) {
2599 if (cmp(lastPos
, pos
) == 0) return;
2602 if (type
== "rect") {
2603 var ranges
= [], tabSize
= cm
.options
.tabSize
;
2604 var startCol
= countColumn(getLine(doc
, start
.line
).text
, start
.ch
, tabSize
);
2605 var posCol
= countColumn(getLine(doc
, pos
.line
).text
, pos
.ch
, tabSize
);
2606 var left
= Math
.min(startCol
, posCol
), right
= Math
.max(startCol
, posCol
);
2607 for (var line
= Math
.min(start
.line
, pos
.line
), end
= Math
.min(cm
.lastLine(), Math
.max(start
.line
, pos
.line
));
2608 line
<= end
; line
++) {
2609 var text
= getLine(doc
, line
).text
, leftPos
= findColumn(text
, left
, tabSize
);
2611 ranges
.push(new Range(Pos(line
, leftPos
), Pos(line
, leftPos
)));
2612 else if (text
.length
> leftPos
)
2613 ranges
.push(new Range(Pos(line
, leftPos
), Pos(line
, findColumn(text
, right
, tabSize
))));
2615 if (!ranges
.length
) ranges
.push(new Range(start
, start
));
2616 setSelection(doc
, normalizeSelection(startSel
.ranges
.slice(0, ourIndex
).concat(ranges
), ourIndex
), sel_mouse
);
2618 var oldRange
= ourRange
;
2619 var anchor
= oldRange
.anchor
, head
= pos
;
2620 if (type
!= "single") {
2621 if (type
== "double")
2622 var range
= findWordAt(doc
, pos
);
2624 var range
= new Range(Pos(pos
.line
, 0), clipPos(doc
, Pos(pos
.line
+ 1, 0)));
2625 if (cmp(range
.anchor
, anchor
) > 0) {
2627 anchor
= minPos(oldRange
.from(), range
.anchor
);
2629 head
= range
.anchor
;
2630 anchor
= maxPos(oldRange
.to(), range
.head
);
2633 var ranges
= startSel
.ranges
.slice(0);
2634 ranges
[ourIndex
] = new Range(clipPos(doc
, anchor
), head
);
2635 setSelection(doc
, normalizeSelection(ranges
, ourIndex
), sel_mouse
);
2639 var editorSize
= display
.wrapper
.getBoundingClientRect();
2640 // Used to ensure timeout re-tries don't fire when another extend
2641 // happened in the meantime (clearTimeout isn't reliable -- at
2642 // least on Chrome, the timeouts still happen even when cleared,
2643 // if the clear happens after their scheduled firing time).
2646 function extend(e
) {
2647 var curCount
= ++counter
;
2648 var cur
= posFromMouse(cm
, e
, true, type
== "rect");
2650 if (cmp(cur
, lastPos
) != 0) {
2653 var visible
= visibleLines(display
, doc
);
2654 if (cur
.line
>= visible
.to
|| cur
.line
< visible
.from)
2655 setTimeout(operation(cm
, function(){if (counter
== curCount
) extend(e
);}), 150);
2657 var outside
= e
.clientY
< editorSize
.top
? -20 : e
.clientY
> editorSize
.bottom
? 20 : 0;
2658 if (outside
) setTimeout(operation(cm
, function() {
2659 if (counter
!= curCount
) return;
2660 display
.scroller
.scrollTop
+= outside
;
2668 e_preventDefault(e
);
2670 off(document
, "mousemove", move);
2671 off(document
, "mouseup", up
);
2672 doc
.history
.lastSelOrigin
= null;
2675 var move = operation(cm
, function(e
) {
2676 if ((ie
&& !ie_upto9
) ? !e
.buttons
: !e_button(e
)) done(e
);
2679 var up
= operation(cm
, done
);
2680 on(document
, "mousemove", move);
2681 on(document
, "mouseup", up
);
2684 // Determines whether an event happened in the gutter, and fires the
2685 // handlers for the corresponding event.
2686 function gutterEvent(cm
, e
, type
, prevent
, signalfn
) {
2687 try { var mX
= e
.clientX
, mY
= e
.clientY
; }
2688 catch(e
) { return false; }
2689 if (mX
>= Math
.floor(cm
.display
.gutters
.getBoundingClientRect().right
)) return false;
2690 if (prevent
) e_preventDefault(e
);
2692 var display
= cm
.display
;
2693 var lineBox
= display
.lineDiv
.getBoundingClientRect();
2695 if (mY
> lineBox
.bottom
|| !hasHandler(cm
, type
)) return e_defaultPrevented(e
);
2696 mY
-= lineBox
.top
- display
.viewOffset
;
2698 for (var i
= 0; i
< cm
.options
.gutters
.length
; ++i
) {
2699 var g
= display
.gutters
.childNodes
[i
];
2700 if (g
&& g
.getBoundingClientRect().right
>= mX
) {
2701 var line
= lineAtHeight(cm
.doc
, mY
);
2702 var gutter
= cm
.options
.gutters
[i
];
2703 signalfn(cm
, type
, cm
, line
, gutter
, e
);
2704 return e_defaultPrevented(e
);
2709 function clickInGutter(cm
, e
) {
2710 return gutterEvent(cm
, e
, "gutterClick", true, signalLater
);
2713 // Kludge to work around strange IE behavior where it'll sometimes
2714 // re-fire a series of drag-related events right after the drop (#1551)
2717 function onDrop(e
) {
2719 if (signalDOMEvent(cm
, e
) || eventInWidget(cm
.display
, e
))
2721 e_preventDefault(e
);
2722 if (ie_upto10
) lastDrop
= +new Date
;
2723 var pos
= posFromMouse(cm
, e
, true), files
= e
.dataTransfer
.files
;
2724 if (!pos
|| isReadOnly(cm
)) return;
2725 // Might be a file drop, in which case we simply extract the text
2727 if (files
&& files
.length
&& window
.FileReader
&& window
.File
) {
2728 var n
= files
.length
, text
= Array(n
), read
= 0;
2729 var loadFile = function(file
, i
) {
2730 var reader
= new FileReader
;
2731 reader
.onload = function() {
2732 text
[i
] = reader
.result
;
2734 pos
= clipPos(cm
.doc
, pos
);
2735 var change
= {from: pos
, to
: pos
, text
: splitLines(text
.join("\n")), origin
: "paste"};
2736 makeChange(cm
.doc
, change
);
2737 setSelectionReplaceHistory(cm
.doc
, simpleSelection(pos
, changeEnd(change
)));
2740 reader
.readAsText(file
);
2742 for (var i
= 0; i
< n
; ++i
) loadFile(files
[i
], i
);
2743 } else { // Normal drop
2744 // Don't do a replace if the drop happened inside of the selected text.
2745 if (cm
.state
.draggingText
&& cm
.doc
.sel
.contains(pos
) > -1) {
2746 cm
.state
.draggingText(e
);
2747 // Ensure the editor is re-focused
2748 setTimeout(bind(focusInput
, cm
), 20);
2752 var text
= e
.dataTransfer
.getData("Text");
2754 var selected
= cm
.state
.draggingText
&& cm
.listSelections();
2755 setSelectionNoUndo(cm
.doc
, simpleSelection(pos
, pos
));
2756 if (selected
) for (var i
= 0; i
< selected
.length
; ++i
)
2757 replaceRange(cm
.doc
, "", selected
[i
].anchor
, selected
[i
].head
, "drag");
2758 cm
.replaceSelection(text
, "around", "paste");
2766 function onDragStart(cm
, e
) {
2767 if (ie_upto10
&& (!cm
.state
.draggingText
|| +new Date
- lastDrop
< 100)) { e_stop(e
); return; }
2768 if (signalDOMEvent(cm
, e
) || eventInWidget(cm
.display
, e
)) return;
2770 e
.dataTransfer
.setData("Text", cm
.getSelection());
2772 // Use dummy image instead of default browsers image.
2773 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
2774 if (e
.dataTransfer
.setDragImage
&& !safari
) {
2775 var img
= elt("img", null, null, "position: fixed; left: 0; top: 0;");
2776 img
.src
= "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
2778 img
.width
= img
.height
= 1;
2779 cm
.display
.wrapper
.appendChild(img
);
2780 // Force a relayout, or Opera won't use our image for some obscure reason
2781 img
._top
= img
.offsetTop
;
2783 e
.dataTransfer
.setDragImage(img
, 0, 0);
2784 if (presto
) img
.parentNode
.removeChild(img
);
2790 // Sync the scrollable area and scrollbars, ensure the viewport
2791 // covers the visible area.
2792 function setScrollTop(cm
, val
) {
2793 if (Math
.abs(cm
.doc
.scrollTop
- val
) < 2) return;
2794 cm
.doc
.scrollTop
= val
;
2795 if (!gecko
) updateDisplay(cm
, {top
: val
});
2796 if (cm
.display
.scroller
.scrollTop
!= val
) cm
.display
.scroller
.scrollTop
= val
;
2797 if (cm
.display
.scrollbarV
.scrollTop
!= val
) cm
.display
.scrollbarV
.scrollTop
= val
;
2798 if (gecko
) updateDisplay(cm
);
2799 startWorker(cm
, 100);
2801 // Sync scroller and scrollbar, ensure the gutter elements are
2803 function setScrollLeft(cm
, val
, isScroller
) {
2804 if (isScroller
? val
== cm
.doc
.scrollLeft
: Math
.abs(cm
.doc
.scrollLeft
- val
) < 2) return;
2805 val
= Math
.min(val
, cm
.display
.scroller
.scrollWidth
- cm
.display
.scroller
.clientWidth
);
2806 cm
.doc
.scrollLeft
= val
;
2807 alignHorizontally(cm
);
2808 if (cm
.display
.scroller
.scrollLeft
!= val
) cm
.display
.scroller
.scrollLeft
= val
;
2809 if (cm
.display
.scrollbarH
.scrollLeft
!= val
) cm
.display
.scrollbarH
.scrollLeft
= val
;
2812 // Since the delta values reported on mouse wheel events are
2813 // unstandardized between browsers and even browser versions, and
2814 // generally horribly unpredictable, this code starts by measuring
2815 // the scroll effect that the first few mouse wheel events have,
2816 // and, from that, detects the way it can convert deltas to pixel
2817 // offsets afterwards.
2819 // The reason we want to know the amount a wheel event will scroll
2820 // is that it gives us a chance to update the display before the
2821 // actual scrolling happens, reducing flickering.
2823 var wheelSamples
= 0, wheelPixelsPerUnit
= null;
2824 // Fill in a browser-detected starting value on browsers where we
2825 // know one. These don't have to be accurate -- the result of them
2826 // being wrong would just be a slight flicker on the first wheel
2827 // scroll (if it is large enough).
2828 if (ie
) wheelPixelsPerUnit
= -.53;
2829 else if (gecko
) wheelPixelsPerUnit
= 15;
2830 else if (chrome
) wheelPixelsPerUnit
= -.7;
2831 else if (safari
) wheelPixelsPerUnit
= -1/3;
2833 function onScrollWheel(cm
, e
) {
2834 var dx
= e
.wheelDeltaX
, dy
= e
.wheelDeltaY
;
2835 if (dx
== null && e
.detail
&& e
.axis
== e
.HORIZONTAL_AXIS
) dx
= e
.detail
;
2836 if (dy
== null && e
.detail
&& e
.axis
== e
.VERTICAL_AXIS
) dy
= e
.detail
;
2837 else if (dy
== null) dy
= e
.wheelDelta
;
2839 var display
= cm
.display
, scroll
= display
.scroller
;
2840 // Quit if there's nothing to scroll here
2841 if (!(dx
&& scroll
.scrollWidth
> scroll
.clientWidth
||
2842 dy
&& scroll
.scrollHeight
> scroll
.clientHeight
)) return;
2844 // Webkit browsers on OS X abort momentum scrolls when the target
2845 // of the scroll event is removed from the scrollable element.
2846 // This hack (see related code in patchDisplay) makes sure the
2847 // element is kept around.
2848 if (dy
&& mac
&& webkit
) {
2849 outer
: for (var cur
= e
.target
, view
= display
.view
; cur
!= scroll
; cur
= cur
.parentNode
) {
2850 for (var i
= 0; i
< view
.length
; i
++) {
2851 if (view
[i
].node
== cur
) {
2852 cm
.display
.currentWheelTarget
= cur
;
2859 // On some browsers, horizontal scrolling will cause redraws to
2860 // happen before the gutter has been realigned, causing it to
2861 // wriggle around in a most unseemly way. When we have an
2862 // estimated pixels/delta value, we just handle horizontal
2863 // scrolling entirely here. It'll be slightly off from native, but
2864 // better than glitching out.
2865 if (dx
&& !gecko
&& !presto
&& wheelPixelsPerUnit
!= null) {
2867 setScrollTop(cm
, Math
.max(0, Math
.min(scroll
.scrollTop
+ dy
* wheelPixelsPerUnit
, scroll
.scrollHeight
- scroll
.clientHeight
)));
2868 setScrollLeft(cm
, Math
.max(0, Math
.min(scroll
.scrollLeft
+ dx
* wheelPixelsPerUnit
, scroll
.scrollWidth
- scroll
.clientWidth
)));
2869 e_preventDefault(e
);
2870 display
.wheelStartX
= null; // Abort measurement, if in progress
2874 // 'Project' the visible viewport to cover the area that is being
2875 // scrolled into view (if we know enough to estimate it).
2876 if (dy
&& wheelPixelsPerUnit
!= null) {
2877 var pixels
= dy
* wheelPixelsPerUnit
;
2878 var top
= cm
.doc
.scrollTop
, bot
= top
+ display
.wrapper
.clientHeight
;
2879 if (pixels
< 0) top
= Math
.max(0, top
+ pixels
- 50);
2880 else bot
= Math
.min(cm
.doc
.height
, bot
+ pixels
+ 50);
2881 updateDisplay(cm
, {top
: top
, bottom
: bot
});
2884 if (wheelSamples
< 20) {
2885 if (display
.wheelStartX
== null) {
2886 display
.wheelStartX
= scroll
.scrollLeft
; display
.wheelStartY
= scroll
.scrollTop
;
2887 display
.wheelDX
= dx
; display
.wheelDY
= dy
;
2888 setTimeout(function() {
2889 if (display
.wheelStartX
== null) return;
2890 var movedX
= scroll
.scrollLeft
- display
.wheelStartX
;
2891 var movedY
= scroll
.scrollTop
- display
.wheelStartY
;
2892 var sample
= (movedY
&& display
.wheelDY
&& movedY
/ display
.wheelDY
) ||
2893 (movedX
&& display
.wheelDX
&& movedX
/ display
.wheelDX
);
2894 display
.wheelStartX
= display
.wheelStartY
= null;
2895 if (!sample
) return;
2896 wheelPixelsPerUnit
= (wheelPixelsPerUnit
* wheelSamples
+ sample
) / (wheelSamples
+ 1);
2900 display
.wheelDX
+= dx
; display
.wheelDY
+= dy
;
2907 // Run a handler that was bound to a key.
2908 function doHandleBinding(cm
, bound
, dropShift
) {
2909 if (typeof bound
== "string") {
2910 bound
= commands
[bound
];
2911 if (!bound
) return false;
2913 // Ensure previous input has been read, so that the handler sees a
2914 // consistent view of the document
2915 if (cm
.display
.pollingFast
&& readInput(cm
)) cm
.display
.pollingFast
= false;
2916 var prevShift
= cm
.display
.shift
, done
= false;
2918 if (isReadOnly(cm
)) cm
.state
.suppressEdits
= true;
2919 if (dropShift
) cm
.display
.shift
= false;
2920 done
= bound(cm
) != Pass
;
2922 cm
.display
.shift
= prevShift
;
2923 cm
.state
.suppressEdits
= false;
2928 // Collect the currently active keymaps.
2929 function allKeyMaps(cm
) {
2930 var maps
= cm
.state
.keyMaps
.slice(0);
2931 if (cm
.options
.extraKeys
) maps
.push(cm
.options
.extraKeys
);
2932 maps
.push(cm
.options
.keyMap
);
2936 var maybeTransition
;
2937 // Handle a key from the keydown event.
2938 function handleKeyBinding(cm
, e
) {
2939 // Handle automatic keymap transitions
2940 var startMap
= getKeyMap(cm
.options
.keyMap
), next
= startMap
.auto
;
2941 clearTimeout(maybeTransition
);
2942 if (next
&& !isModifierKey(e
)) maybeTransition
= setTimeout(function() {
2943 if (getKeyMap(cm
.options
.keyMap
) == startMap
) {
2944 cm
.options
.keyMap
= (next
.call
? next
.call(null, cm
) : next
);
2949 var name
= keyName(e
, true), handled
= false;
2950 if (!name
) return false;
2951 var keymaps
= allKeyMaps(cm
);
2954 // First try to resolve full name (including 'Shift-'). Failing
2955 // that, see if there is a cursor-motion command (starting with
2956 // 'go') bound to the keyname without 'Shift-'.
2957 handled
= lookupKey("Shift-" + name
, keymaps
, function(b
) {return doHandleBinding(cm
, b
, true);})
2958 || lookupKey(name
, keymaps
, function(b
) {
2959 if (typeof b
== "string" ? /^go[A-Z]/.test(b
) : b
.motion
)
2960 return doHandleBinding(cm
, b
);
2963 handled
= lookupKey(name
, keymaps
, function(b
) { return doHandleBinding(cm
, b
); });
2967 e_preventDefault(e
);
2969 signalLater(cm
, "keyHandled", cm
, name
, e
);
2974 // Handle a key from the keypress event
2975 function handleCharBinding(cm
, e
, ch
) {
2976 var handled
= lookupKey("'" + ch
+ "'", allKeyMaps(cm
),
2977 function(b
) { return doHandleBinding(cm
, b
, true); });
2979 e_preventDefault(e
);
2981 signalLater(cm
, "keyHandled", cm
, "'" + ch
+ "'", e
);
2986 var lastStoppedKey
= null;
2987 function onKeyDown(e
) {
2990 if (signalDOMEvent(cm
, e
)) return;
2991 // IE does strange things with escape.
2992 if (ie_upto10
&& e
.keyCode
== 27) e
.returnValue
= false;
2993 var code
= e
.keyCode
;
2994 cm
.display
.shift
= code
== 16 || e
.shiftKey
;
2995 var handled
= handleKeyBinding(cm
, e
);
2997 lastStoppedKey
= handled
? code
: null;
2998 // Opera has no cut event... we try to at least catch the key combo
2999 if (!handled
&& code
== 88 && !hasCopyEvent
&& (mac
? e
.metaKey
: e
.ctrlKey
))
3000 cm
.replaceSelection("", null, "cut");
3004 function onKeyUp(e
) {
3005 if (signalDOMEvent(this, e
)) return;
3006 if (e
.keyCode
== 16) this.doc
.sel
.shift
= false;
3009 function onKeyPress(e
) {
3011 if (signalDOMEvent(cm
, e
)) return;
3012 var keyCode
= e
.keyCode
, charCode
= e
.charCode
;
3013 if (presto
&& keyCode
== lastStoppedKey
) {lastStoppedKey
= null; e_preventDefault(e
); return;}
3014 if (((presto
&& (!e
.which
|| e
.which
< 10)) || khtml
) && handleKeyBinding(cm
, e
)) return;
3015 var ch
= String
.fromCharCode(charCode
== null ? keyCode
: charCode
);
3016 if (handleCharBinding(cm
, e
, ch
)) return;
3017 if (ie
&& !ie_upto8
) cm
.display
.inputHasSelection
= null;
3021 // FOCUS/BLUR EVENTS
3023 function onFocus(cm
) {
3024 if (cm
.options
.readOnly
== "nocursor") return;
3025 if (!cm
.state
.focused
) {
3026 signal(cm
, "focus", cm
);
3027 cm
.state
.focused
= true;
3028 if (cm
.display
.wrapper
.className
.search(/\bCodeMirror-focused\b/) == -1)
3029 cm
.display
.wrapper
.className
+= " CodeMirror-focused";
3032 if (webkit
) setTimeout(bind(resetInput
, cm
, true), 0); // Issue #1730
3038 function onBlur(cm
) {
3039 if (cm
.state
.focused
) {
3040 signal(cm
, "blur", cm
);
3041 cm
.state
.focused
= false;
3042 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(" CodeMirror-focused", "");
3044 clearInterval(cm
.display
.blinker
);
3045 setTimeout(function() {if (!cm
.state
.focused
) cm
.display
.shift
= false;}, 150);
3048 // CONTEXT MENU HANDLING
3050 var detectingSelectAll
;
3051 // To make the context menu work, we need to briefly unhide the
3052 // textarea (making it as unobtrusive as possible) to let the
3053 // right-click take effect on it.
3054 function onContextMenu(cm
, e
) {
3055 if (signalDOMEvent(cm
, e
, "contextmenu")) return;
3056 var display
= cm
.display
;
3057 if (eventInWidget(display
, e
) || contextMenuInGutter(cm
, e
)) return;
3059 var pos
= posFromMouse(cm
, e
), scrollPos
= display
.scroller
.scrollTop
;
3060 if (!pos
|| presto
) return; // Opera is difficult.
3062 // Reset the current text selection only if the click is done outside of the selection
3063 // and 'resetSelectionOnContextMenu' option is true.
3064 var reset
= cm
.options
.resetSelectionOnContextMenu
;
3065 if (reset
&& cm
.doc
.sel
.contains(pos
) == -1)
3066 operation(cm
, setSelection
)(cm
.doc
, simpleSelection(pos
), sel_dontScroll
);
3068 var oldCSS
= display
.input
.style
.cssText
;
3069 display
.inputDiv
.style
.position
= "absolute";
3070 display
.input
.style
.cssText
= "position: fixed; width: 30px; height: 30px; top: " + (e
.clientY
- 5) +
3071 "px; left: " + (e
.clientX
- 5) + "px; z-index: 1000; background: " +
3072 (ie
? "rgba(255, 255, 255, .05)" : "transparent") +
3073 "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
3076 // Adds "Select all" to context menu in FF
3077 if (!cm
.somethingSelected()) display
.input
.value
= display
.prevInput
= " ";
3079 // Select-all will be greyed out if there's nothing to select, so
3080 // this adds a zero-width space so that we can later check whether
3082 function prepareSelectAllHack() {
3083 if (display
.input
.selectionStart
!= null) {
3084 var extval
= display
.input
.value
= "\u200b" + (cm
.somethingSelected() ? display
.input
.value
: "");
3085 display
.prevInput
= "\u200b";
3086 display
.input
.selectionStart
= 1; display
.input
.selectionEnd
= extval
.length
;
3090 display
.inputDiv
.style
.position
= "relative";
3091 display
.input
.style
.cssText
= oldCSS
;
3092 if (ie_upto8
) display
.scrollbarV
.scrollTop
= display
.scroller
.scrollTop
= scrollPos
;
3095 // Try to detect the user choosing select-all
3096 if (display
.input
.selectionStart
!= null) {
3097 if (!ie
|| ie_upto8
) prepareSelectAllHack();
3098 clearTimeout(detectingSelectAll
);
3099 var i
= 0, poll = function(){
3100 if (display
.prevInput
== "\u200b" && display
.input
.selectionStart
== 0)
3101 operation(cm
, commands
.selectAll
)(cm
);
3102 else if (i
++ < 10) detectingSelectAll
= setTimeout(poll
, 500);
3103 else resetInput(cm
);
3105 detectingSelectAll
= setTimeout(poll
, 200);
3109 if (ie
&& !ie_upto8
) prepareSelectAllHack();
3110 if (captureRightClick
) {
3112 var mouseup = function() {
3113 off(window
, "mouseup", mouseup
);
3114 setTimeout(rehide
, 20);
3116 on(window
, "mouseup", mouseup
);
3118 setTimeout(rehide
, 50);
3122 function contextMenuInGutter(cm
, e
) {
3123 if (!hasHandler(cm
, "gutterContextMenu")) return false;
3124 return gutterEvent(cm
, e
, "gutterContextMenu", false, signal
);
3129 // Compute the position of the end of a change (its 'to' property
3130 // refers to the pre-change end).
3131 var changeEnd
= CodeMirror
.changeEnd = function(change
) {
3132 if (!change
.text
) return change
.to
;
3133 return Pos(change
.from.line
+ change
.text
.length
- 1,
3134 lst(change
.text
).length
+ (change
.text
.length
== 1 ? change
.from.ch
: 0));
3137 // Adjust a position to refer to the post-change position of the
3138 // same text, or the end of the change if the change covers it.
3139 function adjustForChange(pos
, change
) {
3140 if (cmp(pos
, change
.from) < 0) return pos
;
3141 if (cmp(pos
, change
.to
) <= 0) return changeEnd(change
);
3143 var line
= pos
.line
+ change
.text
.length
- (change
.to
.line
- change
.from.line
) - 1, ch
= pos
.ch
;
3144 if (pos
.line
== change
.to
.line
) ch
+= changeEnd(change
).ch
- change
.to
.ch
;
3145 return Pos(line
, ch
);
3148 function computeSelAfterChange(doc
, change
) {
3150 for (var i
= 0; i
< doc
.sel
.ranges
.length
; i
++) {
3151 var range
= doc
.sel
.ranges
[i
];
3152 out
.push(new Range(adjustForChange(range
.anchor
, change
),
3153 adjustForChange(range
.head
, change
)));
3155 return normalizeSelection(out
, doc
.sel
.primIndex
);
3158 function offsetPos(pos
, old
, nw
) {
3159 if (pos
.line
== old
.line
)
3160 return Pos(nw
.line
, pos
.ch
- old
.ch
+ nw
.ch
);
3162 return Pos(nw
.line
+ (pos
.line
- old
.line
), pos
.ch
);
3165 // Used by replaceSelections to allow moving the selection to the
3166 // start or around the replaced test. Hint may be "start" or "around".
3167 function computeReplacedSel(doc
, changes
, hint
) {
3169 var oldPrev
= Pos(doc
.first
, 0), newPrev
= oldPrev
;
3170 for (var i
= 0; i
< changes
.length
; i
++) {
3171 var change
= changes
[i
];
3172 var from = offsetPos(change
.from, oldPrev
, newPrev
);
3173 var to
= offsetPos(changeEnd(change
), oldPrev
, newPrev
);
3174 oldPrev
= change
.to
;
3176 if (hint
== "around") {
3177 var range
= doc
.sel
.ranges
[i
], inv
= cmp(range
.head
, range
.anchor
) < 0;
3178 out
[i
] = new Range(inv
? to
: from, inv
? from : to
);
3180 out
[i
] = new Range(from, from);
3183 return new Selection(out
, doc
.sel
.primIndex
);
3186 // Allow "beforeChange" event handlers to influence a change
3187 function filterChange(doc
, change
, update
) {
3193 origin
: change
.origin
,
3194 cancel: function() { this.canceled
= true; }
3196 if (update
) obj
.update = function(from, to
, text
, origin
) {
3197 if (from) this.from = clipPos(doc
, from);
3198 if (to
) this.to
= clipPos(doc
, to
);
3199 if (text
) this.text
= text
;
3200 if (origin
!== undefined) this.origin
= origin
;
3202 signal(doc
, "beforeChange", doc
, obj
);
3203 if (doc
.cm
) signal(doc
.cm
, "beforeChange", doc
.cm
, obj
);
3205 if (obj
.canceled
) return null;
3206 return {from: obj
.from, to
: obj
.to
, text
: obj
.text
, origin
: obj
.origin
};
3209 // Apply a change to a document, and add it to the document's
3210 // history, and propagating it to all linked documents.
3211 function makeChange(doc
, change
, ignoreReadOnly
) {
3213 if (!doc
.cm
.curOp
) return operation(doc
.cm
, makeChange
)(doc
, change
, ignoreReadOnly
);
3214 if (doc
.cm
.state
.suppressEdits
) return;
3217 if (hasHandler(doc
, "beforeChange") || doc
.cm
&& hasHandler(doc
.cm
, "beforeChange")) {
3218 change
= filterChange(doc
, change
, true);
3219 if (!change
) return;
3222 // Possibly split or suppress the update based on the presence
3223 // of read-only spans in its range.
3224 var split
= sawReadOnlySpans
&& !ignoreReadOnly
&& removeReadOnlyRanges(doc
, change
.from, change
.to
);
3226 for (var i
= split
.length
- 1; i
>= 0; --i
)
3227 makeChangeInner(doc
, {from: split
[i
].from, to
: split
[i
].to
, text
: i
? [""] : change
.text
});
3229 makeChangeInner(doc
, change
);
3233 function makeChangeInner(doc
, change
) {
3234 if (change
.text
.length
== 1 && change
.text
[0] == "" && cmp(change
.from, change
.to
) == 0) return;
3235 var selAfter
= computeSelAfterChange(doc
, change
);
3236 addChangeToHistory(doc
, change
, selAfter
, doc
.cm
? doc
.cm
.curOp
.id
: NaN
);
3238 makeChangeSingleDoc(doc
, change
, selAfter
, stretchSpansOverChange(doc
, change
));
3241 linkedDocs(doc
, function(doc
, sharedHist
) {
3242 if (!sharedHist
&& indexOf(rebased
, doc
.history
) == -1) {
3243 rebaseHist(doc
.history
, change
);
3244 rebased
.push(doc
.history
);
3246 makeChangeSingleDoc(doc
, change
, null, stretchSpansOverChange(doc
, change
));
3250 // Revert a change stored in a document's history.
3251 function makeChangeFromHistory(doc
, type
, allowSelectionOnly
) {
3252 if (doc
.cm
&& doc
.cm
.state
.suppressEdits
) return;
3254 var hist
= doc
.history
, event
, selAfter
= doc
.sel
;
3255 var source
= type
== "undo" ? hist
.done
: hist
.undone
, dest
= type
== "undo" ? hist
.undone
: hist
.done
;
3257 // Verify that there is a useable event (so that ctrl-z won't
3258 // needlessly clear selection events)
3259 for (var i
= 0; i
< source
.length
; i
++) {
3261 if (allowSelectionOnly
? event
.ranges
&& !event
.equals(doc
.sel
) : !event
.ranges
)
3264 if (i
== source
.length
) return;
3265 hist
.lastOrigin
= hist
.lastSelOrigin
= null;
3268 event
= source
.pop();
3270 pushSelectionToHistory(event
, dest
);
3271 if (allowSelectionOnly
&& !event
.equals(doc
.sel
)) {
3272 setSelection(doc
, event
, {clearRedo
: false});
3280 // Build up a reverse change object to add to the opposite history
3281 // stack (redo when undoing, and vice versa).
3282 var antiChanges
= [];
3283 pushSelectionToHistory(selAfter
, dest
);
3284 dest
.push({changes
: antiChanges
, generation
: hist
.generation
});
3285 hist
.generation
= event
.generation
|| ++hist
.maxGeneration
;
3287 var filter
= hasHandler(doc
, "beforeChange") || doc
.cm
&& hasHandler(doc
.cm
, "beforeChange");
3289 for (var i
= event
.changes
.length
- 1; i
>= 0; --i
) {
3290 var change
= event
.changes
[i
];
3291 change
.origin
= type
;
3292 if (filter
&& !filterChange(doc
, change
, false)) {
3297 antiChanges
.push(historyChangeFromChange(doc
, change
));
3299 var after
= i
? computeSelAfterChange(doc
, change
, null) : lst(source
);
3300 makeChangeSingleDoc(doc
, change
, after
, mergeOldSpans(doc
, change
));
3301 if (doc
.cm
) ensureCursorVisible(doc
.cm
);
3304 // Propagate to the linked documents
3305 linkedDocs(doc
, function(doc
, sharedHist
) {
3306 if (!sharedHist
&& indexOf(rebased
, doc
.history
) == -1) {
3307 rebaseHist(doc
.history
, change
);
3308 rebased
.push(doc
.history
);
3310 makeChangeSingleDoc(doc
, change
, null, mergeOldSpans(doc
, change
));
3315 // Sub-views need their line numbers shifted when text is added
3316 // above or below them in the parent document.
3317 function shiftDoc(doc
, distance
) {
3318 doc
.first
+= distance
;
3319 doc
.sel
= new Selection(map(doc
.sel
.ranges
, function(range
) {
3320 return new Range(Pos(range
.anchor
.line
+ distance
, range
.anchor
.ch
),
3321 Pos(range
.head
.line
+ distance
, range
.head
.ch
));
3322 }), doc
.sel
.primIndex
);
3323 if (doc
.cm
) regChange(doc
.cm
, doc
.first
, doc
.first
- distance
, distance
);
3326 // More lower-level change function, handling only a single document
3327 // (not linked ones).
3328 function makeChangeSingleDoc(doc
, change
, selAfter
, spans
) {
3329 if (doc
.cm
&& !doc
.cm
.curOp
)
3330 return operation(doc
.cm
, makeChangeSingleDoc
)(doc
, change
, selAfter
, spans
);
3332 if (change
.to
.line
< doc
.first
) {
3333 shiftDoc(doc
, change
.text
.length
- 1 - (change
.to
.line
- change
.from.line
));
3336 if (change
.from.line
> doc
.lastLine()) return;
3338 // Clip the change to the size of this doc
3339 if (change
.from.line
< doc
.first
) {
3340 var shift
= change
.text
.length
- 1 - (doc
.first
- change
.from.line
);
3341 shiftDoc(doc
, shift
);
3342 change
= {from: Pos(doc
.first
, 0), to
: Pos(change
.to
.line
+ shift
, change
.to
.ch
),
3343 text
: [lst(change
.text
)], origin
: change
.origin
};
3345 var last
= doc
.lastLine();
3346 if (change
.to
.line
> last
) {
3347 change
= {from: change
.from, to
: Pos(last
, getLine(doc
, last
).text
.length
),
3348 text
: [change
.text
[0]], origin
: change
.origin
};
3351 change
.removed
= getBetween(doc
, change
.from, change
.to
);
3353 if (!selAfter
) selAfter
= computeSelAfterChange(doc
, change
, null);
3354 if (doc
.cm
) makeChangeSingleDocInEditor(doc
.cm
, change
, spans
);
3355 else updateDoc(doc
, change
, spans
);
3356 setSelectionNoUndo(doc
, selAfter
, sel_dontScroll
);
3359 // Handle the interaction of a change to a document with the editor
3360 // that this document is part of.
3361 function makeChangeSingleDocInEditor(cm
, change
, spans
) {
3362 var doc
= cm
.doc
, display
= cm
.display
, from = change
.from, to
= change
.to
;
3364 var recomputeMaxLength
= false, checkWidthStart
= from.line
;
3365 if (!cm
.options
.lineWrapping
) {
3366 checkWidthStart
= lineNo(visualLine(getLine(doc
, from.line
)));
3367 doc
.iter(checkWidthStart
, to
.line
+ 1, function(line
) {
3368 if (line
== display
.maxLine
) {
3369 recomputeMaxLength
= true;
3375 if (doc
.sel
.contains(change
.from, change
.to
) > -1)
3376 cm
.curOp
.cursorActivity
= true;
3378 updateDoc(doc
, change
, spans
, estimateHeight(cm
));
3380 if (!cm
.options
.lineWrapping
) {
3381 doc
.iter(checkWidthStart
, from.line
+ change
.text
.length
, function(line
) {
3382 var len
= lineLength(line
);
3383 if (len
> display
.maxLineLength
) {
3384 display
.maxLine
= line
;
3385 display
.maxLineLength
= len
;
3386 display
.maxLineChanged
= true;
3387 recomputeMaxLength
= false;
3390 if (recomputeMaxLength
) cm
.curOp
.updateMaxLine
= true;
3393 // Adjust frontier, schedule worker
3394 doc
.frontier
= Math
.min(doc
.frontier
, from.line
);
3395 startWorker(cm
, 400);
3397 var lendiff
= change
.text
.length
- (to
.line
- from.line
) - 1;
3398 // Remember that these lines changed, for updating the display
3399 if (from.line
== to
.line
&& change
.text
.length
== 1 && !isWholeLineUpdate(cm
.doc
, change
))
3400 regLineChange(cm
, from.line
, "text");
3402 regChange(cm
, from.line
, to
.line
+ 1, lendiff
);
3404 if (hasHandler(cm
, "change") || hasHandler(cm
, "changes"))
3405 (cm
.curOp
.changeObjs
|| (cm
.curOp
.changeObjs
= [])).push({
3408 removed
: change
.removed
,
3409 origin
: change
.origin
3413 function replaceRange(doc
, code
, from, to
, origin
) {
3415 if (cmp(to
, from) < 0) { var tmp
= to
; to
= from; from = tmp
; }
3416 if (typeof code
== "string") code
= splitLines(code
);
3417 makeChange(doc
, {from: from, to
: to
, text
: code
, origin
: origin
});
3420 // SCROLLING THINGS INTO VIEW
3422 // If an editor sits on the top or bottom of the window, partially
3423 // scrolled out of view, this ensures that the cursor is visible.
3424 function maybeScrollWindow(cm
, coords
) {
3425 var display
= cm
.display
, box
= display
.sizer
.getBoundingClientRect(), doScroll
= null;
3426 if (coords
.top
+ box
.top
< 0) doScroll
= true;
3427 else if (coords
.bottom
+ box
.top
> (window
.innerHeight
|| document
.documentElement
.clientHeight
)) doScroll
= false;
3428 if (doScroll
!= null && !phantom
) {
3429 var scrollNode
= elt("div", "\u200b", null, "position: absolute; top: " +
3430 (coords
.top
- display
.viewOffset
- paddingTop(cm
.display
)) + "px; height: " +
3431 (coords
.bottom
- coords
.top
+ scrollerCutOff
) + "px; left: " +
3432 coords
.left
+ "px; width: 2px;");
3433 cm
.display
.lineSpace
.appendChild(scrollNode
);
3434 scrollNode
.scrollIntoView(doScroll
);
3435 cm
.display
.lineSpace
.removeChild(scrollNode
);
3439 // Scroll a given position into view (immediately), verifying that
3440 // it actually became visible (as line heights are accurately
3441 // measured, the position of something may 'drift' during drawing).
3442 function scrollPosIntoView(cm
, pos
, end
, margin
) {
3443 if (margin
== null) margin
= 0;
3445 var changed
= false, coords
= cursorCoords(cm
, pos
);
3446 var endCoords
= !end
|| end
== pos
? coords
: cursorCoords(cm
, end
);
3447 var scrollPos
= calculateScrollPos(cm
, Math
.min(coords
.left
, endCoords
.left
),
3448 Math
.min(coords
.top
, endCoords
.top
) - margin
,
3449 Math
.max(coords
.left
, endCoords
.left
),
3450 Math
.max(coords
.bottom
, endCoords
.bottom
) + margin
);
3451 var startTop
= cm
.doc
.scrollTop
, startLeft
= cm
.doc
.scrollLeft
;
3452 if (scrollPos
.scrollTop
!= null) {
3453 setScrollTop(cm
, scrollPos
.scrollTop
);
3454 if (Math
.abs(cm
.doc
.scrollTop
- startTop
) > 1) changed
= true;
3456 if (scrollPos
.scrollLeft
!= null) {
3457 setScrollLeft(cm
, scrollPos
.scrollLeft
);
3458 if (Math
.abs(cm
.doc
.scrollLeft
- startLeft
) > 1) changed
= true;
3460 if (!changed
) return coords
;
3464 // Scroll a given set of coordinates into view (immediately).
3465 function scrollIntoView(cm
, x1
, y1
, x2
, y2
) {
3466 var scrollPos
= calculateScrollPos(cm
, x1
, y1
, x2
, y2
);
3467 if (scrollPos
.scrollTop
!= null) setScrollTop(cm
, scrollPos
.scrollTop
);
3468 if (scrollPos
.scrollLeft
!= null) setScrollLeft(cm
, scrollPos
.scrollLeft
);
3471 // Calculate a new scroll position needed to scroll the given
3472 // rectangle into view. Returns an object with scrollTop and
3473 // scrollLeft properties. When these are undefined, the
3474 // vertical/horizontal position does not need to be adjusted.
3475 function calculateScrollPos(cm
, x1
, y1
, x2
, y2
) {
3476 var display
= cm
.display
, snapMargin
= textHeight(cm
.display
);
3478 var screentop
= cm
.curOp
&& cm
.curOp
.scrollTop
!= null ? cm
.curOp
.scrollTop
: display
.scroller
.scrollTop
;
3479 var screen
= display
.scroller
.clientHeight
- scrollerCutOff
, result
= {};
3480 var docBottom
= cm
.doc
.height
+ paddingVert(display
);
3481 var atTop
= y1
< snapMargin
, atBottom
= y2
> docBottom
- snapMargin
;
3482 if (y1
< screentop
) {
3483 result
.scrollTop
= atTop
? 0 : y1
;
3484 } else if (y2
> screentop
+ screen
) {
3485 var newTop
= Math
.min(y1
, (atBottom
? docBottom
: y2
) - screen
);
3486 if (newTop
!= screentop
) result
.scrollTop
= newTop
;
3489 var screenleft
= cm
.curOp
&& cm
.curOp
.scrollLeft
!= null ? cm
.curOp
.scrollLeft
: display
.scroller
.scrollLeft
;
3490 var screenw
= display
.scroller
.clientWidth
- scrollerCutOff
;
3491 x1
+= display
.gutters
.offsetWidth
; x2
+= display
.gutters
.offsetWidth
;
3492 var gutterw
= display
.gutters
.offsetWidth
;
3493 var atLeft
= x1
< gutterw
+ 10;
3494 if (x1
< screenleft
+ gutterw
|| atLeft
) {
3496 result
.scrollLeft
= Math
.max(0, x1
- 10 - gutterw
);
3497 } else if (x2
> screenw
+ screenleft
- 3) {
3498 result
.scrollLeft
= x2
+ 10 - screenw
;
3503 // Store a relative adjustment to the scroll position in the current
3504 // operation (to be applied when the operation finishes).
3505 function addToScrollPos(cm
, left
, top
) {
3506 if (left
!= null || top
!= null) resolveScrollToPos(cm
);
3508 cm
.curOp
.scrollLeft
= (cm
.curOp
.scrollLeft
== null ? cm
.doc
.scrollLeft
: cm
.curOp
.scrollLeft
) + left
;
3510 cm
.curOp
.scrollTop
= (cm
.curOp
.scrollTop
== null ? cm
.doc
.scrollTop
: cm
.curOp
.scrollTop
) + top
;
3513 // Make sure that at the end of the operation the current cursor is
3515 function ensureCursorVisible(cm
) {
3516 resolveScrollToPos(cm
);
3517 var cur
= cm
.getCursor(), from = cur
, to
= cur
;
3518 if (!cm
.options
.lineWrapping
) {
3519 from = cur
.ch
? Pos(cur
.line
, cur
.ch
- 1) : cur
;
3520 to
= Pos(cur
.line
, cur
.ch
+ 1);
3522 cm
.curOp
.scrollToPos
= {from: from, to
: to
, margin
: cm
.options
.cursorScrollMargin
, isCursor
: true};
3525 // When an operation has its scrollToPos property set, and another
3526 // scroll action is applied before the end of the operation, this
3527 // 'simulates' scrolling that position into view in a cheap way, so
3528 // that the effect of intermediate scroll commands is not ignored.
3529 function resolveScrollToPos(cm
) {
3530 var range
= cm
.curOp
.scrollToPos
;
3532 cm
.curOp
.scrollToPos
= null;
3533 var from = estimateCoords(cm
, range
.from), to
= estimateCoords(cm
, range
.to
);
3534 var sPos
= calculateScrollPos(cm
, Math
.min(from.left
, to
.left
),
3535 Math
.min(from.top
, to
.top
) - range
.margin
,
3536 Math
.max(from.right
, to
.right
),
3537 Math
.max(from.bottom
, to
.bottom
) + range
.margin
);
3538 cm
.scrollTo(sPos
.scrollLeft
, sPos
.scrollTop
);
3544 // Indent the given line. The how parameter can be "smart",
3545 // "add"/null, "subtract", or "prev". When aggressive is false
3546 // (typically set to true for forced single-line indents), empty
3547 // lines are not indented, and places where the mode returns Pass
3549 function indentLine(cm
, n
, how
, aggressive
) {
3550 var doc
= cm
.doc
, state
;
3551 if (how
== null) how
= "add";
3552 if (how
== "smart") {
3553 // Fall back to "prev" when the mode doesn't have an indentation
3555 if (!cm
.doc
.mode
.indent
) how
= "prev";
3556 else state
= getStateBefore(cm
, n
);
3559 var tabSize
= cm
.options
.tabSize
;
3560 var line
= getLine(doc
, n
), curSpace
= countColumn(line
.text
, null, tabSize
);
3561 if (line
.stateAfter
) line
.stateAfter
= null;
3562 var curSpaceString
= line
.text
.match(/^\s*/)[0], indentation
;
3563 if (!aggressive
&& !/\S/.test(line
.text
)) {
3566 } else if (how
== "smart") {
3567 indentation
= cm
.doc
.mode
.indent(state
, line
.text
.slice(curSpaceString
.length
), line
.text
);
3568 if (indentation
== Pass
) {
3569 if (!aggressive
) return;
3573 if (how
== "prev") {
3574 if (n
> doc
.first
) indentation
= countColumn(getLine(doc
, n
-1).text
, null, tabSize
);
3575 else indentation
= 0;
3576 } else if (how
== "add") {
3577 indentation
= curSpace
+ cm
.options
.indentUnit
;
3578 } else if (how
== "subtract") {
3579 indentation
= curSpace
- cm
.options
.indentUnit
;
3580 } else if (typeof how
== "number") {
3581 indentation
= curSpace
+ how
;
3583 indentation
= Math
.max(0, indentation
);
3585 var indentString
= "", pos
= 0;
3586 if (cm
.options
.indentWithTabs
)
3587 for (var i
= Math
.floor(indentation
/ tabSize
); i
; --i
) {pos
+= tabSize
; indentString
+= "\t";}
3588 if (pos
< indentation
) indentString
+= spaceStr(indentation
- pos
);
3590 if (indentString
!= curSpaceString
) {
3591 replaceRange(cm
.doc
, indentString
, Pos(n
, 0), Pos(n
, curSpaceString
.length
), "+input");
3593 // Ensure that, if the cursor was in the whitespace at the start
3594 // of the line, it is moved to the end of that space.
3595 for (var i
= 0; i
< doc
.sel
.ranges
.length
; i
++) {
3596 var range
= doc
.sel
.ranges
[i
];
3597 if (range
.head
.line
== n
&& range
.head
.ch
< curSpaceString
.length
) {
3598 var pos
= Pos(n
, curSpaceString
.length
);
3599 replaceOneSelection(doc
, i
, new Range(pos
, pos
));
3604 line
.stateAfter
= null;
3607 // Utility for applying a change to a line by handle or number,
3608 // returning the number and optionally registering the line as
3610 function changeLine(cm
, handle
, changeType
, op
) {
3611 var no
= handle
, line
= handle
, doc
= cm
.doc
;
3612 if (typeof handle
== "number") line
= getLine(doc
, clipLine(doc
, handle
));
3613 else no
= lineNo(handle
);
3614 if (no
== null) return null;
3615 if (op(line
, no
)) regLineChange(cm
, no
, changeType
);
3620 // Helper for deleting text near the selection(s), used to implement
3621 // backspace, delete, and similar functionality.
3622 function deleteNearSelection(cm
, compute
) {
3623 var ranges
= cm
.doc
.sel
.ranges
, kill
= [];
3624 // Build up a set of ranges to kill first, merging overlapping
3626 for (var i
= 0; i
< ranges
.length
; i
++) {
3627 var toKill
= compute(ranges
[i
]);
3628 while (kill
.length
&& cmp(toKill
.from, lst(kill
).to
) <= 0) {
3629 var replaced
= kill
.pop();
3630 if (cmp(replaced
.from, toKill
.from) < 0) {
3631 toKill
.from = replaced
.from;
3637 // Next, remove those actual ranges.
3638 runInOp(cm
, function() {
3639 for (var i
= kill
.length
- 1; i
>= 0; i
--)
3640 replaceRange(cm
.doc
, "", kill
[i
].from, kill
[i
].to
, "+delete");
3641 ensureCursorVisible(cm
);
3645 // Used for horizontal relative motion. Dir is -1 or 1 (left or
3646 // right), unit can be "char", "column" (like char, but doesn't
3647 // cross line boundaries), "word" (across next word), or "group" (to
3648 // the start of next group of word or non-word-non-whitespace
3649 // chars). The visually param controls whether, in right-to-left
3650 // text, direction 1 means to move towards the next index in the
3651 // string, or towards the character to the right of the current
3652 // position. The resulting position will have a hitSide=true
3653 // property if it reached the end of the document.
3654 function findPosH(doc
, pos
, dir
, unit
, visually
) {
3655 var line
= pos
.line
, ch
= pos
.ch
, origDir
= dir
;
3656 var lineObj
= getLine(doc
, line
);
3657 var possible
= true;
3658 function findNextLine() {
3660 if (l
< doc
.first
|| l
>= doc
.first
+ doc
.size
) return (possible
= false);
3662 return lineObj
= getLine(doc
, l
);
3664 function moveOnce(boundToLine
) {
3665 var next
= (visually
? moveVisually
: moveLogically
)(lineObj
, ch
, dir
, true);
3667 if (!boundToLine
&& findNextLine()) {
3668 if (visually
) ch
= (dir
< 0 ? lineRight
: lineLeft
)(lineObj
);
3669 else ch
= dir
< 0 ? lineObj
.text
.length
: 0;
3670 } else return (possible
= false);
3675 if (unit
== "char") moveOnce();
3676 else if (unit
== "column") moveOnce(true);
3677 else if (unit
== "word" || unit
== "group") {
3678 var sawType
= null, group
= unit
== "group";
3679 for (var first
= true;; first
= false) {
3680 if (dir
< 0 && !moveOnce(!first
)) break;
3681 var cur
= lineObj
.text
.charAt(ch
) || "\n";
3682 var type
= isWordChar(cur
) ? "w"
3683 : group
&& cur
== "\n" ? "n"
3684 : !group
|| /\s/.test(cur
) ? null
3686 if (group
&& !first
&& !type
) type
= "s";
3687 if (sawType
&& sawType
!= type
) {
3688 if (dir
< 0) {dir
= 1; moveOnce();}
3692 if (type
) sawType
= type
;
3693 if (dir
> 0 && !moveOnce(!first
)) break;
3696 var result
= skipAtomic(doc
, Pos(line
, ch
), origDir
, true);
3697 if (!possible
) result
.hitSide
= true;
3701 // For relative vertical movement. Dir may be -1 or 1. Unit can be
3702 // "page" or "line". The resulting position will have a hitSide=true
3703 // property if it reached the end of the document.
3704 function findPosV(cm
, pos
, dir
, unit
) {
3705 var doc
= cm
.doc
, x
= pos
.left
, y
;
3706 if (unit
== "page") {
3707 var pageSize
= Math
.min(cm
.display
.wrapper
.clientHeight
, window
.innerHeight
|| document
.documentElement
.clientHeight
);
3708 y
= pos
.top
+ dir
* (pageSize
- (dir
< 0 ? 1.5 : .5) * textHeight(cm
.display
));
3709 } else if (unit
== "line") {
3710 y
= dir
> 0 ? pos
.bottom
+ 3 : pos
.top
- 3;
3713 var target
= coordsChar(cm
, x
, y
);
3714 if (!target
.outside
) break;
3715 if (dir
< 0 ? y
<= 0 : y
>= doc
.height
) { target
.hitSide
= true; break; }
3721 // Find the word at the given position (as returned by coordsChar).
3722 function findWordAt(doc
, pos
) {
3723 var line
= getLine(doc
, pos
.line
).text
;
3724 var start
= pos
.ch
, end
= pos
.ch
;
3726 if ((pos
.xRel
< 0 || end
== line
.length
) && start
) --start
; else ++end
;
3727 var startChar
= line
.charAt(start
);
3728 var check
= isWordChar(startChar
) ? isWordChar
3729 : /\s/.test(startChar
) ? function(ch
) {return /\s/.test(ch
);}
3730 : function(ch
) {return !/\s/.test(ch
) && !isWordChar(ch
);};
3731 while (start
> 0 && check(line
.charAt(start
- 1))) --start
;
3732 while (end
< line
.length
&& check(line
.charAt(end
))) ++end
;
3734 return new Range(Pos(pos
.line
, start
), Pos(pos
.line
, end
));
3739 // The publicly visible API. Note that methodOp(f) means
3740 // 'wrap f in an operation, performed on its `this` parameter'.
3742 // This is not the complete set of editor methods. Most of the
3743 // methods defined on the Doc type are also injected into
3744 // CodeMirror.prototype, for backwards compatibility and
3747 CodeMirror
.prototype = {
3748 constructor: CodeMirror
,
3749 focus: function(){window
.focus(); focusInput(this); fastPoll(this);},
3751 setOption: function(option
, value
) {
3752 var options
= this.options
, old
= options
[option
];
3753 if (options
[option
] == value
&& option
!= "mode") return;
3754 options
[option
] = value
;
3755 if (optionHandlers
.hasOwnProperty(option
))
3756 operation(this, optionHandlers
[option
])(this, value
, old
);
3759 getOption: function(option
) {return this.options
[option
];},
3760 getDoc: function() {return this.doc
;},
3762 addKeyMap: function(map
, bottom
) {
3763 this.state
.keyMaps
[bottom
? "push" : "unshift"](map
);
3765 removeKeyMap: function(map
) {
3766 var maps
= this.state
.keyMaps
;
3767 for (var i
= 0; i
< maps
.length
; ++i
)
3768 if (maps
[i
] == map
|| (typeof maps
[i
] != "string" && maps
[i
].name
== map
)) {
3774 addOverlay
: methodOp(function(spec
, options
) {
3775 var mode
= spec
.token
? spec
: CodeMirror
.getMode(this.options
, spec
);
3776 if (mode
.startState
) throw new Error("Overlays may not be stateful.");
3777 this.state
.overlays
.push({mode
: mode
, modeSpec
: spec
, opaque
: options
&& options
.opaque
});
3778 this.state
.modeGen
++;
3781 removeOverlay
: methodOp(function(spec
) {
3782 var overlays
= this.state
.overlays
;
3783 for (var i
= 0; i
< overlays
.length
; ++i
) {
3784 var cur
= overlays
[i
].modeSpec
;
3785 if (cur
== spec
|| typeof spec
== "string" && cur
.name
== spec
) {
3786 overlays
.splice(i
, 1);
3787 this.state
.modeGen
++;
3794 indentLine
: methodOp(function(n
, dir
, aggressive
) {
3795 if (typeof dir
!= "string" && typeof dir
!= "number") {
3796 if (dir
== null) dir
= this.options
.smartIndent
? "smart" : "prev";
3797 else dir
= dir
? "add" : "subtract";
3799 if (isLine(this.doc
, n
)) indentLine(this, n
, dir
, aggressive
);
3801 indentSelection
: methodOp(function(how
) {
3802 var ranges
= this.doc
.sel
.ranges
, end
= -1;
3803 for (var i
= 0; i
< ranges
.length
; i
++) {
3804 var range
= ranges
[i
];
3805 if (!range
.empty()) {
3806 var start
= Math
.max(end
, range
.from().line
);
3807 var to
= range
.to();
3808 end
= Math
.min(this.lastLine(), to
.line
- (to
.ch
? 0 : 1)) + 1;
3809 for (var j
= start
; j
< end
; ++j
)
3810 indentLine(this, j
, how
);
3811 } else if (range
.head
.line
> end
) {
3812 indentLine(this, range
.head
.line
, how
, true);
3813 end
= range
.head
.line
;
3814 if (i
== this.doc
.sel
.primIndex
) ensureCursorVisible(this);
3819 // Fetch the parser token for a given character. Useful for hacks
3820 // that want to inspect the mode state (say, for completion).
3821 getTokenAt: function(pos
, precise
) {
3823 pos
= clipPos(doc
, pos
);
3824 var state
= getStateBefore(this, pos
.line
, precise
), mode
= this.doc
.mode
;
3825 var line
= getLine(doc
, pos
.line
);
3826 var stream
= new StringStream(line
.text
, this.options
.tabSize
);
3827 while (stream
.pos
< pos
.ch
&& !stream
.eol()) {
3828 stream
.start
= stream
.pos
;
3829 var style
= mode
.token(stream
, state
);
3831 return {start
: stream
.start
,
3833 string
: stream
.current(),
3834 type
: style
|| null,
3838 getTokenTypeAt: function(pos
) {
3839 pos
= clipPos(this.doc
, pos
);
3840 var styles
= getLineStyles(this, getLine(this.doc
, pos
.line
));
3841 var before
= 0, after
= (styles
.length
- 1) / 2, ch
= pos
.ch
;
3842 if (ch
== 0) return styles
[2];
3844 var mid
= (before
+ after
) >> 1;
3845 if ((mid
? styles
[mid
* 2 - 1] : 0) >= ch
) after
= mid
;
3846 else if (styles
[mid
* 2 + 1] < ch
) before
= mid
+ 1;
3847 else return styles
[mid
* 2 + 2];
3851 getModeAt: function(pos
) {
3852 var mode
= this.doc
.mode
;
3853 if (!mode
.innerMode
) return mode
;
3854 return CodeMirror
.innerMode(mode
, this.getTokenAt(pos
).state
).mode
;
3857 getHelper: function(pos
, type
) {
3858 return this.getHelpers(pos
, type
)[0];
3861 getHelpers: function(pos
, type
) {
3863 if (!helpers
.hasOwnProperty(type
)) return helpers
;
3864 var help
= helpers
[type
], mode
= this.getModeAt(pos
);
3865 if (typeof mode
[type
] == "string") {
3866 if (help
[mode
[type
]]) found
.push(help
[mode
[type
]]);
3867 } else if (mode
[type
]) {
3868 for (var i
= 0; i
< mode
[type
].length
; i
++) {
3869 var val
= help
[mode
[type
][i
]];
3870 if (val
) found
.push(val
);
3872 } else if (mode
.helperType
&& help
[mode
.helperType
]) {
3873 found
.push(help
[mode
.helperType
]);
3874 } else if (help
[mode
.name
]) {
3875 found
.push(help
[mode
.name
]);
3877 for (var i
= 0; i
< help
._global
.length
; i
++) {
3878 var cur
= help
._global
[i
];
3879 if (cur
.pred(mode
, this) && indexOf(found
, cur
.val
) == -1)
3880 found
.push(cur
.val
);
3885 getStateAfter: function(line
, precise
) {
3887 line
= clipLine(doc
, line
== null ? doc
.first
+ doc
.size
- 1: line
);
3888 return getStateBefore(this, line
+ 1, precise
);
3891 cursorCoords: function(start
, mode
) {
3892 var pos
, range
= this.doc
.sel
.primary();
3893 if (start
== null) pos
= range
.head
;
3894 else if (typeof start
== "object") pos
= clipPos(this.doc
, start
);
3895 else pos
= start
? range
.from() : range
.to();
3896 return cursorCoords(this, pos
, mode
|| "page");
3899 charCoords: function(pos
, mode
) {
3900 return charCoords(this, clipPos(this.doc
, pos
), mode
|| "page");
3903 coordsChar: function(coords
, mode
) {
3904 coords
= fromCoordSystem(this, coords
, mode
|| "page");
3905 return coordsChar(this, coords
.left
, coords
.top
);
3908 lineAtHeight: function(height
, mode
) {
3909 height
= fromCoordSystem(this, {top
: height
, left
: 0}, mode
|| "page").top
;
3910 return lineAtHeight(this.doc
, height
+ this.display
.viewOffset
);
3912 heightAtLine: function(line
, mode
) {
3913 var end
= false, last
= this.doc
.first
+ this.doc
.size
- 1;
3914 if (line
< this.doc
.first
) line
= this.doc
.first
;
3915 else if (line
> last
) { line
= last
; end
= true; }
3916 var lineObj
= getLine(this.doc
, line
);
3917 return intoCoordSystem(this, lineObj
, {top
: 0, left
: 0}, mode
|| "page").top
+
3918 (end
? this.doc
.height
- heightAtLine(lineObj
) : 0);
3921 defaultTextHeight: function() { return textHeight(this.display
); },
3922 defaultCharWidth: function() { return charWidth(this.display
); },
3924 setGutterMarker
: methodOp(function(line
, gutterID
, value
) {
3925 return changeLine(this, line
, "gutter", function(line
) {
3926 var markers
= line
.gutterMarkers
|| (line
.gutterMarkers
= {});
3927 markers
[gutterID
] = value
;
3928 if (!value
&& isEmpty(markers
)) line
.gutterMarkers
= null;
3933 clearGutter
: methodOp(function(gutterID
) {
3934 var cm
= this, doc
= cm
.doc
, i
= doc
.first
;
3935 doc
.iter(function(line
) {
3936 if (line
.gutterMarkers
&& line
.gutterMarkers
[gutterID
]) {
3937 line
.gutterMarkers
[gutterID
] = null;
3938 regLineChange(cm
, i
, "gutter");
3939 if (isEmpty(line
.gutterMarkers
)) line
.gutterMarkers
= null;
3945 addLineClass
: methodOp(function(handle
, where
, cls
) {
3946 return changeLine(this, handle
, "class", function(line
) {
3947 var prop
= where
== "text" ? "textClass" : where
== "background" ? "bgClass" : "wrapClass";
3948 if (!line
[prop
]) line
[prop
] = cls
;
3949 else if (new RegExp("(?:^|\\s)" + cls
+ "(?:$|\\s)").test(line
[prop
])) return false;
3950 else line
[prop
] += " " + cls
;
3955 removeLineClass
: methodOp(function(handle
, where
, cls
) {
3956 return changeLine(this, handle
, "class", function(line
) {
3957 var prop
= where
== "text" ? "textClass" : where
== "background" ? "bgClass" : "wrapClass";
3958 var cur
= line
[prop
];
3959 if (!cur
) return false;
3960 else if (cls
== null) line
[prop
] = null;
3962 var found
= cur
.match(new RegExp("(?:^|\\s+)" + cls
+ "(?:$|\\s+)"));
3963 if (!found
) return false;
3964 var end
= found
.index
+ found
[0].length
;
3965 line
[prop
] = cur
.slice(0, found
.index
) + (!found
.index
|| end
== cur
.length
? "" : " ") + cur
.slice(end
) || null;
3971 addLineWidget
: methodOp(function(handle
, node
, options
) {
3972 return addLineWidget(this, handle
, node
, options
);
3975 removeLineWidget: function(widget
) { widget
.clear(); },
3977 lineInfo: function(line
) {
3978 if (typeof line
== "number") {
3979 if (!isLine(this.doc
, line
)) return null;
3981 line
= getLine(this.doc
, line
);
3982 if (!line
) return null;
3984 var n
= lineNo(line
);
3985 if (n
== null) return null;
3987 return {line
: n
, handle
: line
, text
: line
.text
, gutterMarkers
: line
.gutterMarkers
,
3988 textClass
: line
.textClass
, bgClass
: line
.bgClass
, wrapClass
: line
.wrapClass
,
3989 widgets
: line
.widgets
};
3992 getViewport: function() { return {from: this.display
.viewFrom
, to
: this.display
.viewTo
};},
3994 addWidget: function(pos
, node
, scroll
, vert
, horiz
) {
3995 var display
= this.display
;
3996 pos
= cursorCoords(this, clipPos(this.doc
, pos
));
3997 var top
= pos
.bottom
, left
= pos
.left
;
3998 node
.style
.position
= "absolute";
3999 display
.sizer
.appendChild(node
);
4000 if (vert
== "over") {
4002 } else if (vert
== "above" || vert
== "near") {
4003 var vspace
= Math
.max(display
.wrapper
.clientHeight
, this.doc
.height
),
4004 hspace
= Math
.max(display
.sizer
.clientWidth
, display
.lineSpace
.clientWidth
);
4005 // Default to positioning above (if specified and possible); otherwise default to positioning below
4006 if ((vert
== 'above' || pos
.bottom
+ node
.offsetHeight
> vspace
) && pos
.top
> node
.offsetHeight
)
4007 top
= pos
.top
- node
.offsetHeight
;
4008 else if (pos
.bottom
+ node
.offsetHeight
<= vspace
)
4010 if (left
+ node
.offsetWidth
> hspace
)
4011 left
= hspace
- node
.offsetWidth
;
4013 node
.style
.top
= top
+ "px";
4014 node
.style
.left
= node
.style
.right
= "";
4015 if (horiz
== "right") {
4016 left
= display
.sizer
.clientWidth
- node
.offsetWidth
;
4017 node
.style
.right
= "0px";
4019 if (horiz
== "left") left
= 0;
4020 else if (horiz
== "middle") left
= (display
.sizer
.clientWidth
- node
.offsetWidth
) / 2;
4021 node
.style
.left
= left
+ "px";
4024 scrollIntoView(this, left
, top
, left
+ node
.offsetWidth
, top
+ node
.offsetHeight
);
4027 triggerOnKeyDown
: methodOp(onKeyDown
),
4028 triggerOnKeyPress
: methodOp(onKeyPress
),
4029 triggerOnKeyUp
: methodOp(onKeyUp
),
4031 execCommand: function(cmd
) {
4032 if (commands
.hasOwnProperty(cmd
))
4033 return commands
[cmd
](this);
4036 findPosH: function(from, amount
, unit
, visually
) {
4038 if (amount
< 0) { dir
= -1; amount
= -amount
; }
4039 for (var i
= 0, cur
= clipPos(this.doc
, from); i
< amount
; ++i
) {
4040 cur
= findPosH(this.doc
, cur
, dir
, unit
, visually
);
4041 if (cur
.hitSide
) break;
4046 moveH
: methodOp(function(dir
, unit
) {
4048 cm
.extendSelectionsBy(function(range
) {
4049 if (cm
.display
.shift
|| cm
.doc
.extend
|| range
.empty())
4050 return findPosH(cm
.doc
, range
.head
, dir
, unit
, cm
.options
.rtlMoveVisually
);
4052 return dir
< 0 ? range
.from() : range
.to();
4056 deleteH
: methodOp(function(dir
, unit
) {
4057 var sel
= this.doc
.sel
, doc
= this.doc
;
4058 if (sel
.somethingSelected())
4059 doc
.replaceSelection("", null, "+delete");
4061 deleteNearSelection(this, function(range
) {
4062 var other
= findPosH(doc
, range
.head
, dir
, unit
, false);
4063 return dir
< 0 ? {from: other
, to
: range
.head
} : {from: range
.head
, to
: other
};
4067 findPosV: function(from, amount
, unit
, goalColumn
) {
4068 var dir
= 1, x
= goalColumn
;
4069 if (amount
< 0) { dir
= -1; amount
= -amount
; }
4070 for (var i
= 0, cur
= clipPos(this.doc
, from); i
< amount
; ++i
) {
4071 var coords
= cursorCoords(this, cur
, "div");
4072 if (x
== null) x
= coords
.left
;
4073 else coords
.left
= x
;
4074 cur
= findPosV(this, coords
, dir
, unit
);
4075 if (cur
.hitSide
) break;
4080 moveV
: methodOp(function(dir
, unit
) {
4081 var cm
= this, doc
= this.doc
, goals
= [];
4082 var collapse
= !cm
.display
.shift
&& !doc
.extend
&& doc
.sel
.somethingSelected();
4083 doc
.extendSelectionsBy(function(range
) {
4085 return dir
< 0 ? range
.from() : range
.to();
4086 var headPos
= cursorCoords(cm
, range
.head
, "div");
4087 if (range
.goalColumn
!= null) headPos
.left
= range
.goalColumn
;
4088 goals
.push(headPos
.left
);
4089 var pos
= findPosV(cm
, headPos
, dir
, unit
);
4090 if (unit
== "page" && range
== doc
.sel
.primary())
4091 addToScrollPos(cm
, null, charCoords(cm
, pos
, "div").top
- headPos
.top
);
4094 if (goals
.length
) for (var i
= 0; i
< doc
.sel
.ranges
.length
; i
++)
4095 doc
.sel
.ranges
[i
].goalColumn
= goals
[i
];
4098 toggleOverwrite: function(value
) {
4099 if (value
!= null && value
== this.state
.overwrite
) return;
4100 if (this.state
.overwrite
= !this.state
.overwrite
)
4101 this.display
.cursorDiv
.className
+= " CodeMirror-overwrite";
4103 this.display
.cursorDiv
.className
= this.display
.cursorDiv
.className
.replace(" CodeMirror-overwrite", "");
4105 signal(this, "overwriteToggle", this, this.state
.overwrite
);
4107 hasFocus: function() { return activeElt() == this.display
.input
; },
4109 scrollTo
: methodOp(function(x
, y
) {
4110 if (x
!= null || y
!= null) resolveScrollToPos(this);
4111 if (x
!= null) this.curOp
.scrollLeft
= x
;
4112 if (y
!= null) this.curOp
.scrollTop
= y
;
4114 getScrollInfo: function() {
4115 var scroller
= this.display
.scroller
, co
= scrollerCutOff
;
4116 return {left
: scroller
.scrollLeft
, top
: scroller
.scrollTop
,
4117 height
: scroller
.scrollHeight
- co
, width
: scroller
.scrollWidth
- co
,
4118 clientHeight
: scroller
.clientHeight
- co
, clientWidth
: scroller
.clientWidth
- co
};
4121 scrollIntoView
: methodOp(function(range
, margin
) {
4122 if (range
== null) {
4123 range
= {from: this.doc
.sel
.primary().head
, to
: null};
4124 if (margin
== null) margin
= this.options
.cursorScrollMargin
;
4125 } else if (typeof range
== "number") {
4126 range
= {from: Pos(range
, 0), to
: null};
4127 } else if (range
.from == null) {
4128 range
= {from: range
, to
: null};
4130 if (!range
.to
) range
.to
= range
.from;
4131 range
.margin
= margin
|| 0;
4133 if (range
.from.line
!= null) {
4134 resolveScrollToPos(this);
4135 this.curOp
.scrollToPos
= range
;
4137 var sPos
= calculateScrollPos(this, Math
.min(range
.from.left
, range
.to
.left
),
4138 Math
.min(range
.from.top
, range
.to
.top
) - range
.margin
,
4139 Math
.max(range
.from.right
, range
.to
.right
),
4140 Math
.max(range
.from.bottom
, range
.to
.bottom
) + range
.margin
);
4141 this.scrollTo(sPos
.scrollLeft
, sPos
.scrollTop
);
4145 setSize
: methodOp(function(width
, height
) {
4146 function interpret(val
) {
4147 return typeof val
== "number" || /^\d+$/.test(String(val
)) ? val
+ "px" : val
;
4149 if (width
!= null) this.display
.wrapper
.style
.width
= interpret(width
);
4150 if (height
!= null) this.display
.wrapper
.style
.height
= interpret(height
);
4151 if (this.options
.lineWrapping
) clearLineMeasurementCache(this);
4152 this.curOp
.forceUpdate
= true;
4153 signal(this, "refresh", this);
4156 operation: function(f
){return runInOp(this, f
);},
4158 refresh
: methodOp(function() {
4159 var oldHeight
= this.display
.cachedTextHeight
;
4162 this.scrollTo(this.doc
.scrollLeft
, this.doc
.scrollTop
);
4163 if (oldHeight
== null || Math
.abs(oldHeight
- textHeight(this.display
)) > .5)
4164 estimateLineHeights(this);
4165 signal(this, "refresh", this);
4168 swapDoc
: methodOp(function(doc
) {
4171 attachDoc(this, doc
);
4174 this.scrollTo(doc
.scrollLeft
, doc
.scrollTop
);
4175 signalLater(this, "swapDoc", this, old
);
4179 getInputField: function(){return this.display
.input
;},
4180 getWrapperElement: function(){return this.display
.wrapper
;},
4181 getScrollerElement: function(){return this.display
.scroller
;},
4182 getGutterElement: function(){return this.display
.gutters
;}
4184 eventMixin(CodeMirror
);
4188 // The default configuration options.
4189 var defaults
= CodeMirror
.defaults
= {};
4190 // Functions to run when options are changed.
4191 var optionHandlers
= CodeMirror
.optionHandlers
= {};
4193 function option(name
, deflt
, handle
, notOnInit
) {
4194 CodeMirror
.defaults
[name
] = deflt
;
4195 if (handle
) optionHandlers
[name
] =
4196 notOnInit
? function(cm
, val
, old
) {if (old
!= Init
) handle(cm
, val
, old
);} : handle
;
4199 // Passed to option handlers when there is no old value.
4200 var Init
= CodeMirror
.Init
= {toString: function(){return "CodeMirror.Init";}};
4202 // These two are, on init, called from the constructor because they
4203 // have to be initialized before the editor can start at all.
4204 option("value", "", function(cm
, val
) {
4207 option("mode", null, function(cm
, val
) {
4208 cm
.doc
.modeOption
= val
;
4212 option("indentUnit", 2, loadMode
, true);
4213 option("indentWithTabs", false);
4214 option("smartIndent", true);
4215 option("tabSize", 4, function(cm
) {
4220 option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm
, val
) {
4221 cm
.options
.specialChars
= new RegExp(val
.source
+ (val
.test("\t") ? "" : "|\t"), "g");
4224 option("specialCharPlaceholder", defaultSpecialCharPlaceholder
, function(cm
) {cm
.refresh();}, true);
4225 option("electricChars", true);
4226 option("rtlMoveVisually", !windows
);
4227 option("wholeLineUpdateBefore", true);
4229 option("theme", "default", function(cm
) {
4233 option("keyMap", "default", keyMapChanged
);
4234 option("extraKeys", null);
4236 option("lineWrapping", false, wrappingChanged
, true);
4237 option("gutters", [], function(cm
) {
4238 setGuttersForLineNumbers(cm
.options
);
4241 option("fixedGutter", true, function(cm
, val
) {
4242 cm
.display
.gutters
.style
.left
= val
? compensateForHScroll(cm
.display
) + "px" : "0";
4245 option("coverGutterNextToScrollbar", false, updateScrollbars
, true);
4246 option("lineNumbers", false, function(cm
) {
4247 setGuttersForLineNumbers(cm
.options
);
4250 option("firstLineNumber", 1, guttersChanged
, true);
4251 option("lineNumberFormatter", function(integer
) {return integer
;}, guttersChanged
, true);
4252 option("showCursorWhenSelecting", false, updateSelection
, true);
4254 option("resetSelectionOnContextMenu", true);
4256 option("readOnly", false, function(cm
, val
) {
4257 if (val
== "nocursor") {
4259 cm
.display
.input
.blur();
4260 cm
.display
.disabled
= true;
4262 cm
.display
.disabled
= false;
4263 if (!val
) resetInput(cm
);
4266 option("disableInput", false, function(cm
, val
) {if (!val
) resetInput(cm
);}, true);
4267 option("dragDrop", true);
4269 option("cursorBlinkRate", 530);
4270 option("cursorScrollMargin", 0);
4271 option("cursorHeight", 1);
4272 option("workTime", 100);
4273 option("workDelay", 100);
4274 option("flattenSpans", true, resetModeState
, true);
4275 option("addModeClass", false, resetModeState
, true);
4276 option("pollInterval", 100);
4277 option("undoDepth", 200, function(cm
, val
){cm
.doc
.history
.undoDepth
= val
;});
4278 option("historyEventDelay", 1250);
4279 option("viewportMargin", 10, function(cm
){cm
.refresh();}, true);
4280 option("maxHighlightLength", 10000, resetModeState
, true);
4281 option("moveInputWithCursor", true, function(cm
, val
) {
4282 if (!val
) cm
.display
.inputDiv
.style
.top
= cm
.display
.inputDiv
.style
.left
= 0;
4285 option("tabindex", null, function(cm
, val
) {
4286 cm
.display
.input
.tabIndex
= val
|| "";
4288 option("autofocus", null);
4290 // MODE DEFINITION AND QUERYING
4292 // Known modes, by name and by MIME
4293 var modes
= CodeMirror
.modes
= {}, mimeModes
= CodeMirror
.mimeModes
= {};
4295 // Extra arguments are stored as the mode's dependencies, which is
4296 // used by (legacy) mechanisms like loadmode.js to automatically
4297 // load a mode. (Preferred mechanism is the require/define calls.)
4298 CodeMirror
.defineMode = function(name
, mode
) {
4299 if (!CodeMirror
.defaults
.mode
&& name
!= "null") CodeMirror
.defaults
.mode
= name
;
4300 if (arguments
.length
> 2) {
4301 mode
.dependencies
= [];
4302 for (var i
= 2; i
< arguments
.length
; ++i
) mode
.dependencies
.push(arguments
[i
]);
4307 CodeMirror
.defineMIME = function(mime
, spec
) {
4308 mimeModes
[mime
] = spec
;
4311 // Given a MIME type, a {name, ...options} config object, or a name
4312 // string, return a mode config object.
4313 CodeMirror
.resolveMode = function(spec
) {
4314 if (typeof spec
== "string" && mimeModes
.hasOwnProperty(spec
)) {
4315 spec
= mimeModes
[spec
];
4316 } else if (spec
&& typeof spec
.name
== "string" && mimeModes
.hasOwnProperty(spec
.name
)) {
4317 var found
= mimeModes
[spec
.name
];
4318 if (typeof found
== "string") found
= {name
: found
};
4319 spec
= createObj(found
, spec
);
4320 spec
.name
= found
.name
;
4321 } else if (typeof spec
== "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec
)) {
4322 return CodeMirror
.resolveMode("application/xml");
4324 if (typeof spec
== "string") return {name
: spec
};
4325 else return spec
|| {name
: "null"};
4328 // Given a mode spec (anything that resolveMode accepts), find and
4329 // initialize an actual mode object.
4330 CodeMirror
.getMode = function(options
, spec
) {
4331 var spec
= CodeMirror
.resolveMode(spec
);
4332 var mfactory
= modes
[spec
.name
];
4333 if (!mfactory
) return CodeMirror
.getMode(options
, "text/plain");
4334 var modeObj
= mfactory(options
, spec
);
4335 if (modeExtensions
.hasOwnProperty(spec
.name
)) {
4336 var exts
= modeExtensions
[spec
.name
];
4337 for (var prop
in exts
) {
4338 if (!exts
.hasOwnProperty(prop
)) continue;
4339 if (modeObj
.hasOwnProperty(prop
)) modeObj
["_" + prop
] = modeObj
[prop
];
4340 modeObj
[prop
] = exts
[prop
];
4343 modeObj
.name
= spec
.name
;
4344 if (spec
.helperType
) modeObj
.helperType
= spec
.helperType
;
4345 if (spec
.modeProps
) for (var prop
in spec
.modeProps
)
4346 modeObj
[prop
] = spec
.modeProps
[prop
];
4351 // Minimal default mode.
4352 CodeMirror
.defineMode("null", function() {
4353 return {token: function(stream
) {stream
.skipToEnd();}};
4355 CodeMirror
.defineMIME("text/plain", "null");
4357 // This can be used to attach properties to mode objects from
4358 // outside the actual mode definition.
4359 var modeExtensions
= CodeMirror
.modeExtensions
= {};
4360 CodeMirror
.extendMode = function(mode
, properties
) {
4361 var exts
= modeExtensions
.hasOwnProperty(mode
) ? modeExtensions
[mode
] : (modeExtensions
[mode
] = {});
4362 copyObj(properties
, exts
);
4367 CodeMirror
.defineExtension = function(name
, func
) {
4368 CodeMirror
.prototype[name
] = func
;
4370 CodeMirror
.defineDocExtension = function(name
, func
) {
4371 Doc
.prototype[name
] = func
;
4373 CodeMirror
.defineOption
= option
;
4376 CodeMirror
.defineInitHook = function(f
) {initHooks
.push(f
);};
4378 var helpers
= CodeMirror
.helpers
= {};
4379 CodeMirror
.registerHelper = function(type
, name
, value
) {
4380 if (!helpers
.hasOwnProperty(type
)) helpers
[type
] = CodeMirror
[type
] = {_global
: []};
4381 helpers
[type
][name
] = value
;
4383 CodeMirror
.registerGlobalHelper = function(type
, name
, predicate
, value
) {
4384 CodeMirror
.registerHelper(type
, name
, value
);
4385 helpers
[type
]._global
.push({pred
: predicate
, val
: value
});
4388 // MODE STATE HANDLING
4390 // Utility functions for working with state. Exported because nested
4391 // modes need to do this for their inner modes.
4393 var copyState
= CodeMirror
.copyState = function(mode
, state
) {
4394 if (state
=== true) return state
;
4395 if (mode
.copyState
) return mode
.copyState(state
);
4397 for (var n
in state
) {
4399 if (val
instanceof Array
) val
= val
.concat([]);
4405 var startState
= CodeMirror
.startState = function(mode
, a1
, a2
) {
4406 return mode
.startState
? mode
.startState(a1
, a2
) : true;
4409 // Given a mode and a state (for that mode), find the inner mode and
4410 // state at the position that the state refers to.
4411 CodeMirror
.innerMode = function(mode
, state
) {
4412 while (mode
.innerMode
) {
4413 var info
= mode
.innerMode(state
);
4414 if (!info
|| info
.mode
== mode
) break;
4418 return info
|| {mode
: mode
, state
: state
};
4421 // STANDARD COMMANDS
4423 // Commands are parameter-less actions that can be performed on an
4424 // editor, mostly used for keybindings.
4425 var commands
= CodeMirror
.commands
= {
4426 selectAll: function(cm
) {cm
.setSelection(Pos(cm
.firstLine(), 0), Pos(cm
.lastLine()), sel_dontScroll
);},
4427 singleSelection: function(cm
) {
4428 cm
.setSelection(cm
.getCursor("anchor"), cm
.getCursor("head"), sel_dontScroll
);
4430 killLine: function(cm
) {
4431 deleteNearSelection(cm
, function(range
) {
4432 if (range
.empty()) {
4433 var len
= getLine(cm
.doc
, range
.head
.line
).text
.length
;
4434 if (range
.head
.ch
== len
&& range
.head
.line
< cm
.lastLine())
4435 return {from: range
.head
, to
: Pos(range
.head
.line
+ 1, 0)};
4437 return {from: range
.head
, to
: Pos(range
.head
.line
, len
)};
4439 return {from: range
.from(), to
: range
.to()};
4443 deleteLine: function(cm
) {
4444 deleteNearSelection(cm
, function(range
) {
4445 return {from: Pos(range
.from().line
, 0),
4446 to
: clipPos(cm
.doc
, Pos(range
.to().line
+ 1, 0))};
4449 delLineLeft: function(cm
) {
4450 deleteNearSelection(cm
, function(range
) {
4451 return {from: Pos(range
.from().line
, 0), to
: range
.from()};
4454 undo: function(cm
) {cm
.undo();},
4455 redo: function(cm
) {cm
.redo();},
4456 undoSelection: function(cm
) {cm
.undoSelection();},
4457 redoSelection: function(cm
) {cm
.redoSelection();},
4458 goDocStart: function(cm
) {cm
.extendSelection(Pos(cm
.firstLine(), 0));},
4459 goDocEnd: function(cm
) {cm
.extendSelection(Pos(cm
.lastLine()));},
4460 goLineStart: function(cm
) {
4461 cm
.extendSelectionsBy(function(range
) { return lineStart(cm
, range
.head
.line
); }, sel_move
);
4463 goLineStartSmart: function(cm
) {
4464 cm
.extendSelectionsBy(function(range
) {
4465 var start
= lineStart(cm
, range
.head
.line
);
4466 var line
= cm
.getLineHandle(start
.line
);
4467 var order
= getOrder(line
);
4468 if (!order
|| order
[0].level
== 0) {
4469 var firstNonWS
= Math
.max(0, line
.text
.search(/\S/));
4470 var inWS
= range
.head
.line
== start
.line
&& range
.head
.ch
<= firstNonWS
&& range
.head
.ch
;
4471 return Pos(start
.line
, inWS
? 0 : firstNonWS
);
4476 goLineEnd: function(cm
) {
4477 cm
.extendSelectionsBy(function(range
) { return lineEnd(cm
, range
.head
.line
); }, sel_move
);
4479 goLineRight: function(cm
) {
4480 cm
.extendSelectionsBy(function(range
) {
4481 var top
= cm
.charCoords(range
.head
, "div").top
+ 5;
4482 return cm
.coordsChar({left
: cm
.display
.lineDiv
.offsetWidth
+ 100, top
: top
}, "div");
4485 goLineLeft: function(cm
) {
4486 cm
.extendSelectionsBy(function(range
) {
4487 var top
= cm
.charCoords(range
.head
, "div").top
+ 5;
4488 return cm
.coordsChar({left
: 0, top
: top
}, "div");
4491 goLineUp: function(cm
) {cm
.moveV(-1, "line");},
4492 goLineDown: function(cm
) {cm
.moveV(1, "line");},
4493 goPageUp: function(cm
) {cm
.moveV(-1, "page");},
4494 goPageDown: function(cm
) {cm
.moveV(1, "page");},
4495 goCharLeft: function(cm
) {cm
.moveH(-1, "char");},
4496 goCharRight: function(cm
) {cm
.moveH(1, "char");},
4497 goColumnLeft: function(cm
) {cm
.moveH(-1, "column");},
4498 goColumnRight: function(cm
) {cm
.moveH(1, "column");},
4499 goWordLeft: function(cm
) {cm
.moveH(-1, "word");},
4500 goGroupRight: function(cm
) {cm
.moveH(1, "group");},
4501 goGroupLeft: function(cm
) {cm
.moveH(-1, "group");},
4502 goWordRight: function(cm
) {cm
.moveH(1, "word");},
4503 delCharBefore: function(cm
) {cm
.deleteH(-1, "char");},
4504 delCharAfter: function(cm
) {cm
.deleteH(1, "char");},
4505 delWordBefore: function(cm
) {cm
.deleteH(-1, "word");},
4506 delWordAfter: function(cm
) {cm
.deleteH(1, "word");},
4507 delGroupBefore: function(cm
) {cm
.deleteH(-1, "group");},
4508 delGroupAfter: function(cm
) {cm
.deleteH(1, "group");},
4509 indentAuto: function(cm
) {cm
.indentSelection("smart");},
4510 indentMore: function(cm
) {cm
.indentSelection("add");},
4511 indentLess: function(cm
) {cm
.indentSelection("subtract");},
4512 insertTab: function(cm
) {cm
.replaceSelection("\t");},
4513 defaultTab: function(cm
) {
4514 if (cm
.somethingSelected()) cm
.indentSelection("add");
4515 else cm
.execCommand("insertTab");
4517 transposeChars: function(cm
) {
4518 runInOp(cm
, function() {
4519 var ranges
= cm
.listSelections();
4520 for (var i
= 0; i
< ranges
.length
; i
++) {
4521 var cur
= ranges
[i
].head
, line
= getLine(cm
.doc
, cur
.line
).text
;
4522 if (cur
.ch
> 0 && cur
.ch
< line
.length
- 1)
4523 cm
.replaceRange(line
.charAt(cur
.ch
) + line
.charAt(cur
.ch
- 1),
4524 Pos(cur
.line
, cur
.ch
- 1), Pos(cur
.line
, cur
.ch
+ 1));
4528 newlineAndIndent: function(cm
) {
4529 runInOp(cm
, function() {
4530 var len
= cm
.listSelections().length
;
4531 for (var i
= 0; i
< len
; i
++) {
4532 var range
= cm
.listSelections()[i
];
4533 cm
.replaceRange("\n", range
.anchor
, range
.head
, "+input");
4534 cm
.indentLine(range
.from().line
+ 1, null, true);
4535 ensureCursorVisible(cm
);
4539 toggleOverwrite: function(cm
) {cm
.toggleOverwrite();}
4544 var keyMap
= CodeMirror
.keyMap
= {};
4546 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
4547 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
4548 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
4549 "Tab": "defaultTab", "Shift-Tab": "indentAuto",
4550 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
4551 "Esc": "singleSelection"
4553 // Note that the save and find-related commands aren't defined by
4554 // default. User code or addons can define them. Unknown commands
4555 // are simply ignored.
4556 keyMap
.pcDefault
= {
4557 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
4558 "Ctrl-Home": "goDocStart", "Ctrl-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
4559 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
4560 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
4561 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
4562 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
4563 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection",
4564 fallthrough
: "basic"
4566 keyMap
.macDefault
= {
4567 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
4568 "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
4569 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore",
4570 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
4571 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
4572 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft",
4573 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection",
4574 fallthrough
: ["basic", "emacsy"]
4576 // Very basic readline/emacs-style bindings, which are standard on Mac.
4578 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
4579 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
4580 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
4581 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
4583 keyMap
["default"] = mac
? keyMap
.macDefault
: keyMap
.pcDefault
;
4587 function getKeyMap(val
) {
4588 if (typeof val
== "string") return keyMap
[val
];
4592 // Given an array of keymaps and a key name, call handle on any
4593 // bindings found, until that returns a truthy value, at which point
4594 // we consider the key handled. Implements things like binding a key
4595 // to false stopping further handling and keymap fallthrough.
4596 var lookupKey
= CodeMirror
.lookupKey = function(name
, maps
, handle
) {
4597 function lookup(map
) {
4598 map
= getKeyMap(map
);
4599 var found
= map
[name
];
4600 if (found
=== false) return "stop";
4601 if (found
!= null && handle(found
)) return true;
4602 if (map
.nofallthrough
) return "stop";
4604 var fallthrough
= map
.fallthrough
;
4605 if (fallthrough
== null) return false;
4606 if (Object
.prototype.toString
.call(fallthrough
) != "[object Array]")
4607 return lookup(fallthrough
);
4608 for (var i
= 0; i
< fallthrough
.length
; ++i
) {
4609 var done
= lookup(fallthrough
[i
]);
4610 if (done
) return done
;
4615 for (var i
= 0; i
< maps
.length
; ++i
) {
4616 var done
= lookup(maps
[i
]);
4617 if (done
) return done
!= "stop";
4621 // Modifier key presses don't count as 'real' key presses for the
4622 // purpose of keymap fallthrough.
4623 var isModifierKey
= CodeMirror
.isModifierKey = function(event
) {
4624 var name
= keyNames
[event
.keyCode
];
4625 return name
== "Ctrl" || name
== "Alt" || name
== "Shift" || name
== "Mod";
4628 // Look up the name of a key as indicated by an event object.
4629 var keyName
= CodeMirror
.keyName = function(event
, noShift
) {
4630 if (presto
&& event
.keyCode
== 34 && event
["char"]) return false;
4631 var name
= keyNames
[event
.keyCode
];
4632 if (name
== null || event
.altGraphKey
) return false;
4633 if (event
.altKey
) name
= "Alt-" + name
;
4634 if (flipCtrlCmd
? event
.metaKey
: event
.ctrlKey
) name
= "Ctrl-" + name
;
4635 if (flipCtrlCmd
? event
.ctrlKey
: event
.metaKey
) name
= "Cmd-" + name
;
4636 if (!noShift
&& event
.shiftKey
) name
= "Shift-" + name
;
4642 CodeMirror
.fromTextArea = function(textarea
, options
) {
4643 if (!options
) options
= {};
4644 options
.value
= textarea
.value
;
4645 if (!options
.tabindex
&& textarea
.tabindex
)
4646 options
.tabindex
= textarea
.tabindex
;
4647 if (!options
.placeholder
&& textarea
.placeholder
)
4648 options
.placeholder
= textarea
.placeholder
;
4649 // Set autofocus to true if this textarea is focused, or if it has
4650 // autofocus and no other element is focused.
4651 if (options
.autofocus
== null) {
4652 var hasFocus
= activeElt();
4653 options
.autofocus
= hasFocus
== textarea
||
4654 textarea
.getAttribute("autofocus") != null && hasFocus
== document
.body
;
4657 function save() {textarea
.value
= cm
.getValue();}
4658 if (textarea
.form
) {
4659 on(textarea
.form
, "submit", save
);
4660 // Deplorable hack to make the submit method do the right thing.
4661 if (!options
.leaveSubmitMethodAlone
) {
4662 var form
= textarea
.form
, realSubmit
= form
.submit
;
4664 var wrappedSubmit
= form
.submit = function() {
4666 form
.submit
= realSubmit
;
4668 form
.submit
= wrappedSubmit
;
4674 textarea
.style
.display
= "none";
4675 var cm
= CodeMirror(function(node
) {
4676 textarea
.parentNode
.insertBefore(node
, textarea
.nextSibling
);
4679 cm
.getTextArea = function() { return textarea
; };
4680 cm
.toTextArea = function() {
4682 textarea
.parentNode
.removeChild(cm
.getWrapperElement());
4683 textarea
.style
.display
= "";
4684 if (textarea
.form
) {
4685 off(textarea
.form
, "submit", save
);
4686 if (typeof textarea
.form
.submit
== "function")
4687 textarea
.form
.submit
= realSubmit
;
4695 // Fed to the mode parsers, provides helper functions to make
4696 // parsers more succinct.
4698 var StringStream
= CodeMirror
.StringStream = function(string
, tabSize
) {
4699 this.pos
= this.start
= 0;
4700 this.string
= string
;
4701 this.tabSize
= tabSize
|| 8;
4702 this.lastColumnPos
= this.lastColumnValue
= 0;
4706 StringStream
.prototype = {
4707 eol: function() {return this.pos
>= this.string
.length
;},
4708 sol: function() {return this.pos
== this.lineStart
;},
4709 peek: function() {return this.string
.charAt(this.pos
) || undefined;},
4711 if (this.pos
< this.string
.length
)
4712 return this.string
.charAt(this.pos
++);
4714 eat: function(match
) {
4715 var ch
= this.string
.charAt(this.pos
);
4716 if (typeof match
== "string") var ok
= ch
== match
;
4717 else var ok
= ch
&& (match
.test
? match
.test(ch
) : match(ch
));
4718 if (ok
) {++this.pos
; return ch
;}
4720 eatWhile: function(match
) {
4721 var start
= this.pos
;
4722 while (this.eat(match
)){}
4723 return this.pos
> start
;
4725 eatSpace: function() {
4726 var start
= this.pos
;
4727 while (/[\s\u00a0]/.test(this.string
.charAt(this.pos
))) ++this.pos
;
4728 return this.pos
> start
;
4730 skipToEnd: function() {this.pos
= this.string
.length
;},
4731 skipTo: function(ch
) {
4732 var found
= this.string
.indexOf(ch
, this.pos
);
4733 if (found
> -1) {this.pos
= found
; return true;}
4735 backUp: function(n
) {this.pos
-= n
;},
4736 column: function() {
4737 if (this.lastColumnPos
< this.start
) {
4738 this.lastColumnValue
= countColumn(this.string
, this.start
, this.tabSize
, this.lastColumnPos
, this.lastColumnValue
);
4739 this.lastColumnPos
= this.start
;
4741 return this.lastColumnValue
- (this.lineStart
? countColumn(this.string
, this.lineStart
, this.tabSize
) : 0);
4743 indentation: function() {
4744 return countColumn(this.string
, null, this.tabSize
) -
4745 (this.lineStart
? countColumn(this.string
, this.lineStart
, this.tabSize
) : 0);
4747 match: function(pattern
, consume
, caseInsensitive
) {
4748 if (typeof pattern
== "string") {
4749 var cased = function(str
) {return caseInsensitive
? str
.toLowerCase() : str
;};
4750 var substr
= this.string
.substr(this.pos
, pattern
.length
);
4751 if (cased(substr
) == cased(pattern
)) {
4752 if (consume
!== false) this.pos
+= pattern
.length
;
4756 var match
= this.string
.slice(this.pos
).match(pattern
);
4757 if (match
&& match
.index
> 0) return null;
4758 if (match
&& consume
!== false) this.pos
+= match
[0].length
;
4762 current: function(){return this.string
.slice(this.start
, this.pos
);},
4763 hideFirstChars: function(n
, inner
) {
4764 this.lineStart
+= n
;
4765 try { return inner(); }
4766 finally { this.lineStart
-= n
; }
4772 // Created with markText and setBookmark methods. A TextMarker is a
4773 // handle that can be used to clear or find a marked position in the
4774 // document. Line objects hold arrays (markedSpans) containing
4775 // {from, to, marker} object pointing to such marker objects, and
4776 // indicating that such a marker is present on that line. Multiple
4777 // lines may point to the same marker when it spans across lines.
4778 // The spans will have null for their from/to properties when the
4779 // marker continues beyond the start/end of the line. Markers have
4780 // links back to the lines they currently touch.
4782 var TextMarker
= CodeMirror
.TextMarker = function(doc
, type
) {
4787 eventMixin(TextMarker
);
4789 // Clear the marker.
4790 TextMarker
.prototype.clear = function() {
4791 if (this.explicitlyCleared
) return;
4792 var cm
= this.doc
.cm
, withOp
= cm
&& !cm
.curOp
;
4793 if (withOp
) startOperation(cm
);
4794 if (hasHandler(this, "clear")) {
4795 var found
= this.find();
4796 if (found
) signalLater(this, "clear", found
.from, found
.to
);
4798 var min
= null, max
= null;
4799 for (var i
= 0; i
< this.lines
.length
; ++i
) {
4800 var line
= this.lines
[i
];
4801 var span
= getMarkedSpanFor(line
.markedSpans
, this);
4802 if (cm
&& !this.collapsed
) regLineChange(cm
, lineNo(line
), "text");
4804 if (span
.to
!= null) max
= lineNo(line
);
4805 if (span
.from != null) min
= lineNo(line
);
4807 line
.markedSpans
= removeMarkedSpan(line
.markedSpans
, span
);
4808 if (span
.from == null && this.collapsed
&& !lineIsHidden(this.doc
, line
) && cm
)
4809 updateLineHeight(line
, textHeight(cm
.display
));
4811 if (cm
&& this.collapsed
&& !cm
.options
.lineWrapping
) for (var i
= 0; i
< this.lines
.length
; ++i
) {
4812 var visual
= visualLine(this.lines
[i
]), len
= lineLength(visual
);
4813 if (len
> cm
.display
.maxLineLength
) {
4814 cm
.display
.maxLine
= visual
;
4815 cm
.display
.maxLineLength
= len
;
4816 cm
.display
.maxLineChanged
= true;
4820 if (min
!= null && cm
&& this.collapsed
) regChange(cm
, min
, max
+ 1);
4821 this.lines
.length
= 0;
4822 this.explicitlyCleared
= true;
4823 if (this.atomic
&& this.doc
.cantEdit
) {
4824 this.doc
.cantEdit
= false;
4825 if (cm
) reCheckSelection(cm
.doc
);
4827 if (cm
) signalLater(cm
, "markerCleared", cm
, this);
4828 if (withOp
) endOperation(cm
);
4831 // Find the position of the marker in the document. Returns a {from,
4832 // to} object by default. Side can be passed to get a specific side
4833 // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
4834 // Pos objects returned contain a line object, rather than a line
4835 // number (used to prevent looking up the same line twice).
4836 TextMarker
.prototype.find = function(side
, lineObj
) {
4837 if (side
== null && this.type
== "bookmark") side
= 1;
4839 for (var i
= 0; i
< this.lines
.length
; ++i
) {
4840 var line
= this.lines
[i
];
4841 var span
= getMarkedSpanFor(line
.markedSpans
, this);
4842 if (span
.from != null) {
4843 from = Pos(lineObj
? line
: lineNo(line
), span
.from);
4844 if (side
== -1) return from;
4846 if (span
.to
!= null) {
4847 to
= Pos(lineObj
? line
: lineNo(line
), span
.to
);
4848 if (side
== 1) return to
;
4851 return from && {from: from, to
: to
};
4854 // Signals that the marker's widget changed, and surrounding layout
4855 // should be recomputed.
4856 TextMarker
.prototype.changed = function() {
4857 var pos
= this.find(-1, true), widget
= this, cm
= this.doc
.cm
;
4858 if (!pos
|| !cm
) return;
4859 runInOp(cm
, function() {
4860 var line
= pos
.line
, lineN
= lineNo(pos
.line
);
4861 var view
= findViewForLine(cm
, lineN
);
4863 clearLineMeasurementCacheFor(view
);
4864 cm
.curOp
.selectionChanged
= cm
.curOp
.forceUpdate
= true;
4866 cm
.curOp
.updateMaxLine
= true;
4867 if (!lineIsHidden(widget
.doc
, line
) && widget
.height
!= null) {
4868 var oldHeight
= widget
.height
;
4869 widget
.height
= null;
4870 var dHeight
= widgetHeight(widget
) - oldHeight
;
4872 updateLineHeight(line
, line
.height
+ dHeight
);
4877 TextMarker
.prototype.attachLine = function(line
) {
4878 if (!this.lines
.length
&& this.doc
.cm
) {
4879 var op
= this.doc
.cm
.curOp
;
4880 if (!op
.maybeHiddenMarkers
|| indexOf(op
.maybeHiddenMarkers
, this) == -1)
4881 (op
.maybeUnhiddenMarkers
|| (op
.maybeUnhiddenMarkers
= [])).push(this);
4883 this.lines
.push(line
);
4885 TextMarker
.prototype.detachLine = function(line
) {
4886 this.lines
.splice(indexOf(this.lines
, line
), 1);
4887 if (!this.lines
.length
&& this.doc
.cm
) {
4888 var op
= this.doc
.cm
.curOp
;
4889 (op
.maybeHiddenMarkers
|| (op
.maybeHiddenMarkers
= [])).push(this);
4893 // Collapsed markers have unique ids, in order to be able to order
4894 // them, which is needed for uniquely determining an outer marker
4895 // when they overlap (they may nest, but not partially overlap).
4896 var nextMarkerId
= 0;
4898 // Create a marker, wire it up to the right lines, and
4899 function markText(doc
, from, to
, options
, type
) {
4900 // Shared markers (across linked documents) are handled separately
4901 // (markTextShared will call out to this again, once per
4903 if (options
&& options
.shared
) return markTextShared(doc
, from, to
, options
, type
);
4904 // Ensure we are in an operation.
4905 if (doc
.cm
&& !doc
.cm
.curOp
) return operation(doc
.cm
, markText
)(doc
, from, to
, options
, type
);
4907 var marker
= new TextMarker(doc
, type
), diff
= cmp(from, to
);
4908 if (options
) copyObj(options
, marker
);
4909 // Don't connect empty markers unless clearWhenEmpty is false
4910 if (diff
> 0 || diff
== 0 && marker
.clearWhenEmpty
!== false)
4912 if (marker
.replacedWith
) {
4913 // Showing up as a widget implies collapsed (widget replaces text)
4914 marker
.collapsed
= true;
4915 marker
.widgetNode
= elt("span", [marker
.replacedWith
], "CodeMirror-widget");
4916 if (!options
.handleMouseEvents
) marker
.widgetNode
.ignoreEvents
= true;
4917 if (options
.insertLeft
) marker
.widgetNode
.insertLeft
= true;
4919 if (marker
.collapsed
) {
4920 if (conflictingCollapsedRange(doc
, from.line
, from, to
, marker
) ||
4921 from.line
!= to
.line
&& conflictingCollapsedRange(doc
, to
.line
, from, to
, marker
))
4922 throw new Error("Inserting collapsed marker partially overlapping an existing one");
4923 sawCollapsedSpans
= true;
4926 if (marker
.addToHistory
)
4927 addChangeToHistory(doc
, {from: from, to
: to
, origin
: "markText"}, doc
.sel
, NaN
);
4929 var curLine
= from.line
, cm
= doc
.cm
, updateMaxLine
;
4930 doc
.iter(curLine
, to
.line
+ 1, function(line
) {
4931 if (cm
&& marker
.collapsed
&& !cm
.options
.lineWrapping
&& visualLine(line
) == cm
.display
.maxLine
)
4932 updateMaxLine
= true;
4933 if (marker
.collapsed
&& curLine
!= from.line
) updateLineHeight(line
, 0);
4934 addMarkedSpan(line
, new MarkedSpan(marker
,
4935 curLine
== from.line
? from.ch
: null,
4936 curLine
== to
.line
? to
.ch
: null));
4939 // lineIsHidden depends on the presence of the spans, so needs a second pass
4940 if (marker
.collapsed
) doc
.iter(from.line
, to
.line
+ 1, function(line
) {
4941 if (lineIsHidden(doc
, line
)) updateLineHeight(line
, 0);
4944 if (marker
.clearOnEnter
) on(marker
, "beforeCursorEnter", function() { marker
.clear(); });
4946 if (marker
.readOnly
) {
4947 sawReadOnlySpans
= true;
4948 if (doc
.history
.done
.length
|| doc
.history
.undone
.length
)
4951 if (marker
.collapsed
) {
4952 marker
.id
= ++nextMarkerId
;
4953 marker
.atomic
= true;
4956 // Sync editor state
4957 if (updateMaxLine
) cm
.curOp
.updateMaxLine
= true;
4958 if (marker
.collapsed
)
4959 regChange(cm
, from.line
, to
.line
+ 1);
4960 else if (marker
.className
|| marker
.title
|| marker
.startStyle
|| marker
.endStyle
)
4961 for (var i
= from.line
; i
<= to
.line
; i
++) regLineChange(cm
, i
, "text");
4962 if (marker
.atomic
) reCheckSelection(cm
.doc
);
4963 signalLater(cm
, "markerAdded", cm
, marker
);
4968 // SHARED TEXTMARKERS
4970 // A shared marker spans multiple linked documents. It is
4971 // implemented as a meta-marker-object controlling multiple normal
4973 var SharedTextMarker
= CodeMirror
.SharedTextMarker = function(markers
, primary
) {
4974 this.markers
= markers
;
4975 this.primary
= primary
;
4976 for (var i
= 0, me
= this; i
< markers
.length
; ++i
) {
4977 markers
[i
].parent
= this;
4978 on(markers
[i
], "clear", function(){me
.clear();});
4981 eventMixin(SharedTextMarker
);
4983 SharedTextMarker
.prototype.clear = function() {
4984 if (this.explicitlyCleared
) return;
4985 this.explicitlyCleared
= true;
4986 for (var i
= 0; i
< this.markers
.length
; ++i
)
4987 this.markers
[i
].clear();
4988 signalLater(this, "clear");
4990 SharedTextMarker
.prototype.find = function(side
, lineObj
) {
4991 return this.primary
.find(side
, lineObj
);
4994 function markTextShared(doc
, from, to
, options
, type
) {
4995 options
= copyObj(options
);
4996 options
.shared
= false;
4997 var markers
= [markText(doc
, from, to
, options
, type
)], primary
= markers
[0];
4998 var widget
= options
.widgetNode
;
4999 linkedDocs(doc
, function(doc
) {
5000 if (widget
) options
.widgetNode
= widget
.cloneNode(true);
5001 markers
.push(markText(doc
, clipPos(doc
, from), clipPos(doc
, to
), options
, type
));
5002 for (var i
= 0; i
< doc
.linked
.length
; ++i
)
5003 if (doc
.linked
[i
].isParent
) return;
5004 primary
= lst(markers
);
5006 return new SharedTextMarker(markers
, primary
);
5011 function MarkedSpan(marker
, from, to
) {
5012 this.marker
= marker
;
5013 this.from = from; this.to
= to
;
5016 // Search an array of spans for a span matching the given marker.
5017 function getMarkedSpanFor(spans
, marker
) {
5018 if (spans
) for (var i
= 0; i
< spans
.length
; ++i
) {
5019 var span
= spans
[i
];
5020 if (span
.marker
== marker
) return span
;
5023 // Remove a span from an array, returning undefined if no spans are
5024 // left (we don't store arrays for lines without spans).
5025 function removeMarkedSpan(spans
, span
) {
5026 for (var r
, i
= 0; i
< spans
.length
; ++i
)
5027 if (spans
[i
] != span
) (r
|| (r
= [])).push(spans
[i
]);
5030 // Add a span to a line.
5031 function addMarkedSpan(line
, span
) {
5032 line
.markedSpans
= line
.markedSpans
? line
.markedSpans
.concat([span
]) : [span
];
5033 span
.marker
.attachLine(line
);
5036 // Used for the algorithm that adjusts markers for a change in the
5037 // document. These functions cut an array of spans at a given
5038 // character position, returning an array of remaining chunks (or
5039 // undefined if nothing remains).
5040 function markedSpansBefore(old
, startCh
, isInsert
) {
5041 if (old
) for (var i
= 0, nw
; i
< old
.length
; ++i
) {
5042 var span
= old
[i
], marker
= span
.marker
;
5043 var startsBefore
= span
.from == null || (marker
.inclusiveLeft
? span
.from <= startCh
: span
.from < startCh
);
5044 if (startsBefore
|| span
.from == startCh
&& marker
.type
== "bookmark" && (!isInsert
|| !span
.marker
.insertLeft
)) {
5045 var endsAfter
= span
.to
== null || (marker
.inclusiveRight
? span
.to
>= startCh
: span
.to
> startCh
);
5046 (nw
|| (nw
= [])).push(new MarkedSpan(marker
, span
.from, endsAfter
? null : span
.to
));
5051 function markedSpansAfter(old
, endCh
, isInsert
) {
5052 if (old
) for (var i
= 0, nw
; i
< old
.length
; ++i
) {
5053 var span
= old
[i
], marker
= span
.marker
;
5054 var endsAfter
= span
.to
== null || (marker
.inclusiveRight
? span
.to
>= endCh
: span
.to
> endCh
);
5055 if (endsAfter
|| span
.from == endCh
&& marker
.type
== "bookmark" && (!isInsert
|| span
.marker
.insertLeft
)) {
5056 var startsBefore
= span
.from == null || (marker
.inclusiveLeft
? span
.from <= endCh
: span
.from < endCh
);
5057 (nw
|| (nw
= [])).push(new MarkedSpan(marker
, startsBefore
? null : span
.from - endCh
,
5058 span
.to
== null ? null : span
.to
- endCh
));
5064 // Given a change object, compute the new set of marker spans that
5065 // cover the line in which the change took place. Removes spans
5066 // entirely within the change, reconnects spans belonging to the
5067 // same marker that appear on both sides of the change, and cuts off
5068 // spans partially within the change. Returns an array of span
5069 // arrays with one element for each line in (after) the change.
5070 function stretchSpansOverChange(doc
, change
) {
5071 var oldFirst
= isLine(doc
, change
.from.line
) && getLine(doc
, change
.from.line
).markedSpans
;
5072 var oldLast
= isLine(doc
, change
.to
.line
) && getLine(doc
, change
.to
.line
).markedSpans
;
5073 if (!oldFirst
&& !oldLast
) return null;
5075 var startCh
= change
.from.ch
, endCh
= change
.to
.ch
, isInsert
= cmp(change
.from, change
.to
) == 0;
5076 // Get the spans that 'stick out' on both sides
5077 var first
= markedSpansBefore(oldFirst
, startCh
, isInsert
);
5078 var last
= markedSpansAfter(oldLast
, endCh
, isInsert
);
5080 // Next, merge those two ends
5081 var sameLine
= change
.text
.length
== 1, offset
= lst(change
.text
).length
+ (sameLine
? startCh
: 0);
5083 // Fix up .to properties of first
5084 for (var i
= 0; i
< first
.length
; ++i
) {
5085 var span
= first
[i
];
5086 if (span
.to
== null) {
5087 var found
= getMarkedSpanFor(last
, span
.marker
);
5088 if (!found
) span
.to
= startCh
;
5089 else if (sameLine
) span
.to
= found
.to
== null ? null : found
.to
+ offset
;
5094 // Fix up .from in last (or move them into first in case of sameLine)
5095 for (var i
= 0; i
< last
.length
; ++i
) {
5097 if (span
.to
!= null) span
.to
+= offset
;
5098 if (span
.from == null) {
5099 var found
= getMarkedSpanFor(first
, span
.marker
);
5102 if (sameLine
) (first
|| (first
= [])).push(span
);
5105 span
.from += offset
;
5106 if (sameLine
) (first
|| (first
= [])).push(span
);
5110 // Make sure we didn't create any zero-length spans
5111 if (first
) first
= clearEmptySpans(first
);
5112 if (last
&& last
!= first
) last
= clearEmptySpans(last
);
5114 var newMarkers
= [first
];
5116 // Fill gap with whole-line-spans
5117 var gap
= change
.text
.length
- 2, gapMarkers
;
5118 if (gap
> 0 && first
)
5119 for (var i
= 0; i
< first
.length
; ++i
)
5120 if (first
[i
].to
== null)
5121 (gapMarkers
|| (gapMarkers
= [])).push(new MarkedSpan(first
[i
].marker
, null, null));
5122 for (var i
= 0; i
< gap
; ++i
)
5123 newMarkers
.push(gapMarkers
);
5124 newMarkers
.push(last
);
5129 // Remove spans that are empty and don't have a clearWhenEmpty
5131 function clearEmptySpans(spans
) {
5132 for (var i
= 0; i
< spans
.length
; ++i
) {
5133 var span
= spans
[i
];
5134 if (span
.from != null && span
.from == span
.to
&& span
.marker
.clearWhenEmpty
!== false)
5135 spans
.splice(i
--, 1);
5137 if (!spans
.length
) return null;
5141 // Used for un/re-doing changes from the history. Combines the
5142 // result of computing the existing spans with the set of spans that
5143 // existed in the history (so that deleting around a span and then
5144 // undoing brings back the span).
5145 function mergeOldSpans(doc
, change
) {
5146 var old
= getOldSpans(doc
, change
);
5147 var stretched
= stretchSpansOverChange(doc
, change
);
5148 if (!old
) return stretched
;
5149 if (!stretched
) return old
;
5151 for (var i
= 0; i
< old
.length
; ++i
) {
5152 var oldCur
= old
[i
], stretchCur
= stretched
[i
];
5153 if (oldCur
&& stretchCur
) {
5154 spans
: for (var j
= 0; j
< stretchCur
.length
; ++j
) {
5155 var span
= stretchCur
[j
];
5156 for (var k
= 0; k
< oldCur
.length
; ++k
)
5157 if (oldCur
[k
].marker
== span
.marker
) continue spans
;
5160 } else if (stretchCur
) {
5161 old
[i
] = stretchCur
;
5167 // Used to 'clip' out readOnly ranges when making a change.
5168 function removeReadOnlyRanges(doc
, from, to
) {
5170 doc
.iter(from.line
, to
.line
+ 1, function(line
) {
5171 if (line
.markedSpans
) for (var i
= 0; i
< line
.markedSpans
.length
; ++i
) {
5172 var mark
= line
.markedSpans
[i
].marker
;
5173 if (mark
.readOnly
&& (!markers
|| indexOf(markers
, mark
) == -1))
5174 (markers
|| (markers
= [])).push(mark
);
5177 if (!markers
) return null;
5178 var parts
= [{from: from, to
: to
}];
5179 for (var i
= 0; i
< markers
.length
; ++i
) {
5180 var mk
= markers
[i
], m
= mk
.find(0);
5181 for (var j
= 0; j
< parts
.length
; ++j
) {
5183 if (cmp(p
.to
, m
.from) < 0 || cmp(p
.from, m
.to
) > 0) continue;
5184 var newParts
= [j
, 1], dfrom
= cmp(p
.from, m
.from), dto
= cmp(p
.to
, m
.to
);
5185 if (dfrom
< 0 || !mk
.inclusiveLeft
&& !dfrom
)
5186 newParts
.push({from: p
.from, to
: m
.from});
5187 if (dto
> 0 || !mk
.inclusiveRight
&& !dto
)
5188 newParts
.push({from: m
.to
, to
: p
.to
});
5189 parts
.splice
.apply(parts
, newParts
);
5190 j
+= newParts
.length
- 1;
5196 // Connect or disconnect spans from a line.
5197 function detachMarkedSpans(line
) {
5198 var spans
= line
.markedSpans
;
5200 for (var i
= 0; i
< spans
.length
; ++i
)
5201 spans
[i
].marker
.detachLine(line
);
5202 line
.markedSpans
= null;
5204 function attachMarkedSpans(line
, spans
) {
5206 for (var i
= 0; i
< spans
.length
; ++i
)
5207 spans
[i
].marker
.attachLine(line
);
5208 line
.markedSpans
= spans
;
5211 // Helpers used when computing which overlapping collapsed span
5212 // counts as the larger one.
5213 function extraLeft(marker
) { return marker
.inclusiveLeft
? -1 : 0; }
5214 function extraRight(marker
) { return marker
.inclusiveRight
? 1 : 0; }
5216 // Returns a number indicating which of two overlapping collapsed
5217 // spans is larger (and thus includes the other). Falls back to
5218 // comparing ids when the spans cover exactly the same range.
5219 function compareCollapsedMarkers(a
, b
) {
5220 var lenDiff
= a
.lines
.length
- b
.lines
.length
;
5221 if (lenDiff
!= 0) return lenDiff
;
5222 var aPos
= a
.find(), bPos
= b
.find();
5223 var fromCmp
= cmp(aPos
.from, bPos
.from) || extraLeft(a
) - extraLeft(b
);
5224 if (fromCmp
) return -fromCmp
;
5225 var toCmp
= cmp(aPos
.to
, bPos
.to
) || extraRight(a
) - extraRight(b
);
5226 if (toCmp
) return toCmp
;
5230 // Find out whether a line ends or starts in a collapsed span. If
5231 // so, return the marker for that span.
5232 function collapsedSpanAtSide(line
, start
) {
5233 var sps
= sawCollapsedSpans
&& line
.markedSpans
, found
;
5234 if (sps
) for (var sp
, i
= 0; i
< sps
.length
; ++i
) {
5236 if (sp
.marker
.collapsed
&& (start
? sp
.from : sp
.to
) == null &&
5237 (!found
|| compareCollapsedMarkers(found
, sp
.marker
) < 0))
5242 function collapsedSpanAtStart(line
) { return collapsedSpanAtSide(line
, true); }
5243 function collapsedSpanAtEnd(line
) { return collapsedSpanAtSide(line
, false); }
5245 // Test whether there exists a collapsed span that partially
5246 // overlaps (covers the start or end, but not both) of a new span.
5247 // Such overlap is not allowed.
5248 function conflictingCollapsedRange(doc
, lineNo
, from, to
, marker
) {
5249 var line
= getLine(doc
, lineNo
);
5250 var sps
= sawCollapsedSpans
&& line
.markedSpans
;
5251 if (sps
) for (var i
= 0; i
< sps
.length
; ++i
) {
5253 if (!sp
.marker
.collapsed
) continue;
5254 var found
= sp
.marker
.find(0);
5255 var fromCmp
= cmp(found
.from, from) || extraLeft(sp
.marker
) - extraLeft(marker
);
5256 var toCmp
= cmp(found
.to
, to
) || extraRight(sp
.marker
) - extraRight(marker
);
5257 if (fromCmp
>= 0 && toCmp
<= 0 || fromCmp
<= 0 && toCmp
>= 0) continue;
5258 if (fromCmp
<= 0 && (cmp(found
.to
, from) || extraRight(sp
.marker
) - extraLeft(marker
)) > 0 ||
5259 fromCmp
>= 0 && (cmp(found
.from, to
) || extraLeft(sp
.marker
) - extraRight(marker
)) < 0)
5264 // A visual line is a line as drawn on the screen. Folding, for
5265 // example, can cause multiple logical lines to appear on the same
5266 // visual line. This finds the start of the visual line that the
5267 // given line is part of (usually that is the line itself).
5268 function visualLine(line
) {
5270 while (merged
= collapsedSpanAtStart(line
))
5271 line
= merged
.find(-1, true).line
;
5275 // Returns an array of logical lines that continue the visual line
5276 // started by the argument, or undefined if there are no such lines.
5277 function visualLineContinued(line
) {
5279 while (merged
= collapsedSpanAtEnd(line
)) {
5280 line
= merged
.find(1, true).line
;
5281 (lines
|| (lines
= [])).push(line
);
5286 // Get the line number of the start of the visual line that the
5287 // given line number is part of.
5288 function visualLineNo(doc
, lineN
) {
5289 var line
= getLine(doc
, lineN
), vis
= visualLine(line
);
5290 if (line
== vis
) return lineN
;
5293 // Get the line number of the start of the next visual line after
5295 function visualLineEndNo(doc
, lineN
) {
5296 if (lineN
> doc
.lastLine()) return lineN
;
5297 var line
= getLine(doc
, lineN
), merged
;
5298 if (!lineIsHidden(doc
, line
)) return lineN
;
5299 while (merged
= collapsedSpanAtEnd(line
))
5300 line
= merged
.find(1, true).line
;
5301 return lineNo(line
) + 1;
5304 // Compute whether a line is hidden. Lines count as hidden when they
5305 // are part of a visual line that starts with another line, or when
5306 // they are entirely covered by collapsed, non-widget span.
5307 function lineIsHidden(doc
, line
) {
5308 var sps
= sawCollapsedSpans
&& line
.markedSpans
;
5309 if (sps
) for (var sp
, i
= 0; i
< sps
.length
; ++i
) {
5311 if (!sp
.marker
.collapsed
) continue;
5312 if (sp
.from == null) return true;
5313 if (sp
.marker
.widgetNode
) continue;
5314 if (sp
.from == 0 && sp
.marker
.inclusiveLeft
&& lineIsHiddenInner(doc
, line
, sp
))
5318 function lineIsHiddenInner(doc
, line
, span
) {
5319 if (span
.to
== null) {
5320 var end
= span
.marker
.find(1, true);
5321 return lineIsHiddenInner(doc
, end
.line
, getMarkedSpanFor(end
.line
.markedSpans
, span
.marker
));
5323 if (span
.marker
.inclusiveRight
&& span
.to
== line
.text
.length
)
5325 for (var sp
, i
= 0; i
< line
.markedSpans
.length
; ++i
) {
5326 sp
= line
.markedSpans
[i
];
5327 if (sp
.marker
.collapsed
&& !sp
.marker
.widgetNode
&& sp
.from == span
.to
&&
5328 (sp
.to
== null || sp
.to
!= span
.from) &&
5329 (sp
.marker
.inclusiveLeft
|| span
.marker
.inclusiveRight
) &&
5330 lineIsHiddenInner(doc
, line
, sp
)) return true;
5336 // Line widgets are block elements displayed above or below a line.
5338 var LineWidget
= CodeMirror
.LineWidget = function(cm
, node
, options
) {
5339 if (options
) for (var opt
in options
) if (options
.hasOwnProperty(opt
))
5340 this[opt
] = options
[opt
];
5344 eventMixin(LineWidget
);
5346 function adjustScrollWhenAboveVisible(cm
, line
, diff
) {
5347 if (heightAtLine(line
) < ((cm
.curOp
&& cm
.curOp
.scrollTop
) || cm
.doc
.scrollTop
))
5348 addToScrollPos(cm
, null, diff
);
5351 LineWidget
.prototype.clear = function() {
5352 var cm
= this.cm
, ws
= this.line
.widgets
, line
= this.line
, no
= lineNo(line
);
5353 if (no
== null || !ws
) return;
5354 for (var i
= 0; i
< ws
.length
; ++i
) if (ws
[i
] == this) ws
.splice(i
--, 1);
5355 if (!ws
.length
) line
.widgets
= null;
5356 var height
= widgetHeight(this);
5357 runInOp(cm
, function() {
5358 adjustScrollWhenAboveVisible(cm
, line
, -height
);
5359 regLineChange(cm
, no
, "widget");
5360 updateLineHeight(line
, Math
.max(0, line
.height
- height
));
5363 LineWidget
.prototype.changed = function() {
5364 var oldH
= this.height
, cm
= this.cm
, line
= this.line
;
5366 var diff
= widgetHeight(this) - oldH
;
5368 runInOp(cm
, function() {
5369 cm
.curOp
.forceUpdate
= true;
5370 adjustScrollWhenAboveVisible(cm
, line
, diff
);
5371 updateLineHeight(line
, line
.height
+ diff
);
5375 function widgetHeight(widget
) {
5376 if (widget
.height
!= null) return widget
.height
;
5377 if (!contains(document
.body
, widget
.node
))
5378 removeChildrenAndAdd(widget
.cm
.display
.measure
, elt("div", [widget
.node
], null, "position: relative"));
5379 return widget
.height
= widget
.node
.offsetHeight
;
5382 function addLineWidget(cm
, handle
, node
, options
) {
5383 var widget
= new LineWidget(cm
, node
, options
);
5384 if (widget
.noHScroll
) cm
.display
.alignWidgets
= true;
5385 changeLine(cm
, handle
, "widget", function(line
) {
5386 var widgets
= line
.widgets
|| (line
.widgets
= []);
5387 if (widget
.insertAt
== null) widgets
.push(widget
);
5388 else widgets
.splice(Math
.min(widgets
.length
- 1, Math
.max(0, widget
.insertAt
)), 0, widget
);
5390 if (!lineIsHidden(cm
.doc
, line
)) {
5391 var aboveVisible
= heightAtLine(line
) < cm
.doc
.scrollTop
;
5392 updateLineHeight(line
, line
.height
+ widgetHeight(widget
));
5393 if (aboveVisible
) addToScrollPos(cm
, null, widget
.height
);
5394 cm
.curOp
.forceUpdate
= true;
5401 // LINE DATA STRUCTURE
5403 // Line objects. These hold state related to a line, including
5404 // highlighting info (the styles array).
5405 var Line
= CodeMirror
.Line = function(text
, markedSpans
, estimateHeight
) {
5407 attachMarkedSpans(this, markedSpans
);
5408 this.height
= estimateHeight
? estimateHeight(this) : 1;
5411 Line
.prototype.lineNo = function() { return lineNo(this); };
5413 // Change the content (text, markers) of a line. Automatically
5414 // invalidates cached information and tries to re-estimate the
5416 function updateLine(line
, text
, markedSpans
, estimateHeight
) {
5418 if (line
.stateAfter
) line
.stateAfter
= null;
5419 if (line
.styles
) line
.styles
= null;
5420 if (line
.order
!= null) line
.order
= null;
5421 detachMarkedSpans(line
);
5422 attachMarkedSpans(line
, markedSpans
);
5423 var estHeight
= estimateHeight
? estimateHeight(line
) : 1;
5424 if (estHeight
!= line
.height
) updateLineHeight(line
, estHeight
);
5427 // Detach a line from the document tree and its markers.
5428 function cleanUpLine(line
) {
5430 detachMarkedSpans(line
);
5433 // Run the given mode's parser over a line, calling f for each token.
5434 function runMode(cm
, text
, mode
, state
, f
, forceToEnd
) {
5435 var flattenSpans
= mode
.flattenSpans
;
5436 if (flattenSpans
== null) flattenSpans
= cm
.options
.flattenSpans
;
5437 var curStart
= 0, curStyle
= null;
5438 var stream
= new StringStream(text
, cm
.options
.tabSize
), style
;
5439 if (text
== "" && mode
.blankLine
) mode
.blankLine(state
);
5440 while (!stream
.eol()) {
5441 if (stream
.pos
> cm
.options
.maxHighlightLength
) {
5442 flattenSpans
= false;
5443 if (forceToEnd
) processLine(cm
, text
, state
, stream
.pos
);
5444 stream
.pos
= text
.length
;
5447 style
= mode
.token(stream
, state
);
5449 if (cm
.options
.addModeClass
) {
5450 var mName
= CodeMirror
.innerMode(mode
, state
).mode
.name
;
5451 if (mName
) style
= "m-" + (style
? mName
+ " " + style
: mName
);
5453 if (!flattenSpans
|| curStyle
!= style
) {
5454 if (curStart
< stream
.start
) f(stream
.start
, curStyle
);
5455 curStart
= stream
.start
; curStyle
= style
;
5457 stream
.start
= stream
.pos
;
5459 while (curStart
< stream
.pos
) {
5460 // Webkit seems to refuse to render text nodes longer than 57444 characters
5461 var pos
= Math
.min(stream
.pos
, curStart
+ 50000);
5467 // Compute a style array (an array starting with a mode generation
5468 // -- for invalidation -- followed by pairs of end positions and
5469 // style strings), which is used to highlight the tokens on the
5471 function highlightLine(cm
, line
, state
, forceToEnd
) {
5472 // A styles array always starts with a number identifying the
5473 // mode/overlays that it is based on (for easy invalidation).
5474 var st
= [cm
.state
.modeGen
];
5475 // Compute the base array of styles
5476 runMode(cm
, line
.text
, cm
.doc
.mode
, state
, function(end
, style
) {
5477 st
.push(end
, style
);
5480 // Run overlays, adjust style array.
5481 for (var o
= 0; o
< cm
.state
.overlays
.length
; ++o
) {
5482 var overlay
= cm
.state
.overlays
[o
], i
= 1, at
= 0;
5483 runMode(cm
, line
.text
, overlay
.mode
, true, function(end
, style
) {
5485 // Ensure there's a token end at the current position, and that i points at it
5489 st
.splice(i
, 1, end
, st
[i
+1], i_end
);
5491 at
= Math
.min(end
, i_end
);
5494 if (overlay
.opaque
) {
5495 st
.splice(start
, i
- start
, end
, style
);
5498 for (; start
< i
; start
+= 2) {
5499 var cur
= st
[start
+1];
5500 st
[start
+1] = cur
? cur
+ " " + style
: style
;
5509 function getLineStyles(cm
, line
) {
5510 if (!line
.styles
|| line
.styles
[0] != cm
.state
.modeGen
)
5511 line
.styles
= highlightLine(cm
, line
, line
.stateAfter
= getStateBefore(cm
, lineNo(line
)));
5515 // Lightweight form of highlight -- proceed over this line and
5516 // update state, but don't save a style array. Used for lines that
5517 // aren't currently visible.
5518 function processLine(cm
, text
, state
, startAt
) {
5519 var mode
= cm
.doc
.mode
;
5520 var stream
= new StringStream(text
, cm
.options
.tabSize
);
5521 stream
.start
= stream
.pos
= startAt
|| 0;
5522 if (text
== "" && mode
.blankLine
) mode
.blankLine(state
);
5523 while (!stream
.eol() && stream
.pos
<= cm
.options
.maxHighlightLength
) {
5524 mode
.token(stream
, state
);
5525 stream
.start
= stream
.pos
;
5529 // Convert a style as returned by a mode (either null, or a string
5530 // containing one or more styles) to a CSS style. This is cached,
5531 // and also looks for line-wide styles.
5532 var styleToClassCache
= {}, styleToClassCacheWithMode
= {};
5533 function interpretTokenStyle(style
, builder
) {
5534 if (!style
) return null;
5536 var lineClass
= style
.match(/(?:^|\s+)line-(background-)?(\S+)/);
5537 if (!lineClass
) break;
5538 style
= style
.slice(0, lineClass
.index
) + style
.slice(lineClass
.index
+ lineClass
[0].length
);
5539 var prop
= lineClass
[1] ? "bgClass" : "textClass";
5540 if (builder
[prop
] == null)
5541 builder
[prop
] = lineClass
[2];
5542 else if (!(new RegExp("(?:^|\s)" + lineClass
[2] + "(?:$|\s)")).test(builder
[prop
]))
5543 builder
[prop
] += " " + lineClass
[2];
5545 if (/^\s*$/.test(style
)) return null;
5546 var cache
= builder
.cm
.options
.addModeClass
? styleToClassCacheWithMode
: styleToClassCache
;
5547 return cache
[style
] ||
5548 (cache
[style
] = style
.replace(/\S+/g, "cm-$&"));
5551 // Render the DOM representation of the text of a line. Also builds
5552 // up a 'line map', which points at the DOM nodes that represent
5553 // specific stretches of text, and is used by the measuring code.
5554 // The returned object contains the DOM node, this map, and
5555 // information about line-wide styles that were set by the mode.
5556 function buildLineContent(cm
, lineView
) {
5557 // The padding-right forces the element to have a 'border', which
5558 // is needed on Webkit to be able to get line-level bounding
5559 // rectangles for it (in measureChar).
5560 var content
= elt("span", null, null, webkit
? "padding-right: .1px" : null);
5561 var builder
= {pre
: elt("pre", [content
]), content
: content
, col
: 0, pos
: 0, cm
: cm
};
5562 lineView
.measure
= {};
5564 // Iterate over the logical lines that make up this visual line.
5565 for (var i
= 0; i
<= (lineView
.rest
? lineView
.rest
.length
: 0); i
++) {
5566 var line
= i
? lineView
.rest
[i
- 1] : lineView
.line
, order
;
5568 builder
.addToken
= buildToken
;
5569 // Optionally wire in some hacks into the token-rendering
5570 // algorithm, to deal with browser quirks.
5571 if ((ie
|| webkit
) && cm
.getOption("lineWrapping"))
5572 builder
.addToken
= buildTokenSplitSpaces(builder
.addToken
);
5573 if (hasBadBidiRects(cm
.display
.measure
) && (order
= getOrder(line
)))
5574 builder
.addToken
= buildTokenBadBidi(builder
.addToken
, order
);
5576 insertLineContent(line
, builder
, getLineStyles(cm
, line
));
5578 // Ensure at least a single node is present, for measuring.
5579 if (builder
.map
.length
== 0)
5580 builder
.map
.push(0, 0, builder
.content
.appendChild(zeroWidthElement(cm
.display
.measure
)));
5582 // Store the map and a cache object for the current logical line
5584 lineView
.measure
.map
= builder
.map
;
5585 lineView
.measure
.cache
= {};
5587 (lineView
.measure
.maps
|| (lineView
.measure
.maps
= [])).push(builder
.map
);
5588 (lineView
.measure
.caches
|| (lineView
.measure
.caches
= [])).push({});
5592 signal(cm
, "renderLine", cm
, lineView
.line
, builder
.pre
);
5596 function defaultSpecialCharPlaceholder(ch
) {
5597 var token
= elt("span", "\u2022", "cm-invalidchar");
5598 token
.title
= "\\u" + ch
.charCodeAt(0).toString(16);
5602 // Build up the DOM representation for a single token, and add it to
5603 // the line map. Takes care to render special characters separately.
5604 function buildToken(builder
, text
, style
, startStyle
, endStyle
, title
) {
5606 var special
= builder
.cm
.options
.specialChars
, mustWrap
= false;
5607 if (!special
.test(text
)) {
5608 builder
.col
+= text
.length
;
5609 var content
= document
.createTextNode(text
);
5610 builder
.map
.push(builder
.pos
, builder
.pos
+ text
.length
, content
);
5611 if (ie_upto8
) mustWrap
= true;
5612 builder
.pos
+= text
.length
;
5614 var content
= document
.createDocumentFragment(), pos
= 0;
5616 special
.lastIndex
= pos
;
5617 var m
= special
.exec(text
);
5618 var skipped
= m
? m
.index
- pos
: text
.length
- pos
;
5620 var txt
= document
.createTextNode(text
.slice(pos
, pos
+ skipped
));
5621 if (ie_upto8
) content
.appendChild(elt("span", [txt
]));
5622 else content
.appendChild(txt
);
5623 builder
.map
.push(builder
.pos
, builder
.pos
+ skipped
, txt
);
5624 builder
.col
+= skipped
;
5625 builder
.pos
+= skipped
;
5630 var tabSize
= builder
.cm
.options
.tabSize
, tabWidth
= tabSize
- builder
.col
% tabSize
;
5631 var txt
= content
.appendChild(elt("span", spaceStr(tabWidth
), "cm-tab"));
5632 builder
.col
+= tabWidth
;
5634 var txt
= builder
.cm
.options
.specialCharPlaceholder(m
[0]);
5635 if (ie_upto8
) content
.appendChild(elt("span", [txt
]));
5636 else content
.appendChild(txt
);
5639 builder
.map
.push(builder
.pos
, builder
.pos
+ 1, txt
);
5643 if (style
|| startStyle
|| endStyle
|| mustWrap
) {
5644 var fullStyle
= style
|| "";
5645 if (startStyle
) fullStyle
+= startStyle
;
5646 if (endStyle
) fullStyle
+= endStyle
;
5647 var token
= elt("span", [content
], fullStyle
);
5648 if (title
) token
.title
= title
;
5649 return builder
.content
.appendChild(token
);
5651 builder
.content
.appendChild(content
);
5654 function buildTokenSplitSpaces(inner
) {
5655 function split(old
) {
5657 for (var i
= 0; i
< old
.length
- 2; ++i
) out
+= i
% 2 ? " " : "\u00a0";
5661 return function(builder
, text
, style
, startStyle
, endStyle
, title
) {
5662 inner(builder
, text
.replace(/ {3,}/g
, split
), style
, startStyle
, endStyle
, title
);
5666 // Work around nonsense dimensions being reported for stretches of
5667 // right-to-left text.
5668 function buildTokenBadBidi(inner
, order
) {
5669 return function(builder
, text
, style
, startStyle
, endStyle
, title
) {
5670 style
= style
? style
+ " cm-force-border" : "cm-force-border";
5671 var start
= builder
.pos
, end
= start
+ text
.length
;
5673 // Find the part that overlaps with the start of this text
5674 for (var i
= 0; i
< order
.length
; i
++) {
5675 var part
= order
[i
];
5676 if (part
.to
> start
&& part
.from <= start
) break;
5678 if (part
.to
>= end
) return inner(builder
, text
, style
, startStyle
, endStyle
, title
);
5679 inner(builder
, text
.slice(0, part
.to
- start
), style
, startStyle
, null, title
);
5681 text
= text
.slice(part
.to
- start
);
5687 function buildCollapsedSpan(builder
, size
, marker
, ignoreWidget
) {
5688 var widget
= !ignoreWidget
&& marker
.widgetNode
;
5690 builder
.map
.push(builder
.pos
, builder
.pos
+ size
, widget
);
5691 builder
.content
.appendChild(widget
);
5693 builder
.pos
+= size
;
5696 // Outputs a number of spans to make up a line, taking highlighting
5697 // and marked text into account.
5698 function insertLineContent(line
, builder
, styles
) {
5699 var spans
= line
.markedSpans
, allText
= line
.text
, at
= 0;
5701 for (var i
= 1; i
< styles
.length
; i
+=2)
5702 builder
.addToken(builder
, allText
.slice(at
, at
= styles
[i
]), interpretTokenStyle(styles
[i
+1], builder
));
5706 var len
= allText
.length
, pos
= 0, i
= 1, text
= "", style
;
5707 var nextChange
= 0, spanStyle
, spanEndStyle
, spanStartStyle
, title
, collapsed
;
5709 if (nextChange
== pos
) { // Update current marker set
5710 spanStyle
= spanEndStyle
= spanStartStyle
= title
= "";
5711 collapsed
= null; nextChange
= Infinity
;
5712 var foundBookmarks
= [];
5713 for (var j
= 0; j
< spans
.length
; ++j
) {
5714 var sp
= spans
[j
], m
= sp
.marker
;
5715 if (sp
.from <= pos
&& (sp
.to
== null || sp
.to
> pos
)) {
5716 if (sp
.to
!= null && nextChange
> sp
.to
) { nextChange
= sp
.to
; spanEndStyle
= ""; }
5717 if (m
.className
) spanStyle
+= " " + m
.className
;
5718 if (m
.startStyle
&& sp
.from == pos
) spanStartStyle
+= " " + m
.startStyle
;
5719 if (m
.endStyle
&& sp
.to
== nextChange
) spanEndStyle
+= " " + m
.endStyle
;
5720 if (m
.title
&& !title
) title
= m
.title
;
5721 if (m
.collapsed
&& (!collapsed
|| compareCollapsedMarkers(collapsed
.marker
, m
) < 0))
5723 } else if (sp
.from > pos
&& nextChange
> sp
.from) {
5724 nextChange
= sp
.from;
5726 if (m
.type
== "bookmark" && sp
.from == pos
&& m
.widgetNode
) foundBookmarks
.push(m
);
5728 if (collapsed
&& (collapsed
.from || 0) == pos
) {
5729 buildCollapsedSpan(builder
, (collapsed
.to
== null ? len
+ 1 : collapsed
.to
) - pos
,
5730 collapsed
.marker
, collapsed
.from == null);
5731 if (collapsed
.to
== null) return;
5733 if (!collapsed
&& foundBookmarks
.length
) for (var j
= 0; j
< foundBookmarks
.length
; ++j
)
5734 buildCollapsedSpan(builder
, 0, foundBookmarks
[j
]);
5736 if (pos
>= len
) break;
5738 var upto
= Math
.min(len
, nextChange
);
5741 var end
= pos
+ text
.length
;
5743 var tokenText
= end
> upto
? text
.slice(0, upto
- pos
) : text
;
5744 builder
.addToken(builder
, tokenText
, style
? style
+ spanStyle
: spanStyle
,
5745 spanStartStyle
, pos
+ tokenText
.length
== nextChange
? spanEndStyle
: "", title
);
5747 if (end
>= upto
) {text
= text
.slice(upto
- pos
); pos
= upto
; break;}
5749 spanStartStyle
= "";
5751 text
= allText
.slice(at
, at
= styles
[i
++]);
5752 style
= interpretTokenStyle(styles
[i
++], builder
);
5757 // DOCUMENT DATA STRUCTURE
5759 // By default, updates that start and end at the beginning of a line
5760 // are treated specially, in order to make the association of line
5761 // widgets and marker elements with the text behave more intuitive.
5762 function isWholeLineUpdate(doc
, change
) {
5763 return change
.from.ch
== 0 && change
.to
.ch
== 0 && lst(change
.text
) == "" &&
5764 (!doc
.cm
|| doc
.cm
.options
.wholeLineUpdateBefore
);
5767 // Perform a change on the document data structure.
5768 function updateDoc(doc
, change
, markedSpans
, estimateHeight
) {
5769 function spansFor(n
) {return markedSpans
? markedSpans
[n
] : null;}
5770 function update(line
, text
, spans
) {
5771 updateLine(line
, text
, spans
, estimateHeight
);
5772 signalLater(line
, "change", line
, change
);
5775 var from = change
.from, to
= change
.to
, text
= change
.text
;
5776 var firstLine
= getLine(doc
, from.line
), lastLine
= getLine(doc
, to
.line
);
5777 var lastText
= lst(text
), lastSpans
= spansFor(text
.length
- 1), nlines
= to
.line
- from.line
;
5779 // Adjust the line structure
5780 if (isWholeLineUpdate(doc
, change
)) {
5781 // This is a whole-line replace. Treated specially to make
5782 // sure line objects move the way they are supposed to.
5783 for (var i
= 0, added
= []; i
< text
.length
- 1; ++i
)
5784 added
.push(new Line(text
[i
], spansFor(i
), estimateHeight
));
5785 update(lastLine
, lastLine
.text
, lastSpans
);
5786 if (nlines
) doc
.remove(from.line
, nlines
);
5787 if (added
.length
) doc
.insert(from.line
, added
);
5788 } else if (firstLine
== lastLine
) {
5789 if (text
.length
== 1) {
5790 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + lastText
+ firstLine
.text
.slice(to
.ch
), lastSpans
);
5792 for (var added
= [], i
= 1; i
< text
.length
- 1; ++i
)
5793 added
.push(new Line(text
[i
], spansFor(i
), estimateHeight
));
5794 added
.push(new Line(lastText
+ firstLine
.text
.slice(to
.ch
), lastSpans
, estimateHeight
));
5795 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + text
[0], spansFor(0));
5796 doc
.insert(from.line
+ 1, added
);
5798 } else if (text
.length
== 1) {
5799 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + text
[0] + lastLine
.text
.slice(to
.ch
), spansFor(0));
5800 doc
.remove(from.line
+ 1, nlines
);
5802 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + text
[0], spansFor(0));
5803 update(lastLine
, lastText
+ lastLine
.text
.slice(to
.ch
), lastSpans
);
5804 for (var i
= 1, added
= []; i
< text
.length
- 1; ++i
)
5805 added
.push(new Line(text
[i
], spansFor(i
), estimateHeight
));
5806 if (nlines
> 1) doc
.remove(from.line
+ 1, nlines
- 1);
5807 doc
.insert(from.line
+ 1, added
);
5810 signalLater(doc
, "change", doc
, change
);
5813 // The document is represented as a BTree consisting of leaves, with
5814 // chunk of lines in them, and branches, with up to ten leaves or
5815 // other branch nodes below them. The top node is always a branch
5816 // node, and is the document object itself (meaning it has
5817 // additional methods and properties).
5819 // All nodes have parent links. The tree is used both to go from
5820 // line numbers to line objects, and to go from objects to numbers.
5821 // It also indexes by height, and is used to convert between height
5822 // and line object, and to find the total height of the document.
5824 // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
5826 function LeafChunk(lines
) {
5829 for (var i
= 0, height
= 0; i
< lines
.length
; ++i
) {
5830 lines
[i
].parent
= this;
5831 height
+= lines
[i
].height
;
5833 this.height
= height
;
5836 LeafChunk
.prototype = {
5837 chunkSize: function() { return this.lines
.length
; },
5838 // Remove the n lines at offset 'at'.
5839 removeInner: function(at
, n
) {
5840 for (var i
= at
, e
= at
+ n
; i
< e
; ++i
) {
5841 var line
= this.lines
[i
];
5842 this.height
-= line
.height
;
5844 signalLater(line
, "delete");
5846 this.lines
.splice(at
, n
);
5848 // Helper used to collapse a small branch into a single leaf.
5849 collapse: function(lines
) {
5850 lines
.push
.apply(lines
, this.lines
);
5852 // Insert the given array of lines at offset 'at', count them as
5853 // having the given height.
5854 insertInner: function(at
, lines
, height
) {
5855 this.height
+= height
;
5856 this.lines
= this.lines
.slice(0, at
).concat(lines
).concat(this.lines
.slice(at
));
5857 for (var i
= 0; i
< lines
.length
; ++i
) lines
[i
].parent
= this;
5859 // Used to iterate over a part of the tree.
5860 iterN: function(at
, n
, op
) {
5861 for (var e
= at
+ n
; at
< e
; ++at
)
5862 if (op(this.lines
[at
])) return true;
5866 function BranchChunk(children
) {
5867 this.children
= children
;
5868 var size
= 0, height
= 0;
5869 for (var i
= 0; i
< children
.length
; ++i
) {
5870 var ch
= children
[i
];
5871 size
+= ch
.chunkSize(); height
+= ch
.height
;
5875 this.height
= height
;
5879 BranchChunk
.prototype = {
5880 chunkSize: function() { return this.size
; },
5881 removeInner: function(at
, n
) {
5883 for (var i
= 0; i
< this.children
.length
; ++i
) {
5884 var child
= this.children
[i
], sz
= child
.chunkSize();
5886 var rm
= Math
.min(n
, sz
- at
), oldHeight
= child
.height
;
5887 child
.removeInner(at
, rm
);
5888 this.height
-= oldHeight
- child
.height
;
5889 if (sz
== rm
) { this.children
.splice(i
--, 1); child
.parent
= null; }
5890 if ((n
-= rm
) == 0) break;
5894 // If the result is smaller than 25 lines, ensure that it is a
5895 // single leaf node.
5896 if (this.size
- n
< 25 &&
5897 (this.children
.length
> 1 || !(this.children
[0] instanceof LeafChunk
))) {
5899 this.collapse(lines
);
5900 this.children
= [new LeafChunk(lines
)];
5901 this.children
[0].parent
= this;
5904 collapse: function(lines
) {
5905 for (var i
= 0; i
< this.children
.length
; ++i
) this.children
[i
].collapse(lines
);
5907 insertInner: function(at
, lines
, height
) {
5908 this.size
+= lines
.length
;
5909 this.height
+= height
;
5910 for (var i
= 0; i
< this.children
.length
; ++i
) {
5911 var child
= this.children
[i
], sz
= child
.chunkSize();
5913 child
.insertInner(at
, lines
, height
);
5914 if (child
.lines
&& child
.lines
.length
> 50) {
5915 while (child
.lines
.length
> 50) {
5916 var spilled
= child
.lines
.splice(child
.lines
.length
- 25, 25);
5917 var newleaf
= new LeafChunk(spilled
);
5918 child
.height
-= newleaf
.height
;
5919 this.children
.splice(i
+ 1, 0, newleaf
);
5920 newleaf
.parent
= this;
5929 // When a node has grown, check whether it should be split.
5930 maybeSpill: function() {
5931 if (this.children
.length
<= 10) return;
5934 var spilled
= me
.children
.splice(me
.children
.length
- 5, 5);
5935 var sibling
= new BranchChunk(spilled
);
5936 if (!me
.parent
) { // Become the parent node
5937 var copy
= new BranchChunk(me
.children
);
5939 me
.children
= [copy
, sibling
];
5942 me
.size
-= sibling
.size
;
5943 me
.height
-= sibling
.height
;
5944 var myIndex
= indexOf(me
.parent
.children
, me
);
5945 me
.parent
.children
.splice(myIndex
+ 1, 0, sibling
);
5947 sibling
.parent
= me
.parent
;
5948 } while (me
.children
.length
> 10);
5949 me
.parent
.maybeSpill();
5951 iterN: function(at
, n
, op
) {
5952 for (var i
= 0; i
< this.children
.length
; ++i
) {
5953 var child
= this.children
[i
], sz
= child
.chunkSize();
5955 var used
= Math
.min(n
, sz
- at
);
5956 if (child
.iterN(at
, used
, op
)) return true;
5957 if ((n
-= used
) == 0) break;
5965 var Doc
= CodeMirror
.Doc = function(text
, mode
, firstLine
) {
5966 if (!(this instanceof Doc
)) return new Doc(text
, mode
, firstLine
);
5967 if (firstLine
== null) firstLine
= 0;
5969 BranchChunk
.call(this, [new LeafChunk([new Line("", null)])]);
5970 this.first
= firstLine
;
5971 this.scrollTop
= this.scrollLeft
= 0;
5972 this.cantEdit
= false;
5973 this.cleanGeneration
= 1;
5974 this.frontier
= firstLine
;
5975 var start
= Pos(firstLine
, 0);
5976 this.sel
= simpleSelection(start
);
5977 this.history
= new History(null);
5978 this.id
= ++nextDocId
;
5979 this.modeOption
= mode
;
5981 if (typeof text
== "string") text
= splitLines(text
);
5982 updateDoc(this, {from: start
, to
: start
, text
: text
});
5983 setSelection(this, simpleSelection(start
), sel_dontScroll
);
5986 Doc
.prototype = createObj(BranchChunk
.prototype, {
5988 // Iterate over the document. Supports two forms -- with only one
5989 // argument, it calls that for each line in the document. With
5990 // three, it iterates over the range given by the first two (with
5991 // the second being non-inclusive).
5992 iter: function(from, to
, op
) {
5993 if (op
) this.iterN(from - this.first
, to
- from, op
);
5994 else this.iterN(this.first
, this.first
+ this.size
, from);
5997 // Non-public interface for adding and removing lines.
5998 insert: function(at
, lines
) {
6000 for (var i
= 0; i
< lines
.length
; ++i
) height
+= lines
[i
].height
;
6001 this.insertInner(at
- this.first
, lines
, height
);
6003 remove: function(at
, n
) { this.removeInner(at
- this.first
, n
); },
6005 // From here, the methods are part of the public interface. Most
6006 // are also available from CodeMirror (editor) instances.
6008 getValue: function(lineSep
) {
6009 var lines
= getLines(this, this.first
, this.first
+ this.size
);
6010 if (lineSep
=== false) return lines
;
6011 return lines
.join(lineSep
|| "\n");
6013 setValue
: docMethodOp(function(code
) {
6014 var top
= Pos(this.first
, 0), last
= this.first
+ this.size
- 1;
6015 makeChange(this, {from: top
, to
: Pos(last
, getLine(this, last
).text
.length
),
6016 text
: splitLines(code
), origin
: "setValue"}, true);
6017 setSelection(this, simpleSelection(top
));
6019 replaceRange: function(code
, from, to
, origin
) {
6020 from = clipPos(this, from);
6021 to
= to
? clipPos(this, to
) : from;
6022 replaceRange(this, code
, from, to
, origin
);
6024 getRange: function(from, to
, lineSep
) {
6025 var lines
= getBetween(this, clipPos(this, from), clipPos(this, to
));
6026 if (lineSep
=== false) return lines
;
6027 return lines
.join(lineSep
|| "\n");
6030 getLine: function(line
) {var l
= this.getLineHandle(line
); return l
&& l
.text
;},
6032 getLineHandle: function(line
) {if (isLine(this, line
)) return getLine(this, line
);},
6033 getLineNumber: function(line
) {return lineNo(line
);},
6035 getLineHandleVisualStart: function(line
) {
6036 if (typeof line
== "number") line
= getLine(this, line
);
6037 return visualLine(line
);
6040 lineCount: function() {return this.size
;},
6041 firstLine: function() {return this.first
;},
6042 lastLine: function() {return this.first
+ this.size
- 1;},
6044 clipPos: function(pos
) {return clipPos(this, pos
);},
6046 getCursor: function(start
) {
6047 var range
= this.sel
.primary(), pos
;
6048 if (start
== null || start
== "head") pos
= range
.head
;
6049 else if (start
== "anchor") pos
= range
.anchor
;
6050 else if (start
== "end" || start
== "to" || start
=== false) pos
= range
.to();
6051 else pos
= range
.from();
6054 listSelections: function() { return this.sel
.ranges
; },
6055 somethingSelected: function() {return this.sel
.somethingSelected();},
6057 setCursor
: docMethodOp(function(line
, ch
, options
) {
6058 setSimpleSelection(this, clipPos(this, typeof line
== "number" ? Pos(line
, ch
|| 0) : line
), null, options
);
6060 setSelection
: docMethodOp(function(anchor
, head
, options
) {
6061 setSimpleSelection(this, clipPos(this, anchor
), clipPos(this, head
|| anchor
), options
);
6063 extendSelection
: docMethodOp(function(head
, other
, options
) {
6064 extendSelection(this, clipPos(this, head
), other
&& clipPos(this, other
), options
);
6066 extendSelections
: docMethodOp(function(heads
, options
) {
6067 extendSelections(this, clipPosArray(this, heads
, options
));
6069 extendSelectionsBy
: docMethodOp(function(f
, options
) {
6070 extendSelections(this, map(this.sel
.ranges
, f
), options
);
6072 setSelections
: docMethodOp(function(ranges
, primary
, options
) {
6073 if (!ranges
.length
) return;
6074 for (var i
= 0, out
= []; i
< ranges
.length
; i
++)
6075 out
[i
] = new Range(clipPos(this, ranges
[i
].anchor
),
6076 clipPos(this, ranges
[i
].head
));
6077 if (primary
== null) primary
= Math
.min(ranges
.length
- 1, this.sel
.primIndex
);
6078 setSelection(this, normalizeSelection(out
, primary
), options
);
6080 addSelection
: docMethodOp(function(anchor
, head
, options
) {
6081 var ranges
= this.sel
.ranges
.slice(0);
6082 ranges
.push(new Range(clipPos(this, anchor
), clipPos(this, head
|| anchor
)));
6083 setSelection(this, normalizeSelection(ranges
, ranges
.length
- 1), options
);
6086 getSelection: function(lineSep
) {
6087 var ranges
= this.sel
.ranges
, lines
;
6088 for (var i
= 0; i
< ranges
.length
; i
++) {
6089 var sel
= getBetween(this, ranges
[i
].from(), ranges
[i
].to());
6090 lines
= lines
? lines
.concat(sel
) : sel
;
6092 if (lineSep
=== false) return lines
;
6093 else return lines
.join(lineSep
|| "\n");
6095 getSelections: function(lineSep
) {
6096 var parts
= [], ranges
= this.sel
.ranges
;
6097 for (var i
= 0; i
< ranges
.length
; i
++) {
6098 var sel
= getBetween(this, ranges
[i
].from(), ranges
[i
].to());
6099 if (lineSep
!== false) sel
= sel
.join(lineSep
|| "\n");
6104 replaceSelection
: docMethodOp(function(code
, collapse
, origin
) {
6106 for (var i
= 0; i
< this.sel
.ranges
.length
; i
++)
6108 this.replaceSelections(dup
, collapse
, origin
|| "+input");
6110 replaceSelections: function(code
, collapse
, origin
) {
6111 var changes
= [], sel
= this.sel
;
6112 for (var i
= 0; i
< sel
.ranges
.length
; i
++) {
6113 var range
= sel
.ranges
[i
];
6114 changes
[i
] = {from: range
.from(), to
: range
.to(), text
: splitLines(code
[i
]), origin
: origin
};
6116 var newSel
= collapse
&& collapse
!= "end" && computeReplacedSel(this, changes
, collapse
);
6117 for (var i
= changes
.length
- 1; i
>= 0; i
--)
6118 makeChange(this, changes
[i
]);
6119 if (newSel
) setSelectionReplaceHistory(this, newSel
);
6120 else if (this.cm
) ensureCursorVisible(this.cm
);
6122 undo
: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
6123 redo
: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
6124 undoSelection
: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}),
6125 redoSelection
: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}),
6127 setExtending: function(val
) {this.extend
= val
;},
6128 getExtending: function() {return this.extend
;},
6130 historySize: function() {
6131 var hist
= this.history
, done
= 0, undone
= 0;
6132 for (var i
= 0; i
< hist
.done
.length
; i
++) if (!hist
.done
[i
].ranges
) ++done
;
6133 for (var i
= 0; i
< hist
.undone
.length
; i
++) if (!hist
.undone
[i
].ranges
) ++undone
;
6134 return {undo
: done
, redo
: undone
};
6136 clearHistory: function() {this.history
= new History(this.history
.maxGeneration
);},
6138 markClean: function() {
6139 this.cleanGeneration
= this.changeGeneration(true);
6141 changeGeneration: function(forceSplit
) {
6143 this.history
.lastOp
= this.history
.lastOrigin
= null;
6144 return this.history
.generation
;
6146 isClean: function (gen
) {
6147 return this.history
.generation
== (gen
|| this.cleanGeneration
);
6150 getHistory: function() {
6151 return {done
: copyHistoryArray(this.history
.done
),
6152 undone
: copyHistoryArray(this.history
.undone
)};
6154 setHistory: function(histData
) {
6155 var hist
= this.history
= new History(this.history
.maxGeneration
);
6156 hist
.done
= copyHistoryArray(histData
.done
.slice(0), null, true);
6157 hist
.undone
= copyHistoryArray(histData
.undone
.slice(0), null, true);
6160 markText: function(from, to
, options
) {
6161 return markText(this, clipPos(this, from), clipPos(this, to
), options
, "range");
6163 setBookmark: function(pos
, options
) {
6164 var realOpts
= {replacedWith
: options
&& (options
.nodeType
== null ? options
.widget
: options
),
6165 insertLeft
: options
&& options
.insertLeft
,
6166 clearWhenEmpty
: false, shared
: options
&& options
.shared
};
6167 pos
= clipPos(this, pos
);
6168 return markText(this, pos
, pos
, realOpts
, "bookmark");
6170 findMarksAt: function(pos
) {
6171 pos
= clipPos(this, pos
);
6172 var markers
= [], spans
= getLine(this, pos
.line
).markedSpans
;
6173 if (spans
) for (var i
= 0; i
< spans
.length
; ++i
) {
6174 var span
= spans
[i
];
6175 if ((span
.from == null || span
.from <= pos
.ch
) &&
6176 (span
.to
== null || span
.to
>= pos
.ch
))
6177 markers
.push(span
.marker
.parent
|| span
.marker
);
6181 findMarks: function(from, to
) {
6182 from = clipPos(this, from); to
= clipPos(this, to
);
6183 var found
= [], lineNo
= from.line
;
6184 this.iter(from.line
, to
.line
+ 1, function(line
) {
6185 var spans
= line
.markedSpans
;
6186 if (spans
) for (var i
= 0; i
< spans
.length
; i
++) {
6187 var span
= spans
[i
];
6188 if (!(lineNo
== from.line
&& from.ch
> span
.to
||
6189 span
.from == null && lineNo
!= from.line
||
6190 lineNo
== to
.line
&& span
.from > to
.ch
))
6191 found
.push(span
.marker
.parent
|| span
.marker
);
6197 getAllMarks: function() {
6199 this.iter(function(line
) {
6200 var sps
= line
.markedSpans
;
6201 if (sps
) for (var i
= 0; i
< sps
.length
; ++i
)
6202 if (sps
[i
].from != null) markers
.push(sps
[i
].marker
);
6207 posFromIndex: function(off
) {
6208 var ch
, lineNo
= this.first
;
6209 this.iter(function(line
) {
6210 var sz
= line
.text
.length
+ 1;
6211 if (sz
> off
) { ch
= off
; return true; }
6215 return clipPos(this, Pos(lineNo
, ch
));
6217 indexFromPos: function (coords
) {
6218 coords
= clipPos(this, coords
);
6219 var index
= coords
.ch
;
6220 if (coords
.line
< this.first
|| coords
.ch
< 0) return 0;
6221 this.iter(this.first
, coords
.line
, function (line
) {
6222 index
+= line
.text
.length
+ 1;
6227 copy: function(copyHistory
) {
6228 var doc
= new Doc(getLines(this, this.first
, this.first
+ this.size
), this.modeOption
, this.first
);
6229 doc
.scrollTop
= this.scrollTop
; doc
.scrollLeft
= this.scrollLeft
;
6233 doc
.history
.undoDepth
= this.history
.undoDepth
;
6234 doc
.setHistory(this.getHistory());
6239 linkedDoc: function(options
) {
6240 if (!options
) options
= {};
6241 var from = this.first
, to
= this.first
+ this.size
;
6242 if (options
.from != null && options
.from > from) from = options
.from;
6243 if (options
.to
!= null && options
.to
< to
) to
= options
.to
;
6244 var copy
= new Doc(getLines(this, from, to
), options
.mode
|| this.modeOption
, from);
6245 if (options
.sharedHist
) copy
.history
= this.history
;
6246 (this.linked
|| (this.linked
= [])).push({doc
: copy
, sharedHist
: options
.sharedHist
});
6247 copy
.linked
= [{doc
: this, isParent
: true, sharedHist
: options
.sharedHist
}];
6250 unlinkDoc: function(other
) {
6251 if (other
instanceof CodeMirror
) other
= other
.doc
;
6252 if (this.linked
) for (var i
= 0; i
< this.linked
.length
; ++i
) {
6253 var link
= this.linked
[i
];
6254 if (link
.doc
!= other
) continue;
6255 this.linked
.splice(i
, 1);
6256 other
.unlinkDoc(this);
6259 // If the histories were shared, split them again
6260 if (other
.history
== this.history
) {
6261 var splitIds
= [other
.id
];
6262 linkedDocs(other
, function(doc
) {splitIds
.push(doc
.id
);}, true);
6263 other
.history
= new History(null);
6264 other
.history
.done
= copyHistoryArray(this.history
.done
, splitIds
);
6265 other
.history
.undone
= copyHistoryArray(this.history
.undone
, splitIds
);
6268 iterLinkedDocs: function(f
) {linkedDocs(this, f
);},
6270 getMode: function() {return this.mode
;},
6271 getEditor: function() {return this.cm
;}
6275 Doc
.prototype.eachLine
= Doc
.prototype.iter
;
6277 // Set up methods on CodeMirror's prototype to redirect to the editor's document.
6278 var dontDelegate
= "iter insert remove copy getEditor".split(" ");
6279 for (var prop
in Doc
.prototype) if (Doc
.prototype.hasOwnProperty(prop
) && indexOf(dontDelegate
, prop
) < 0)
6280 CodeMirror
.prototype[prop
] = (function(method
) {
6281 return function() {return method
.apply(this.doc
, arguments
);};
6282 })(Doc
.prototype[prop
]);
6286 // Call f for all linked documents.
6287 function linkedDocs(doc
, f
, sharedHistOnly
) {
6288 function propagate(doc
, skip
, sharedHist
) {
6289 if (doc
.linked
) for (var i
= 0; i
< doc
.linked
.length
; ++i
) {
6290 var rel
= doc
.linked
[i
];
6291 if (rel
.doc
== skip
) continue;
6292 var shared
= sharedHist
&& rel
.sharedHist
;
6293 if (sharedHistOnly
&& !shared
) continue;
6295 propagate(rel
.doc
, doc
, shared
);
6298 propagate(doc
, null, true);
6301 // Attach a document to an editor.
6302 function attachDoc(cm
, doc
) {
6303 if (doc
.cm
) throw new Error("This document is already in use.");
6306 estimateLineHeights(cm
);
6308 if (!cm
.options
.lineWrapping
) findMaxLine(cm
);
6309 cm
.options
.mode
= doc
.modeOption
;
6315 // Find the line object corresponding to the given line number.
6316 function getLine(doc
, n
) {
6318 if (n
< 0 || n
>= doc
.size
) throw new Error("There is no line " + (n
+ doc
.first
) + " in the document.");
6319 for (var chunk
= doc
; !chunk
.lines
;) {
6320 for (var i
= 0;; ++i
) {
6321 var child
= chunk
.children
[i
], sz
= child
.chunkSize();
6322 if (n
< sz
) { chunk
= child
; break; }
6326 return chunk
.lines
[n
];
6329 // Get the part of a document between two positions, as an array of
6331 function getBetween(doc
, start
, end
) {
6332 var out
= [], n
= start
.line
;
6333 doc
.iter(start
.line
, end
.line
+ 1, function(line
) {
6334 var text
= line
.text
;
6335 if (n
== end
.line
) text
= text
.slice(0, end
.ch
);
6336 if (n
== start
.line
) text
= text
.slice(start
.ch
);
6342 // Get the lines between from and to, as array of strings.
6343 function getLines(doc
, from, to
) {
6345 doc
.iter(from, to
, function(line
) { out
.push(line
.text
); });
6349 // Update the height of a line, propagating the height change
6350 // upwards to parent nodes.
6351 function updateLineHeight(line
, height
) {
6352 var diff
= height
- line
.height
;
6353 if (diff
) for (var n
= line
; n
; n
= n
.parent
) n
.height
+= diff
;
6356 // Given a line object, find its line number by walking up through
6357 // its parent links.
6358 function lineNo(line
) {
6359 if (line
.parent
== null) return null;
6360 var cur
= line
.parent
, no
= indexOf(cur
.lines
, line
);
6361 for (var chunk
= cur
.parent
; chunk
; cur
= chunk
, chunk
= chunk
.parent
) {
6362 for (var i
= 0;; ++i
) {
6363 if (chunk
.children
[i
] == cur
) break;
6364 no
+= chunk
.children
[i
].chunkSize();
6367 return no
+ cur
.first
;
6370 // Find the line at the given vertical position, using the height
6371 // information in the document tree.
6372 function lineAtHeight(chunk
, h
) {
6373 var n
= chunk
.first
;
6375 for (var i
= 0; i
< chunk
.children
.length
; ++i
) {
6376 var child
= chunk
.children
[i
], ch
= child
.height
;
6377 if (h
< ch
) { chunk
= child
; continue outer
; }
6379 n
+= child
.chunkSize();
6382 } while (!chunk
.lines
);
6383 for (var i
= 0; i
< chunk
.lines
.length
; ++i
) {
6384 var line
= chunk
.lines
[i
], lh
= line
.height
;
6392 // Find the height above the given line.
6393 function heightAtLine(lineObj
) {
6394 lineObj
= visualLine(lineObj
);
6396 var h
= 0, chunk
= lineObj
.parent
;
6397 for (var i
= 0; i
< chunk
.lines
.length
; ++i
) {
6398 var line
= chunk
.lines
[i
];
6399 if (line
== lineObj
) break;
6400 else h
+= line
.height
;
6402 for (var p
= chunk
.parent
; p
; chunk
= p
, p
= chunk
.parent
) {
6403 for (var i
= 0; i
< p
.children
.length
; ++i
) {
6404 var cur
= p
.children
[i
];
6405 if (cur
== chunk
) break;
6406 else h
+= cur
.height
;
6412 // Get the bidi ordering for the given line (and cache it). Returns
6413 // false for lines that are fully left-to-right, and an array of
6414 // BidiSpan objects otherwise.
6415 function getOrder(line
) {
6416 var order
= line
.order
;
6417 if (order
== null) order
= line
.order
= bidiOrdering(line
.text
);
6423 function History(startGen
) {
6424 // Arrays of change events and selections. Doing something adds an
6425 // event to done and clears undo. Undoing moves events from done
6426 // to undone, redoing moves them in the other direction.
6427 this.done
= []; this.undone
= [];
6428 this.undoDepth
= Infinity
;
6429 // Used to track when changes can be merged into a single undo
6431 this.lastModTime
= this.lastSelTime
= 0;
6433 this.lastOrigin
= this.lastSelOrigin
= null;
6434 // Used by the isClean() method
6435 this.generation
= this.maxGeneration
= startGen
|| 1;
6438 // Create a history change event from an updateDoc-style change
6440 function historyChangeFromChange(doc
, change
) {
6441 var histChange
= {from: copyPos(change
.from), to
: changeEnd(change
), text
: getBetween(doc
, change
.from, change
.to
)};
6442 attachLocalSpans(doc
, histChange
, change
.from.line
, change
.to
.line
+ 1);
6443 linkedDocs(doc
, function(doc
) {attachLocalSpans(doc
, histChange
, change
.from.line
, change
.to
.line
+ 1);}, true);
6447 // Pop all selection events off the end of a history array. Stop at
6449 function clearSelectionEvents(array
) {
6450 while (array
.length
) {
6451 var last
= lst(array
);
6452 if (last
.ranges
) array
.pop();
6457 // Find the top change event in the history. Pop off selection
6458 // events that are in the way.
6459 function lastChangeEvent(hist
, force
) {
6461 clearSelectionEvents(hist
.done
);
6462 return lst(hist
.done
);
6463 } else if (hist
.done
.length
&& !lst(hist
.done
).ranges
) {
6464 return lst(hist
.done
);
6465 } else if (hist
.done
.length
> 1 && !hist
.done
[hist
.done
.length
- 2].ranges
) {
6467 return lst(hist
.done
);
6471 // Register a change in the history. Merges changes that are within
6472 // a single operation, ore are close together with an origin that
6473 // allows merging (starting with "+") into a single event.
6474 function addChangeToHistory(doc
, change
, selAfter
, opId
) {
6475 var hist
= doc
.history
;
6476 hist
.undone
.length
= 0;
6477 var time
= +new Date
, cur
;
6479 if ((hist
.lastOp
== opId
||
6480 hist
.lastOrigin
== change
.origin
&& change
.origin
&&
6481 ((change
.origin
.charAt(0) == "+" && doc
.cm
&& hist
.lastModTime
> time
- doc
.cm
.options
.historyEventDelay
) ||
6482 change
.origin
.charAt(0) == "*")) &&
6483 (cur
= lastChangeEvent(hist
, hist
.lastOp
== opId
))) {
6484 // Merge this change into the last event
6485 var last
= lst(cur
.changes
);
6486 if (cmp(change
.from, change
.to
) == 0 && cmp(change
.from, last
.to
) == 0) {
6487 // Optimized case for simple insertion -- don't want to add
6488 // new changesets for every character typed
6489 last
.to
= changeEnd(change
);
6491 // Add new sub-event
6492 cur
.changes
.push(historyChangeFromChange(doc
, change
));
6495 // Can not be merged, start a new event.
6496 var before
= lst(hist
.done
);
6497 if (!before
|| !before
.ranges
)
6498 pushSelectionToHistory(doc
.sel
, hist
.done
);
6499 cur
= {changes
: [historyChangeFromChange(doc
, change
)],
6500 generation
: hist
.generation
};
6501 hist
.done
.push(cur
);
6502 while (hist
.done
.length
> hist
.undoDepth
) {
6504 if (!hist
.done
[0].ranges
) hist
.done
.shift();
6507 hist
.done
.push(selAfter
);
6508 hist
.generation
= ++hist
.maxGeneration
;
6509 hist
.lastModTime
= hist
.lastSelTime
= time
;
6511 hist
.lastOrigin
= hist
.lastSelOrigin
= change
.origin
;
6513 if (!last
) signal(doc
, "historyAdded");
6516 function selectionEventCanBeMerged(doc
, origin
, prev
, sel
) {
6517 var ch
= origin
.charAt(0);
6520 prev
.ranges
.length
== sel
.ranges
.length
&&
6521 prev
.somethingSelected() == sel
.somethingSelected() &&
6522 new Date
- doc
.history
.lastSelTime
<= (doc
.cm
? doc
.cm
.options
.historyEventDelay
: 500);
6525 // Called whenever the selection changes, sets the new selection as
6526 // the pending selection in the history, and pushes the old pending
6527 // selection into the 'done' array when it was significantly
6528 // different (in number of selected ranges, emptiness, or time).
6529 function addSelectionToHistory(doc
, sel
, opId
, options
) {
6530 var hist
= doc
.history
, origin
= options
&& options
.origin
;
6532 // A new event is started when the previous origin does not match
6533 // the current, or the origins don't allow matching. Origins
6534 // starting with * are always merged, those starting with + are
6535 // merged when similar and close together in time.
6536 if (opId
== hist
.lastOp
||
6537 (origin
&& hist
.lastSelOrigin
== origin
&&
6538 (hist
.lastModTime
== hist
.lastSelTime
&& hist
.lastOrigin
== origin
||
6539 selectionEventCanBeMerged(doc
, origin
, lst(hist
.done
), sel
))))
6540 hist
.done
[hist
.done
.length
- 1] = sel
;
6542 pushSelectionToHistory(sel
, hist
.done
);
6544 hist
.lastSelTime
= +new Date
;
6545 hist
.lastSelOrigin
= origin
;
6547 if (options
&& options
.clearRedo
!== false)
6548 clearSelectionEvents(hist
.undone
);
6551 function pushSelectionToHistory(sel
, dest
) {
6552 var top
= lst(dest
);
6553 if (!(top
&& top
.ranges
&& top
.equals(sel
)))
6557 // Used to store marked span information in the history.
6558 function attachLocalSpans(doc
, change
, from, to
) {
6559 var existing
= change
["spans_" + doc
.id
], n
= 0;
6560 doc
.iter(Math
.max(doc
.first
, from), Math
.min(doc
.first
+ doc
.size
, to
), function(line
) {
6561 if (line
.markedSpans
)
6562 (existing
|| (existing
= change
["spans_" + doc
.id
] = {}))[n
] = line
.markedSpans
;
6567 // When un/re-doing restores text containing marked spans, those
6568 // that have been explicitly cleared should not be restored.
6569 function removeClearedSpans(spans
) {
6570 if (!spans
) return null;
6571 for (var i
= 0, out
; i
< spans
.length
; ++i
) {
6572 if (spans
[i
].marker
.explicitlyCleared
) { if (!out
) out
= spans
.slice(0, i
); }
6573 else if (out
) out
.push(spans
[i
]);
6575 return !out
? spans
: out
.length
? out
: null;
6578 // Retrieve and filter the old marked spans stored in a change event.
6579 function getOldSpans(doc
, change
) {
6580 var found
= change
["spans_" + doc
.id
];
6581 if (!found
) return null;
6582 for (var i
= 0, nw
= []; i
< change
.text
.length
; ++i
)
6583 nw
.push(removeClearedSpans(found
[i
]));
6587 // Used both to provide a JSON-safe object in .getHistory, and, when
6588 // detaching a document, to split the history in two
6589 function copyHistoryArray(events
, newGroup
, instantiateSel
) {
6590 for (var i
= 0, copy
= []; i
< events
.length
; ++i
) {
6591 var event
= events
[i
];
6593 copy
.push(instantiateSel
? Selection
.prototype.deepCopy
.call(event
) : event
);
6596 var changes
= event
.changes
, newChanges
= [];
6597 copy
.push({changes
: newChanges
});
6598 for (var j
= 0; j
< changes
.length
; ++j
) {
6599 var change
= changes
[j
], m
;
6600 newChanges
.push({from: change
.from, to
: change
.to
, text
: change
.text
});
6601 if (newGroup
) for (var prop
in change
) if (m
= prop
.match(/^spans_(\d+)$/)) {
6602 if (indexOf(newGroup
, Number(m
[1])) > -1) {
6603 lst(newChanges
)[prop
] = change
[prop
];
6604 delete change
[prop
];
6612 // Rebasing/resetting history to deal with externally-sourced changes
6614 function rebaseHistSelSingle(pos
, from, to
, diff
) {
6615 if (to
< pos
.line
) {
6617 } else if (from < pos
.line
) {
6623 // Tries to rebase an array of history events given a change in the
6624 // document. If the change touches the same lines as the event, the
6625 // event, and everything 'behind' it, is discarded. If the change is
6626 // before the event, the event's positions are updated. Uses a
6627 // copy-on-write scheme for the positions, to avoid having to
6628 // reallocate them all on every rebase, but also avoid problems with
6629 // shared position objects being unsafely updated.
6630 function rebaseHistArray(array
, from, to
, diff
) {
6631 for (var i
= 0; i
< array
.length
; ++i
) {
6632 var sub
= array
[i
], ok
= true;
6634 if (!sub
.copied
) { sub
= array
[i
] = sub
.deepCopy(); sub
.copied
= true; }
6635 for (var j
= 0; j
< sub
.ranges
.length
; j
++) {
6636 rebaseHistSelSingle(sub
.ranges
[j
].anchor
, from, to
, diff
);
6637 rebaseHistSelSingle(sub
.ranges
[j
].head
, from, to
, diff
);
6641 for (var j
= 0; j
< sub
.changes
.length
; ++j
) {
6642 var cur
= sub
.changes
[j
];
6643 if (to
< cur
.from.line
) {
6644 cur
.from = Pos(cur
.from.line
+ diff
, cur
.from.ch
);
6645 cur
.to
= Pos(cur
.to
.line
+ diff
, cur
.to
.ch
);
6646 } else if (from <= cur
.to
.line
) {
6652 array
.splice(0, i
+ 1);
6658 function rebaseHist(hist
, change
) {
6659 var from = change
.from.line
, to
= change
.to
.line
, diff
= change
.text
.length
- (to
- from) - 1;
6660 rebaseHistArray(hist
.done
, from, to
, diff
);
6661 rebaseHistArray(hist
.undone
, from, to
, diff
);
6666 // Due to the fact that we still support jurassic IE versions, some
6667 // compatibility wrappers are needed.
6669 var e_preventDefault
= CodeMirror
.e_preventDefault = function(e
) {
6670 if (e
.preventDefault
) e
.preventDefault();
6671 else e
.returnValue
= false;
6673 var e_stopPropagation
= CodeMirror
.e_stopPropagation = function(e
) {
6674 if (e
.stopPropagation
) e
.stopPropagation();
6675 else e
.cancelBubble
= true;
6677 function e_defaultPrevented(e
) {
6678 return e
.defaultPrevented
!= null ? e
.defaultPrevented
: e
.returnValue
== false;
6680 var e_stop
= CodeMirror
.e_stop = function(e
) {e_preventDefault(e
); e_stopPropagation(e
);};
6682 function e_target(e
) {return e
.target
|| e
.srcElement
;}
6683 function e_button(e
) {
6686 if (e
.button
& 1) b
= 1;
6687 else if (e
.button
& 2) b
= 3;
6688 else if (e
.button
& 4) b
= 2;
6690 if (mac
&& e
.ctrlKey
&& b
== 1) b
= 3;
6696 // Lightweight event framework. on/off also work on DOM nodes,
6697 // registering native DOM handlers.
6699 var on
= CodeMirror
.on = function(emitter
, type
, f
) {
6700 if (emitter
.addEventListener
)
6701 emitter
.addEventListener(type
, f
, false);
6702 else if (emitter
.attachEvent
)
6703 emitter
.attachEvent("on" + type
, f
);
6705 var map
= emitter
._handlers
|| (emitter
._handlers
= {});
6706 var arr
= map
[type
] || (map
[type
] = []);
6711 var off
= CodeMirror
.off = function(emitter
, type
, f
) {
6712 if (emitter
.removeEventListener
)
6713 emitter
.removeEventListener(type
, f
, false);
6714 else if (emitter
.detachEvent
)
6715 emitter
.detachEvent("on" + type
, f
);
6717 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
6719 for (var i
= 0; i
< arr
.length
; ++i
)
6720 if (arr
[i
] == f
) { arr
.splice(i
, 1); break; }
6724 var signal
= CodeMirror
.signal = function(emitter
, type
/*, values...*/) {
6725 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
6727 var args
= Array
.prototype.slice
.call(arguments
, 2);
6728 for (var i
= 0; i
< arr
.length
; ++i
) arr
[i
].apply(null, args
);
6731 // Often, we want to signal events at a point where we are in the
6732 // middle of some work, but don't want the handler to start calling
6733 // other methods on the editor, which might be in an inconsistent
6734 // state or simply not expect any other events to happen.
6735 // signalLater looks whether there are any handlers, and schedules
6736 // them to be executed when the last operation ends, or, if no
6737 // operation is active, when a timeout fires.
6738 var delayedCallbacks
, delayedCallbackDepth
= 0;
6739 function signalLater(emitter
, type
/*, values...*/) {
6740 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
6742 var args
= Array
.prototype.slice
.call(arguments
, 2);
6743 if (!delayedCallbacks
) {
6744 ++delayedCallbackDepth
;
6745 delayedCallbacks
= [];
6746 setTimeout(fireDelayed
, 0);
6748 function bnd(f
) {return function(){f
.apply(null, args
);};};
6749 for (var i
= 0; i
< arr
.length
; ++i
)
6750 delayedCallbacks
.push(bnd(arr
[i
]));
6753 function fireDelayed() {
6754 --delayedCallbackDepth
;
6755 var delayed
= delayedCallbacks
;
6756 delayedCallbacks
= null;
6757 for (var i
= 0; i
< delayed
.length
; ++i
) delayed
[i
]();
6760 // The DOM events that CodeMirror handles can be overridden by
6761 // registering a (non-DOM) handler on the editor for the event name,
6762 // and preventDefault-ing the event in that handler.
6763 function signalDOMEvent(cm
, e
, override
) {
6764 signal(cm
, override
|| e
.type
, cm
, e
);
6765 return e_defaultPrevented(e
) || e
.codemirrorIgnore
;
6768 function hasHandler(emitter
, type
) {
6769 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
6770 return arr
&& arr
.length
> 0;
6773 // Add on and off methods to a constructor's prototype, to make
6774 // registering events on such objects more convenient.
6775 function eventMixin(ctor
) {
6776 ctor
.prototype.on = function(type
, f
) {on(this, type
, f
);};
6777 ctor
.prototype.off = function(type
, f
) {off(this, type
, f
);};
6782 // Number of pixels added to scroller and sizer to hide scrollbar
6783 var scrollerCutOff
= 30;
6785 // Returned or thrown by various protocols to signal 'I'm not
6787 var Pass
= CodeMirror
.Pass
= {toString: function(){return "CodeMirror.Pass";}};
6789 // Reused option objects for setSelection & friends
6790 var sel_dontScroll
= {scroll
: false}, sel_mouse
= {origin
: "*mouse"}, sel_move
= {origin
: "+move"};
6792 function Delayed() {this.id
= null;}
6793 Delayed
.prototype.set = function(ms
, f
) {
6794 clearTimeout(this.id
);
6795 this.id
= setTimeout(f
, ms
);
6798 // Counts the column offset in a string, taking tabs into account.
6799 // Used mostly to find indentation.
6800 var countColumn
= CodeMirror
.countColumn = function(string
, end
, tabSize
, startIndex
, startValue
) {
6802 end
= string
.search(/[^\s\u00a0]/);
6803 if (end
== -1) end
= string
.length
;
6805 for (var i
= startIndex
|| 0, n
= startValue
|| 0;;) {
6806 var nextTab
= string
.indexOf("\t", i
);
6807 if (nextTab
< 0 || nextTab
>= end
)
6808 return n
+ (end
- i
);
6810 n
+= tabSize
- (n
% tabSize
);
6815 // The inverse of countColumn -- find the offset that corresponds to
6816 // a particular column.
6817 function findColumn(string
, goal
, tabSize
) {
6818 for (var pos
= 0, col
= 0;;) {
6819 var nextTab
= string
.indexOf("\t", pos
);
6820 if (nextTab
== -1) nextTab
= string
.length
;
6821 var skipped
= nextTab
- pos
;
6822 if (nextTab
== string
.length
|| col
+ skipped
>= goal
)
6823 return pos
+ Math
.min(skipped
, goal
- col
);
6824 col
+= nextTab
- pos
;
6825 col
+= tabSize
- (col
% tabSize
);
6827 if (col
>= goal
) return pos
;
6831 var spaceStrs
= [""];
6832 function spaceStr(n
) {
6833 while (spaceStrs
.length
<= n
)
6834 spaceStrs
.push(lst(spaceStrs
) + " ");
6835 return spaceStrs
[n
];
6838 function lst(arr
) { return arr
[arr
.length
-1]; }
6840 var selectInput = function(node
) { node
.select(); };
6841 if (ios
) // Mobile Safari apparently has a bug where select() is broken.
6842 selectInput = function(node
) { node
.selectionStart
= 0; node
.selectionEnd
= node
.value
.length
; };
6843 else if (ie
) // Suppress mysterious IE10 errors
6844 selectInput = function(node
) { try { node
.select(); } catch(_e
) {} };
6846 function indexOf(array
, elt
) {
6847 for (var i
= 0; i
< array
.length
; ++i
)
6848 if (array
[i
] == elt
) return i
;
6851 if ([].indexOf
) indexOf = function(array
, elt
) { return array
.indexOf(elt
); };
6852 function map(array
, f
) {
6854 for (var i
= 0; i
< array
.length
; i
++) out
[i
] = f(array
[i
], i
);
6857 if ([].map
) map = function(array
, f
) { return array
.map(f
); };
6859 function createObj(base
, props
) {
6861 if (Object
.create
) {
6862 inst
= Object
.create(base
);
6864 var ctor = function() {};
6865 ctor
.prototype = base
;
6868 if (props
) copyObj(props
, inst
);
6872 function copyObj(obj
, target
) {
6873 if (!target
) target
= {};
6874 for (var prop
in obj
) if (obj
.hasOwnProperty(prop
)) target
[prop
] = obj
[prop
];
6879 var args
= Array
.prototype.slice
.call(arguments
, 1);
6880 return function(){return f
.apply(null, args
);};
6883 var nonASCIISingleCaseWordChar
= /[\u00df\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
6884 var isWordChar
= CodeMirror
.isWordChar = function(ch
) {
6885 return /\w/.test(ch
) || ch
> "\x80" &&
6886 (ch
.toUpperCase() != ch
.toLowerCase() || nonASCIISingleCaseWordChar
.test(ch
));
6889 function isEmpty(obj
) {
6890 for (var n
in obj
) if (obj
.hasOwnProperty(n
) && obj
[n
]) return false;
6894 // Extending unicode characters. A series of a non-extending char +
6895 // any number of extending chars is treated as a single unit as far
6896 // as editing and measuring is concerned. This is not fully correct,
6897 // since some scripts/fonts/browsers also treat other configurations
6898 // of code points as a group.
6899 var extendingChars
= /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;
6900 function isExtendingChar(ch
) { return ch
.charCodeAt(0) >= 768 && extendingChars
.test(ch
); }
6904 function elt(tag
, content
, className
, style
) {
6905 var e
= document
.createElement(tag
);
6906 if (className
) e
.className
= className
;
6907 if (style
) e
.style
.cssText
= style
;
6908 if (typeof content
== "string") e
.appendChild(document
.createTextNode(content
));
6909 else if (content
) for (var i
= 0; i
< content
.length
; ++i
) e
.appendChild(content
[i
]);
6914 if (document
.createRange
) range = function(node
, start
, end
) {
6915 var r
= document
.createRange();
6916 r
.setEnd(node
, end
);
6917 r
.setStart(node
, start
);
6920 else range = function(node
, start
, end
) {
6921 var r
= document
.body
.createTextRange();
6922 r
.moveToElementText(node
.parentNode
);
6924 r
.moveEnd("character", end
);
6925 r
.moveStart("character", start
);
6929 function removeChildren(e
) {
6930 for (var count
= e
.childNodes
.length
; count
> 0; --count
)
6931 e
.removeChild(e
.firstChild
);
6935 function removeChildrenAndAdd(parent
, e
) {
6936 return removeChildren(parent
).appendChild(e
);
6939 function contains(parent
, child
) {
6940 if (parent
.contains
)
6941 return parent
.contains(child
);
6942 while (child
= child
.parentNode
)
6943 if (child
== parent
) return true;
6946 function activeElt() { return document
.activeElement
; }
6947 // Older versions of IE throws unspecified error when touching
6948 // document.activeElement in some cases (during loading, in iframe)
6949 if (ie_upto10
) activeElt = function() {
6950 try { return document
.activeElement
; }
6951 catch(e
) { return document
.body
; }
6954 // FEATURE DETECTION
6956 // Detect drag-and-drop
6957 var dragAndDrop = function() {
6958 // There is *some* kind of drag-and-drop support in IE6-8, but I
6959 // couldn't get it to work yet.
6960 if (ie_upto8
) return false;
6961 var div
= elt('div');
6962 return "draggable" in div
|| "dragDrop" in div
;
6965 var knownScrollbarWidth
;
6966 function scrollbarWidth(measure
) {
6967 if (knownScrollbarWidth
!= null) return knownScrollbarWidth
;
6968 var test
= elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
6969 removeChildrenAndAdd(measure
, test
);
6970 if (test
.offsetWidth
)
6971 knownScrollbarWidth
= test
.offsetHeight
- test
.clientHeight
;
6972 return knownScrollbarWidth
|| 0;
6976 function zeroWidthElement(measure
) {
6977 if (zwspSupported
== null) {
6978 var test
= elt("span", "\u200b");
6979 removeChildrenAndAdd(measure
, elt("span", [test
, document
.createTextNode("x")]));
6980 if (measure
.firstChild
.offsetHeight
!= 0)
6981 zwspSupported
= test
.offsetWidth
<= 1 && test
.offsetHeight
> 2 && !ie_upto7
;
6983 if (zwspSupported
) return elt("span", "\u200b");
6984 else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
6987 // Feature-detect IE's crummy client rect reporting for bidi text
6989 function hasBadBidiRects(measure
) {
6990 if (badBidiRects
!= null) return badBidiRects
;
6991 var txt
= removeChildrenAndAdd(measure
, document
.createTextNode("A\u062eA"));
6992 var r0
= range(txt
, 0, 1).getBoundingClientRect();
6993 if (r0
.left
== r0
.right
) return false;
6994 var r1
= range(txt
, 1, 2).getBoundingClientRect();
6995 return badBidiRects
= (r1
.right
- r0
.right
< 3);
6998 // See if "".split is the broken IE version, if so, provide an
6999 // alternative way to split lines.
7000 var splitLines
= CodeMirror
.splitLines
= "\n\nb".split(/\n/).length
!= 3 ? function(string
) {
7001 var pos
= 0, result
= [], l
= string
.length
;
7003 var nl
= string
.indexOf("\n", pos
);
7004 if (nl
== -1) nl
= string
.length
;
7005 var line
= string
.slice(pos
, string
.charAt(nl
- 1) == "\r" ? nl
- 1 : nl
);
7006 var rt
= line
.indexOf("\r");
7008 result
.push(line
.slice(0, rt
));
7016 } : function(string
){return string
.split(/\r\n?|\n/);};
7018 var hasSelection
= window
.getSelection
? function(te
) {
7019 try { return te
.selectionStart
!= te
.selectionEnd
; }
7020 catch(e
) { return false; }
7022 try {var range
= te
.ownerDocument
.selection
.createRange();}
7024 if (!range
|| range
.parentElement() != te
) return false;
7025 return range
.compareEndPoints("StartToEnd", range
) != 0;
7028 var hasCopyEvent
= (function() {
7030 if ("oncopy" in e
) return true;
7031 e
.setAttribute("oncopy", "return;");
7032 return typeof e
.oncopy
== "function";
7037 var keyNames
= {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
7038 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
7039 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
7040 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete",
7041 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
7042 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
7043 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"};
7044 CodeMirror
.keyNames
= keyNames
;
7047 for (var i
= 0; i
< 10; i
++) keyNames
[i
+ 48] = keyNames
[i
+ 96] = String(i
);
7049 for (var i
= 65; i
<= 90; i
++) keyNames
[i
] = String
.fromCharCode(i
);
7051 for (var i
= 1; i
<= 12; i
++) keyNames
[i
+ 111] = keyNames
[i
+ 63235] = "F" + i
;
7056 function iterateBidiSections(order
, from, to
, f
) {
7057 if (!order
) return f(from, to
, "ltr");
7059 for (var i
= 0; i
< order
.length
; ++i
) {
7060 var part
= order
[i
];
7061 if (part
.from < to
&& part
.to
> from || from == to
&& part
.to
== from) {
7062 f(Math
.max(part
.from, from), Math
.min(part
.to
, to
), part
.level
== 1 ? "rtl" : "ltr");
7066 if (!found
) f(from, to
, "ltr");
7069 function bidiLeft(part
) { return part
.level
% 2 ? part
.to
: part
.from; }
7070 function bidiRight(part
) { return part
.level
% 2 ? part
.from : part
.to
; }
7072 function lineLeft(line
) { var order
= getOrder(line
); return order
? bidiLeft(order
[0]) : 0; }
7073 function lineRight(line
) {
7074 var order
= getOrder(line
);
7075 if (!order
) return line
.text
.length
;
7076 return bidiRight(lst(order
));
7079 function lineStart(cm
, lineN
) {
7080 var line
= getLine(cm
.doc
, lineN
);
7081 var visual
= visualLine(line
);
7082 if (visual
!= line
) lineN
= lineNo(visual
);
7083 var order
= getOrder(visual
);
7084 var ch
= !order
? 0 : order
[0].level
% 2 ? lineRight(visual
) : lineLeft(visual
);
7085 return Pos(lineN
, ch
);
7087 function lineEnd(cm
, lineN
) {
7088 var merged
, line
= getLine(cm
.doc
, lineN
);
7089 while (merged
= collapsedSpanAtEnd(line
)) {
7090 line
= merged
.find(1, true).line
;
7093 var order
= getOrder(line
);
7094 var ch
= !order
? line
.text
.length
: order
[0].level
% 2 ? lineLeft(line
) : lineRight(line
);
7095 return Pos(lineN
== null ? lineNo(line
) : lineN
, ch
);
7098 function compareBidiLevel(order
, a
, b
) {
7099 var linedir
= order
[0].level
;
7100 if (a
== linedir
) return true;
7101 if (b
== linedir
) return false;
7105 function getBidiPartAt(order
, pos
) {
7107 for (var i
= 0, found
; i
< order
.length
; ++i
) {
7109 if (cur
.from < pos
&& cur
.to
> pos
) return i
;
7110 if ((cur
.from == pos
|| cur
.to
== pos
)) {
7111 if (found
== null) {
7113 } else if (compareBidiLevel(order
, cur
.level
, order
[found
].level
)) {
7114 if (cur
.from != cur
.to
) bidiOther
= found
;
7117 if (cur
.from != cur
.to
) bidiOther
= i
;
7125 function moveInLine(line
, pos
, dir
, byUnit
) {
7126 if (!byUnit
) return pos
+ dir
;
7128 while (pos
> 0 && isExtendingChar(line
.text
.charAt(pos
)));
7132 // This is needed in order to move 'visually' through bi-directional
7133 // text -- i.e., pressing left should make the cursor go left, even
7134 // when in RTL text. The tricky part is the 'jumps', where RTL and
7135 // LTR text touch each other. This often requires the cursor offset
7136 // to move more than one unit, in order to visually move one unit.
7137 function moveVisually(line
, start
, dir
, byUnit
) {
7138 var bidi
= getOrder(line
);
7139 if (!bidi
) return moveLogically(line
, start
, dir
, byUnit
);
7140 var pos
= getBidiPartAt(bidi
, start
), part
= bidi
[pos
];
7141 var target
= moveInLine(line
, start
, part
.level
% 2 ? -dir
: dir
, byUnit
);
7144 if (target
> part
.from && target
< part
.to
) return target
;
7145 if (target
== part
.from || target
== part
.to
) {
7146 if (getBidiPartAt(bidi
, target
) == pos
) return target
;
7147 part
= bidi
[pos
+= dir
];
7148 return (dir
> 0) == part
.level
% 2 ? part
.to
: part
.from;
7150 part
= bidi
[pos
+= dir
];
7151 if (!part
) return null;
7152 if ((dir
> 0) == part
.level
% 2)
7153 target
= moveInLine(line
, part
.to
, -1, byUnit
);
7155 target
= moveInLine(line
, part
.from, 1, byUnit
);
7160 function moveLogically(line
, start
, dir
, byUnit
) {
7161 var target
= start
+ dir
;
7162 if (byUnit
) while (target
> 0 && isExtendingChar(line
.text
.charAt(target
))) target
+= dir
;
7163 return target
< 0 || target
> line
.text
.length
? null : target
;
7166 // Bidirectional ordering algorithm
7167 // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
7168 // that this (partially) implements.
7170 // One-char codes used for character types:
7171 // L (L): Left-to-Right
7172 // R (R): Right-to-Left
7173 // r (AL): Right-to-Left Arabic
7174 // 1 (EN): European Number
7175 // + (ES): European Number Separator
7176 // % (ET): European Number Terminator
7177 // n (AN): Arabic Number
7178 // , (CS): Common Number Separator
7179 // m (NSM): Non-Spacing Mark
7180 // b (BN): Boundary Neutral
7181 // s (B): Paragraph Separator
7182 // t (S): Segment Separator
7183 // w (WS): Whitespace
7184 // N (ON): Other Neutrals
7186 // Returns null if characters are ordered as they appear
7187 // (left-to-right), or an array of sections ({from, to, level}
7188 // objects) in the order in which they occur visually.
7189 var bidiOrdering
= (function() {
7190 // Character types for codepoints 0 to 0xff
7191 var lowTypes
= "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";
7192 // Character types for codepoints 0x600 to 0x6ff
7193 var arabicTypes
= "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm";
7194 function charType(code
) {
7195 if (code
<= 0xf7) return lowTypes
.charAt(code
);
7196 else if (0x590 <= code
&& code
<= 0x5f4) return "R";
7197 else if (0x600 <= code
&& code
<= 0x6ed) return arabicTypes
.charAt(code
- 0x600);
7198 else if (0x6ee <= code
&& code
<= 0x8ac) return "r";
7199 else if (0x2000 <= code
&& code
<= 0x200b) return "w";
7200 else if (code
== 0x200c) return "b";
7204 var bidiRE
= /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
7205 var isNeutral
= /[stwN]/, isStrong
= /[LRr]/, countsAsLeft
= /[Lb1n]/, countsAsNum
= /[1n]/;
7206 // Browsers seem to always treat the boundaries of block elements as being L.
7207 var outerType
= "L";
7209 function BidiSpan(level
, from, to
) {
7211 this.from = from; this.to
= to
;
7214 return function(str
) {
7215 if (!bidiRE
.test(str
)) return false;
7216 var len
= str
.length
, types
= [];
7217 for (var i
= 0, type
; i
< len
; ++i
)
7218 types
.push(type
= charType(str
.charCodeAt(i
)));
7220 // W1. Examine each non-spacing mark (NSM) in the level run, and
7221 // change the type of the NSM to the type of the previous
7222 // character. If the NSM is at the start of the level run, it will
7223 // get the type of sor.
7224 for (var i
= 0, prev
= outerType
; i
< len
; ++i
) {
7225 var type
= types
[i
];
7226 if (type
== "m") types
[i
] = prev
;
7230 // W2. Search backwards from each instance of a European number
7231 // until the first strong type (R, L, AL, or sor) is found. If an
7232 // AL is found, change the type of the European number to Arabic
7234 // W3. Change all ALs to R.
7235 for (var i
= 0, cur
= outerType
; i
< len
; ++i
) {
7236 var type
= types
[i
];
7237 if (type
== "1" && cur
== "r") types
[i
] = "n";
7238 else if (isStrong
.test(type
)) { cur
= type
; if (type
== "r") types
[i
] = "R"; }
7241 // W4. A single European separator between two European numbers
7242 // changes to a European number. A single common separator between
7243 // two numbers of the same type changes to that type.
7244 for (var i
= 1, prev
= types
[0]; i
< len
- 1; ++i
) {
7245 var type
= types
[i
];
7246 if (type
== "+" && prev
== "1" && types
[i
+1] == "1") types
[i
] = "1";
7247 else if (type
== "," && prev
== types
[i
+1] &&
7248 (prev
== "1" || prev
== "n")) types
[i
] = prev
;
7252 // W5. A sequence of European terminators adjacent to European
7253 // numbers changes to all European numbers.
7254 // W6. Otherwise, separators and terminators change to Other
7256 for (var i
= 0; i
< len
; ++i
) {
7257 var type
= types
[i
];
7258 if (type
== ",") types
[i
] = "N";
7259 else if (type
== "%") {
7260 for (var end
= i
+ 1; end
< len
&& types
[end
] == "%"; ++end
) {}
7261 var replace
= (i
&& types
[i
-1] == "!") || (end
< len
&& types
[end
] == "1") ? "1" : "N";
7262 for (var j
= i
; j
< end
; ++j
) types
[j
] = replace
;
7267 // W7. Search backwards from each instance of a European number
7268 // until the first strong type (R, L, or sor) is found. If an L is
7269 // found, then change the type of the European number to L.
7270 for (var i
= 0, cur
= outerType
; i
< len
; ++i
) {
7271 var type
= types
[i
];
7272 if (cur
== "L" && type
== "1") types
[i
] = "L";
7273 else if (isStrong
.test(type
)) cur
= type
;
7276 // N1. A sequence of neutrals takes the direction of the
7277 // surrounding strong text if the text on both sides has the same
7278 // direction. European and Arabic numbers act as if they were R in
7279 // terms of their influence on neutrals. Start-of-level-run (sor)
7280 // and end-of-level-run (eor) are used at level run boundaries.
7281 // N2. Any remaining neutrals take the embedding direction.
7282 for (var i
= 0; i
< len
; ++i
) {
7283 if (isNeutral
.test(types
[i
])) {
7284 for (var end
= i
+ 1; end
< len
&& isNeutral
.test(types
[end
]); ++end
) {}
7285 var before
= (i
? types
[i
-1] : outerType
) == "L";
7286 var after
= (end
< len
? types
[end
] : outerType
) == "L";
7287 var replace
= before
|| after
? "L" : "R";
7288 for (var j
= i
; j
< end
; ++j
) types
[j
] = replace
;
7293 // Here we depart from the documented algorithm, in order to avoid
7294 // building up an actual levels array. Since there are only three
7295 // levels (0, 1, 2) in an implementation that doesn't take
7296 // explicit embedding into account, we can build up the order on
7297 // the fly, without following the level-based algorithm.
7299 for (var i
= 0; i
< len
;) {
7300 if (countsAsLeft
.test(types
[i
])) {
7302 for (++i
; i
< len
&& countsAsLeft
.test(types
[i
]); ++i
) {}
7303 order
.push(new BidiSpan(0, start
, i
));
7305 var pos
= i
, at
= order
.length
;
7306 for (++i
; i
< len
&& types
[i
] != "L"; ++i
) {}
7307 for (var j
= pos
; j
< i
;) {
7308 if (countsAsNum
.test(types
[j
])) {
7309 if (pos
< j
) order
.splice(at
, 0, new BidiSpan(1, pos
, j
));
7311 for (++j
; j
< i
&& countsAsNum
.test(types
[j
]); ++j
) {}
7312 order
.splice(at
, 0, new BidiSpan(2, nstart
, j
));
7316 if (pos
< i
) order
.splice(at
, 0, new BidiSpan(1, pos
, i
));
7319 if (order
[0].level
== 1 && (m
= str
.match(/^\s+/))) {
7320 order
[0].from = m
[0].length
;
7321 order
.unshift(new BidiSpan(0, 0, m
[0].length
));
7323 if (lst(order
).level
== 1 && (m
= str
.match(/\s+$/))) {
7324 lst(order
).to
-= m
[0].length
;
7325 order
.push(new BidiSpan(0, len
- m
[0].length
, len
));
7327 if (order
[0].level
!= lst(order
).level
)
7328 order
.push(new BidiSpan(order
[0].level
, len
, len
));
7336 CodeMirror
.version
= "4.0.3";