1 // CodeMirror is the only global var we claim
2 window
.CodeMirror
= (function() {
7 // Crude, but necessary to handle a number of hard-to-feature-detect
8 // bugs and behavior differences.
9 var gecko
= /gecko\/\d/i.test(navigator
.userAgent
);
10 // IE11 currently doesn't count as 'ie', since it has almost none of
11 // the same bugs as earlier versions. Use ie_gt10 to handle
12 // incompatibilities in that version.
13 var old_ie
= /MSIE \d/.test(navigator
.userAgent
);
14 var ie_lt8
= old_ie
&& (document
.documentMode
== null || document
.documentMode
< 8);
15 var ie_lt9
= old_ie
&& (document
.documentMode
== null || document
.documentMode
< 9);
16 var ie_gt10
= /Trident\/([7-9]|\d{2,})\./.test(navigator
.userAgent
);
17 var ie
= old_ie
|| ie_gt10
;
18 var webkit
= /WebKit\//.test(navigator
.userAgent
);
19 var qtwebkit
= webkit
&& /Qt\/\d+\.\d+/.test(navigator
.userAgent
);
20 var chrome
= /Chrome\//.test(navigator
.userAgent
);
21 var opera
= /Opera\//.test(navigator
.userAgent
);
22 var safari
= /Apple Computer/.test(navigator
.vendor
);
23 var khtml
= /KHTML\//.test(navigator
.userAgent
);
24 var mac_geLion
= /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator
.userAgent
);
25 var mac_geMountainLion
= /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator
.userAgent
);
26 var phantom
= /PhantomJS/.test(navigator
.userAgent
);
28 var ios
= /AppleWebKit/.test(navigator
.userAgent
) && /Mobile\/\w+/.test(navigator
.userAgent
);
29 // This is woefully incomplete. Suggestions for alternative methods welcome.
30 var mobile
= ios
|| /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator
.userAgent
);
31 var mac
= ios
|| /Mac/.test(navigator
.platform
);
32 var windows
= /win/i.test(navigator
.platform
);
34 var opera_version
= opera
&& navigator
.userAgent
.match(/Version\/(\d*\.\d*)/);
35 if (opera_version
) opera_version
= Number(opera_version
[1]);
36 if (opera_version
&& opera_version
>= 15) { opera
= false; webkit
= true; }
37 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
38 var flipCtrlCmd
= mac
&& (qtwebkit
|| opera
&& (opera_version
== null || opera_version
< 12.11));
39 var captureMiddleClick
= gecko
|| (ie
&& !ie_lt9
);
41 // Optimize some code when these features are not used
42 var sawReadOnlySpans
= false, sawCollapsedSpans
= false;
46 function CodeMirror(place
, options
) {
47 if (!(this instanceof CodeMirror
)) return new CodeMirror(place
, options
);
49 this.options
= options
= options
|| {};
50 // Determine effective options based on given values and defaults.
51 for (var opt
in defaults
) if (!options
.hasOwnProperty(opt
) && defaults
.hasOwnProperty(opt
))
52 options
[opt
] = defaults
[opt
];
53 setGuttersForLineNumbers(options
);
55 var docStart
= typeof options
.value
== "string" ? 0 : options
.value
.first
;
56 var display
= this.display
= makeDisplay(place
, docStart
);
57 display
.wrapper
.CodeMirror
= this;
59 if (options
.autofocus
&& !mobile
) focusInput(this);
61 this.state
= {keyMaps
: [],
64 overwrite
: false, focused
: false,
66 pasteIncoming
: false, cutIncoming
: false,
68 highlight
: new Delayed()};
71 if (options
.lineWrapping
)
72 this.display
.wrapper
.className
+= " CodeMirror-wrap";
74 var doc
= options
.value
;
75 if (typeof doc
== "string") doc
= new Doc(options
.value
, options
.mode
);
76 operation(this, attachDoc
)(this, doc
);
78 // Override magic textarea content restore that IE sometimes does
79 // on our hidden textarea on reload
80 if (old_ie
) setTimeout(bind(resetInput
, this, true), 20);
82 registerEventHandlers(this);
83 // IE throws unspecified error in certain cases, when
84 // trying to access activeElement before onload
85 var hasFocus
; try { hasFocus
= (document
.activeElement
== display
.input
); } catch(e
) { }
86 if (hasFocus
|| (options
.autofocus
&& !mobile
)) setTimeout(bind(onFocus
, this), 20);
89 operation(this, function() {
90 for (var opt
in optionHandlers
)
91 if (optionHandlers
.propertyIsEnumerable(opt
))
92 optionHandlers
[opt
](this, options
[opt
], Init
);
93 for (var i
= 0; i
< initHooks
.length
; ++i
) initHooks
[i
](this);
97 // DISPLAY CONSTRUCTOR
99 function makeDisplay(place
, docStart
) {
102 var input
= d
.input
= elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");
103 if (webkit
) input
.style
.width
= "1000px";
104 else input
.setAttribute("wrap", "off");
105 // if border: 0; -- iOS fails to open keyboard (issue #1287)
106 if (ios
) input
.style
.border
= "1px solid black";
107 input
.setAttribute("autocorrect", "off"); input
.setAttribute("autocapitalize", "off"); input
.setAttribute("spellcheck", "false");
109 // Wraps and hides input textarea
110 d
.inputDiv
= elt("div", [input
], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
111 // The actual fake scrollbars.
112 d
.scrollbarH
= elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
113 d
.scrollbarV
= elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
114 d
.scrollbarFiller
= elt("div", null, "CodeMirror-scrollbar-filler");
115 d
.gutterFiller
= elt("div", null, "CodeMirror-gutter-filler");
116 // DIVs containing the selection and the actual code
117 d
.lineDiv
= elt("div", null, "CodeMirror-code");
118 d
.selectionDiv
= elt("div", null, null, "position: relative; z-index: 1");
119 // Blinky cursor, and element used to ensure cursor fits at the end of a line
120 d
.cursor
= elt("div", "\u00a0", "CodeMirror-cursor");
121 // Secondary cursor, shown when on a 'jump' in bi-directional text
122 d
.otherCursor
= elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
123 // Used to measure text size
124 d
.measure
= elt("div", null, "CodeMirror-measure");
125 // Wraps everything that needs to exist inside the vertically-padded coordinate system
126 d
.lineSpace
= elt("div", [d
.measure
, d
.selectionDiv
, d
.lineDiv
, d
.cursor
, d
.otherCursor
],
127 null, "position: relative; outline: none");
128 // Moved around its parent to cover visible view
129 d
.mover
= elt("div", [elt("div", [d
.lineSpace
], "CodeMirror-lines")], null, "position: relative");
130 // Set to the height of the text, causes scrolling
131 d
.sizer
= elt("div", [d
.mover
], "CodeMirror-sizer");
132 // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
133 d
.heightForcer
= elt("div", null, null, "position: absolute; height: " + scrollerCutOff
+ "px; width: 1px;");
134 // Will contain the gutters, if any
135 d
.gutters
= elt("div", null, "CodeMirror-gutters");
137 // Provides scrolling
138 d
.scroller
= elt("div", [d
.sizer
, d
.heightForcer
, d
.gutters
], "CodeMirror-scroll");
139 d
.scroller
.setAttribute("tabIndex", "-1");
140 // The element in which the editor lives.
141 d
.wrapper
= elt("div", [d
.inputDiv
, d
.scrollbarH
, d
.scrollbarV
,
142 d
.scrollbarFiller
, d
.gutterFiller
, d
.scroller
], "CodeMirror");
143 // Work around IE7 z-index bug
144 if (ie_lt8
) { d
.gutters
.style
.zIndex
= -1; d
.scroller
.style
.paddingRight
= 0; }
145 if (place
.appendChild
) place
.appendChild(d
.wrapper
); else place(d
.wrapper
);
147 // Needed to hide big blue blinking cursor on Mobile Safari
148 if (ios
) input
.style
.width
= "0px";
149 if (!webkit
) d
.scroller
.draggable
= true;
150 // Needed to handle Tab key in KHTML
151 if (khtml
) { d
.inputDiv
.style
.height
= "1px"; d
.inputDiv
.style
.position
= "absolute"; }
152 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
153 else if (ie_lt8
) d
.scrollbarH
.style
.minWidth
= d
.scrollbarV
.style
.minWidth
= "18px";
155 // Current visible range (may be bigger than the view window).
156 d
.viewOffset
= d
.lastSizeC
= 0;
157 d
.showingFrom
= d
.showingTo
= docStart
;
159 // Used to only resize the line number gutter when necessary (when
160 // the amount of lines crosses a boundary that makes its width change)
161 d
.lineNumWidth
= d
.lineNumInnerWidth
= d
.lineNumChars
= null;
162 // See readInput and resetInput
164 // Set to true when a non-horizontal-scrolling widget is added. As
165 // an optimization, widget aligning is skipped when d is false.
166 d
.alignWidgets
= false;
167 // Flag that indicates whether we currently expect input to appear
168 // (after some event like 'keypress' or 'input') and are polling
170 d
.pollingFast
= false;
171 // Self-resetting timeout for the poller
172 d
.poll
= new Delayed();
174 d
.cachedCharWidth
= d
.cachedTextHeight
= null;
175 d
.measureLineCache
= [];
176 d
.measureLineCachePos
= 0;
178 // Tracks when resetInput has punted to just putting a short
179 // string instead of the (large) selection.
180 d
.inaccurateSelection
= false;
182 // Tracks the maximum line length so that the horizontal scrollbar
183 // can be kept static when scrolling.
186 d
.maxLineChanged
= false;
188 // Used for measuring wheel scrolling granularity
189 d
.wheelDX
= d
.wheelDY
= d
.wheelStartX
= d
.wheelStartY
= null;
196 // Used to get the editor into a consistent state again when options change.
198 function loadMode(cm
) {
199 cm
.doc
.mode
= CodeMirror
.getMode(cm
.options
, cm
.doc
.modeOption
);
203 function resetModeState(cm
) {
204 cm
.doc
.iter(function(line
) {
205 if (line
.stateAfter
) line
.stateAfter
= null;
206 if (line
.styles
) line
.styles
= null;
208 cm
.doc
.frontier
= cm
.doc
.first
;
209 startWorker(cm
, 100);
211 if (cm
.curOp
) regChange(cm
);
214 function wrappingChanged(cm
) {
215 if (cm
.options
.lineWrapping
) {
216 cm
.display
.wrapper
.className
+= " CodeMirror-wrap";
217 cm
.display
.sizer
.style
.minWidth
= "";
219 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(" CodeMirror-wrap", "");
220 computeMaxLength(cm
);
222 estimateLineHeights(cm
);
225 setTimeout(function(){updateScrollbars(cm
);}, 100);
228 function estimateHeight(cm
) {
229 var th
= textHeight(cm
.display
), wrapping
= cm
.options
.lineWrapping
;
230 var perLine
= wrapping
&& Math
.max(5, cm
.display
.scroller
.clientWidth
/ charWidth(cm
.display
) - 3);
231 return function(line
) {
232 if (lineIsHidden(cm
.doc
, line
))
235 return (Math
.ceil(line
.text
.length
/ perLine
) || 1) * th
;
241 function estimateLineHeights(cm
) {
242 var doc
= cm
.doc
, est
= estimateHeight(cm
);
243 doc
.iter(function(line
) {
244 var estHeight
= est(line
);
245 if (estHeight
!= line
.height
) updateLineHeight(line
, estHeight
);
249 function keyMapChanged(cm
) {
250 var map
= keyMap
[cm
.options
.keyMap
], style
= map
.style
;
251 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(/\s*cm-keymap-\S+/g, "") +
252 (style
? " cm-keymap-" + style
: "");
255 function themeChanged(cm
) {
256 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(/\s*cm-s-\S+/g, "") +
257 cm
.options
.theme
.replace(/(^|\s)\s*/g, " cm-s-");
261 function guttersChanged(cm
) {
264 setTimeout(function(){alignHorizontally(cm
);}, 20);
267 function updateGutters(cm
) {
268 var gutters
= cm
.display
.gutters
, specs
= cm
.options
.gutters
;
269 removeChildren(gutters
);
270 for (var i
= 0; i
< specs
.length
; ++i
) {
271 var gutterClass
= specs
[i
];
272 var gElt
= gutters
.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass
));
273 if (gutterClass
== "CodeMirror-linenumbers") {
274 cm
.display
.lineGutter
= gElt
;
275 gElt
.style
.width
= (cm
.display
.lineNumWidth
|| 1) + "px";
278 gutters
.style
.display
= i
? "" : "none";
281 function lineLength(doc
, line
) {
282 if (line
.height
== 0) return 0;
283 var len
= line
.text
.length
, merged
, cur
= line
;
284 while (merged
= collapsedSpanAtStart(cur
)) {
285 var found
= merged
.find();
286 cur
= getLine(doc
, found
.from.line
);
287 len
+= found
.from.ch
- found
.to
.ch
;
290 while (merged
= collapsedSpanAtEnd(cur
)) {
291 var found
= merged
.find();
292 len
-= cur
.text
.length
- found
.from.ch
;
293 cur
= getLine(doc
, found
.to
.line
);
294 len
+= cur
.text
.length
- found
.to
.ch
;
299 function computeMaxLength(cm
) {
300 var d
= cm
.display
, doc
= cm
.doc
;
301 d
.maxLine
= getLine(doc
, doc
.first
);
302 d
.maxLineLength
= lineLength(doc
, d
.maxLine
);
303 d
.maxLineChanged
= true;
304 doc
.iter(function(line
) {
305 var len
= lineLength(doc
, line
);
306 if (len
> d
.maxLineLength
) {
307 d
.maxLineLength
= len
;
313 // Make sure the gutters options contains the element
314 // "CodeMirror-linenumbers" when the lineNumbers option is true.
315 function setGuttersForLineNumbers(options
) {
316 var found
= indexOf(options
.gutters
, "CodeMirror-linenumbers");
317 if (found
== -1 && options
.lineNumbers
) {
318 options
.gutters
= options
.gutters
.concat(["CodeMirror-linenumbers"]);
319 } else if (found
> -1 && !options
.lineNumbers
) {
320 options
.gutters
= options
.gutters
.slice(0);
321 options
.gutters
.splice(found
, 1);
327 // Re-synchronize the fake scrollbars with the actual size of the
328 // content. Optionally force a scrollTop.
329 function updateScrollbars(cm
) {
330 var d
= cm
.display
, docHeight
= cm
.doc
.height
;
331 var totalHeight
= docHeight
+ paddingVert(d
);
332 d
.sizer
.style
.minHeight
= d
.heightForcer
.style
.top
= totalHeight
+ "px";
333 d
.gutters
.style
.height
= Math
.max(totalHeight
, d
.scroller
.clientHeight
- scrollerCutOff
) + "px";
334 var scrollHeight
= Math
.max(totalHeight
, d
.scroller
.scrollHeight
);
335 var needsH
= d
.scroller
.scrollWidth
> (d
.scroller
.clientWidth
+ 1);
336 var needsV
= scrollHeight
> (d
.scroller
.clientHeight
+ 1);
338 d
.scrollbarV
.style
.display
= "block";
339 d
.scrollbarV
.style
.bottom
= needsH
? scrollbarWidth(d
.measure
) + "px" : "0";
340 // A bug in IE8 can cause this value to be negative, so guard it.
341 d
.scrollbarV
.firstChild
.style
.height
=
342 Math
.max(0, scrollHeight
- d
.scroller
.clientHeight
+ d
.scrollbarV
.clientHeight
) + "px";
344 d
.scrollbarV
.style
.display
= "";
345 d
.scrollbarV
.firstChild
.style
.height
= "0";
348 d
.scrollbarH
.style
.display
= "block";
349 d
.scrollbarH
.style
.right
= needsV
? scrollbarWidth(d
.measure
) + "px" : "0";
350 d
.scrollbarH
.firstChild
.style
.width
=
351 (d
.scroller
.scrollWidth
- d
.scroller
.clientWidth
+ d
.scrollbarH
.clientWidth
) + "px";
353 d
.scrollbarH
.style
.display
= "";
354 d
.scrollbarH
.firstChild
.style
.width
= "0";
356 if (needsH
&& needsV
) {
357 d
.scrollbarFiller
.style
.display
= "block";
358 d
.scrollbarFiller
.style
.height
= d
.scrollbarFiller
.style
.width
= scrollbarWidth(d
.measure
) + "px";
359 } else d
.scrollbarFiller
.style
.display
= "";
360 if (needsH
&& cm
.options
.coverGutterNextToScrollbar
&& cm
.options
.fixedGutter
) {
361 d
.gutterFiller
.style
.display
= "block";
362 d
.gutterFiller
.style
.height
= scrollbarWidth(d
.measure
) + "px";
363 d
.gutterFiller
.style
.width
= d
.gutters
.offsetWidth
+ "px";
364 } else d
.gutterFiller
.style
.display
= "";
366 if (mac_geLion
&& scrollbarWidth(d
.measure
) === 0) {
367 d
.scrollbarV
.style
.minWidth
= d
.scrollbarH
.style
.minHeight
= mac_geMountainLion
? "18px" : "12px";
368 d
.scrollbarV
.style
.pointerEvents
= d
.scrollbarH
.style
.pointerEvents
= "none";
372 function visibleLines(display
, doc
, viewPort
) {
373 var top
= display
.scroller
.scrollTop
, height
= display
.wrapper
.clientHeight
;
374 if (typeof viewPort
== "number") top
= viewPort
;
375 else if (viewPort
) {top
= viewPort
.top
; height
= viewPort
.bottom
- viewPort
.top
;}
376 top
= Math
.floor(top
- paddingTop(display
));
377 var bottom
= Math
.ceil(top
+ height
);
378 return {from: lineAtHeight(doc
, top
), to
: lineAtHeight(doc
, bottom
)};
383 function alignHorizontally(cm
) {
384 var display
= cm
.display
;
385 if (!display
.alignWidgets
&& (!display
.gutters
.firstChild
|| !cm
.options
.fixedGutter
)) return;
386 var comp
= compensateForHScroll(display
) - display
.scroller
.scrollLeft
+ cm
.doc
.scrollLeft
;
387 var gutterW
= display
.gutters
.offsetWidth
, l
= comp
+ "px";
388 for (var n
= display
.lineDiv
.firstChild
; n
; n
= n
.nextSibling
) if (n
.alignable
) {
389 for (var i
= 0, a
= n
.alignable
; i
< a
.length
; ++i
) a
[i
].style
.left
= l
;
391 if (cm
.options
.fixedGutter
)
392 display
.gutters
.style
.left
= (comp
+ gutterW
) + "px";
395 function maybeUpdateLineNumberWidth(cm
) {
396 if (!cm
.options
.lineNumbers
) return false;
397 var doc
= cm
.doc
, last
= lineNumberFor(cm
.options
, doc
.first
+ doc
.size
- 1), display
= cm
.display
;
398 if (last
.length
!= display
.lineNumChars
) {
399 var test
= display
.measure
.appendChild(elt("div", [elt("div", last
)],
400 "CodeMirror-linenumber CodeMirror-gutter-elt"));
401 var innerW
= test
.firstChild
.offsetWidth
, padding
= test
.offsetWidth
- innerW
;
402 display
.lineGutter
.style
.width
= "";
403 display
.lineNumInnerWidth
= Math
.max(innerW
, display
.lineGutter
.offsetWidth
- padding
);
404 display
.lineNumWidth
= display
.lineNumInnerWidth
+ padding
;
405 display
.lineNumChars
= display
.lineNumInnerWidth
? last
.length
: -1;
406 display
.lineGutter
.style
.width
= display
.lineNumWidth
+ "px";
412 function lineNumberFor(options
, i
) {
413 return String(options
.lineNumberFormatter(i
+ options
.firstLineNumber
));
415 function compensateForHScroll(display
) {
416 return getRect(display
.scroller
).left
- getRect(display
.sizer
).left
;
421 function updateDisplay(cm
, changes
, viewPort
, forced
) {
422 var oldFrom
= cm
.display
.showingFrom
, oldTo
= cm
.display
.showingTo
, updated
;
423 var visible
= visibleLines(cm
.display
, cm
.doc
, viewPort
);
424 for (var first
= true;; first
= false) {
425 var oldWidth
= cm
.display
.scroller
.clientWidth
;
426 if (!updateDisplayInner(cm
, changes
, visible
, forced
)) break;
430 updateScrollbars(cm
);
431 if (first
&& cm
.options
.lineWrapping
&& oldWidth
!= cm
.display
.scroller
.clientWidth
) {
437 // Clip forced viewport to actual scrollable area
439 viewPort
= Math
.min(cm
.display
.scroller
.scrollHeight
- cm
.display
.scroller
.clientHeight
,
440 typeof viewPort
== "number" ? viewPort
: viewPort
.top
);
441 visible
= visibleLines(cm
.display
, cm
.doc
, viewPort
);
442 if (visible
.from >= cm
.display
.showingFrom
&& visible
.to
<= cm
.display
.showingTo
)
447 signalLater(cm
, "update", cm
);
448 if (cm
.display
.showingFrom
!= oldFrom
|| cm
.display
.showingTo
!= oldTo
)
449 signalLater(cm
, "viewportChange", cm
, cm
.display
.showingFrom
, cm
.display
.showingTo
);
454 // Uses a set of changes plus the current scroll position to
455 // determine which DOM updates have to be made, and makes the
457 function updateDisplayInner(cm
, changes
, visible
, forced
) {
458 var display
= cm
.display
, doc
= cm
.doc
;
459 if (!display
.wrapper
.offsetWidth
) {
460 display
.showingFrom
= display
.showingTo
= doc
.first
;
461 display
.viewOffset
= 0;
465 // Bail out if the visible area is already rendered and nothing changed.
466 if (!forced
&& changes
.length
== 0 &&
467 visible
.from > display
.showingFrom
&& visible
.to
< display
.showingTo
)
470 if (maybeUpdateLineNumberWidth(cm
))
471 changes
= [{from: doc
.first
, to
: doc
.first
+ doc
.size
}];
472 var gutterW
= display
.sizer
.style
.marginLeft
= display
.gutters
.offsetWidth
+ "px";
473 display
.scrollbarH
.style
.left
= cm
.options
.fixedGutter
? gutterW
: "0";
475 // Used to determine which lines need their line numbers updated
476 var positionsChangedFrom
= Infinity
;
477 if (cm
.options
.lineNumbers
)
478 for (var i
= 0; i
< changes
.length
; ++i
)
479 if (changes
[i
].diff
&& changes
[i
].from < positionsChangedFrom
) { positionsChangedFrom
= changes
[i
].from; }
481 var end
= doc
.first
+ doc
.size
;
482 var from = Math
.max(visible
.from - cm
.options
.viewportMargin
, doc
.first
);
483 var to
= Math
.min(end
, visible
.to
+ cm
.options
.viewportMargin
);
484 if (display
.showingFrom
< from && from - display
.showingFrom
< 20) from = Math
.max(doc
.first
, display
.showingFrom
);
485 if (display
.showingTo
> to
&& display
.showingTo
- to
< 20) to
= Math
.min(end
, display
.showingTo
);
486 if (sawCollapsedSpans
) {
487 from = lineNo(visualLine(doc
, getLine(doc
, from)));
488 while (to
< end
&& lineIsHidden(doc
, getLine(doc
, to
))) ++to
;
491 // Create a range of theoretically intact lines, and punch holes
492 // in that using the change info.
493 var intact
= [{from: Math
.max(display
.showingFrom
, doc
.first
),
494 to
: Math
.min(display
.showingTo
, end
)}];
495 if (intact
[0].from >= intact
[0].to
) intact
= [];
496 else intact
= computeIntact(intact
, changes
);
497 // When merged lines are present, we might have to reduce the
498 // intact ranges because changes in continued fragments of the
499 // intact lines do require the lines to be redrawn.
500 if (sawCollapsedSpans
)
501 for (var i
= 0; i
< intact
.length
; ++i
) {
502 var range
= intact
[i
], merged
;
503 while (merged
= collapsedSpanAtEnd(getLine(doc
, range
.to
- 1))) {
504 var newTo
= merged
.find().from.line
;
505 if (newTo
> range
.from) range
.to
= newTo
;
506 else { intact
.splice(i
--, 1); break; }
510 // Clip off the parts that won't be visible
512 for (var i
= 0; i
< intact
.length
; ++i
) {
513 var range
= intact
[i
];
514 if (range
.from < from) range
.from = from;
515 if (range
.to
> to
) range
.to
= to
;
516 if (range
.from >= range
.to
) intact
.splice(i
--, 1);
517 else intactLines
+= range
.to
- range
.from;
519 if (!forced
&& intactLines
== to
- from && from == display
.showingFrom
&& to
== display
.showingTo
) {
520 updateViewOffset(cm
);
523 intact
.sort(function(a
, b
) {return a
.from - b
.from;});
525 // Avoid crashing on IE's "unspecified error" when in iframes
527 var focused
= document
.activeElement
;
529 if (intactLines
< (to
- from) * .7) display
.lineDiv
.style
.display
= "none";
530 patchDisplay(cm
, from, to
, intact
, positionsChangedFrom
);
531 display
.lineDiv
.style
.display
= "";
532 if (focused
&& document
.activeElement
!= focused
&& focused
.offsetHeight
) focused
.focus();
534 var different
= from != display
.showingFrom
|| to
!= display
.showingTo
||
535 display
.lastSizeC
!= display
.wrapper
.clientHeight
;
536 // This is just a bogus formula that detects when the editor is
537 // resized or the font size changes.
539 display
.lastSizeC
= display
.wrapper
.clientHeight
;
540 startWorker(cm
, 400);
542 display
.showingFrom
= from; display
.showingTo
= to
;
544 display
.gutters
.style
.height
= "";
545 updateHeightsInViewport(cm
);
546 updateViewOffset(cm
);
551 function updateHeightsInViewport(cm
) {
552 var display
= cm
.display
;
553 var prevBottom
= display
.lineDiv
.offsetTop
;
554 for (var node
= display
.lineDiv
.firstChild
, height
; node
; node
= node
.nextSibling
) if (node
.lineObj
) {
556 var bot
= node
.offsetTop
+ node
.offsetHeight
;
557 height
= bot
- prevBottom
;
560 var box
= getRect(node
);
561 height
= box
.bottom
- box
.top
;
563 var diff
= node
.lineObj
.height
- height
;
564 if (height
< 2) height
= textHeight(display
);
565 if (diff
> .001 || diff
< -.001) {
566 updateLineHeight(node
.lineObj
, height
);
567 var widgets
= node
.lineObj
.widgets
;
568 if (widgets
) for (var i
= 0; i
< widgets
.length
; ++i
)
569 widgets
[i
].height
= widgets
[i
].node
.offsetHeight
;
574 function updateViewOffset(cm
) {
575 var off
= cm
.display
.viewOffset
= heightAtLine(cm
, getLine(cm
.doc
, cm
.display
.showingFrom
));
576 // Position the mover div to align with the current virtual scroll position
577 cm
.display
.mover
.style
.top
= off
+ "px";
580 function computeIntact(intact
, changes
) {
581 for (var i
= 0, l
= changes
.length
|| 0; i
< l
; ++i
) {
582 var change
= changes
[i
], intact2
= [], diff
= change
.diff
|| 0;
583 for (var j
= 0, l2
= intact
.length
; j
< l2
; ++j
) {
584 var range
= intact
[j
];
585 if (change
.to
<= range
.from && change
.diff
) {
586 intact2
.push({from: range
.from + diff
, to
: range
.to
+ diff
});
587 } else if (change
.to
<= range
.from || change
.from >= range
.to
) {
590 if (change
.from > range
.from)
591 intact2
.push({from: range
.from, to
: change
.from});
592 if (change
.to
< range
.to
)
593 intact2
.push({from: change
.to
+ diff
, to
: range
.to
+ diff
});
601 function getDimensions(cm
) {
602 var d
= cm
.display
, left
= {}, width
= {};
603 for (var n
= d
.gutters
.firstChild
, i
= 0; n
; n
= n
.nextSibling
, ++i
) {
604 left
[cm
.options
.gutters
[i
]] = n
.offsetLeft
;
605 width
[cm
.options
.gutters
[i
]] = n
.offsetWidth
;
607 return {fixedPos
: compensateForHScroll(d
),
608 gutterTotalWidth
: d
.gutters
.offsetWidth
,
611 wrapperWidth
: d
.wrapper
.clientWidth
};
614 function patchDisplay(cm
, from, to
, intact
, updateNumbersFrom
) {
615 var dims
= getDimensions(cm
);
616 var display
= cm
.display
, lineNumbers
= cm
.options
.lineNumbers
;
617 if (!intact
.length
&& (!webkit
|| !cm
.display
.currentWheelTarget
))
618 removeChildren(display
.lineDiv
);
619 var container
= display
.lineDiv
, cur
= container
.firstChild
;
622 var next
= node
.nextSibling
;
623 if (webkit
&& mac
&& cm
.display
.currentWheelTarget
== node
) {
624 node
.style
.display
= "none";
627 node
.parentNode
.removeChild(node
);
632 var nextIntact
= intact
.shift(), lineN
= from;
633 cm
.doc
.iter(from, to
, function(line
) {
634 if (nextIntact
&& nextIntact
.to
== lineN
) nextIntact
= intact
.shift();
635 if (lineIsHidden(cm
.doc
, line
)) {
636 if (line
.height
!= 0) updateLineHeight(line
, 0);
637 if (line
.widgets
&& cur
&& cur
.previousSibling
) for (var i
= 0; i
< line
.widgets
.length
; ++i
) {
638 var w
= line
.widgets
[i
];
639 if (w
.showIfHidden
) {
640 var prev
= cur
.previousSibling
;
641 if (/pre/i.test(prev
.nodeName
)) {
642 var wrap
= elt("div", null, null, "position: relative");
643 prev
.parentNode
.replaceChild(wrap
, prev
);
644 wrap
.appendChild(prev
);
647 var wnode
= prev
.appendChild(elt("div", [w
.node
], "CodeMirror-linewidget"));
648 if (!w
.handleMouseEvents
) wnode
.ignoreEvents
= true;
649 positionLineWidget(w
, wnode
, prev
, dims
);
652 } else if (nextIntact
&& nextIntact
.from <= lineN
&& nextIntact
.to
> lineN
) {
653 // This line is intact. Skip to the actual node. Update its
654 // line number if needed.
655 while (cur
.lineObj
!= line
) cur
= rm(cur
);
656 if (lineNumbers
&& updateNumbersFrom
<= lineN
&& cur
.lineNumber
)
657 setTextContent(cur
.lineNumber
, lineNumberFor(cm
.options
, lineN
));
658 cur
= cur
.nextSibling
;
660 // For lines with widgets, make an attempt to find and reuse
661 // the existing element, so that widgets aren't needlessly
662 // removed and re-inserted into the dom
663 if (line
.widgets
) for (var j
= 0, search
= cur
, reuse
; search
&& j
< 20; ++j
, search
= search
.nextSibling
)
664 if (search
.lineObj
== line
&& /div/i.test(search
.nodeName
)) { reuse
= search
; break; }
665 // This line needs to be generated.
666 var lineNode
= buildLineElement(cm
, line
, lineN
, dims
, reuse
);
667 if (lineNode
!= reuse
) {
668 container
.insertBefore(lineNode
, cur
);
670 while (cur
!= reuse
) cur
= rm(cur
);
671 cur
= cur
.nextSibling
;
674 lineNode
.lineObj
= line
;
678 while (cur
) cur
= rm(cur
);
681 function buildLineElement(cm
, line
, lineNo
, dims
, reuse
) {
682 var built
= buildLineContent(cm
, line
), lineElement
= built
.pre
;
683 var markers
= line
.gutterMarkers
, display
= cm
.display
, wrap
;
685 var bgClass
= built
.bgClass
? built
.bgClass
+ " " + (line
.bgClass
|| "") : line
.bgClass
;
686 if (!cm
.options
.lineNumbers
&& !markers
&& !bgClass
&& !line
.wrapClass
&& !line
.widgets
)
689 // Lines with gutter elements, widgets or a background class need
690 // to be wrapped again, and have the extra elements added to the
694 reuse
.alignable
= null;
695 var isOk
= true, widgetsSeen
= 0, insertBefore
= null;
696 for (var n
= reuse
.firstChild
, next
; n
; n
= next
) {
697 next
= n
.nextSibling
;
698 if (!/\bCodeMirror-linewidget\b/.test(n
.className
)) {
699 reuse
.removeChild(n
);
701 for (var i
= 0; i
< line
.widgets
.length
; ++i
) {
702 var widget
= line
.widgets
[i
];
703 if (widget
.node
== n
.firstChild
) {
704 if (!widget
.above
&& !insertBefore
) insertBefore
= n
;
705 positionLineWidget(widget
, n
, reuse
, dims
);
710 if (i
== line
.widgets
.length
) { isOk
= false; break; }
713 reuse
.insertBefore(lineElement
, insertBefore
);
714 if (isOk
&& widgetsSeen
== line
.widgets
.length
) {
716 reuse
.className
= line
.wrapClass
|| "";
720 wrap
= elt("div", null, line
.wrapClass
, "position: relative");
721 wrap
.appendChild(lineElement
);
723 // Kludge to make sure the styled element lies behind the selection (by z-index)
725 wrap
.insertBefore(elt("div", null, bgClass
+ " CodeMirror-linebackground"), wrap
.firstChild
);
726 if (cm
.options
.lineNumbers
|| markers
) {
727 var gutterWrap
= wrap
.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " +
728 (cm
.options
.fixedGutter
? dims
.fixedPos
: -dims
.gutterTotalWidth
) + "px"),
730 if (cm
.options
.fixedGutter
) (wrap
.alignable
|| (wrap
.alignable
= [])).push(gutterWrap
);
731 if (cm
.options
.lineNumbers
&& (!markers
|| !markers
["CodeMirror-linenumbers"]))
732 wrap
.lineNumber
= gutterWrap
.appendChild(
733 elt("div", lineNumberFor(cm
.options
, lineNo
),
734 "CodeMirror-linenumber CodeMirror-gutter-elt",
735 "left: " + dims
.gutterLeft
["CodeMirror-linenumbers"] + "px; width: "
736 + display
.lineNumInnerWidth
+ "px"));
738 for (var k
= 0; k
< cm
.options
.gutters
.length
; ++k
) {
739 var id
= cm
.options
.gutters
[k
], found
= markers
.hasOwnProperty(id
) && markers
[id
];
741 gutterWrap
.appendChild(elt("div", [found
], "CodeMirror-gutter-elt", "left: " +
742 dims
.gutterLeft
[id
] + "px; width: " + dims
.gutterWidth
[id
] + "px"));
745 if (ie_lt8
) wrap
.style
.zIndex
= 2;
746 if (line
.widgets
&& wrap
!= reuse
) for (var i
= 0, ws
= line
.widgets
; i
< ws
.length
; ++i
) {
747 var widget
= ws
[i
], node
= elt("div", [widget
.node
], "CodeMirror-linewidget");
748 if (!widget
.handleMouseEvents
) node
.ignoreEvents
= true;
749 positionLineWidget(widget
, node
, wrap
, dims
);
751 wrap
.insertBefore(node
, cm
.options
.lineNumbers
&& line
.height
!= 0 ? gutterWrap
: lineElement
);
753 wrap
.appendChild(node
);
754 signalLater(widget
, "redraw");
759 function positionLineWidget(widget
, node
, wrap
, dims
) {
760 if (widget
.noHScroll
) {
761 (wrap
.alignable
|| (wrap
.alignable
= [])).push(node
);
762 var width
= dims
.wrapperWidth
;
763 node
.style
.left
= dims
.fixedPos
+ "px";
764 if (!widget
.coverGutter
) {
765 width
-= dims
.gutterTotalWidth
;
766 node
.style
.paddingLeft
= dims
.gutterTotalWidth
+ "px";
768 node
.style
.width
= width
+ "px";
770 if (widget
.coverGutter
) {
771 node
.style
.zIndex
= 5;
772 node
.style
.position
= "relative";
773 if (!widget
.noHScroll
) node
.style
.marginLeft
= -dims
.gutterTotalWidth
+ "px";
777 // SELECTION / CURSOR
779 function updateSelection(cm
) {
780 var display
= cm
.display
;
781 var collapsed
= posEq(cm
.doc
.sel
.from, cm
.doc
.sel
.to
);
782 if (collapsed
|| cm
.options
.showCursorWhenSelecting
)
783 updateSelectionCursor(cm
);
785 display
.cursor
.style
.display
= display
.otherCursor
.style
.display
= "none";
787 updateSelectionRange(cm
);
789 display
.selectionDiv
.style
.display
= "none";
791 // Move the hidden textarea near the cursor to prevent scrolling artifacts
792 if (cm
.options
.moveInputWithCursor
) {
793 var headPos
= cursorCoords(cm
, cm
.doc
.sel
.head
, "div");
794 var wrapOff
= getRect(display
.wrapper
), lineOff
= getRect(display
.lineDiv
);
795 display
.inputDiv
.style
.top
= Math
.max(0, Math
.min(display
.wrapper
.clientHeight
- 10,
796 headPos
.top
+ lineOff
.top
- wrapOff
.top
)) + "px";
797 display
.inputDiv
.style
.left
= Math
.max(0, Math
.min(display
.wrapper
.clientWidth
- 10,
798 headPos
.left
+ lineOff
.left
- wrapOff
.left
)) + "px";
802 // No selection, plain cursor
803 function updateSelectionCursor(cm
) {
804 var display
= cm
.display
, pos
= cursorCoords(cm
, cm
.doc
.sel
.head
, "div");
805 display
.cursor
.style
.left
= pos
.left
+ "px";
806 display
.cursor
.style
.top
= pos
.top
+ "px";
807 display
.cursor
.style
.height
= Math
.max(0, pos
.bottom
- pos
.top
) * cm
.options
.cursorHeight
+ "px";
808 display
.cursor
.style
.display
= "";
811 display
.otherCursor
.style
.display
= "";
812 display
.otherCursor
.style
.left
= pos
.other
.left
+ "px";
813 display
.otherCursor
.style
.top
= pos
.other
.top
+ "px";
814 display
.otherCursor
.style
.height
= (pos
.other
.bottom
- pos
.other
.top
) * .85 + "px";
815 } else { display
.otherCursor
.style
.display
= "none"; }
818 // Highlight selection
819 function updateSelectionRange(cm
) {
820 var display
= cm
.display
, doc
= cm
.doc
, sel
= cm
.doc
.sel
;
821 var fragment
= document
.createDocumentFragment();
822 var clientWidth
= display
.lineSpace
.offsetWidth
, pl
= paddingLeft(cm
.display
);
824 function add(left
, top
, width
, bottom
) {
825 if (top
< 0) top
= 0;
826 fragment
.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left
+
827 "px; top: " + top
+ "px; width: " + (width
== null ? clientWidth
- left
: width
) +
828 "px; height: " + (bottom
- top
) + "px"));
831 function drawForLine(line
, fromArg
, toArg
) {
832 var lineObj
= getLine(doc
, line
);
833 var lineLen
= lineObj
.text
.length
;
835 function coords(ch
, bias
) {
836 return charCoords(cm
, Pos(line
, ch
), "div", lineObj
, bias
);
839 iterateBidiSections(getOrder(lineObj
), fromArg
|| 0, toArg
== null ? lineLen
: toArg
, function(from, to
, dir
) {
840 var leftPos
= coords(from, "left"), rightPos
, left
, right
;
843 left
= right
= leftPos
.left
;
845 rightPos
= coords(to
- 1, "right");
846 if (dir
== "rtl") { var tmp
= leftPos
; leftPos
= rightPos
; rightPos
= tmp
; }
848 right
= rightPos
.right
;
850 if (fromArg
== null && from == 0) left
= pl
;
851 if (rightPos
.top
- leftPos
.top
> 3) { // Different lines, draw top part
852 add(left
, leftPos
.top
, null, leftPos
.bottom
);
854 if (leftPos
.bottom
< rightPos
.top
) add(left
, leftPos
.bottom
, null, rightPos
.top
);
856 if (toArg
== null && to
== lineLen
) right
= clientWidth
;
857 if (!start
|| leftPos
.top
< start
.top
|| leftPos
.top
== start
.top
&& leftPos
.left
< start
.left
)
859 if (!end
|| rightPos
.bottom
> end
.bottom
|| rightPos
.bottom
== end
.bottom
&& rightPos
.right
> end
.right
)
861 if (left
< pl
+ 1) left
= pl
;
862 add(left
, rightPos
.top
, right
- left
, rightPos
.bottom
);
864 return {start
: start
, end
: end
};
867 if (sel
.from.line
== sel
.to
.line
) {
868 drawForLine(sel
.from.line
, sel
.from.ch
, sel
.to
.ch
);
870 var fromLine
= getLine(doc
, sel
.from.line
), toLine
= getLine(doc
, sel
.to
.line
);
871 var singleVLine
= visualLine(doc
, fromLine
) == visualLine(doc
, toLine
);
872 var leftEnd
= drawForLine(sel
.from.line
, sel
.from.ch
, singleVLine
? fromLine
.text
.length
: null).end
;
873 var rightStart
= drawForLine(sel
.to
.line
, singleVLine
? 0 : null, sel
.to
.ch
).start
;
875 if (leftEnd
.top
< rightStart
.top
- 2) {
876 add(leftEnd
.right
, leftEnd
.top
, null, leftEnd
.bottom
);
877 add(pl
, rightStart
.top
, rightStart
.left
, rightStart
.bottom
);
879 add(leftEnd
.right
, leftEnd
.top
, rightStart
.left
- leftEnd
.right
, leftEnd
.bottom
);
882 if (leftEnd
.bottom
< rightStart
.top
)
883 add(pl
, leftEnd
.bottom
, null, rightStart
.top
);
886 removeChildrenAndAdd(display
.selectionDiv
, fragment
);
887 display
.selectionDiv
.style
.display
= "";
891 function restartBlink(cm
) {
892 if (!cm
.state
.focused
) return;
893 var display
= cm
.display
;
894 clearInterval(display
.blinker
);
896 display
.cursor
.style
.visibility
= display
.otherCursor
.style
.visibility
= "";
897 if (cm
.options
.cursorBlinkRate
> 0)
898 display
.blinker
= setInterval(function() {
899 display
.cursor
.style
.visibility
= display
.otherCursor
.style
.visibility
= (on
= !on
) ? "" : "hidden";
900 }, cm
.options
.cursorBlinkRate
);
905 function startWorker(cm
, time
) {
906 if (cm
.doc
.mode
.startState
&& cm
.doc
.frontier
< cm
.display
.showingTo
)
907 cm
.state
.highlight
.set(time
, bind(highlightWorker
, cm
));
910 function highlightWorker(cm
) {
912 if (doc
.frontier
< doc
.first
) doc
.frontier
= doc
.first
;
913 if (doc
.frontier
>= cm
.display
.showingTo
) return;
914 var end
= +new Date
+ cm
.options
.workTime
;
915 var state
= copyState(doc
.mode
, getStateBefore(cm
, doc
.frontier
));
916 var changed
= [], prevChange
;
917 doc
.iter(doc
.frontier
, Math
.min(doc
.first
+ doc
.size
, cm
.display
.showingTo
+ 500), function(line
) {
918 if (doc
.frontier
>= cm
.display
.showingFrom
) { // Visible
919 var oldStyles
= line
.styles
;
920 line
.styles
= highlightLine(cm
, line
, state
, true);
921 var ischange
= !oldStyles
|| oldStyles
.length
!= line
.styles
.length
;
922 for (var i
= 0; !ischange
&& i
< oldStyles
.length
; ++i
) ischange
= oldStyles
[i
] != line
.styles
[i
];
924 if (prevChange
&& prevChange
.end
== doc
.frontier
) prevChange
.end
++;
925 else changed
.push(prevChange
= {start
: doc
.frontier
, end
: doc
.frontier
+ 1});
927 line
.stateAfter
= copyState(doc
.mode
, state
);
929 processLine(cm
, line
.text
, state
);
930 line
.stateAfter
= doc
.frontier
% 5 == 0 ? copyState(doc
.mode
, state
) : null;
933 if (+new Date
> end
) {
934 startWorker(cm
, cm
.options
.workDelay
);
939 operation(cm
, function() {
940 for (var i
= 0; i
< changed
.length
; ++i
)
941 regChange(this, changed
[i
].start
, changed
[i
].end
);
945 // Finds the line to start with when starting a parse. Tries to
946 // find a line with a stateAfter, so that it can start with a
947 // valid state. If that fails, it returns the line with the
948 // smallest indentation, which tends to need the least context to
950 function findStartLine(cm
, n
, precise
) {
951 var minindent
, minline
, doc
= cm
.doc
;
952 var lim
= precise
? -1 : n
- (cm
.doc
.mode
.innerMode
? 1000 : 100);
953 for (var search
= n
; search
> lim
; --search
) {
954 if (search
<= doc
.first
) return doc
.first
;
955 var line
= getLine(doc
, search
- 1);
956 if (line
.stateAfter
&& (!precise
|| search
<= doc
.frontier
)) return search
;
957 var indented
= countColumn(line
.text
, null, cm
.options
.tabSize
);
958 if (minline
== null || minindent
> indented
) {
959 minline
= search
- 1;
960 minindent
= indented
;
966 function getStateBefore(cm
, n
, precise
) {
967 var doc
= cm
.doc
, display
= cm
.display
;
968 if (!doc
.mode
.startState
) return true;
969 var pos
= findStartLine(cm
, n
, precise
), state
= pos
> doc
.first
&& getLine(doc
, pos
-1).stateAfter
;
970 if (!state
) state
= startState(doc
.mode
);
971 else state
= copyState(doc
.mode
, state
);
972 doc
.iter(pos
, n
, function(line
) {
973 processLine(cm
, line
.text
, state
);
974 var save
= pos
== n
- 1 || pos
% 5 == 0 || pos
>= display
.showingFrom
&& pos
< display
.showingTo
;
975 line
.stateAfter
= save
? copyState(doc
.mode
, state
) : null;
978 if (precise
) doc
.frontier
= pos
;
982 // POSITION MEASUREMENT
984 function paddingTop(display
) {return display
.lineSpace
.offsetTop
;}
985 function paddingVert(display
) {return display
.mover
.offsetHeight
- display
.lineSpace
.offsetHeight
;}
986 function paddingLeft(display
) {
987 var e
= removeChildrenAndAdd(display
.measure
, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x"));
991 function measureChar(cm
, line
, ch
, data
, bias
) {
993 data
= data
|| measureLine(cm
, line
);
995 var left
= data
.left
+ ch
* data
.width
;
996 return {left
: left
, right
: left
+ data
.width
, top
: data
.top
, bottom
: data
.bottom
};
999 for (var pos
= ch
;; pos
+= dir
) {
1002 if (dir
< 0 && pos
== 0) dir
= 1;
1004 bias
= pos
> ch
? "left" : pos
< ch
? "right" : bias
;
1005 if (bias
== "left" && r
.leftSide
) r
= r
.leftSide
;
1006 else if (bias
== "right" && r
.rightSide
) r
= r
.rightSide
;
1007 return {left
: pos
< ch
? r
.right
: r
.left
,
1008 right
: pos
> ch
? r
.left
: r
.right
,
1013 function findCachedMeasurement(cm
, line
) {
1014 var cache
= cm
.display
.measureLineCache
;
1015 for (var i
= 0; i
< cache
.length
; ++i
) {
1016 var memo
= cache
[i
];
1017 if (memo
.text
== line
.text
&& memo
.markedSpans
== line
.markedSpans
&&
1018 cm
.display
.scroller
.clientWidth
== memo
.width
&&
1019 memo
.classes
== line
.textClass
+ "|" + line
.wrapClass
)
1024 function clearCachedMeasurement(cm
, line
) {
1025 var exists
= findCachedMeasurement(cm
, line
);
1026 if (exists
) exists
.text
= exists
.measure
= exists
.markedSpans
= null;
1029 function measureLine(cm
, line
) {
1030 // First look in the cache
1031 var cached
= findCachedMeasurement(cm
, line
);
1032 if (cached
) return cached
.measure
;
1034 // Failing that, recompute and store result in cache
1035 var measure
= measureLineInner(cm
, line
);
1036 var cache
= cm
.display
.measureLineCache
;
1037 var memo
= {text
: line
.text
, width
: cm
.display
.scroller
.clientWidth
,
1038 markedSpans
: line
.markedSpans
, measure
: measure
,
1039 classes
: line
.textClass
+ "|" + line
.wrapClass
};
1040 if (cache
.length
== 16) cache
[++cm
.display
.measureLineCachePos
% 16] = memo
;
1041 else cache
.push(memo
);
1045 function measureLineInner(cm
, line
) {
1046 if (!cm
.options
.lineWrapping
&& line
.text
.length
>= cm
.options
.crudeMeasuringFrom
)
1047 return crudelyMeasureLine(cm
, line
);
1049 var display
= cm
.display
, measure
= emptyArray(line
.text
.length
);
1050 var pre
= buildLineContent(cm
, line
, measure
, true).pre
;
1052 // IE does not cache element positions of inline elements between
1053 // calls to getBoundingClientRect. This makes the loop below,
1054 // which gathers the positions of all the characters on the line,
1055 // do an amount of layout work quadratic to the number of
1056 // characters. When line wrapping is off, we try to improve things
1057 // by first subdividing the line into a bunch of inline blocks, so
1058 // that IE can reuse most of the layout information from caches
1059 // for those blocks. This does interfere with line wrapping, so it
1060 // doesn't work when wrapping is on, but in that case the
1061 // situation is slightly better, since IE does cache line-wrapping
1062 // information and only recomputes per-line.
1063 if (old_ie
&& !ie_lt8
&& !cm
.options
.lineWrapping
&& pre
.childNodes
.length
> 100) {
1064 var fragment
= document
.createDocumentFragment();
1065 var chunk
= 10, n
= pre
.childNodes
.length
;
1066 for (var i
= 0, chunks
= Math
.ceil(n
/ chunk
); i
< chunks
; ++i
) {
1067 var wrap
= elt("div", null, null, "display: inline-block");
1068 for (var j
= 0; j
< chunk
&& n
; ++j
) {
1069 wrap
.appendChild(pre
.firstChild
);
1072 fragment
.appendChild(wrap
);
1074 pre
.appendChild(fragment
);
1077 removeChildrenAndAdd(display
.measure
, pre
);
1079 var outer
= getRect(display
.lineDiv
);
1080 var vranges
= [], data
= emptyArray(line
.text
.length
), maxBot
= pre
.offsetHeight
;
1081 // Work around an IE7/8 bug where it will sometimes have randomly
1082 // replaced our pre with a clone at this point.
1083 if (ie_lt9
&& display
.measure
.first
!= pre
)
1084 removeChildrenAndAdd(display
.measure
, pre
);
1086 function measureRect(rect
) {
1087 var top
= rect
.top
- outer
.top
, bot
= rect
.bottom
- outer
.top
;
1088 if (bot
> maxBot
) bot
= maxBot
;
1089 if (top
< 0) top
= 0;
1090 for (var i
= vranges
.length
- 2; i
>= 0; i
-= 2) {
1091 var rtop
= vranges
[i
], rbot
= vranges
[i
+1];
1092 if (rtop
> bot
|| rbot
< top
) continue;
1093 if (rtop
<= top
&& rbot
>= bot
||
1094 top
<= rtop
&& bot
>= rbot
||
1095 Math
.min(bot
, rbot
) - Math
.max(top
, rtop
) >= (bot
- top
) >> 1) {
1096 vranges
[i
] = Math
.min(top
, rtop
);
1097 vranges
[i
+1] = Math
.max(bot
, rbot
);
1101 if (i
< 0) { i
= vranges
.length
; vranges
.push(top
, bot
); }
1102 return {left
: rect
.left
- outer
.left
,
1103 right
: rect
.right
- outer
.left
,
1104 top
: i
, bottom
: null};
1106 function finishRect(rect
) {
1107 rect
.bottom
= vranges
[rect
.top
+1];
1108 rect
.top
= vranges
[rect
.top
];
1111 for (var i
= 0, cur
; i
< measure
.length
; ++i
) if (cur
= measure
[i
]) {
1112 var node
= cur
, rect
= null;
1113 // A widget might wrap, needs special care
1114 if (/\bCodeMirror-widget\b/.test(cur
.className
) && cur
.getClientRects
) {
1115 if (cur
.firstChild
.nodeType
== 1) node
= cur
.firstChild
;
1116 var rects
= node
.getClientRects();
1117 if (rects
.length
> 1) {
1118 rect
= data
[i
] = measureRect(rects
[0]);
1119 rect
.rightSide
= measureRect(rects
[rects
.length
- 1]);
1122 if (!rect
) rect
= data
[i
] = measureRect(getRect(node
));
1123 if (cur
.measureRight
) rect
.right
= getRect(cur
.measureRight
).left
- outer
.left
;
1124 if (cur
.leftSide
) rect
.leftSide
= measureRect(getRect(cur
.leftSide
));
1126 removeChildren(cm
.display
.measure
);
1127 for (var i
= 0, cur
; i
< data
.length
; ++i
) if (cur
= data
[i
]) {
1129 if (cur
.leftSide
) finishRect(cur
.leftSide
);
1130 if (cur
.rightSide
) finishRect(cur
.rightSide
);
1135 function crudelyMeasureLine(cm
, line
) {
1136 var copy
= new Line(line
.text
.slice(0, 100), null);
1137 if (line
.textClass
) copy
.textClass
= line
.textClass
;
1138 var measure
= measureLineInner(cm
, copy
);
1139 var left
= measureChar(cm
, copy
, 0, measure
, "left");
1140 var right
= measureChar(cm
, copy
, 99, measure
, "right");
1141 return {crude
: true, top
: left
.top
, left
: left
.left
, bottom
: left
.bottom
, width
: (right
.right
- left
.left
) / 100};
1144 function measureLineWidth(cm
, line
) {
1145 var hasBadSpan
= false;
1146 if (line
.markedSpans
) for (var i
= 0; i
< line
.markedSpans
; ++i
) {
1147 var sp
= line
.markedSpans
[i
];
1148 if (sp
.collapsed
&& (sp
.to
== null || sp
.to
== line
.text
.length
)) hasBadSpan
= true;
1150 var cached
= !hasBadSpan
&& findCachedMeasurement(cm
, line
);
1151 if (cached
|| line
.text
.length
>= cm
.options
.crudeMeasuringFrom
)
1152 return measureChar(cm
, line
, line
.text
.length
, cached
&& cached
.measure
, "right").right
;
1154 var pre
= buildLineContent(cm
, line
, null, true).pre
;
1155 var end
= pre
.appendChild(zeroWidthElement(cm
.display
.measure
));
1156 removeChildrenAndAdd(cm
.display
.measure
, pre
);
1157 return getRect(end
).right
- getRect(cm
.display
.lineDiv
).left
;
1160 function clearCaches(cm
) {
1161 cm
.display
.measureLineCache
.length
= cm
.display
.measureLineCachePos
= 0;
1162 cm
.display
.cachedCharWidth
= cm
.display
.cachedTextHeight
= null;
1163 if (!cm
.options
.lineWrapping
) cm
.display
.maxLineChanged
= true;
1164 cm
.display
.lineNumChars
= null;
1167 function pageScrollX() { return window
.pageXOffset
|| (document
.documentElement
|| document
.body
).scrollLeft
; }
1168 function pageScrollY() { return window
.pageYOffset
|| (document
.documentElement
|| document
.body
).scrollTop
; }
1170 // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
1171 function intoCoordSystem(cm
, lineObj
, rect
, context
) {
1172 if (lineObj
.widgets
) for (var i
= 0; i
< lineObj
.widgets
.length
; ++i
) if (lineObj
.widgets
[i
].above
) {
1173 var size
= widgetHeight(lineObj
.widgets
[i
]);
1174 rect
.top
+= size
; rect
.bottom
+= size
;
1176 if (context
== "line") return rect
;
1177 if (!context
) context
= "local";
1178 var yOff
= heightAtLine(cm
, lineObj
);
1179 if (context
== "local") yOff
+= paddingTop(cm
.display
);
1180 else yOff
-= cm
.display
.viewOffset
;
1181 if (context
== "page" || context
== "window") {
1182 var lOff
= getRect(cm
.display
.lineSpace
);
1183 yOff
+= lOff
.top
+ (context
== "window" ? 0 : pageScrollY());
1184 var xOff
= lOff
.left
+ (context
== "window" ? 0 : pageScrollX());
1185 rect
.left
+= xOff
; rect
.right
+= xOff
;
1187 rect
.top
+= yOff
; rect
.bottom
+= yOff
;
1191 // Context may be "window", "page", "div", or "local"/null
1192 // Result is in "div" coords
1193 function fromCoordSystem(cm
, coords
, context
) {
1194 if (context
== "div") return coords
;
1195 var left
= coords
.left
, top
= coords
.top
;
1196 // First move into "page" coordinate system
1197 if (context
== "page") {
1198 left
-= pageScrollX();
1199 top
-= pageScrollY();
1200 } else if (context
== "local" || !context
) {
1201 var localBox
= getRect(cm
.display
.sizer
);
1202 left
+= localBox
.left
;
1203 top
+= localBox
.top
;
1206 var lineSpaceBox
= getRect(cm
.display
.lineSpace
);
1207 return {left
: left
- lineSpaceBox
.left
, top
: top
- lineSpaceBox
.top
};
1210 function charCoords(cm
, pos
, context
, lineObj
, bias
) {
1211 if (!lineObj
) lineObj
= getLine(cm
.doc
, pos
.line
);
1212 return intoCoordSystem(cm
, lineObj
, measureChar(cm
, lineObj
, pos
.ch
, null, bias
), context
);
1215 function cursorCoords(cm
, pos
, context
, lineObj
, measurement
) {
1216 lineObj
= lineObj
|| getLine(cm
.doc
, pos
.line
);
1217 if (!measurement
) measurement
= measureLine(cm
, lineObj
);
1218 function get(ch
, right
) {
1219 var m
= measureChar(cm
, lineObj
, ch
, measurement
, right
? "right" : "left");
1220 if (right
) m
.left
= m
.right
; else m
.right
= m
.left
;
1221 return intoCoordSystem(cm
, lineObj
, m
, context
);
1223 function getBidi(ch
, partPos
) {
1224 var part
= order
[partPos
], right
= part
.level
% 2;
1225 if (ch
== bidiLeft(part
) && partPos
&& part
.level
< order
[partPos
- 1].level
) {
1226 part
= order
[--partPos
];
1227 ch
= bidiRight(part
) - (part
.level
% 2 ? 0 : 1);
1229 } else if (ch
== bidiRight(part
) && partPos
< order
.length
- 1 && part
.level
< order
[partPos
+ 1].level
) {
1230 part
= order
[++partPos
];
1231 ch
= bidiLeft(part
) - part
.level
% 2;
1234 if (right
&& ch
== part
.to
&& ch
> part
.from) return get(ch
- 1);
1235 return get(ch
, right
);
1237 var order
= getOrder(lineObj
), ch
= pos
.ch
;
1238 if (!order
) return get(ch
);
1239 var partPos
= getBidiPartAt(order
, ch
);
1240 var val
= getBidi(ch
, partPos
);
1241 if (bidiOther
!= null) val
.other
= getBidi(ch
, bidiOther
);
1245 function PosWithInfo(line
, ch
, outside
, xRel
) {
1246 var pos
= new Pos(line
, ch
);
1248 if (outside
) pos
.outside
= true;
1252 // Coords must be lineSpace-local
1253 function coordsChar(cm
, x
, y
) {
1255 y
+= cm
.display
.viewOffset
;
1256 if (y
< 0) return PosWithInfo(doc
.first
, 0, true, -1);
1257 var lineNo
= lineAtHeight(doc
, y
), last
= doc
.first
+ doc
.size
- 1;
1259 return PosWithInfo(doc
.first
+ doc
.size
- 1, getLine(doc
, last
).text
.length
, true, 1);
1263 var lineObj
= getLine(doc
, lineNo
);
1264 var found
= coordsCharInner(cm
, lineObj
, lineNo
, x
, y
);
1265 var merged
= collapsedSpanAtEnd(lineObj
);
1266 var mergedPos
= merged
&& merged
.find();
1267 if (merged
&& (found
.ch
> mergedPos
.from.ch
|| found
.ch
== mergedPos
.from.ch
&& found
.xRel
> 0))
1268 lineNo
= mergedPos
.to
.line
;
1274 function coordsCharInner(cm
, lineObj
, lineNo
, x
, y
) {
1275 var innerOff
= y
- heightAtLine(cm
, lineObj
);
1276 var wrongLine
= false, adjust
= 2 * cm
.display
.wrapper
.clientWidth
;
1277 var measurement
= measureLine(cm
, lineObj
);
1280 var sp
= cursorCoords(cm
, Pos(lineNo
, ch
), "line",
1281 lineObj
, measurement
);
1283 if (innerOff
> sp
.bottom
) return sp
.left
- adjust
;
1284 else if (innerOff
< sp
.top
) return sp
.left
+ adjust
;
1285 else wrongLine
= false;
1289 var bidi
= getOrder(lineObj
), dist
= lineObj
.text
.length
;
1290 var from = lineLeft(lineObj
), to
= lineRight(lineObj
);
1291 var fromX
= getX(from), fromOutside
= wrongLine
, toX
= getX(to
), toOutside
= wrongLine
;
1293 if (x
> toX
) return PosWithInfo(lineNo
, to
, toOutside
, 1);
1294 // Do a binary search between these bounds.
1296 if (bidi
? to
== from || to
== moveVisually(lineObj
, from, 1) : to
- from <= 1) {
1297 var ch
= x
< fromX
|| x
- fromX
<= toX
- x
? from : to
;
1298 var xDiff
= x
- (ch
== from ? fromX
: toX
);
1299 while (isExtendingChar(lineObj
.text
.charAt(ch
))) ++ch
;
1300 var pos
= PosWithInfo(lineNo
, ch
, ch
== from ? fromOutside
: toOutside
,
1301 xDiff
< 0 ? -1 : xDiff
? 1 : 0);
1304 var step
= Math
.ceil(dist
/ 2), middle
= from + step
;
1307 for (var i
= 0; i
< step
; ++i
) middle
= moveVisually(lineObj
, middle
, 1);
1309 var middleX
= getX(middle
);
1310 if (middleX
> x
) {to
= middle
; toX
= middleX
; if (toOutside
= wrongLine
) toX
+= 1000; dist
= step
;}
1311 else {from = middle
; fromX
= middleX
; fromOutside
= wrongLine
; dist
-= step
;}
1316 function textHeight(display
) {
1317 if (display
.cachedTextHeight
!= null) return display
.cachedTextHeight
;
1318 if (measureText
== null) {
1319 measureText
= elt("pre");
1320 // Measure a bunch of lines, for browsers that compute
1321 // fractional heights.
1322 for (var i
= 0; i
< 49; ++i
) {
1323 measureText
.appendChild(document
.createTextNode("x"));
1324 measureText
.appendChild(elt("br"));
1326 measureText
.appendChild(document
.createTextNode("x"));
1328 removeChildrenAndAdd(display
.measure
, measureText
);
1329 var height
= measureText
.offsetHeight
/ 50;
1330 if (height
> 3) display
.cachedTextHeight
= height
;
1331 removeChildren(display
.measure
);
1335 function charWidth(display
) {
1336 if (display
.cachedCharWidth
!= null) return display
.cachedCharWidth
;
1337 var anchor
= elt("span", "x");
1338 var pre
= elt("pre", [anchor
]);
1339 removeChildrenAndAdd(display
.measure
, pre
);
1340 var width
= anchor
.offsetWidth
;
1341 if (width
> 2) display
.cachedCharWidth
= width
;
1347 // Operations are used to wrap changes in such a way that each
1348 // change won't have to update the cursor and display (which would
1349 // be awkward, slow, and error-prone), but instead updates are
1350 // batched and then all combined and executed at once.
1353 function startOperation(cm
) {
1355 // An array of ranges of lines that have to be updated. See
1360 userSelChange
: null,
1362 selectionChanged
: false,
1363 cursorActivity
: false,
1364 updateMaxLine
: false,
1365 updateScrollPos
: false,
1368 if (!delayedCallbackDepth
++) delayedCallbacks
= [];
1371 function endOperation(cm
) {
1372 var op
= cm
.curOp
, doc
= cm
.doc
, display
= cm
.display
;
1375 if (op
.updateMaxLine
) computeMaxLength(cm
);
1376 if (display
.maxLineChanged
&& !cm
.options
.lineWrapping
&& display
.maxLine
) {
1377 var width
= measureLineWidth(cm
, display
.maxLine
);
1378 display
.sizer
.style
.minWidth
= Math
.max(0, width
+ 3 + scrollerCutOff
) + "px";
1379 display
.maxLineChanged
= false;
1380 var maxScrollLeft
= Math
.max(0, display
.sizer
.offsetLeft
+ display
.sizer
.offsetWidth
- display
.scroller
.clientWidth
);
1381 if (maxScrollLeft
< doc
.scrollLeft
&& !op
.updateScrollPos
)
1382 setScrollLeft(cm
, Math
.min(display
.scroller
.scrollLeft
, maxScrollLeft
), true);
1384 var newScrollPos
, updated
;
1385 if (op
.updateScrollPos
) {
1386 newScrollPos
= op
.updateScrollPos
;
1387 } else if (op
.selectionChanged
&& display
.scroller
.clientHeight
) { // don't rescroll if not visible
1388 var coords
= cursorCoords(cm
, doc
.sel
.head
);
1389 newScrollPos
= calculateScrollPos(cm
, coords
.left
, coords
.top
, coords
.left
, coords
.bottom
);
1391 if (op
.changes
.length
|| op
.forceUpdate
|| newScrollPos
&& newScrollPos
.scrollTop
!= null) {
1392 updated
= updateDisplay(cm
, op
.changes
, newScrollPos
&& newScrollPos
.scrollTop
, op
.forceUpdate
);
1393 if (cm
.display
.scroller
.offsetHeight
) cm
.doc
.scrollTop
= cm
.display
.scroller
.scrollTop
;
1395 if (!updated
&& op
.selectionChanged
) updateSelection(cm
);
1396 if (op
.updateScrollPos
) {
1397 var top
= Math
.max(0, Math
.min(display
.scroller
.scrollHeight
- display
.scroller
.clientHeight
, newScrollPos
.scrollTop
));
1398 var left
= Math
.max(0, Math
.min(display
.scroller
.scrollWidth
- display
.scroller
.clientWidth
, newScrollPos
.scrollLeft
));
1399 display
.scroller
.scrollTop
= display
.scrollbarV
.scrollTop
= doc
.scrollTop
= top
;
1400 display
.scroller
.scrollLeft
= display
.scrollbarH
.scrollLeft
= doc
.scrollLeft
= left
;
1401 alignHorizontally(cm
);
1403 scrollPosIntoView(cm
, clipPos(cm
.doc
, op
.scrollToPos
.from),
1404 clipPos(cm
.doc
, op
.scrollToPos
.to
), op
.scrollToPos
.margin
);
1405 } else if (newScrollPos
) {
1406 scrollCursorIntoView(cm
);
1408 if (op
.selectionChanged
) restartBlink(cm
);
1410 if (cm
.state
.focused
&& op
.updateInput
)
1411 resetInput(cm
, op
.userSelChange
);
1413 var hidden
= op
.maybeHiddenMarkers
, unhidden
= op
.maybeUnhiddenMarkers
;
1414 if (hidden
) for (var i
= 0; i
< hidden
.length
; ++i
)
1415 if (!hidden
[i
].lines
.length
) signal(hidden
[i
], "hide");
1416 if (unhidden
) for (var i
= 0; i
< unhidden
.length
; ++i
)
1417 if (unhidden
[i
].lines
.length
) signal(unhidden
[i
], "unhide");
1420 if (!--delayedCallbackDepth
) {
1421 delayed
= delayedCallbacks
;
1422 delayedCallbacks
= null;
1425 signal(cm
, "change", cm
, op
.textChanged
);
1426 if (op
.cursorActivity
) signal(cm
, "cursorActivity", cm
);
1427 if (delayed
) for (var i
= 0; i
< delayed
.length
; ++i
) delayed
[i
]();
1430 // Wraps a function in an operation. Returns the wrapped function.
1431 function operation(cm1
, f
) {
1433 var cm
= cm1
|| this, withOp
= !cm
.curOp
;
1434 if (withOp
) startOperation(cm
);
1435 try { var result
= f
.apply(cm
, arguments
); }
1436 finally { if (withOp
) endOperation(cm
); }
1440 function docOperation(f
) {
1442 var withOp
= this.cm
&& !this.cm
.curOp
, result
;
1443 if (withOp
) startOperation(this.cm
);
1444 try { result
= f
.apply(this, arguments
); }
1445 finally { if (withOp
) endOperation(this.cm
); }
1449 function runInOp(cm
, f
) {
1450 var withOp
= !cm
.curOp
, result
;
1451 if (withOp
) startOperation(cm
);
1452 try { result
= f(); }
1453 finally { if (withOp
) endOperation(cm
); }
1457 function regChange(cm
, from, to
, lendiff
) {
1458 if (from == null) from = cm
.doc
.first
;
1459 if (to
== null) to
= cm
.doc
.first
+ cm
.doc
.size
;
1460 cm
.curOp
.changes
.push({from: from, to
: to
, diff
: lendiff
});
1465 function slowPoll(cm
) {
1466 if (cm
.display
.pollingFast
) return;
1467 cm
.display
.poll
.set(cm
.options
.pollInterval
, function() {
1469 if (cm
.state
.focused
) slowPoll(cm
);
1473 function fastPoll(cm
) {
1475 cm
.display
.pollingFast
= true;
1477 var changed
= readInput(cm
);
1478 if (!changed
&& !missed
) {missed
= true; cm
.display
.poll
.set(60, p
);}
1479 else {cm
.display
.pollingFast
= false; slowPoll(cm
);}
1481 cm
.display
.poll
.set(20, p
);
1484 // prevInput is a hack to work with IME. If we reset the textarea
1485 // on every change, that breaks IME. So we look for changes
1486 // compared to the previous content instead. (Modern browsers have
1487 // events that indicate IME taking place, but these are not widely
1488 // supported or compatible enough yet to rely on.)
1489 function readInput(cm
) {
1490 var input
= cm
.display
.input
, prevInput
= cm
.display
.prevInput
, doc
= cm
.doc
, sel
= doc
.sel
;
1491 if (!cm
.state
.focused
|| hasSelection(input
) || isReadOnly(cm
) || cm
.options
.disableInput
) return false;
1492 if (cm
.state
.pasteIncoming
&& cm
.state
.fakedLastChar
) {
1493 input
.value
= input
.value
.substring(0, input
.value
.length
- 1);
1494 cm
.state
.fakedLastChar
= false;
1496 var text
= input
.value
;
1497 if (text
== prevInput
&& posEq(sel
.from, sel
.to
)) return false;
1498 if (ie
&& !ie_lt9
&& cm
.display
.inputHasSelection
=== text
) {
1499 resetInput(cm
, true);
1503 var withOp
= !cm
.curOp
;
1504 if (withOp
) startOperation(cm
);
1506 var same
= 0, l
= Math
.min(prevInput
.length
, text
.length
);
1507 while (same
< l
&& prevInput
.charCodeAt(same
) == text
.charCodeAt(same
)) ++same
;
1508 var from = sel
.from, to
= sel
.to
;
1509 var inserted
= text
.slice(same
);
1510 if (same
< prevInput
.length
)
1511 from = Pos(from.line
, from.ch
- (prevInput
.length
- same
));
1512 else if (cm
.state
.overwrite
&& posEq(from, to
) && !cm
.state
.pasteIncoming
)
1513 to
= Pos(to
.line
, Math
.min(getLine(doc
, to
.line
).text
.length
, to
.ch
+ inserted
.length
));
1515 var updateInput
= cm
.curOp
.updateInput
;
1516 var changeEvent
= {from: from, to
: to
, text
: splitLines(inserted
),
1517 origin
: cm
.state
.pasteIncoming
? "paste" : cm
.state
.cutIncoming
? "cut" : "+input"};
1518 makeChange(cm
.doc
, changeEvent
, "end");
1519 cm
.curOp
.updateInput
= updateInput
;
1520 signalLater(cm
, "inputRead", cm
, changeEvent
);
1521 if (inserted
&& !cm
.state
.pasteIncoming
&& cm
.options
.electricChars
&&
1522 cm
.options
.smartIndent
&& sel
.head
.ch
< 100) {
1523 var electric
= cm
.getModeAt(sel
.head
).electricChars
;
1524 if (electric
) for (var i
= 0; i
< electric
.length
; i
++)
1525 if (inserted
.indexOf(electric
.charAt(i
)) > -1) {
1526 indentLine(cm
, sel
.head
.line
, "smart");
1531 if (text
.length
> 1000 || text
.indexOf("\n") > -1) input
.value
= cm
.display
.prevInput
= "";
1532 else cm
.display
.prevInput
= text
;
1533 if (withOp
) endOperation(cm
);
1534 cm
.state
.pasteIncoming
= cm
.state
.cutIncoming
= false;
1538 function resetInput(cm
, user
) {
1539 var minimal
, selected
, doc
= cm
.doc
;
1540 if (!posEq(doc
.sel
.from, doc
.sel
.to
)) {
1541 cm
.display
.prevInput
= "";
1542 minimal
= hasCopyEvent
&&
1543 (doc
.sel
.to
.line
- doc
.sel
.from.line
> 100 || (selected
= cm
.getSelection()).length
> 1000);
1544 var content
= minimal
? "-" : selected
|| cm
.getSelection();
1545 cm
.display
.input
.value
= content
;
1546 if (cm
.state
.focused
) selectInput(cm
.display
.input
);
1547 if (ie
&& !ie_lt9
) cm
.display
.inputHasSelection
= content
;
1549 cm
.display
.prevInput
= cm
.display
.input
.value
= "";
1550 if (ie
&& !ie_lt9
) cm
.display
.inputHasSelection
= null;
1552 cm
.display
.inaccurateSelection
= minimal
;
1555 function focusInput(cm
) {
1556 if (cm
.options
.readOnly
!= "nocursor" && (!mobile
|| document
.activeElement
!= cm
.display
.input
))
1557 cm
.display
.input
.focus();
1560 function isReadOnly(cm
) {
1561 return cm
.options
.readOnly
|| cm
.doc
.cantEdit
;
1566 function registerEventHandlers(cm
) {
1568 on(d
.scroller
, "mousedown", operation(cm
, onMouseDown
));
1570 on(d
.scroller
, "dblclick", operation(cm
, function(e
) {
1571 if (signalDOMEvent(cm
, e
)) return;
1572 var pos
= posFromMouse(cm
, e
);
1573 if (!pos
|| clickInGutter(cm
, e
) || eventInWidget(cm
.display
, e
)) return;
1574 e_preventDefault(e
);
1575 var word
= findWordAt(getLine(cm
.doc
, pos
.line
).text
, pos
);
1576 extendSelection(cm
.doc
, word
.from, word
.to
);
1579 on(d
.scroller
, "dblclick", function(e
) { signalDOMEvent(cm
, e
) || e_preventDefault(e
); });
1580 on(d
.lineSpace
, "selectstart", function(e
) {
1581 if (!eventInWidget(d
, e
)) e_preventDefault(e
);
1583 // Gecko browsers fire contextmenu *after* opening the menu, at
1584 // which point we can't mess with it anymore. Context menu is
1585 // handled in onMouseDown for Gecko.
1586 if (!captureMiddleClick
) on(d
.scroller
, "contextmenu", function(e
) {onContextMenu(cm
, e
);});
1588 on(d
.scroller
, "scroll", function() {
1589 if (d
.scroller
.clientHeight
) {
1590 setScrollTop(cm
, d
.scroller
.scrollTop
);
1591 setScrollLeft(cm
, d
.scroller
.scrollLeft
, true);
1592 signal(cm
, "scroll", cm
);
1595 on(d
.scrollbarV
, "scroll", function() {
1596 if (d
.scroller
.clientHeight
) setScrollTop(cm
, d
.scrollbarV
.scrollTop
);
1598 on(d
.scrollbarH
, "scroll", function() {
1599 if (d
.scroller
.clientHeight
) setScrollLeft(cm
, d
.scrollbarH
.scrollLeft
);
1602 on(d
.scroller
, "mousewheel", function(e
){onScrollWheel(cm
, e
);});
1603 on(d
.scroller
, "DOMMouseScroll", function(e
){onScrollWheel(cm
, e
);});
1605 function reFocus() { if (cm
.state
.focused
) setTimeout(bind(focusInput
, cm
), 0); }
1606 on(d
.scrollbarH
, "mousedown", reFocus
);
1607 on(d
.scrollbarV
, "mousedown", reFocus
);
1608 // Prevent wrapper from ever scrolling
1609 on(d
.wrapper
, "scroll", function() { d
.wrapper
.scrollTop
= d
.wrapper
.scrollLeft
= 0; });
1612 function onResize() {
1613 if (resizeTimer
== null) resizeTimer
= setTimeout(function() {
1615 // Might be a text scaling operation, clear size caches.
1616 d
.cachedCharWidth
= d
.cachedTextHeight
= knownScrollbarWidth
= null;
1618 runInOp(cm
, bind(regChange
, cm
));
1621 on(window
, "resize", onResize
);
1622 // Above handler holds on to the editor and its data structures.
1623 // Here we poll to unregister it when the editor is no longer in
1624 // the document, so that it can be garbage-collected.
1625 function unregister() {
1626 for (var p
= d
.wrapper
.parentNode
; p
&& p
!= document
.body
; p
= p
.parentNode
) {}
1627 if (p
) setTimeout(unregister
, 5000);
1628 else off(window
, "resize", onResize
);
1630 setTimeout(unregister
, 5000);
1632 on(d
.input
, "keyup", operation(cm
, onKeyUp
));
1633 on(d
.input
, "input", function() {
1634 if (ie
&& !ie_lt9
&& cm
.display
.inputHasSelection
) cm
.display
.inputHasSelection
= null;
1637 on(d
.input
, "keydown", operation(cm
, onKeyDown
));
1638 on(d
.input
, "keypress", operation(cm
, onKeyPress
));
1639 on(d
.input
, "focus", bind(onFocus
, cm
));
1640 on(d
.input
, "blur", bind(onBlur
, cm
));
1643 if (signalDOMEvent(cm
, e
) || cm
.options
.onDragEvent
&& cm
.options
.onDragEvent(cm
, addStop(e
))) return;
1646 if (cm
.options
.dragDrop
) {
1647 on(d
.scroller
, "dragstart", function(e
){onDragStart(cm
, e
);});
1648 on(d
.scroller
, "dragenter", drag_
);
1649 on(d
.scroller
, "dragover", drag_
);
1650 on(d
.scroller
, "drop", operation(cm
, onDrop
));
1652 on(d
.scroller
, "paste", function(e
) {
1653 if (eventInWidget(d
, e
)) return;
1657 on(d
.input
, "paste", function() {
1658 // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
1659 // Add a char to the end of textarea before paste occur so that
1660 // selection doesn't span to the end of textarea.
1661 if (webkit
&& !cm
.state
.fakedLastChar
&& !(new Date
- cm
.state
.lastMiddleDown
< 200)) {
1662 var start
= d
.input
.selectionStart
, end
= d
.input
.selectionEnd
;
1663 d
.input
.value
+= "$";
1664 d
.input
.selectionStart
= start
;
1665 d
.input
.selectionEnd
= end
;
1666 cm
.state
.fakedLastChar
= true;
1668 cm
.state
.pasteIncoming
= true;
1672 function prepareCopy(e
) {
1673 if (d
.inaccurateSelection
) {
1675 d
.inaccurateSelection
= false;
1676 d
.input
.value
= cm
.getSelection();
1677 selectInput(d
.input
);
1679 if (e
.type
== "cut") cm
.state
.cutIncoming
= true;
1681 on(d
.input
, "cut", prepareCopy
);
1682 on(d
.input
, "copy", prepareCopy
);
1684 // Needed to handle Tab key in KHTML
1685 if (khtml
) on(d
.sizer
, "mouseup", function() {
1686 if (document
.activeElement
== d
.input
) d
.input
.blur();
1691 function eventInWidget(display
, e
) {
1692 for (var n
= e_target(e
); n
!= display
.wrapper
; n
= n
.parentNode
) {
1693 if (!n
|| n
.ignoreEvents
|| n
.parentNode
== display
.sizer
&& n
!= display
.mover
) return true;
1697 function posFromMouse(cm
, e
, liberal
) {
1698 var display
= cm
.display
;
1700 var target
= e_target(e
);
1701 if (target
== display
.scrollbarH
|| target
== display
.scrollbarH
.firstChild
||
1702 target
== display
.scrollbarV
|| target
== display
.scrollbarV
.firstChild
||
1703 target
== display
.scrollbarFiller
|| target
== display
.gutterFiller
) return null;
1705 var x
, y
, space
= getRect(display
.lineSpace
);
1706 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1707 try { x
= e
.clientX
; y
= e
.clientY
; } catch (e
) { return null; }
1708 return coordsChar(cm
, x
- space
.left
, y
- space
.top
);
1711 var lastClick
, lastDoubleClick
;
1712 function onMouseDown(e
) {
1713 if (signalDOMEvent(this, e
)) return;
1714 var cm
= this, display
= cm
.display
, doc
= cm
.doc
, sel
= doc
.sel
;
1715 sel
.shift
= e
.shiftKey
;
1717 if (eventInWidget(display
, e
)) {
1719 display
.scroller
.draggable
= false;
1720 setTimeout(function(){display
.scroller
.draggable
= true;}, 100);
1724 if (clickInGutter(cm
, e
)) return;
1725 var start
= posFromMouse(cm
, e
);
1728 switch (e_button(e
)) {
1730 if (captureMiddleClick
) onContextMenu
.call(cm
, cm
, e
);
1733 if (webkit
) cm
.state
.lastMiddleDown
= +new Date
;
1734 if (start
) extendSelection(cm
.doc
, start
);
1735 setTimeout(bind(focusInput
, cm
), 20);
1736 e_preventDefault(e
);
1739 // For button 1, if it was clicked inside the editor
1740 // (posFromMouse returning non-null), we have to adjust the
1742 if (!start
) {if (e_target(e
) == display
.scroller
) e_preventDefault(e
); return;}
1744 if (!cm
.state
.focused
) onFocus(cm
);
1746 var now
= +new Date
, type
= "single";
1747 if (lastDoubleClick
&& lastDoubleClick
.time
> now
- 400 && posEq(lastDoubleClick
.pos
, start
)) {
1749 e_preventDefault(e
);
1750 setTimeout(bind(focusInput
, cm
), 20);
1751 selectLine(cm
, start
.line
);
1752 } else if (lastClick
&& lastClick
.time
> now
- 400 && posEq(lastClick
.pos
, start
)) {
1754 lastDoubleClick
= {time
: now
, pos
: start
};
1755 e_preventDefault(e
);
1756 var word
= findWordAt(getLine(doc
, start
.line
).text
, start
);
1757 extendSelection(cm
.doc
, word
.from, word
.to
);
1758 } else { lastClick
= {time
: now
, pos
: start
}; }
1761 if (cm
.options
.dragDrop
&& dragAndDrop
&& !isReadOnly(cm
) && !posEq(sel
.from, sel
.to
) &&
1762 !posLess(start
, sel
.from) && !posLess(sel
.to
, start
) && type
== "single") {
1763 var dragEnd
= operation(cm
, function(e2
) {
1764 if (webkit
) display
.scroller
.draggable
= false;
1765 cm
.state
.draggingText
= false;
1766 off(document
, "mouseup", dragEnd
);
1767 off(display
.scroller
, "drop", dragEnd
);
1768 if (Math
.abs(e
.clientX
- e2
.clientX
) + Math
.abs(e
.clientY
- e2
.clientY
) < 10) {
1769 e_preventDefault(e2
);
1770 extendSelection(cm
.doc
, start
);
1772 // Work around unexplainable focus problem in IE9 (#2127)
1773 if (old_ie
&& !ie_lt9
)
1774 setTimeout(function() {document
.body
.focus(); focusInput(cm
);}, 20);
1777 // Let the drag handler handle this.
1778 if (webkit
) display
.scroller
.draggable
= true;
1779 cm
.state
.draggingText
= dragEnd
;
1780 // IE's approach to draggable
1781 if (display
.scroller
.dragDrop
) display
.scroller
.dragDrop();
1782 on(document
, "mouseup", dragEnd
);
1783 on(display
.scroller
, "drop", dragEnd
);
1786 e_preventDefault(e
);
1787 if (type
== "single") extendSelection(cm
.doc
, clipPos(doc
, start
));
1789 var startstart
= sel
.from, startend
= sel
.to
, lastPos
= start
;
1791 function doSelect(cur
) {
1792 if (posEq(lastPos
, cur
)) return;
1795 if (type
== "single") {
1796 extendSelection(cm
.doc
, clipPos(doc
, start
), cur
);
1800 startstart
= clipPos(doc
, startstart
);
1801 startend
= clipPos(doc
, startend
);
1802 if (type
== "double") {
1803 var word
= findWordAt(getLine(doc
, cur
.line
).text
, cur
);
1804 if (posLess(cur
, startstart
)) extendSelection(cm
.doc
, word
.from, startend
);
1805 else extendSelection(cm
.doc
, startstart
, word
.to
);
1806 } else if (type
== "triple") {
1807 if (posLess(cur
, startstart
)) extendSelection(cm
.doc
, startend
, clipPos(doc
, Pos(cur
.line
, 0)));
1808 else extendSelection(cm
.doc
, startstart
, clipPos(doc
, Pos(cur
.line
+ 1, 0)));
1812 var editorSize
= getRect(display
.wrapper
);
1813 // Used to ensure timeout re-tries don't fire when another extend
1814 // happened in the meantime (clearTimeout isn't reliable -- at
1815 // least on Chrome, the timeouts still happen even when cleared,
1816 // if the clear happens after their scheduled firing time).
1819 function extend(e
) {
1820 var curCount
= ++counter
;
1821 var cur
= posFromMouse(cm
, e
, true);
1823 if (!posEq(cur
, last
)) {
1824 if (!cm
.state
.focused
) onFocus(cm
);
1827 var visible
= visibleLines(display
, doc
);
1828 if (cur
.line
>= visible
.to
|| cur
.line
< visible
.from)
1829 setTimeout(operation(cm
, function(){if (counter
== curCount
) extend(e
);}), 150);
1831 var outside
= e
.clientY
< editorSize
.top
? -20 : e
.clientY
> editorSize
.bottom
? 20 : 0;
1832 if (outside
) setTimeout(operation(cm
, function() {
1833 if (counter
!= curCount
) return;
1834 display
.scroller
.scrollTop
+= outside
;
1842 e_preventDefault(e
);
1844 off(document
, "mousemove", move);
1845 off(document
, "mouseup", up
);
1848 var move = operation(cm
, function(e
) {
1849 if (!old_ie
&& !e_button(e
)) done(e
);
1852 var up
= operation(cm
, done
);
1853 on(document
, "mousemove", move);
1854 on(document
, "mouseup", up
);
1857 function gutterEvent(cm
, e
, type
, prevent
, signalfn
) {
1858 try { var mX
= e
.clientX
, mY
= e
.clientY
; }
1859 catch(e
) { return false; }
1860 if (mX
>= Math
.floor(getRect(cm
.display
.gutters
).right
)) return false;
1861 if (prevent
) e_preventDefault(e
);
1863 var display
= cm
.display
;
1864 var lineBox
= getRect(display
.lineDiv
);
1866 if (mY
> lineBox
.bottom
|| !hasHandler(cm
, type
)) return e_defaultPrevented(e
);
1867 mY
-= lineBox
.top
- display
.viewOffset
;
1869 for (var i
= 0; i
< cm
.options
.gutters
.length
; ++i
) {
1870 var g
= display
.gutters
.childNodes
[i
];
1871 if (g
&& getRect(g
).right
>= mX
) {
1872 var line
= lineAtHeight(cm
.doc
, mY
);
1873 var gutter
= cm
.options
.gutters
[i
];
1874 signalfn(cm
, type
, cm
, line
, gutter
, e
);
1875 return e_defaultPrevented(e
);
1880 function contextMenuInGutter(cm
, e
) {
1881 if (!hasHandler(cm
, "gutterContextMenu")) return false;
1882 return gutterEvent(cm
, e
, "gutterContextMenu", false, signal
);
1885 function clickInGutter(cm
, e
) {
1886 return gutterEvent(cm
, e
, "gutterClick", true, signalLater
);
1889 // Kludge to work around strange IE behavior where it'll sometimes
1890 // re-fire a series of drag-related events right after the drop (#1551)
1893 function onDrop(e
) {
1895 if (signalDOMEvent(cm
, e
) || eventInWidget(cm
.display
, e
) || (cm
.options
.onDragEvent
&& cm
.options
.onDragEvent(cm
, addStop(e
))))
1897 e_preventDefault(e
);
1898 if (ie
) lastDrop
= +new Date
;
1899 var pos
= posFromMouse(cm
, e
, true), files
= e
.dataTransfer
.files
;
1900 if (!pos
|| isReadOnly(cm
)) return;
1901 if (files
&& files
.length
&& window
.FileReader
&& window
.File
) {
1902 var n
= files
.length
, text
= Array(n
), read
= 0;
1903 var loadFile = function(file
, i
) {
1904 var reader
= new FileReader
;
1905 reader
.onload = function() {
1906 text
[i
] = reader
.result
;
1908 pos
= clipPos(cm
.doc
, pos
);
1909 makeChange(cm
.doc
, {from: pos
, to
: pos
, text
: splitLines(text
.join("\n")), origin
: "paste"}, "around");
1912 reader
.readAsText(file
);
1914 for (var i
= 0; i
< n
; ++i
) loadFile(files
[i
], i
);
1916 // Don't do a replace if the drop happened inside of the selected text.
1917 if (cm
.state
.draggingText
&& !(posLess(pos
, cm
.doc
.sel
.from) || posLess(cm
.doc
.sel
.to
, pos
))) {
1918 cm
.state
.draggingText(e
);
1919 // Ensure the editor is re-focused
1920 setTimeout(bind(focusInput
, cm
), 20);
1924 var text
= e
.dataTransfer
.getData("Text");
1926 var curFrom
= cm
.doc
.sel
.from, curTo
= cm
.doc
.sel
.to
;
1927 setSelection(cm
.doc
, pos
, pos
);
1928 if (cm
.state
.draggingText
) replaceRange(cm
.doc
, "", curFrom
, curTo
, "paste");
1929 cm
.replaceSelection(text
, null, "paste");
1937 function onDragStart(cm
, e
) {
1938 if (ie
&& (!cm
.state
.draggingText
|| +new Date
- lastDrop
< 100)) { e_stop(e
); return; }
1939 if (signalDOMEvent(cm
, e
) || eventInWidget(cm
.display
, e
)) return;
1941 var txt
= cm
.getSelection();
1942 e
.dataTransfer
.setData("Text", txt
);
1944 // Use dummy image instead of default browsers image.
1945 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1946 if (e
.dataTransfer
.setDragImage
&& !safari
) {
1947 var img
= elt("img", null, null, "position: fixed; left: 0; top: 0;");
1948 img
.src
= "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
1950 img
.width
= img
.height
= 1;
1951 cm
.display
.wrapper
.appendChild(img
);
1952 // Force a relayout, or Opera won't use our image for some obscure reason
1953 img
._top
= img
.offsetTop
;
1955 e
.dataTransfer
.setDragImage(img
, 0, 0);
1956 if (opera
) img
.parentNode
.removeChild(img
);
1960 function setScrollTop(cm
, val
) {
1961 if (Math
.abs(cm
.doc
.scrollTop
- val
) < 2) return;
1962 cm
.doc
.scrollTop
= val
;
1963 if (!gecko
) updateDisplay(cm
, [], val
);
1964 if (cm
.display
.scroller
.scrollTop
!= val
) cm
.display
.scroller
.scrollTop
= val
;
1965 if (cm
.display
.scrollbarV
.scrollTop
!= val
) cm
.display
.scrollbarV
.scrollTop
= val
;
1966 if (gecko
) updateDisplay(cm
, []);
1967 startWorker(cm
, 100);
1969 function setScrollLeft(cm
, val
, isScroller
) {
1970 if (isScroller
? val
== cm
.doc
.scrollLeft
: Math
.abs(cm
.doc
.scrollLeft
- val
) < 2) return;
1971 val
= Math
.min(val
, cm
.display
.scroller
.scrollWidth
- cm
.display
.scroller
.clientWidth
);
1972 cm
.doc
.scrollLeft
= val
;
1973 alignHorizontally(cm
);
1974 if (cm
.display
.scroller
.scrollLeft
!= val
) cm
.display
.scroller
.scrollLeft
= val
;
1975 if (cm
.display
.scrollbarH
.scrollLeft
!= val
) cm
.display
.scrollbarH
.scrollLeft
= val
;
1978 // Since the delta values reported on mouse wheel events are
1979 // unstandardized between browsers and even browser versions, and
1980 // generally horribly unpredictable, this code starts by measuring
1981 // the scroll effect that the first few mouse wheel events have,
1982 // and, from that, detects the way it can convert deltas to pixel
1983 // offsets afterwards.
1985 // The reason we want to know the amount a wheel event will scroll
1986 // is that it gives us a chance to update the display before the
1987 // actual scrolling happens, reducing flickering.
1989 var wheelSamples
= 0, wheelPixelsPerUnit
= null;
1990 // Fill in a browser-detected starting value on browsers where we
1991 // know one. These don't have to be accurate -- the result of them
1992 // being wrong would just be a slight flicker on the first wheel
1993 // scroll (if it is large enough).
1994 if (ie
) wheelPixelsPerUnit
= -.53;
1995 else if (gecko
) wheelPixelsPerUnit
= 15;
1996 else if (chrome
) wheelPixelsPerUnit
= -.7;
1997 else if (safari
) wheelPixelsPerUnit
= -1/3;
1999 function onScrollWheel(cm
, e
) {
2000 var dx
= e
.wheelDeltaX
, dy
= e
.wheelDeltaY
;
2001 if (dx
== null && e
.detail
&& e
.axis
== e
.HORIZONTAL_AXIS
) dx
= e
.detail
;
2002 if (dy
== null && e
.detail
&& e
.axis
== e
.VERTICAL_AXIS
) dy
= e
.detail
;
2003 else if (dy
== null) dy
= e
.wheelDelta
;
2005 var display
= cm
.display
, scroll
= display
.scroller
;
2006 // Quit if there's nothing to scroll here
2007 if (!(dx
&& scroll
.scrollWidth
> scroll
.clientWidth
||
2008 dy
&& scroll
.scrollHeight
> scroll
.clientHeight
)) return;
2010 // Webkit browsers on OS X abort momentum scrolls when the target
2011 // of the scroll event is removed from the scrollable element.
2012 // This hack (see related code in patchDisplay) makes sure the
2013 // element is kept around.
2014 if (dy
&& mac
&& webkit
) {
2015 for (var cur
= e
.target
; cur
!= scroll
; cur
= cur
.parentNode
) {
2017 cm
.display
.currentWheelTarget
= cur
;
2023 // On some browsers, horizontal scrolling will cause redraws to
2024 // happen before the gutter has been realigned, causing it to
2025 // wriggle around in a most unseemly way. When we have an
2026 // estimated pixels/delta value, we just handle horizontal
2027 // scrolling entirely here. It'll be slightly off from native, but
2028 // better than glitching out.
2029 if (dx
&& !gecko
&& !opera
&& wheelPixelsPerUnit
!= null) {
2031 setScrollTop(cm
, Math
.max(0, Math
.min(scroll
.scrollTop
+ dy
* wheelPixelsPerUnit
, scroll
.scrollHeight
- scroll
.clientHeight
)));
2032 setScrollLeft(cm
, Math
.max(0, Math
.min(scroll
.scrollLeft
+ dx
* wheelPixelsPerUnit
, scroll
.scrollWidth
- scroll
.clientWidth
)));
2033 e_preventDefault(e
);
2034 display
.wheelStartX
= null; // Abort measurement, if in progress
2038 if (dy
&& wheelPixelsPerUnit
!= null) {
2039 var pixels
= dy
* wheelPixelsPerUnit
;
2040 var top
= cm
.doc
.scrollTop
, bot
= top
+ display
.wrapper
.clientHeight
;
2041 if (pixels
< 0) top
= Math
.max(0, top
+ pixels
- 50);
2042 else bot
= Math
.min(cm
.doc
.height
, bot
+ pixels
+ 50);
2043 updateDisplay(cm
, [], {top
: top
, bottom
: bot
});
2046 if (wheelSamples
< 20) {
2047 if (display
.wheelStartX
== null) {
2048 display
.wheelStartX
= scroll
.scrollLeft
; display
.wheelStartY
= scroll
.scrollTop
;
2049 display
.wheelDX
= dx
; display
.wheelDY
= dy
;
2050 setTimeout(function() {
2051 if (display
.wheelStartX
== null) return;
2052 var movedX
= scroll
.scrollLeft
- display
.wheelStartX
;
2053 var movedY
= scroll
.scrollTop
- display
.wheelStartY
;
2054 var sample
= (movedY
&& display
.wheelDY
&& movedY
/ display
.wheelDY
) ||
2055 (movedX
&& display
.wheelDX
&& movedX
/ display
.wheelDX
);
2056 display
.wheelStartX
= display
.wheelStartY
= null;
2057 if (!sample
) return;
2058 wheelPixelsPerUnit
= (wheelPixelsPerUnit
* wheelSamples
+ sample
) / (wheelSamples
+ 1);
2062 display
.wheelDX
+= dx
; display
.wheelDY
+= dy
;
2067 function doHandleBinding(cm
, bound
, dropShift
) {
2068 if (typeof bound
== "string") {
2069 bound
= commands
[bound
];
2070 if (!bound
) return false;
2072 // Ensure previous input has been read, so that the handler sees a
2073 // consistent view of the document
2074 if (cm
.display
.pollingFast
&& readInput(cm
)) cm
.display
.pollingFast
= false;
2075 var doc
= cm
.doc
, prevShift
= doc
.sel
.shift
, done
= false;
2077 if (isReadOnly(cm
)) cm
.state
.suppressEdits
= true;
2078 if (dropShift
) doc
.sel
.shift
= false;
2079 done
= bound(cm
) != Pass
;
2081 doc
.sel
.shift
= prevShift
;
2082 cm
.state
.suppressEdits
= false;
2087 function allKeyMaps(cm
) {
2088 var maps
= cm
.state
.keyMaps
.slice(0);
2089 if (cm
.options
.extraKeys
) maps
.push(cm
.options
.extraKeys
);
2090 maps
.push(cm
.options
.keyMap
);
2094 var maybeTransition
;
2095 function handleKeyBinding(cm
, e
) {
2096 // Handle auto keymap transitions
2097 var startMap
= getKeyMap(cm
.options
.keyMap
), next
= startMap
.auto
;
2098 clearTimeout(maybeTransition
);
2099 if (next
&& !isModifierKey(e
)) maybeTransition
= setTimeout(function() {
2100 if (getKeyMap(cm
.options
.keyMap
) == startMap
) {
2101 cm
.options
.keyMap
= (next
.call
? next
.call(null, cm
) : next
);
2106 var name
= keyName(e
, true), handled
= false;
2107 if (!name
) return false;
2108 var keymaps
= allKeyMaps(cm
);
2111 // First try to resolve full name (including 'Shift-'). Failing
2112 // that, see if there is a cursor-motion command (starting with
2113 // 'go') bound to the keyname without 'Shift-'.
2114 handled
= lookupKey("Shift-" + name
, keymaps
, function(b
) {return doHandleBinding(cm
, b
, true);})
2115 || lookupKey(name
, keymaps
, function(b
) {
2116 if (typeof b
== "string" ? /^go[A-Z]/.test(b
) : b
.motion
)
2117 return doHandleBinding(cm
, b
);
2120 handled
= lookupKey(name
, keymaps
, function(b
) { return doHandleBinding(cm
, b
); });
2124 e_preventDefault(e
);
2126 if (ie_lt9
) { e
.oldKeyCode
= e
.keyCode
; e
.keyCode
= 0; }
2127 signalLater(cm
, "keyHandled", cm
, name
, e
);
2132 function handleCharBinding(cm
, e
, ch
) {
2133 var handled
= lookupKey("'" + ch
+ "'", allKeyMaps(cm
),
2134 function(b
) { return doHandleBinding(cm
, b
, true); });
2136 e_preventDefault(e
);
2138 signalLater(cm
, "keyHandled", cm
, "'" + ch
+ "'", e
);
2143 function onKeyUp(e
) {
2145 if (signalDOMEvent(cm
, e
) || cm
.options
.onKeyEvent
&& cm
.options
.onKeyEvent(cm
, addStop(e
))) return;
2146 if (e
.keyCode
== 16) cm
.doc
.sel
.shift
= false;
2149 var lastStoppedKey
= null;
2150 function onKeyDown(e
) {
2152 if (!cm
.state
.focused
) onFocus(cm
);
2153 if (signalDOMEvent(cm
, e
) || cm
.options
.onKeyEvent
&& cm
.options
.onKeyEvent(cm
, addStop(e
))) return;
2154 if (old_ie
&& e
.keyCode
== 27) e
.returnValue
= false;
2155 var code
= e
.keyCode
;
2156 // IE does strange things with escape.
2157 cm
.doc
.sel
.shift
= code
== 16 || e
.shiftKey
;
2158 // First give onKeyEvent option a chance to handle this.
2159 var handled
= handleKeyBinding(cm
, e
);
2161 lastStoppedKey
= handled
? code
: null;
2162 // Opera has no cut event... we try to at least catch the key combo
2163 if (!handled
&& code
== 88 && !hasCopyEvent
&& (mac
? e
.metaKey
: e
.ctrlKey
))
2164 cm
.replaceSelection("");
2168 function onKeyPress(e
) {
2170 if (signalDOMEvent(cm
, e
) || cm
.options
.onKeyEvent
&& cm
.options
.onKeyEvent(cm
, addStop(e
))) return;
2171 var keyCode
= e
.keyCode
, charCode
= e
.charCode
;
2172 if (opera
&& keyCode
== lastStoppedKey
) {lastStoppedKey
= null; e_preventDefault(e
); return;}
2173 if (((opera
&& (!e
.which
|| e
.which
< 10)) || khtml
) && handleKeyBinding(cm
, e
)) return;
2174 var ch
= String
.fromCharCode(charCode
== null ? keyCode
: charCode
);
2175 if (handleCharBinding(cm
, e
, ch
)) return;
2176 if (ie
&& !ie_lt9
) cm
.display
.inputHasSelection
= null;
2180 function onFocus(cm
) {
2181 if (cm
.options
.readOnly
== "nocursor") return;
2182 if (!cm
.state
.focused
) {
2183 signal(cm
, "focus", cm
);
2184 cm
.state
.focused
= true;
2185 if (cm
.display
.wrapper
.className
.search(/\bCodeMirror-focused\b/) == -1)
2186 cm
.display
.wrapper
.className
+= " CodeMirror-focused";
2188 resetInput(cm
, true);
2189 if (webkit
) setTimeout(bind(resetInput
, cm
, true), 0); // Issue #1730
2195 function onBlur(cm
) {
2196 if (cm
.state
.focused
) {
2197 signal(cm
, "blur", cm
);
2198 cm
.state
.focused
= false;
2199 cm
.display
.wrapper
.className
= cm
.display
.wrapper
.className
.replace(" CodeMirror-focused", "");
2201 clearInterval(cm
.display
.blinker
);
2202 setTimeout(function() {if (!cm
.state
.focused
) cm
.doc
.sel
.shift
= false;}, 150);
2205 var detectingSelectAll
;
2206 function onContextMenu(cm
, e
) {
2207 if (signalDOMEvent(cm
, e
, "contextmenu")) return;
2208 var display
= cm
.display
, sel
= cm
.doc
.sel
;
2209 if (eventInWidget(display
, e
) || contextMenuInGutter(cm
, e
)) return;
2211 var pos
= posFromMouse(cm
, e
), scrollPos
= display
.scroller
.scrollTop
;
2212 if (!pos
|| opera
) return; // Opera is difficult.
2214 // Reset the current text selection only if the click is done outside of the selection
2215 // and 'resetSelectionOnContextMenu' option is true.
2216 var reset
= cm
.options
.resetSelectionOnContextMenu
;
2217 if (reset
&& (posEq(sel
.from, sel
.to
) || posLess(pos
, sel
.from) || !posLess(pos
, sel
.to
)))
2218 operation(cm
, setSelection
)(cm
.doc
, pos
, pos
);
2220 var oldCSS
= display
.input
.style
.cssText
;
2221 display
.inputDiv
.style
.position
= "absolute";
2222 display
.input
.style
.cssText
= "position: fixed; width: 30px; height: 30px; top: " + (e
.clientY
- 5) +
2223 "px; left: " + (e
.clientX
- 5) + "px; z-index: 1000; background: transparent; outline: none;" +
2224 "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
2226 resetInput(cm
, true);
2227 // Adds "Select all" to context menu in FF
2228 if (posEq(sel
.from, sel
.to
)) display
.input
.value
= display
.prevInput
= " ";
2230 function prepareSelectAllHack() {
2231 if (display
.input
.selectionStart
!= null) {
2232 var extval
= display
.input
.value
= "\u200b" + (posEq(sel
.from, sel
.to
) ? "" : display
.input
.value
);
2233 display
.prevInput
= "\u200b";
2234 display
.input
.selectionStart
= 1; display
.input
.selectionEnd
= extval
.length
;
2238 display
.inputDiv
.style
.position
= "relative";
2239 display
.input
.style
.cssText
= oldCSS
;
2240 if (ie_lt9
) display
.scrollbarV
.scrollTop
= display
.scroller
.scrollTop
= scrollPos
;
2243 // Try to detect the user choosing select-all
2244 if (display
.input
.selectionStart
!= null) {
2245 if (!ie
|| ie_lt9
) prepareSelectAllHack();
2246 clearTimeout(detectingSelectAll
);
2247 var i
= 0, poll = function(){
2248 if (display
.prevInput
== "\u200b" && display
.input
.selectionStart
== 0)
2249 operation(cm
, commands
.selectAll
)(cm
);
2250 else if (i
++ < 10) detectingSelectAll
= setTimeout(poll
, 500);
2251 else resetInput(cm
);
2253 detectingSelectAll
= setTimeout(poll
, 200);
2257 if (ie
&& !ie_lt9
) prepareSelectAllHack();
2258 if (captureMiddleClick
) {
2260 var mouseup = function() {
2261 off(window
, "mouseup", mouseup
);
2262 setTimeout(rehide
, 20);
2264 on(window
, "mouseup", mouseup
);
2266 setTimeout(rehide
, 50);
2272 var changeEnd
= CodeMirror
.changeEnd = function(change
) {
2273 if (!change
.text
) return change
.to
;
2274 return Pos(change
.from.line
+ change
.text
.length
- 1,
2275 lst(change
.text
).length
+ (change
.text
.length
== 1 ? change
.from.ch
: 0));
2278 // Make sure a position will be valid after the given change.
2279 function clipPostChange(doc
, change
, pos
) {
2280 if (!posLess(change
.from, pos
)) return clipPos(doc
, pos
);
2281 var diff
= (change
.text
.length
- 1) - (change
.to
.line
- change
.from.line
);
2282 if (pos
.line
> change
.to
.line
+ diff
) {
2283 var preLine
= pos
.line
- diff
, lastLine
= doc
.first
+ doc
.size
- 1;
2284 if (preLine
> lastLine
) return Pos(lastLine
, getLine(doc
, lastLine
).text
.length
);
2285 return clipToLen(pos
, getLine(doc
, preLine
).text
.length
);
2287 if (pos
.line
== change
.to
.line
+ diff
)
2288 return clipToLen(pos
, lst(change
.text
).length
+ (change
.text
.length
== 1 ? change
.from.ch
: 0) +
2289 getLine(doc
, change
.to
.line
).text
.length
- change
.to
.ch
);
2290 var inside
= pos
.line
- change
.from.line
;
2291 return clipToLen(pos
, change
.text
[inside
].length
+ (inside
? 0 : change
.from.ch
));
2294 // Hint can be null|"end"|"start"|"around"|{anchor,head}
2295 function computeSelAfterChange(doc
, change
, hint
) {
2296 if (hint
&& typeof hint
== "object") // Assumed to be {anchor, head} object
2297 return {anchor
: clipPostChange(doc
, change
, hint
.anchor
),
2298 head
: clipPostChange(doc
, change
, hint
.head
)};
2300 if (hint
== "start") return {anchor
: change
.from, head
: change
.from};
2302 var end
= changeEnd(change
);
2303 if (hint
== "around") return {anchor
: change
.from, head
: end
};
2304 if (hint
== "end") return {anchor
: end
, head
: end
};
2306 // hint is null, leave the selection alone as much as possible
2307 var adjustPos = function(pos
) {
2308 if (posLess(pos
, change
.from)) return pos
;
2309 if (!posLess(change
.to
, pos
)) return end
;
2311 var line
= pos
.line
+ change
.text
.length
- (change
.to
.line
- change
.from.line
) - 1, ch
= pos
.ch
;
2312 if (pos
.line
== change
.to
.line
) ch
+= end
.ch
- change
.to
.ch
;
2313 return Pos(line
, ch
);
2315 return {anchor
: adjustPos(doc
.sel
.anchor
), head
: adjustPos(doc
.sel
.head
)};
2318 function filterChange(doc
, change
, update
) {
2324 origin
: change
.origin
,
2325 cancel: function() { this.canceled
= true; }
2327 if (update
) obj
.update = function(from, to
, text
, origin
) {
2328 if (from) this.from = clipPos(doc
, from);
2329 if (to
) this.to
= clipPos(doc
, to
);
2330 if (text
) this.text
= text
;
2331 if (origin
!== undefined) this.origin
= origin
;
2333 signal(doc
, "beforeChange", doc
, obj
);
2334 if (doc
.cm
) signal(doc
.cm
, "beforeChange", doc
.cm
, obj
);
2336 if (obj
.canceled
) return null;
2337 return {from: obj
.from, to
: obj
.to
, text
: obj
.text
, origin
: obj
.origin
};
2340 // Replace the range from from to to by the strings in replacement.
2341 // change is a {from, to, text [, origin]} object
2342 function makeChange(doc
, change
, selUpdate
, ignoreReadOnly
) {
2344 if (!doc
.cm
.curOp
) return operation(doc
.cm
, makeChange
)(doc
, change
, selUpdate
, ignoreReadOnly
);
2345 if (doc
.cm
.state
.suppressEdits
) return;
2348 if (hasHandler(doc
, "beforeChange") || doc
.cm
&& hasHandler(doc
.cm
, "beforeChange")) {
2349 change
= filterChange(doc
, change
, true);
2350 if (!change
) return;
2353 // Possibly split or suppress the update based on the presence
2354 // of read-only spans in its range.
2355 var split
= sawReadOnlySpans
&& !ignoreReadOnly
&& removeReadOnlyRanges(doc
, change
.from, change
.to
);
2357 for (var i
= split
.length
- 1; i
>= 1; --i
)
2358 makeChangeNoReadonly(doc
, {from: split
[i
].from, to
: split
[i
].to
, text
: [""]});
2360 makeChangeNoReadonly(doc
, {from: split
[0].from, to
: split
[0].to
, text
: change
.text
}, selUpdate
);
2362 makeChangeNoReadonly(doc
, change
, selUpdate
);
2366 function makeChangeNoReadonly(doc
, change
, selUpdate
) {
2367 if (change
.text
.length
== 1 && change
.text
[0] == "" && posEq(change
.from, change
.to
)) return;
2368 var selAfter
= computeSelAfterChange(doc
, change
, selUpdate
);
2369 addToHistory(doc
, change
, selAfter
, doc
.cm
? doc
.cm
.curOp
.id
: NaN
);
2371 makeChangeSingleDoc(doc
, change
, selAfter
, stretchSpansOverChange(doc
, change
));
2374 linkedDocs(doc
, function(doc
, sharedHist
) {
2375 if (!sharedHist
&& indexOf(rebased
, doc
.history
) == -1) {
2376 rebaseHist(doc
.history
, change
);
2377 rebased
.push(doc
.history
);
2379 makeChangeSingleDoc(doc
, change
, null, stretchSpansOverChange(doc
, change
));
2383 function makeChangeFromHistory(doc
, type
) {
2384 if (doc
.cm
&& doc
.cm
.state
.suppressEdits
) return;
2386 var hist
= doc
.history
;
2387 var event
= (type
== "undo" ? hist
.done
: hist
.undone
).pop();
2390 var anti
= {changes
: [], anchorBefore
: event
.anchorAfter
, headBefore
: event
.headAfter
,
2391 anchorAfter
: event
.anchorBefore
, headAfter
: event
.headBefore
,
2392 generation
: hist
.generation
};
2393 (type
== "undo" ? hist
.undone
: hist
.done
).push(anti
);
2394 hist
.generation
= event
.generation
|| ++hist
.maxGeneration
;
2396 var filter
= hasHandler(doc
, "beforeChange") || doc
.cm
&& hasHandler(doc
.cm
, "beforeChange");
2398 for (var i
= event
.changes
.length
- 1; i
>= 0; --i
) {
2399 var change
= event
.changes
[i
];
2400 change
.origin
= type
;
2401 if (filter
&& !filterChange(doc
, change
, false)) {
2402 (type
== "undo" ? hist
.done
: hist
.undone
).length
= 0;
2406 anti
.changes
.push(historyChangeFromChange(doc
, change
));
2408 var after
= i
? computeSelAfterChange(doc
, change
, null)
2409 : {anchor
: event
.anchorBefore
, head
: event
.headBefore
};
2410 makeChangeSingleDoc(doc
, change
, after
, mergeOldSpans(doc
, change
));
2413 linkedDocs(doc
, function(doc
, sharedHist
) {
2414 if (!sharedHist
&& indexOf(rebased
, doc
.history
) == -1) {
2415 rebaseHist(doc
.history
, change
);
2416 rebased
.push(doc
.history
);
2418 makeChangeSingleDoc(doc
, change
, null, mergeOldSpans(doc
, change
));
2423 function shiftDoc(doc
, distance
) {
2424 function shiftPos(pos
) {return Pos(pos
.line
+ distance
, pos
.ch
);}
2425 doc
.first
+= distance
;
2426 if (doc
.cm
) regChange(doc
.cm
, doc
.first
, doc
.first
, distance
);
2427 doc
.sel
.head
= shiftPos(doc
.sel
.head
); doc
.sel
.anchor
= shiftPos(doc
.sel
.anchor
);
2428 doc
.sel
.from = shiftPos(doc
.sel
.from); doc
.sel
.to
= shiftPos(doc
.sel
.to
);
2431 function makeChangeSingleDoc(doc
, change
, selAfter
, spans
) {
2432 if (doc
.cm
&& !doc
.cm
.curOp
)
2433 return operation(doc
.cm
, makeChangeSingleDoc
)(doc
, change
, selAfter
, spans
);
2435 if (change
.to
.line
< doc
.first
) {
2436 shiftDoc(doc
, change
.text
.length
- 1 - (change
.to
.line
- change
.from.line
));
2439 if (change
.from.line
> doc
.lastLine()) return;
2441 // Clip the change to the size of this doc
2442 if (change
.from.line
< doc
.first
) {
2443 var shift
= change
.text
.length
- 1 - (doc
.first
- change
.from.line
);
2444 shiftDoc(doc
, shift
);
2445 change
= {from: Pos(doc
.first
, 0), to
: Pos(change
.to
.line
+ shift
, change
.to
.ch
),
2446 text
: [lst(change
.text
)], origin
: change
.origin
};
2448 var last
= doc
.lastLine();
2449 if (change
.to
.line
> last
) {
2450 change
= {from: change
.from, to
: Pos(last
, getLine(doc
, last
).text
.length
),
2451 text
: [change
.text
[0]], origin
: change
.origin
};
2454 change
.removed
= getBetween(doc
, change
.from, change
.to
);
2456 if (!selAfter
) selAfter
= computeSelAfterChange(doc
, change
, null);
2457 if (doc
.cm
) makeChangeSingleDocInEditor(doc
.cm
, change
, spans
, selAfter
);
2458 else updateDoc(doc
, change
, spans
, selAfter
);
2461 function makeChangeSingleDocInEditor(cm
, change
, spans
, selAfter
) {
2462 var doc
= cm
.doc
, display
= cm
.display
, from = change
.from, to
= change
.to
;
2464 var recomputeMaxLength
= false, checkWidthStart
= from.line
;
2465 if (!cm
.options
.lineWrapping
) {
2466 checkWidthStart
= lineNo(visualLine(doc
, getLine(doc
, from.line
)));
2467 doc
.iter(checkWidthStart
, to
.line
+ 1, function(line
) {
2468 if (line
== display
.maxLine
) {
2469 recomputeMaxLength
= true;
2475 if (!posLess(doc
.sel
.head
, change
.from) && !posLess(change
.to
, doc
.sel
.head
))
2476 cm
.curOp
.cursorActivity
= true;
2478 updateDoc(doc
, change
, spans
, selAfter
, estimateHeight(cm
));
2480 if (!cm
.options
.lineWrapping
) {
2481 doc
.iter(checkWidthStart
, from.line
+ change
.text
.length
, function(line
) {
2482 var len
= lineLength(doc
, line
);
2483 if (len
> display
.maxLineLength
) {
2484 display
.maxLine
= line
;
2485 display
.maxLineLength
= len
;
2486 display
.maxLineChanged
= true;
2487 recomputeMaxLength
= false;
2490 if (recomputeMaxLength
) cm
.curOp
.updateMaxLine
= true;
2493 // Adjust frontier, schedule worker
2494 doc
.frontier
= Math
.min(doc
.frontier
, from.line
);
2495 startWorker(cm
, 400);
2497 var lendiff
= change
.text
.length
- (to
.line
- from.line
) - 1;
2498 // Remember that these lines changed, for updating the display
2499 regChange(cm
, from.line
, to
.line
+ 1, lendiff
);
2501 if (hasHandler(cm
, "change")) {
2502 var changeObj
= {from: from, to
: to
,
2504 removed
: change
.removed
,
2505 origin
: change
.origin
};
2506 if (cm
.curOp
.textChanged
) {
2507 for (var cur
= cm
.curOp
.textChanged
; cur
.next
; cur
= cur
.next
) {}
2508 cur
.next
= changeObj
;
2509 } else cm
.curOp
.textChanged
= changeObj
;
2513 function replaceRange(doc
, code
, from, to
, origin
) {
2515 if (posLess(to
, from)) { var tmp
= to
; to
= from; from = tmp
; }
2516 if (typeof code
== "string") code
= splitLines(code
);
2517 makeChange(doc
, {from: from, to
: to
, text
: code
, origin
: origin
}, null);
2522 function Pos(line
, ch
) {
2523 if (!(this instanceof Pos
)) return new Pos(line
, ch
);
2524 this.line
= line
; this.ch
= ch
;
2526 CodeMirror
.Pos
= Pos
;
2528 function posEq(a
, b
) {return a
.line
== b
.line
&& a
.ch
== b
.ch
;}
2529 function posLess(a
, b
) {return a
.line
< b
.line
|| (a
.line
== b
.line
&& a
.ch
< b
.ch
);}
2530 function cmp(a
, b
) {return a
.line
- b
.line
|| a
.ch
- b
.ch
;}
2531 function copyPos(x
) {return Pos(x
.line
, x
.ch
);}
2535 function clipLine(doc
, n
) {return Math
.max(doc
.first
, Math
.min(n
, doc
.first
+ doc
.size
- 1));}
2536 function clipPos(doc
, pos
) {
2537 if (pos
.line
< doc
.first
) return Pos(doc
.first
, 0);
2538 var last
= doc
.first
+ doc
.size
- 1;
2539 if (pos
.line
> last
) return Pos(last
, getLine(doc
, last
).text
.length
);
2540 return clipToLen(pos
, getLine(doc
, pos
.line
).text
.length
);
2542 function clipToLen(pos
, linelen
) {
2544 if (ch
== null || ch
> linelen
) return Pos(pos
.line
, linelen
);
2545 else if (ch
< 0) return Pos(pos
.line
, 0);
2548 function isLine(doc
, l
) {return l
>= doc
.first
&& l
< doc
.first
+ doc
.size
;}
2550 // If shift is held, this will move the selection anchor. Otherwise,
2551 // it'll set the whole selection.
2552 function extendSelection(doc
, pos
, other
, bias
) {
2553 if (doc
.sel
.shift
|| doc
.sel
.extend
) {
2554 var anchor
= doc
.sel
.anchor
;
2556 var posBefore
= posLess(pos
, anchor
);
2557 if (posBefore
!= posLess(other
, anchor
)) {
2560 } else if (posBefore
!= posLess(pos
, other
)) {
2564 setSelection(doc
, anchor
, pos
, bias
);
2566 setSelection(doc
, pos
, other
|| pos
, bias
);
2568 if (doc
.cm
) doc
.cm
.curOp
.userSelChange
= true;
2571 function filterSelectionChange(doc
, anchor
, head
) {
2572 var obj
= {anchor
: anchor
, head
: head
};
2573 signal(doc
, "beforeSelectionChange", doc
, obj
);
2574 if (doc
.cm
) signal(doc
.cm
, "beforeSelectionChange", doc
.cm
, obj
);
2575 obj
.anchor
= clipPos(doc
, obj
.anchor
); obj
.head
= clipPos(doc
, obj
.head
);
2579 // Update the selection. Last two args are only used by
2580 // updateDoc, since they have to be expressed in the line
2581 // numbers before the update.
2582 function setSelection(doc
, anchor
, head
, bias
, checkAtomic
) {
2583 if (!checkAtomic
&& hasHandler(doc
, "beforeSelectionChange") || doc
.cm
&& hasHandler(doc
.cm
, "beforeSelectionChange")) {
2584 var filtered
= filterSelectionChange(doc
, anchor
, head
);
2585 head
= filtered
.head
;
2586 anchor
= filtered
.anchor
;
2590 sel
.goalColumn
= null;
2591 if (bias
== null) bias
= posLess(head
, sel
.head
) ? -1 : 1;
2592 // Skip over atomic spans.
2593 if (checkAtomic
|| !posEq(anchor
, sel
.anchor
))
2594 anchor
= skipAtomic(doc
, anchor
, bias
, checkAtomic
!= "push");
2595 if (checkAtomic
|| !posEq(head
, sel
.head
))
2596 head
= skipAtomic(doc
, head
, bias
, checkAtomic
!= "push");
2598 if (posEq(sel
.anchor
, anchor
) && posEq(sel
.head
, head
)) return;
2600 sel
.anchor
= anchor
; sel
.head
= head
;
2601 var inv
= posLess(head
, anchor
);
2602 sel
.from = inv
? head
: anchor
;
2603 sel
.to
= inv
? anchor
: head
;
2606 doc
.cm
.curOp
.updateInput
= doc
.cm
.curOp
.selectionChanged
=
2607 doc
.cm
.curOp
.cursorActivity
= true;
2609 signalLater(doc
, "cursorActivity", doc
);
2612 function reCheckSelection(cm
) {
2613 setSelection(cm
.doc
, cm
.doc
.sel
.from, cm
.doc
.sel
.to
, null, "push");
2616 function skipAtomic(doc
, pos
, bias
, mayClear
) {
2617 var flipped
= false, curPos
= pos
;
2618 var dir
= bias
|| 1;
2619 doc
.cantEdit
= false;
2621 var line
= getLine(doc
, curPos
.line
);
2622 if (line
.markedSpans
) {
2623 for (var i
= 0; i
< line
.markedSpans
.length
; ++i
) {
2624 var sp
= line
.markedSpans
[i
], m
= sp
.marker
;
2625 if ((sp
.from == null || (m
.inclusiveLeft
? sp
.from <= curPos
.ch
: sp
.from < curPos
.ch
)) &&
2626 (sp
.to
== null || (m
.inclusiveRight
? sp
.to
>= curPos
.ch
: sp
.to
> curPos
.ch
))) {
2628 signal(m
, "beforeCursorEnter");
2629 if (m
.explicitlyCleared
) {
2630 if (!line
.markedSpans
) break;
2631 else {--i
; continue;}
2634 if (!m
.atomic
) continue;
2635 var newPos
= m
.find()[dir
< 0 ? "from" : "to"];
2636 if (posEq(newPos
, curPos
)) {
2638 if (newPos
.ch
< 0) {
2639 if (newPos
.line
> doc
.first
) newPos
= clipPos(doc
, Pos(newPos
.line
- 1));
2641 } else if (newPos
.ch
> line
.text
.length
) {
2642 if (newPos
.line
< doc
.first
+ doc
.size
- 1) newPos
= Pos(newPos
.line
+ 1, 0);
2647 // Driven in a corner -- no valid cursor position found at all
2648 // -- try again *with* clearing, if we didn't already
2649 if (!mayClear
) return skipAtomic(doc
, pos
, bias
, true);
2650 // Otherwise, turn off editing until further notice, and return the start of the doc
2651 doc
.cantEdit
= true;
2652 return Pos(doc
.first
, 0);
2654 flipped
= true; newPos
= pos
; dir
= -dir
;
2668 function scrollCursorIntoView(cm
) {
2669 var coords
= scrollPosIntoView(cm
, cm
.doc
.sel
.head
, null, cm
.options
.cursorScrollMargin
);
2670 if (!cm
.state
.focused
) return;
2671 var display
= cm
.display
, box
= getRect(display
.sizer
), doScroll
= null;
2672 if (coords
.top
+ box
.top
< 0) doScroll
= true;
2673 else if (coords
.bottom
+ box
.top
> (window
.innerHeight
|| document
.documentElement
.clientHeight
)) doScroll
= false;
2674 if (doScroll
!= null && !phantom
) {
2675 var scrollNode
= elt("div", "\u200b", null, "position: absolute; top: " +
2676 (coords
.top
- display
.viewOffset
) + "px; height: " +
2677 (coords
.bottom
- coords
.top
+ scrollerCutOff
) + "px; left: " +
2678 coords
.left
+ "px; width: 2px;");
2679 cm
.display
.lineSpace
.appendChild(scrollNode
);
2680 scrollNode
.scrollIntoView(doScroll
);
2681 cm
.display
.lineSpace
.removeChild(scrollNode
);
2685 function scrollPosIntoView(cm
, pos
, end
, margin
) {
2686 if (margin
== null) margin
= 0;
2688 var changed
= false, coords
= cursorCoords(cm
, pos
);
2689 var endCoords
= !end
|| end
== pos
? coords
: cursorCoords(cm
, end
);
2690 var scrollPos
= calculateScrollPos(cm
, Math
.min(coords
.left
, endCoords
.left
),
2691 Math
.min(coords
.top
, endCoords
.top
) - margin
,
2692 Math
.max(coords
.left
, endCoords
.left
),
2693 Math
.max(coords
.bottom
, endCoords
.bottom
) + margin
);
2694 var startTop
= cm
.doc
.scrollTop
, startLeft
= cm
.doc
.scrollLeft
;
2695 if (scrollPos
.scrollTop
!= null) {
2696 setScrollTop(cm
, scrollPos
.scrollTop
);
2697 if (Math
.abs(cm
.doc
.scrollTop
- startTop
) > 1) changed
= true;
2699 if (scrollPos
.scrollLeft
!= null) {
2700 setScrollLeft(cm
, scrollPos
.scrollLeft
);
2701 if (Math
.abs(cm
.doc
.scrollLeft
- startLeft
) > 1) changed
= true;
2703 if (!changed
) return coords
;
2707 function scrollIntoView(cm
, x1
, y1
, x2
, y2
) {
2708 var scrollPos
= calculateScrollPos(cm
, x1
, y1
, x2
, y2
);
2709 if (scrollPos
.scrollTop
!= null) setScrollTop(cm
, scrollPos
.scrollTop
);
2710 if (scrollPos
.scrollLeft
!= null) setScrollLeft(cm
, scrollPos
.scrollLeft
);
2713 function calculateScrollPos(cm
, x1
, y1
, x2
, y2
) {
2714 var display
= cm
.display
, snapMargin
= textHeight(cm
.display
);
2716 var screen
= display
.scroller
.clientHeight
- scrollerCutOff
, screentop
= display
.scroller
.scrollTop
, result
= {};
2717 var docBottom
= cm
.doc
.height
+ paddingVert(display
);
2718 var atTop
= y1
< snapMargin
, atBottom
= y2
> docBottom
- snapMargin
;
2719 if (y1
< screentop
) {
2720 result
.scrollTop
= atTop
? 0 : y1
;
2721 } else if (y2
> screentop
+ screen
) {
2722 var newTop
= Math
.min(y1
, (atBottom
? docBottom
: y2
) - screen
);
2723 if (newTop
!= screentop
) result
.scrollTop
= newTop
;
2726 var screenw
= display
.scroller
.clientWidth
- scrollerCutOff
, screenleft
= display
.scroller
.scrollLeft
;
2727 x1
+= display
.gutters
.offsetWidth
; x2
+= display
.gutters
.offsetWidth
;
2728 var gutterw
= display
.gutters
.offsetWidth
;
2729 var atLeft
= x1
< gutterw
+ 10;
2730 if (x1
< screenleft
+ gutterw
|| atLeft
) {
2732 result
.scrollLeft
= Math
.max(0, x1
- 10 - gutterw
);
2733 } else if (x2
> screenw
+ screenleft
- 3) {
2734 result
.scrollLeft
= x2
+ 10 - screenw
;
2739 function updateScrollPos(cm
, left
, top
) {
2740 cm
.curOp
.updateScrollPos
= {scrollLeft
: left
== null ? cm
.doc
.scrollLeft
: left
,
2741 scrollTop
: top
== null ? cm
.doc
.scrollTop
: top
};
2744 function addToScrollPos(cm
, left
, top
) {
2745 var pos
= cm
.curOp
.updateScrollPos
|| (cm
.curOp
.updateScrollPos
= {scrollLeft
: cm
.doc
.scrollLeft
, scrollTop
: cm
.doc
.scrollTop
});
2746 var scroll
= cm
.display
.scroller
;
2747 pos
.scrollTop
= Math
.max(0, Math
.min(scroll
.scrollHeight
- scroll
.clientHeight
, pos
.scrollTop
+ top
));
2748 pos
.scrollLeft
= Math
.max(0, Math
.min(scroll
.scrollWidth
- scroll
.clientWidth
, pos
.scrollLeft
+ left
));
2753 function indentLine(cm
, n
, how
, aggressive
) {
2755 if (how
== null) how
= "add";
2756 if (how
== "smart") {
2757 if (!cm
.doc
.mode
.indent
) how
= "prev";
2758 else var state
= getStateBefore(cm
, n
);
2761 var tabSize
= cm
.options
.tabSize
;
2762 var line
= getLine(doc
, n
), curSpace
= countColumn(line
.text
, null, tabSize
);
2763 var curSpaceString
= line
.text
.match(/^\s*/)[0], indentation
;
2764 if (!aggressive
&& !/\S/.test(line
.text
)) {
2767 } else if (how
== "smart") {
2768 indentation
= cm
.doc
.mode
.indent(state
, line
.text
.slice(curSpaceString
.length
), line
.text
);
2769 if (indentation
== Pass
) {
2770 if (!aggressive
) return;
2774 if (how
== "prev") {
2775 if (n
> doc
.first
) indentation
= countColumn(getLine(doc
, n
-1).text
, null, tabSize
);
2776 else indentation
= 0;
2777 } else if (how
== "add") {
2778 indentation
= curSpace
+ cm
.options
.indentUnit
;
2779 } else if (how
== "subtract") {
2780 indentation
= curSpace
- cm
.options
.indentUnit
;
2781 } else if (typeof how
== "number") {
2782 indentation
= curSpace
+ how
;
2784 indentation
= Math
.max(0, indentation
);
2786 var indentString
= "", pos
= 0;
2787 if (cm
.options
.indentWithTabs
)
2788 for (var i
= Math
.floor(indentation
/ tabSize
); i
; --i
) {pos
+= tabSize
; indentString
+= "\t";}
2789 if (pos
< indentation
) indentString
+= spaceStr(indentation
- pos
);
2791 if (indentString
!= curSpaceString
)
2792 replaceRange(cm
.doc
, indentString
, Pos(n
, 0), Pos(n
, curSpaceString
.length
), "+input");
2793 else if (doc
.sel
.head
.line
== n
&& doc
.sel
.head
.ch
< curSpaceString
.length
)
2794 setSelection(doc
, Pos(n
, curSpaceString
.length
), Pos(n
, curSpaceString
.length
), 1);
2795 line
.stateAfter
= null;
2798 function changeLine(cm
, handle
, op
) {
2799 var no
= handle
, line
= handle
, doc
= cm
.doc
;
2800 if (typeof handle
== "number") line
= getLine(doc
, clipLine(doc
, handle
));
2801 else no
= lineNo(handle
);
2802 if (no
== null) return null;
2803 if (op(line
, no
)) regChange(cm
, no
, no
+ 1);
2808 function findPosH(doc
, pos
, dir
, unit
, visually
) {
2809 var line
= pos
.line
, ch
= pos
.ch
, origDir
= dir
;
2810 var lineObj
= getLine(doc
, line
);
2811 var possible
= true;
2812 function findNextLine() {
2814 if (l
< doc
.first
|| l
>= doc
.first
+ doc
.size
) return (possible
= false);
2816 return lineObj
= getLine(doc
, l
);
2818 function moveOnce(boundToLine
) {
2819 var next
= (visually
? moveVisually
: moveLogically
)(lineObj
, ch
, dir
, true);
2821 if (!boundToLine
&& findNextLine()) {
2822 if (visually
) ch
= (dir
< 0 ? lineRight
: lineLeft
)(lineObj
);
2823 else ch
= dir
< 0 ? lineObj
.text
.length
: 0;
2824 } else return (possible
= false);
2829 if (unit
== "char") moveOnce();
2830 else if (unit
== "column") moveOnce(true);
2831 else if (unit
== "word" || unit
== "group") {
2832 var sawType
= null, group
= unit
== "group";
2833 for (var first
= true;; first
= false) {
2834 if (dir
< 0 && !moveOnce(!first
)) break;
2835 var cur
= lineObj
.text
.charAt(ch
) || "\n";
2836 var type
= isWordChar(cur
) ? "w"
2838 : /\s/.test(cur
) ? null
2840 if (sawType
&& sawType
!= type
) {
2841 if (dir
< 0) {dir
= 1; moveOnce();}
2844 if (type
) sawType
= type
;
2845 if (dir
> 0 && !moveOnce(!first
)) break;
2848 var result
= skipAtomic(doc
, Pos(line
, ch
), origDir
, true);
2849 if (!possible
) result
.hitSide
= true;
2853 function findPosV(cm
, pos
, dir
, unit
) {
2854 var doc
= cm
.doc
, x
= pos
.left
, y
;
2855 if (unit
== "page") {
2856 var pageSize
= Math
.min(cm
.display
.wrapper
.clientHeight
, window
.innerHeight
|| document
.documentElement
.clientHeight
);
2857 y
= pos
.top
+ dir
* (pageSize
- (dir
< 0 ? 1.5 : .5) * textHeight(cm
.display
));
2858 } else if (unit
== "line") {
2859 y
= dir
> 0 ? pos
.bottom
+ 3 : pos
.top
- 3;
2862 var target
= coordsChar(cm
, x
, y
);
2863 if (!target
.outside
) break;
2864 if (dir
< 0 ? y
<= 0 : y
>= doc
.height
) { target
.hitSide
= true; break; }
2870 function findWordAt(line
, pos
) {
2871 var start
= pos
.ch
, end
= pos
.ch
;
2873 if ((pos
.xRel
< 0 || end
== line
.length
) && start
) --start
; else ++end
;
2874 var startChar
= line
.charAt(start
);
2875 var check
= isWordChar(startChar
) ? isWordChar
2876 : /\s/.test(startChar
) ? function(ch
) {return /\s/.test(ch
);}
2877 : function(ch
) {return !/\s/.test(ch
) && !isWordChar(ch
);};
2878 while (start
> 0 && check(line
.charAt(start
- 1))) --start
;
2879 while (end
< line
.length
&& check(line
.charAt(end
))) ++end
;
2881 return {from: Pos(pos
.line
, start
), to
: Pos(pos
.line
, end
)};
2884 function selectLine(cm
, line
) {
2885 extendSelection(cm
.doc
, Pos(line
, 0), clipPos(cm
.doc
, Pos(line
+ 1, 0)));
2890 // The publicly visible API. Note that operation(null, f) means
2891 // 'wrap f in an operation, performed on its `this` parameter'
2893 CodeMirror
.prototype = {
2894 constructor: CodeMirror
,
2895 focus: function(){window
.focus(); focusInput(this); fastPoll(this);},
2897 setOption: function(option
, value
) {
2898 var options
= this.options
, old
= options
[option
];
2899 if (options
[option
] == value
&& option
!= "mode") return;
2900 options
[option
] = value
;
2901 if (optionHandlers
.hasOwnProperty(option
))
2902 operation(this, optionHandlers
[option
])(this, value
, old
);
2905 getOption: function(option
) {return this.options
[option
];},
2906 getDoc: function() {return this.doc
;},
2908 addKeyMap: function(map
, bottom
) {
2909 this.state
.keyMaps
[bottom
? "push" : "unshift"](map
);
2911 removeKeyMap: function(map
) {
2912 var maps
= this.state
.keyMaps
;
2913 for (var i
= 0; i
< maps
.length
; ++i
)
2914 if (maps
[i
] == map
|| (typeof maps
[i
] != "string" && maps
[i
].name
== map
)) {
2920 addOverlay
: operation(null, function(spec
, options
) {
2921 var mode
= spec
.token
? spec
: CodeMirror
.getMode(this.options
, spec
);
2922 if (mode
.startState
) throw new Error("Overlays may not be stateful.");
2923 this.state
.overlays
.push({mode
: mode
, modeSpec
: spec
, opaque
: options
&& options
.opaque
});
2924 this.state
.modeGen
++;
2927 removeOverlay
: operation(null, function(spec
) {
2928 var overlays
= this.state
.overlays
;
2929 for (var i
= 0; i
< overlays
.length
; ++i
) {
2930 var cur
= overlays
[i
].modeSpec
;
2931 if (cur
== spec
|| typeof spec
== "string" && cur
.name
== spec
) {
2932 overlays
.splice(i
, 1);
2933 this.state
.modeGen
++;
2940 indentLine
: operation(null, function(n
, dir
, aggressive
) {
2941 if (typeof dir
!= "string" && typeof dir
!= "number") {
2942 if (dir
== null) dir
= this.options
.smartIndent
? "smart" : "prev";
2943 else dir
= dir
? "add" : "subtract";
2945 if (isLine(this.doc
, n
)) indentLine(this, n
, dir
, aggressive
);
2947 indentSelection
: operation(null, function(how
) {
2948 var sel
= this.doc
.sel
;
2949 if (posEq(sel
.from, sel
.to
)) return indentLine(this, sel
.from.line
, how
, true);
2950 var e
= sel
.to
.line
- (sel
.to
.ch
? 0 : 1);
2951 for (var i
= sel
.from.line
; i
<= e
; ++i
) indentLine(this, i
, how
);
2954 // Fetch the parser token for a given character. Useful for hacks
2955 // that want to inspect the mode state (say, for completion).
2956 getTokenAt: function(pos
, precise
) {
2958 pos
= clipPos(doc
, pos
);
2959 var state
= getStateBefore(this, pos
.line
, precise
), mode
= this.doc
.mode
;
2960 var line
= getLine(doc
, pos
.line
);
2961 var stream
= new StringStream(line
.text
, this.options
.tabSize
);
2962 while (stream
.pos
< pos
.ch
&& !stream
.eol()) {
2963 stream
.start
= stream
.pos
;
2964 var style
= mode
.token(stream
, state
);
2966 return {start
: stream
.start
,
2968 string
: stream
.current(),
2969 className
: style
|| null, // Deprecated, use 'type' instead
2970 type
: style
|| null,
2974 getTokenTypeAt: function(pos
) {
2975 pos
= clipPos(this.doc
, pos
);
2976 var styles
= getLineStyles(this, getLine(this.doc
, pos
.line
));
2977 var before
= 0, after
= (styles
.length
- 1) / 2, ch
= pos
.ch
;
2978 if (ch
== 0) return styles
[2];
2980 var mid
= (before
+ after
) >> 1;
2981 if ((mid
? styles
[mid
* 2 - 1] : 0) >= ch
) after
= mid
;
2982 else if (styles
[mid
* 2 + 1] < ch
) before
= mid
+ 1;
2983 else return styles
[mid
* 2 + 2];
2987 getModeAt: function(pos
) {
2988 var mode
= this.doc
.mode
;
2989 if (!mode
.innerMode
) return mode
;
2990 return CodeMirror
.innerMode(mode
, this.getTokenAt(pos
).state
).mode
;
2993 getHelper: function(pos
, type
) {
2994 return this.getHelpers(pos
, type
)[0];
2997 getHelpers: function(pos
, type
) {
2999 if (!helpers
.hasOwnProperty(type
)) return helpers
;
3000 var help
= helpers
[type
], mode
= this.getModeAt(pos
);
3001 if (typeof mode
[type
] == "string") {
3002 if (help
[mode
[type
]]) found
.push(help
[mode
[type
]]);
3003 } else if (mode
[type
]) {
3004 for (var i
= 0; i
< mode
[type
].length
; i
++) {
3005 var val
= help
[mode
[type
][i
]];
3006 if (val
) found
.push(val
);
3008 } else if (mode
.helperType
&& help
[mode
.helperType
]) {
3009 found
.push(help
[mode
.helperType
]);
3010 } else if (help
[mode
.name
]) {
3011 found
.push(help
[mode
.name
]);
3013 for (var i
= 0; i
< help
._global
.length
; i
++) {
3014 var cur
= help
._global
[i
];
3015 if (cur
.pred(mode
, this) && indexOf(found
, cur
.val
) == -1)
3016 found
.push(cur
.val
);
3021 getStateAfter: function(line
, precise
) {
3023 line
= clipLine(doc
, line
== null ? doc
.first
+ doc
.size
- 1: line
);
3024 return getStateBefore(this, line
+ 1, precise
);
3027 cursorCoords: function(start
, mode
) {
3028 var pos
, sel
= this.doc
.sel
;
3029 if (start
== null) pos
= sel
.head
;
3030 else if (typeof start
== "object") pos
= clipPos(this.doc
, start
);
3031 else pos
= start
? sel
.from : sel
.to
;
3032 return cursorCoords(this, pos
, mode
|| "page");
3035 charCoords: function(pos
, mode
) {
3036 return charCoords(this, clipPos(this.doc
, pos
), mode
|| "page");
3039 coordsChar: function(coords
, mode
) {
3040 coords
= fromCoordSystem(this, coords
, mode
|| "page");
3041 return coordsChar(this, coords
.left
, coords
.top
);
3044 lineAtHeight: function(height
, mode
) {
3045 height
= fromCoordSystem(this, {top
: height
, left
: 0}, mode
|| "page").top
;
3046 return lineAtHeight(this.doc
, height
+ this.display
.viewOffset
);
3048 heightAtLine: function(line
, mode
) {
3049 var end
= false, last
= this.doc
.first
+ this.doc
.size
- 1;
3050 if (line
< this.doc
.first
) line
= this.doc
.first
;
3051 else if (line
> last
) { line
= last
; end
= true; }
3052 var lineObj
= getLine(this.doc
, line
);
3053 return intoCoordSystem(this, getLine(this.doc
, line
), {top
: 0, left
: 0}, mode
|| "page").top
+
3054 (end
? lineObj
.height
: 0);
3057 defaultTextHeight: function() { return textHeight(this.display
); },
3058 defaultCharWidth: function() { return charWidth(this.display
); },
3060 setGutterMarker
: operation(null, function(line
, gutterID
, value
) {
3061 return changeLine(this, line
, function(line
) {
3062 var markers
= line
.gutterMarkers
|| (line
.gutterMarkers
= {});
3063 markers
[gutterID
] = value
;
3064 if (!value
&& isEmpty(markers
)) line
.gutterMarkers
= null;
3069 clearGutter
: operation(null, function(gutterID
) {
3070 var cm
= this, doc
= cm
.doc
, i
= doc
.first
;
3071 doc
.iter(function(line
) {
3072 if (line
.gutterMarkers
&& line
.gutterMarkers
[gutterID
]) {
3073 line
.gutterMarkers
[gutterID
] = null;
3074 regChange(cm
, i
, i
+ 1);
3075 if (isEmpty(line
.gutterMarkers
)) line
.gutterMarkers
= null;
3081 addLineClass
: operation(null, function(handle
, where
, cls
) {
3082 return changeLine(this, handle
, function(line
) {
3083 var prop
= where
== "text" ? "textClass" : where
== "background" ? "bgClass" : "wrapClass";
3084 if (!line
[prop
]) line
[prop
] = cls
;
3085 else if (new RegExp("(?:^|\\s)" + cls
+ "(?:$|\\s)").test(line
[prop
])) return false;
3086 else line
[prop
] += " " + cls
;
3091 removeLineClass
: operation(null, function(handle
, where
, cls
) {
3092 return changeLine(this, handle
, function(line
) {
3093 var prop
= where
== "text" ? "textClass" : where
== "background" ? "bgClass" : "wrapClass";
3094 var cur
= line
[prop
];
3095 if (!cur
) return false;
3096 else if (cls
== null) line
[prop
] = null;
3098 var found
= cur
.match(new RegExp("(?:^|\\s+)" + cls
+ "(?:$|\\s+)"));
3099 if (!found
) return false;
3100 var end
= found
.index
+ found
[0].length
;
3101 line
[prop
] = cur
.slice(0, found
.index
) + (!found
.index
|| end
== cur
.length
? "" : " ") + cur
.slice(end
) || null;
3107 addLineWidget
: operation(null, function(handle
, node
, options
) {
3108 return addLineWidget(this, handle
, node
, options
);
3111 removeLineWidget: function(widget
) { widget
.clear(); },
3113 lineInfo: function(line
) {
3114 if (typeof line
== "number") {
3115 if (!isLine(this.doc
, line
)) return null;
3117 line
= getLine(this.doc
, line
);
3118 if (!line
) return null;
3120 var n
= lineNo(line
);
3121 if (n
== null) return null;
3123 return {line
: n
, handle
: line
, text
: line
.text
, gutterMarkers
: line
.gutterMarkers
,
3124 textClass
: line
.textClass
, bgClass
: line
.bgClass
, wrapClass
: line
.wrapClass
,
3125 widgets
: line
.widgets
};
3128 getViewport: function() { return {from: this.display
.showingFrom
, to
: this.display
.showingTo
};},
3130 addWidget: function(pos
, node
, scroll
, vert
, horiz
) {
3131 var display
= this.display
;
3132 pos
= cursorCoords(this, clipPos(this.doc
, pos
));
3133 var top
= pos
.bottom
, left
= pos
.left
;
3134 node
.style
.position
= "absolute";
3135 display
.sizer
.appendChild(node
);
3136 if (vert
== "over") {
3138 } else if (vert
== "above" || vert
== "near") {
3139 var vspace
= Math
.max(display
.wrapper
.clientHeight
, this.doc
.height
),
3140 hspace
= Math
.max(display
.sizer
.clientWidth
, display
.lineSpace
.clientWidth
);
3141 // Default to positioning above (if specified and possible); otherwise default to positioning below
3142 if ((vert
== 'above' || pos
.bottom
+ node
.offsetHeight
> vspace
) && pos
.top
> node
.offsetHeight
)
3143 top
= pos
.top
- node
.offsetHeight
;
3144 else if (pos
.bottom
+ node
.offsetHeight
<= vspace
)
3146 if (left
+ node
.offsetWidth
> hspace
)
3147 left
= hspace
- node
.offsetWidth
;
3149 node
.style
.top
= top
+ "px";
3150 node
.style
.left
= node
.style
.right
= "";
3151 if (horiz
== "right") {
3152 left
= display
.sizer
.clientWidth
- node
.offsetWidth
;
3153 node
.style
.right
= "0px";
3155 if (horiz
== "left") left
= 0;
3156 else if (horiz
== "middle") left
= (display
.sizer
.clientWidth
- node
.offsetWidth
) / 2;
3157 node
.style
.left
= left
+ "px";
3160 scrollIntoView(this, left
, top
, left
+ node
.offsetWidth
, top
+ node
.offsetHeight
);
3163 triggerOnKeyDown
: operation(null, onKeyDown
),
3164 triggerOnKeyPress
: operation(null, onKeyPress
),
3165 triggerOnKeyUp
: operation(null, onKeyUp
),
3167 execCommand: function(cmd
) {
3168 if (commands
.hasOwnProperty(cmd
))
3169 return commands
[cmd
](this);
3172 findPosH: function(from, amount
, unit
, visually
) {
3174 if (amount
< 0) { dir
= -1; amount
= -amount
; }
3175 for (var i
= 0, cur
= clipPos(this.doc
, from); i
< amount
; ++i
) {
3176 cur
= findPosH(this.doc
, cur
, dir
, unit
, visually
);
3177 if (cur
.hitSide
) break;
3182 moveH
: operation(null, function(dir
, unit
) {
3183 var sel
= this.doc
.sel
, pos
;
3184 if (sel
.shift
|| sel
.extend
|| posEq(sel
.from, sel
.to
))
3185 pos
= findPosH(this.doc
, sel
.head
, dir
, unit
, this.options
.rtlMoveVisually
);
3187 pos
= dir
< 0 ? sel
.from : sel
.to
;
3188 extendSelection(this.doc
, pos
, pos
, dir
);
3191 deleteH
: operation(null, function(dir
, unit
) {
3192 var sel
= this.doc
.sel
;
3193 if (!posEq(sel
.from, sel
.to
)) replaceRange(this.doc
, "", sel
.from, sel
.to
, "+delete");
3194 else replaceRange(this.doc
, "", sel
.from, findPosH(this.doc
, sel
.head
, dir
, unit
, false), "+delete");
3195 this.curOp
.userSelChange
= true;
3198 findPosV: function(from, amount
, unit
, goalColumn
) {
3199 var dir
= 1, x
= goalColumn
;
3200 if (amount
< 0) { dir
= -1; amount
= -amount
; }
3201 for (var i
= 0, cur
= clipPos(this.doc
, from); i
< amount
; ++i
) {
3202 var coords
= cursorCoords(this, cur
, "div");
3203 if (x
== null) x
= coords
.left
;
3204 else coords
.left
= x
;
3205 cur
= findPosV(this, coords
, dir
, unit
);
3206 if (cur
.hitSide
) break;
3211 moveV
: operation(null, function(dir
, unit
) {
3212 var sel
= this.doc
.sel
, target
, goal
;
3213 if (sel
.shift
|| sel
.extend
|| posEq(sel
.from, sel
.to
)) {
3214 var pos
= cursorCoords(this, sel
.head
, "div");
3215 if (sel
.goalColumn
!= null) pos
.left
= sel
.goalColumn
;
3216 target
= findPosV(this, pos
, dir
, unit
);
3217 if (unit
== "page") addToScrollPos(this, 0, charCoords(this, target
, "div").top
- pos
.top
);
3220 target
= dir
< 0 ? sel
.from : sel
.to
;
3222 extendSelection(this.doc
, target
, target
, dir
);
3223 if (goal
!= null) sel
.goalColumn
= goal
;
3226 toggleOverwrite: function(value
) {
3227 if (value
!= null && value
== this.state
.overwrite
) return;
3228 if (this.state
.overwrite
= !this.state
.overwrite
)
3229 this.display
.cursor
.className
+= " CodeMirror-overwrite";
3231 this.display
.cursor
.className
= this.display
.cursor
.className
.replace(" CodeMirror-overwrite", "");
3233 signal(this, "overwriteToggle", this, this.state
.overwrite
);
3235 hasFocus: function() { return document
.activeElement
== this.display
.input
; },
3237 scrollTo
: operation(null, function(x
, y
) {
3238 updateScrollPos(this, x
, y
);
3240 getScrollInfo: function() {
3241 var scroller
= this.display
.scroller
, co
= scrollerCutOff
;
3242 return {left
: scroller
.scrollLeft
, top
: scroller
.scrollTop
,
3243 height
: scroller
.scrollHeight
- co
, width
: scroller
.scrollWidth
- co
,
3244 clientHeight
: scroller
.clientHeight
- co
, clientWidth
: scroller
.clientWidth
- co
};
3247 scrollIntoView
: operation(null, function(range
, margin
) {
3248 if (range
== null) range
= {from: this.doc
.sel
.head
, to
: null};
3249 else if (typeof range
== "number") range
= {from: Pos(range
, 0), to
: null};
3250 else if (range
.from == null) range
= {from: range
, to
: null};
3251 if (!range
.to
) range
.to
= range
.from;
3252 if (!margin
) margin
= 0;
3255 if (range
.from.line
!= null) {
3256 this.curOp
.scrollToPos
= {from: range
.from, to
: range
.to
, margin
: margin
};
3257 coords
= {from: cursorCoords(this, range
.from),
3258 to
: cursorCoords(this, range
.to
)};
3260 var sPos
= calculateScrollPos(this, Math
.min(coords
.from.left
, coords
.to
.left
),
3261 Math
.min(coords
.from.top
, coords
.to
.top
) - margin
,
3262 Math
.max(coords
.from.right
, coords
.to
.right
),
3263 Math
.max(coords
.from.bottom
, coords
.to
.bottom
) + margin
);
3264 updateScrollPos(this, sPos
.scrollLeft
, sPos
.scrollTop
);
3267 setSize
: operation(null, function(width
, height
) {
3268 function interpret(val
) {
3269 return typeof val
== "number" || /^\d+$/.test(String(val
)) ? val
+ "px" : val
;
3271 if (width
!= null) this.display
.wrapper
.style
.width
= interpret(width
);
3272 if (height
!= null) this.display
.wrapper
.style
.height
= interpret(height
);
3273 if (this.options
.lineWrapping
)
3274 this.display
.measureLineCache
.length
= this.display
.measureLineCachePos
= 0;
3275 this.curOp
.forceUpdate
= true;
3276 signal(this, "refresh", this);
3279 operation: function(f
){return runInOp(this, f
);},
3281 refresh
: operation(null, function() {
3282 var oldHeight
= this.display
.cachedTextHeight
;
3284 updateScrollPos(this, this.doc
.scrollLeft
, this.doc
.scrollTop
);
3286 if (oldHeight
== null || Math
.abs(oldHeight
- textHeight(this.display
)) > .5)
3287 estimateLineHeights(this);
3288 signal(this, "refresh", this);
3291 swapDoc
: operation(null, function(doc
) {
3294 attachDoc(this, doc
);
3296 resetInput(this, true);
3297 updateScrollPos(this, doc
.scrollLeft
, doc
.scrollTop
);
3298 signalLater(this, "swapDoc", this, old
);
3302 getInputField: function(){return this.display
.input
;},
3303 getWrapperElement: function(){return this.display
.wrapper
;},
3304 getScrollerElement: function(){return this.display
.scroller
;},
3305 getGutterElement: function(){return this.display
.gutters
;}
3307 eventMixin(CodeMirror
);
3311 var optionHandlers
= CodeMirror
.optionHandlers
= {};
3313 // The default configuration options.
3314 var defaults
= CodeMirror
.defaults
= {};
3316 function option(name
, deflt
, handle
, notOnInit
) {
3317 CodeMirror
.defaults
[name
] = deflt
;
3318 if (handle
) optionHandlers
[name
] =
3319 notOnInit
? function(cm
, val
, old
) {if (old
!= Init
) handle(cm
, val
, old
);} : handle
;
3322 var Init
= CodeMirror
.Init
= {toString: function(){return "CodeMirror.Init";}};
3324 // These two are, on init, called from the constructor because they
3325 // have to be initialized before the editor can start at all.
3326 option("value", "", function(cm
, val
) {
3329 option("mode", null, function(cm
, val
) {
3330 cm
.doc
.modeOption
= val
;
3334 option("indentUnit", 2, loadMode
, true);
3335 option("indentWithTabs", false);
3336 option("smartIndent", true);
3337 option("tabSize", 4, function(cm
) {
3342 option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm
, val
) {
3343 cm
.options
.specialChars
= new RegExp(val
.source
+ (val
.test("\t") ? "" : "|\t"), "g");
3346 option("specialCharPlaceholder", defaultSpecialCharPlaceholder
, function(cm
) {cm
.refresh();}, true);
3347 option("electricChars", true);
3348 option("rtlMoveVisually", !windows
);
3349 option("wholeLineUpdateBefore", true);
3351 option("theme", "default", function(cm
) {
3355 option("keyMap", "default", keyMapChanged
);
3356 option("extraKeys", null);
3358 option("onKeyEvent", null);
3359 option("onDragEvent", null);
3361 option("lineWrapping", false, wrappingChanged
, true);
3362 option("gutters", [], function(cm
) {
3363 setGuttersForLineNumbers(cm
.options
);
3366 option("fixedGutter", true, function(cm
, val
) {
3367 cm
.display
.gutters
.style
.left
= val
? compensateForHScroll(cm
.display
) + "px" : "0";
3370 option("coverGutterNextToScrollbar", false, updateScrollbars
, true);
3371 option("lineNumbers", false, function(cm
) {
3372 setGuttersForLineNumbers(cm
.options
);
3375 option("firstLineNumber", 1, guttersChanged
, true);
3376 option("lineNumberFormatter", function(integer
) {return integer
;}, guttersChanged
, true);
3377 option("showCursorWhenSelecting", false, updateSelection
, true);
3379 option("resetSelectionOnContextMenu", true);
3381 option("readOnly", false, function(cm
, val
) {
3382 if (val
== "nocursor") {
3384 cm
.display
.input
.blur();
3385 cm
.display
.disabled
= true;
3387 cm
.display
.disabled
= false;
3388 if (!val
) resetInput(cm
, true);
3391 option("disableInput", false, function(cm
, val
) {if (!val
) resetInput(cm
, true);}, true);
3392 option("dragDrop", true);
3394 option("cursorBlinkRate", 530);
3395 option("cursorScrollMargin", 0);
3396 option("cursorHeight", 1);
3397 option("workTime", 100);
3398 option("workDelay", 100);
3399 option("flattenSpans", true, resetModeState
, true);
3400 option("addModeClass", false, resetModeState
, true);
3401 option("pollInterval", 100);
3402 option("undoDepth", 40, function(cm
, val
){cm
.doc
.history
.undoDepth
= val
;});
3403 option("historyEventDelay", 500);
3404 option("viewportMargin", 10, function(cm
){cm
.refresh();}, true);
3405 option("maxHighlightLength", 10000, resetModeState
, true);
3406 option("crudeMeasuringFrom", 10000);
3407 option("moveInputWithCursor", true, function(cm
, val
) {
3408 if (!val
) cm
.display
.inputDiv
.style
.top
= cm
.display
.inputDiv
.style
.left
= 0;
3411 option("tabindex", null, function(cm
, val
) {
3412 cm
.display
.input
.tabIndex
= val
|| "";
3414 option("autofocus", null);
3416 // MODE DEFINITION AND QUERYING
3418 // Known modes, by name and by MIME
3419 var modes
= CodeMirror
.modes
= {}, mimeModes
= CodeMirror
.mimeModes
= {};
3421 CodeMirror
.defineMode = function(name
, mode
) {
3422 if (!CodeMirror
.defaults
.mode
&& name
!= "null") CodeMirror
.defaults
.mode
= name
;
3423 if (arguments
.length
> 2) {
3424 mode
.dependencies
= [];
3425 for (var i
= 2; i
< arguments
.length
; ++i
) mode
.dependencies
.push(arguments
[i
]);
3430 CodeMirror
.defineMIME = function(mime
, spec
) {
3431 mimeModes
[mime
] = spec
;
3434 CodeMirror
.resolveMode = function(spec
) {
3435 if (typeof spec
== "string" && mimeModes
.hasOwnProperty(spec
)) {
3436 spec
= mimeModes
[spec
];
3437 } else if (spec
&& typeof spec
.name
== "string" && mimeModes
.hasOwnProperty(spec
.name
)) {
3438 var found
= mimeModes
[spec
.name
];
3439 spec
= createObj(found
, spec
);
3440 spec
.name
= found
.name
;
3441 } else if (typeof spec
== "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec
)) {
3442 return CodeMirror
.resolveMode("application/xml");
3444 if (typeof spec
== "string") return {name
: spec
};
3445 else return spec
|| {name
: "null"};
3448 CodeMirror
.getMode = function(options
, spec
) {
3449 var spec
= CodeMirror
.resolveMode(spec
);
3450 var mfactory
= modes
[spec
.name
];
3451 if (!mfactory
) return CodeMirror
.getMode(options
, "text/plain");
3452 var modeObj
= mfactory(options
, spec
);
3453 if (modeExtensions
.hasOwnProperty(spec
.name
)) {
3454 var exts
= modeExtensions
[spec
.name
];
3455 for (var prop
in exts
) {
3456 if (!exts
.hasOwnProperty(prop
)) continue;
3457 if (modeObj
.hasOwnProperty(prop
)) modeObj
["_" + prop
] = modeObj
[prop
];
3458 modeObj
[prop
] = exts
[prop
];
3461 modeObj
.name
= spec
.name
;
3462 if (spec
.helperType
) modeObj
.helperType
= spec
.helperType
;
3463 if (spec
.modeProps
) for (var prop
in spec
.modeProps
)
3464 modeObj
[prop
] = spec
.modeProps
[prop
];
3469 CodeMirror
.defineMode("null", function() {
3470 return {token: function(stream
) {stream
.skipToEnd();}};
3472 CodeMirror
.defineMIME("text/plain", "null");
3474 var modeExtensions
= CodeMirror
.modeExtensions
= {};
3475 CodeMirror
.extendMode = function(mode
, properties
) {
3476 var exts
= modeExtensions
.hasOwnProperty(mode
) ? modeExtensions
[mode
] : (modeExtensions
[mode
] = {});
3477 copyObj(properties
, exts
);
3482 CodeMirror
.defineExtension = function(name
, func
) {
3483 CodeMirror
.prototype[name
] = func
;
3485 CodeMirror
.defineDocExtension = function(name
, func
) {
3486 Doc
.prototype[name
] = func
;
3488 CodeMirror
.defineOption
= option
;
3491 CodeMirror
.defineInitHook = function(f
) {initHooks
.push(f
);};
3493 var helpers
= CodeMirror
.helpers
= {};
3494 CodeMirror
.registerHelper = function(type
, name
, value
) {
3495 if (!helpers
.hasOwnProperty(type
)) helpers
[type
] = CodeMirror
[type
] = {_global
: []};
3496 helpers
[type
][name
] = value
;
3498 CodeMirror
.registerGlobalHelper = function(type
, name
, predicate
, value
) {
3499 CodeMirror
.registerHelper(type
, name
, value
);
3500 helpers
[type
]._global
.push({pred
: predicate
, val
: value
});
3505 CodeMirror
.isWordChar
= isWordChar
;
3507 // MODE STATE HANDLING
3509 // Utility functions for working with state. Exported because modes
3510 // sometimes need to do this.
3511 function copyState(mode
, state
) {
3512 if (state
=== true) return state
;
3513 if (mode
.copyState
) return mode
.copyState(state
);
3515 for (var n
in state
) {
3517 if (val
instanceof Array
) val
= val
.concat([]);
3522 CodeMirror
.copyState
= copyState
;
3524 function startState(mode
, a1
, a2
) {
3525 return mode
.startState
? mode
.startState(a1
, a2
) : true;
3527 CodeMirror
.startState
= startState
;
3529 CodeMirror
.innerMode = function(mode
, state
) {
3530 while (mode
.innerMode
) {
3531 var info
= mode
.innerMode(state
);
3532 if (!info
|| info
.mode
== mode
) break;
3536 return info
|| {mode
: mode
, state
: state
};
3539 // STANDARD COMMANDS
3541 var commands
= CodeMirror
.commands
= {
3542 selectAll: function(cm
) {cm
.setSelection(Pos(cm
.firstLine(), 0), Pos(cm
.lastLine()));},
3543 killLine: function(cm
) {
3544 var from = cm
.getCursor(true), to
= cm
.getCursor(false), sel
= !posEq(from, to
);
3545 if (!sel
&& cm
.getLine(from.line
).length
== from.ch
)
3546 cm
.replaceRange("", from, Pos(from.line
+ 1, 0), "+delete");
3547 else cm
.replaceRange("", from, sel
? to
: Pos(from.line
), "+delete");
3549 deleteLine: function(cm
) {
3550 var l
= cm
.getCursor().line
;
3551 cm
.replaceRange("", Pos(l
, 0), Pos(l
), "+delete");
3553 delLineLeft: function(cm
) {
3554 var cur
= cm
.getCursor();
3555 cm
.replaceRange("", Pos(cur
.line
, 0), cur
, "+delete");
3557 undo: function(cm
) {cm
.undo();},
3558 redo: function(cm
) {cm
.redo();},
3559 goDocStart: function(cm
) {cm
.extendSelection(Pos(cm
.firstLine(), 0));},
3560 goDocEnd: function(cm
) {cm
.extendSelection(Pos(cm
.lastLine()));},
3561 goLineStart: function(cm
) {
3562 cm
.extendSelection(lineStart(cm
, cm
.getCursor().line
));
3564 goLineStartSmart: function(cm
) {
3565 var cur
= cm
.getCursor(), start
= lineStart(cm
, cur
.line
);
3566 var line
= cm
.getLineHandle(start
.line
);
3567 var order
= getOrder(line
);
3568 if (!order
|| order
[0].level
== 0) {
3569 var firstNonWS
= Math
.max(0, line
.text
.search(/\S/));
3570 var inWS
= cur
.line
== start
.line
&& cur
.ch
<= firstNonWS
&& cur
.ch
;
3571 cm
.extendSelection(Pos(start
.line
, inWS
? 0 : firstNonWS
));
3572 } else cm
.extendSelection(start
);
3574 goLineEnd: function(cm
) {
3575 cm
.extendSelection(lineEnd(cm
, cm
.getCursor().line
));
3577 goLineRight: function(cm
) {
3578 var top
= cm
.charCoords(cm
.getCursor(), "div").top
+ 5;
3579 cm
.extendSelection(cm
.coordsChar({left
: cm
.display
.lineDiv
.offsetWidth
+ 100, top
: top
}, "div"));
3581 goLineLeft: function(cm
) {
3582 var top
= cm
.charCoords(cm
.getCursor(), "div").top
+ 5;
3583 cm
.extendSelection(cm
.coordsChar({left
: 0, top
: top
}, "div"));
3585 goLineUp: function(cm
) {cm
.moveV(-1, "line");},
3586 goLineDown: function(cm
) {cm
.moveV(1, "line");},
3587 goPageUp: function(cm
) {cm
.moveV(-1, "page");},
3588 goPageDown: function(cm
) {cm
.moveV(1, "page");},
3589 goCharLeft: function(cm
) {cm
.moveH(-1, "char");},
3590 goCharRight: function(cm
) {cm
.moveH(1, "char");},
3591 goColumnLeft: function(cm
) {cm
.moveH(-1, "column");},
3592 goColumnRight: function(cm
) {cm
.moveH(1, "column");},
3593 goWordLeft: function(cm
) {cm
.moveH(-1, "word");},
3594 goGroupRight: function(cm
) {cm
.moveH(1, "group");},
3595 goGroupLeft: function(cm
) {cm
.moveH(-1, "group");},
3596 goWordRight: function(cm
) {cm
.moveH(1, "word");},
3597 delCharBefore: function(cm
) {cm
.deleteH(-1, "char");},
3598 delCharAfter: function(cm
) {cm
.deleteH(1, "char");},
3599 delWordBefore: function(cm
) {cm
.deleteH(-1, "word");},
3600 delWordAfter: function(cm
) {cm
.deleteH(1, "word");},
3601 delGroupBefore: function(cm
) {cm
.deleteH(-1, "group");},
3602 delGroupAfter: function(cm
) {cm
.deleteH(1, "group");},
3603 indentAuto: function(cm
) {cm
.indentSelection("smart");},
3604 indentMore: function(cm
) {cm
.indentSelection("add");},
3605 indentLess: function(cm
) {cm
.indentSelection("subtract");},
3606 insertTab: function(cm
) {
3607 cm
.replaceSelection("\t", "end", "+input");
3609 defaultTab: function(cm
) {
3610 if (cm
.somethingSelected()) cm
.indentSelection("add");
3611 else cm
.replaceSelection("\t", "end", "+input");
3613 transposeChars: function(cm
) {
3614 var cur
= cm
.getCursor(), line
= cm
.getLine(cur
.line
);
3615 if (cur
.ch
> 0 && cur
.ch
< line
.length
- 1)
3616 cm
.replaceRange(line
.charAt(cur
.ch
) + line
.charAt(cur
.ch
- 1),
3617 Pos(cur
.line
, cur
.ch
- 1), Pos(cur
.line
, cur
.ch
+ 1));
3619 newlineAndIndent: function(cm
) {
3620 operation(cm
, function() {
3621 cm
.replaceSelection("\n", "end", "+input");
3622 cm
.indentLine(cm
.getCursor().line
, null, true);
3625 toggleOverwrite: function(cm
) {cm
.toggleOverwrite();}
3630 var keyMap
= CodeMirror
.keyMap
= {};
3632 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
3633 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
3634 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
3635 "Tab": "defaultTab", "Shift-Tab": "indentAuto",
3636 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
3638 // Note that the save and find-related commands aren't defined by
3639 // default. Unknown commands are simply ignored.
3640 keyMap
.pcDefault
= {
3641 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
3642 "Ctrl-Home": "goDocStart", "Ctrl-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
3643 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
3644 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
3645 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
3646 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
3647 fallthrough
: "basic"
3649 keyMap
.macDefault
= {
3650 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
3651 "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
3652 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore",
3653 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
3654 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
3655 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft",
3656 fallthrough
: ["basic", "emacsy"]
3658 keyMap
["default"] = mac
? keyMap
.macDefault
: keyMap
.pcDefault
;
3660 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
3661 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
3662 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
3663 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
3668 function getKeyMap(val
) {
3669 if (typeof val
== "string") return keyMap
[val
];
3673 function lookupKey(name
, maps
, handle
) {
3674 function lookup(map
) {
3675 map
= getKeyMap(map
);
3676 var found
= map
[name
];
3677 if (found
=== false) return "stop";
3678 if (found
!= null && handle(found
)) return true;
3679 if (map
.nofallthrough
) return "stop";
3681 var fallthrough
= map
.fallthrough
;
3682 if (fallthrough
== null) return false;
3683 if (Object
.prototype.toString
.call(fallthrough
) != "[object Array]")
3684 return lookup(fallthrough
);
3685 for (var i
= 0, e
= fallthrough
.length
; i
< e
; ++i
) {
3686 var done
= lookup(fallthrough
[i
]);
3687 if (done
) return done
;
3692 for (var i
= 0; i
< maps
.length
; ++i
) {
3693 var done
= lookup(maps
[i
]);
3694 if (done
) return done
!= "stop";
3697 function isModifierKey(event
) {
3698 var name
= keyNames
[event
.keyCode
];
3699 return name
== "Ctrl" || name
== "Alt" || name
== "Shift" || name
== "Mod";
3701 function keyName(event
, noShift
) {
3702 if (opera
&& event
.keyCode
== 34 && event
["char"]) return false;
3703 var name
= keyNames
[event
.keyCode
];
3704 if (name
== null || event
.altGraphKey
) return false;
3705 if (event
.altKey
) name
= "Alt-" + name
;
3706 if (flipCtrlCmd
? event
.metaKey
: event
.ctrlKey
) name
= "Ctrl-" + name
;
3707 if (flipCtrlCmd
? event
.ctrlKey
: event
.metaKey
) name
= "Cmd-" + name
;
3708 if (!noShift
&& event
.shiftKey
) name
= "Shift-" + name
;
3711 CodeMirror
.lookupKey
= lookupKey
;
3712 CodeMirror
.isModifierKey
= isModifierKey
;
3713 CodeMirror
.keyName
= keyName
;
3717 CodeMirror
.fromTextArea = function(textarea
, options
) {
3718 if (!options
) options
= {};
3719 options
.value
= textarea
.value
;
3720 if (!options
.tabindex
&& textarea
.tabindex
)
3721 options
.tabindex
= textarea
.tabindex
;
3722 if (!options
.placeholder
&& textarea
.placeholder
)
3723 options
.placeholder
= textarea
.placeholder
;
3724 // Set autofocus to true if this textarea is focused, or if it has
3725 // autofocus and no other element is focused.
3726 if (options
.autofocus
== null) {
3727 var hasFocus
= document
.body
;
3728 // doc.activeElement occasionally throws on IE
3729 try { hasFocus
= document
.activeElement
; } catch(e
) {}
3730 options
.autofocus
= hasFocus
== textarea
||
3731 textarea
.getAttribute("autofocus") != null && hasFocus
== document
.body
;
3734 function save() {textarea
.value
= cm
.getValue();}
3735 if (textarea
.form
) {
3736 on(textarea
.form
, "submit", save
);
3737 // Deplorable hack to make the submit method do the right thing.
3738 if (!options
.leaveSubmitMethodAlone
) {
3739 var form
= textarea
.form
, realSubmit
= form
.submit
;
3741 var wrappedSubmit
= form
.submit = function() {
3743 form
.submit
= realSubmit
;
3745 form
.submit
= wrappedSubmit
;
3751 textarea
.style
.display
= "none";
3752 var cm
= CodeMirror(function(node
) {
3753 textarea
.parentNode
.insertBefore(node
, textarea
.nextSibling
);
3756 cm
.getTextArea = function() { return textarea
; };
3757 cm
.toTextArea = function() {
3759 textarea
.parentNode
.removeChild(cm
.getWrapperElement());
3760 textarea
.style
.display
= "";
3761 if (textarea
.form
) {
3762 off(textarea
.form
, "submit", save
);
3763 if (typeof textarea
.form
.submit
== "function")
3764 textarea
.form
.submit
= realSubmit
;
3772 // Fed to the mode parsers, provides helper functions to make
3773 // parsers more succinct.
3775 // The character stream used by a mode's parser.
3776 function StringStream(string
, tabSize
) {
3777 this.pos
= this.start
= 0;
3778 this.string
= string
;
3779 this.tabSize
= tabSize
|| 8;
3780 this.lastColumnPos
= this.lastColumnValue
= 0;
3784 StringStream
.prototype = {
3785 eol: function() {return this.pos
>= this.string
.length
;},
3786 sol: function() {return this.pos
== this.lineStart
;},
3787 peek: function() {return this.string
.charAt(this.pos
) || undefined;},
3789 if (this.pos
< this.string
.length
)
3790 return this.string
.charAt(this.pos
++);
3792 eat: function(match
) {
3793 var ch
= this.string
.charAt(this.pos
);
3794 if (typeof match
== "string") var ok
= ch
== match
;
3795 else var ok
= ch
&& (match
.test
? match
.test(ch
) : match(ch
));
3796 if (ok
) {++this.pos
; return ch
;}
3798 eatWhile: function(match
) {
3799 var start
= this.pos
;
3800 while (this.eat(match
)){}
3801 return this.pos
> start
;
3803 eatSpace: function() {
3804 var start
= this.pos
;
3805 while (/[\s\u00a0]/.test(this.string
.charAt(this.pos
))) ++this.pos
;
3806 return this.pos
> start
;
3808 skipToEnd: function() {this.pos
= this.string
.length
;},
3809 skipTo: function(ch
) {
3810 var found
= this.string
.indexOf(ch
, this.pos
);
3811 if (found
> -1) {this.pos
= found
; return true;}
3813 backUp: function(n
) {this.pos
-= n
;},
3814 column: function() {
3815 if (this.lastColumnPos
< this.start
) {
3816 this.lastColumnValue
= countColumn(this.string
, this.start
, this.tabSize
, this.lastColumnPos
, this.lastColumnValue
);
3817 this.lastColumnPos
= this.start
;
3819 return this.lastColumnValue
- (this.lineStart
? countColumn(this.string
, this.lineStart
, this.tabSize
) : 0);
3821 indentation: function() {
3822 return countColumn(this.string
, null, this.tabSize
) -
3823 (this.lineStart
? countColumn(this.string
, this.lineStart
, this.tabSize
) : 0);
3825 match: function(pattern
, consume
, caseInsensitive
) {
3826 if (typeof pattern
== "string") {
3827 var cased = function(str
) {return caseInsensitive
? str
.toLowerCase() : str
;};
3828 var substr
= this.string
.substr(this.pos
, pattern
.length
);
3829 if (cased(substr
) == cased(pattern
)) {
3830 if (consume
!== false) this.pos
+= pattern
.length
;
3834 var match
= this.string
.slice(this.pos
).match(pattern
);
3835 if (match
&& match
.index
> 0) return null;
3836 if (match
&& consume
!== false) this.pos
+= match
[0].length
;
3840 current: function(){return this.string
.slice(this.start
, this.pos
);},
3841 hideFirstChars: function(n
, inner
) {
3842 this.lineStart
+= n
;
3843 try { return inner(); }
3844 finally { this.lineStart
-= n
; }
3847 CodeMirror
.StringStream
= StringStream
;
3851 function TextMarker(doc
, type
) {
3856 CodeMirror
.TextMarker
= TextMarker
;
3857 eventMixin(TextMarker
);
3859 TextMarker
.prototype.clear = function() {
3860 if (this.explicitlyCleared
) return;
3861 var cm
= this.doc
.cm
, withOp
= cm
&& !cm
.curOp
;
3862 if (withOp
) startOperation(cm
);
3863 if (hasHandler(this, "clear")) {
3864 var found
= this.find();
3865 if (found
) signalLater(this, "clear", found
.from, found
.to
);
3867 var min
= null, max
= null;
3868 for (var i
= 0; i
< this.lines
.length
; ++i
) {
3869 var line
= this.lines
[i
];
3870 var span
= getMarkedSpanFor(line
.markedSpans
, this);
3871 if (span
.to
!= null) max
= lineNo(line
);
3872 line
.markedSpans
= removeMarkedSpan(line
.markedSpans
, span
);
3873 if (span
.from != null)
3875 else if (this.collapsed
&& !lineIsHidden(this.doc
, line
) && cm
)
3876 updateLineHeight(line
, textHeight(cm
.display
));
3878 if (cm
&& this.collapsed
&& !cm
.options
.lineWrapping
) for (var i
= 0; i
< this.lines
.length
; ++i
) {
3879 var visual
= visualLine(cm
.doc
, this.lines
[i
]), len
= lineLength(cm
.doc
, visual
);
3880 if (len
> cm
.display
.maxLineLength
) {
3881 cm
.display
.maxLine
= visual
;
3882 cm
.display
.maxLineLength
= len
;
3883 cm
.display
.maxLineChanged
= true;
3887 if (min
!= null && cm
) regChange(cm
, min
, max
+ 1);
3888 this.lines
.length
= 0;
3889 this.explicitlyCleared
= true;
3890 if (this.atomic
&& this.doc
.cantEdit
) {
3891 this.doc
.cantEdit
= false;
3892 if (cm
) reCheckSelection(cm
);
3894 if (withOp
) endOperation(cm
);
3897 TextMarker
.prototype.find = function(bothSides
) {
3899 for (var i
= 0; i
< this.lines
.length
; ++i
) {
3900 var line
= this.lines
[i
];
3901 var span
= getMarkedSpanFor(line
.markedSpans
, this);
3902 if (span
.from != null || span
.to
!= null) {
3903 var found
= lineNo(line
);
3904 if (span
.from != null) from = Pos(found
, span
.from);
3905 if (span
.to
!= null) to
= Pos(found
, span
.to
);
3908 if (this.type
== "bookmark" && !bothSides
) return from;
3909 return from && {from: from, to
: to
};
3912 TextMarker
.prototype.changed = function() {
3913 var pos
= this.find(), cm
= this.doc
.cm
;
3914 if (!pos
|| !cm
) return;
3915 if (this.type
!= "bookmark") pos
= pos
.from;
3916 var line
= getLine(this.doc
, pos
.line
);
3917 clearCachedMeasurement(cm
, line
);
3918 if (pos
.line
>= cm
.display
.showingFrom
&& pos
.line
< cm
.display
.showingTo
) {
3919 for (var node
= cm
.display
.lineDiv
.firstChild
; node
; node
= node
.nextSibling
) if (node
.lineObj
== line
) {
3920 if (node
.offsetHeight
!= line
.height
) updateLineHeight(line
, node
.offsetHeight
);
3923 runInOp(cm
, function() {
3924 cm
.curOp
.selectionChanged
= cm
.curOp
.forceUpdate
= cm
.curOp
.updateMaxLine
= true;
3929 TextMarker
.prototype.attachLine = function(line
) {
3930 if (!this.lines
.length
&& this.doc
.cm
) {
3931 var op
= this.doc
.cm
.curOp
;
3932 if (!op
.maybeHiddenMarkers
|| indexOf(op
.maybeHiddenMarkers
, this) == -1)
3933 (op
.maybeUnhiddenMarkers
|| (op
.maybeUnhiddenMarkers
= [])).push(this);
3935 this.lines
.push(line
);
3937 TextMarker
.prototype.detachLine = function(line
) {
3938 this.lines
.splice(indexOf(this.lines
, line
), 1);
3939 if (!this.lines
.length
&& this.doc
.cm
) {
3940 var op
= this.doc
.cm
.curOp
;
3941 (op
.maybeHiddenMarkers
|| (op
.maybeHiddenMarkers
= [])).push(this);
3945 var nextMarkerId
= 0;
3947 function markText(doc
, from, to
, options
, type
) {
3948 if (options
&& options
.shared
) return markTextShared(doc
, from, to
, options
, type
);
3949 if (doc
.cm
&& !doc
.cm
.curOp
) return operation(doc
.cm
, markText
)(doc
, from, to
, options
, type
);
3951 var marker
= new TextMarker(doc
, type
);
3952 if (options
) copyObj(options
, marker
);
3953 if (posLess(to
, from) || posEq(from, to
) && marker
.clearWhenEmpty
!== false)
3955 if (marker
.replacedWith
) {
3956 marker
.collapsed
= true;
3957 marker
.replacedWith
= elt("span", [marker
.replacedWith
], "CodeMirror-widget");
3958 if (!options
.handleMouseEvents
) marker
.replacedWith
.ignoreEvents
= true;
3960 if (marker
.collapsed
) {
3961 if (conflictingCollapsedRange(doc
, from.line
, from, to
, marker
) ||
3962 from.line
!= to
.line
&& conflictingCollapsedRange(doc
, to
.line
, from, to
, marker
))
3963 throw new Error("Inserting collapsed marker partially overlapping an existing one");
3964 sawCollapsedSpans
= true;
3967 if (marker
.addToHistory
)
3968 addToHistory(doc
, {from: from, to
: to
, origin
: "markText"},
3969 {head
: doc
.sel
.head
, anchor
: doc
.sel
.anchor
}, NaN
);
3971 var curLine
= from.line
, cm
= doc
.cm
, updateMaxLine
;
3972 doc
.iter(curLine
, to
.line
+ 1, function(line
) {
3973 if (cm
&& marker
.collapsed
&& !cm
.options
.lineWrapping
&& visualLine(doc
, line
) == cm
.display
.maxLine
)
3974 updateMaxLine
= true;
3975 var span
= {from: null, to
: null, marker
: marker
};
3976 if (curLine
== from.line
) span
.from = from.ch
;
3977 if (curLine
== to
.line
) span
.to
= to
.ch
;
3978 if (marker
.collapsed
&& curLine
!= from.line
) updateLineHeight(line
, 0);
3979 addMarkedSpan(line
, span
);
3982 if (marker
.collapsed
) doc
.iter(from.line
, to
.line
+ 1, function(line
) {
3983 if (lineIsHidden(doc
, line
)) updateLineHeight(line
, 0);
3986 if (marker
.clearOnEnter
) on(marker
, "beforeCursorEnter", function() { marker
.clear(); });
3988 if (marker
.readOnly
) {
3989 sawReadOnlySpans
= true;
3990 if (doc
.history
.done
.length
|| doc
.history
.undone
.length
)
3993 if (marker
.collapsed
) {
3994 marker
.id
= ++nextMarkerId
;
3995 marker
.atomic
= true;
3998 if (updateMaxLine
) cm
.curOp
.updateMaxLine
= true;
3999 if (marker
.className
|| marker
.title
|| marker
.startStyle
|| marker
.endStyle
|| marker
.collapsed
)
4000 regChange(cm
, from.line
, to
.line
+ 1);
4001 if (marker
.atomic
) reCheckSelection(cm
);
4006 // SHARED TEXTMARKERS
4008 function SharedTextMarker(markers
, primary
) {
4009 this.markers
= markers
;
4010 this.primary
= primary
;
4011 for (var i
= 0, me
= this; i
< markers
.length
; ++i
) {
4012 markers
[i
].parent
= this;
4013 on(markers
[i
], "clear", function(){me
.clear();});
4016 CodeMirror
.SharedTextMarker
= SharedTextMarker
;
4017 eventMixin(SharedTextMarker
);
4019 SharedTextMarker
.prototype.clear = function() {
4020 if (this.explicitlyCleared
) return;
4021 this.explicitlyCleared
= true;
4022 for (var i
= 0; i
< this.markers
.length
; ++i
)
4023 this.markers
[i
].clear();
4024 signalLater(this, "clear");
4026 SharedTextMarker
.prototype.find = function() {
4027 return this.primary
.find();
4030 function markTextShared(doc
, from, to
, options
, type
) {
4031 options
= copyObj(options
);
4032 options
.shared
= false;
4033 var markers
= [markText(doc
, from, to
, options
, type
)], primary
= markers
[0];
4034 var widget
= options
.replacedWith
;
4035 linkedDocs(doc
, function(doc
) {
4036 if (widget
) options
.replacedWith
= widget
.cloneNode(true);
4037 markers
.push(markText(doc
, clipPos(doc
, from), clipPos(doc
, to
), options
, type
));
4038 for (var i
= 0; i
< doc
.linked
.length
; ++i
)
4039 if (doc
.linked
[i
].isParent
) return;
4040 primary
= lst(markers
);
4042 return new SharedTextMarker(markers
, primary
);
4047 function getMarkedSpanFor(spans
, marker
) {
4048 if (spans
) for (var i
= 0; i
< spans
.length
; ++i
) {
4049 var span
= spans
[i
];
4050 if (span
.marker
== marker
) return span
;
4053 function removeMarkedSpan(spans
, span
) {
4054 for (var r
, i
= 0; i
< spans
.length
; ++i
)
4055 if (spans
[i
] != span
) (r
|| (r
= [])).push(spans
[i
]);
4058 function addMarkedSpan(line
, span
) {
4059 line
.markedSpans
= line
.markedSpans
? line
.markedSpans
.concat([span
]) : [span
];
4060 span
.marker
.attachLine(line
);
4063 function markedSpansBefore(old
, startCh
, isInsert
) {
4064 if (old
) for (var i
= 0, nw
; i
< old
.length
; ++i
) {
4065 var span
= old
[i
], marker
= span
.marker
;
4066 var startsBefore
= span
.from == null || (marker
.inclusiveLeft
? span
.from <= startCh
: span
.from < startCh
);
4067 if (startsBefore
|| span
.from == startCh
&& marker
.type
== "bookmark" && (!isInsert
|| !span
.marker
.insertLeft
)) {
4068 var endsAfter
= span
.to
== null || (marker
.inclusiveRight
? span
.to
>= startCh
: span
.to
> startCh
);
4069 (nw
|| (nw
= [])).push({from: span
.from,
4070 to
: endsAfter
? null : span
.to
,
4077 function markedSpansAfter(old
, endCh
, isInsert
) {
4078 if (old
) for (var i
= 0, nw
; i
< old
.length
; ++i
) {
4079 var span
= old
[i
], marker
= span
.marker
;
4080 var endsAfter
= span
.to
== null || (marker
.inclusiveRight
? span
.to
>= endCh
: span
.to
> endCh
);
4081 if (endsAfter
|| span
.from == endCh
&& marker
.type
== "bookmark" && (!isInsert
|| span
.marker
.insertLeft
)) {
4082 var startsBefore
= span
.from == null || (marker
.inclusiveLeft
? span
.from <= endCh
: span
.from < endCh
);
4083 (nw
|| (nw
= [])).push({from: startsBefore
? null : span
.from - endCh
,
4084 to
: span
.to
== null ? null : span
.to
- endCh
,
4091 function stretchSpansOverChange(doc
, change
) {
4092 var oldFirst
= isLine(doc
, change
.from.line
) && getLine(doc
, change
.from.line
).markedSpans
;
4093 var oldLast
= isLine(doc
, change
.to
.line
) && getLine(doc
, change
.to
.line
).markedSpans
;
4094 if (!oldFirst
&& !oldLast
) return null;
4096 var startCh
= change
.from.ch
, endCh
= change
.to
.ch
, isInsert
= posEq(change
.from, change
.to
);
4097 // Get the spans that 'stick out' on both sides
4098 var first
= markedSpansBefore(oldFirst
, startCh
, isInsert
);
4099 var last
= markedSpansAfter(oldLast
, endCh
, isInsert
);
4101 // Next, merge those two ends
4102 var sameLine
= change
.text
.length
== 1, offset
= lst(change
.text
).length
+ (sameLine
? startCh
: 0);
4104 // Fix up .to properties of first
4105 for (var i
= 0; i
< first
.length
; ++i
) {
4106 var span
= first
[i
];
4107 if (span
.to
== null) {
4108 var found
= getMarkedSpanFor(last
, span
.marker
);
4109 if (!found
) span
.to
= startCh
;
4110 else if (sameLine
) span
.to
= found
.to
== null ? null : found
.to
+ offset
;
4115 // Fix up .from in last (or move them into first in case of sameLine)
4116 for (var i
= 0; i
< last
.length
; ++i
) {
4118 if (span
.to
!= null) span
.to
+= offset
;
4119 if (span
.from == null) {
4120 var found
= getMarkedSpanFor(first
, span
.marker
);
4123 if (sameLine
) (first
|| (first
= [])).push(span
);
4126 span
.from += offset
;
4127 if (sameLine
) (first
|| (first
= [])).push(span
);
4131 // Make sure we didn't create any zero-length spans
4132 if (first
) first
= clearEmptySpans(first
);
4133 if (last
&& last
!= first
) last
= clearEmptySpans(last
);
4135 var newMarkers
= [first
];
4137 // Fill gap with whole-line-spans
4138 var gap
= change
.text
.length
- 2, gapMarkers
;
4139 if (gap
> 0 && first
)
4140 for (var i
= 0; i
< first
.length
; ++i
)
4141 if (first
[i
].to
== null)
4142 (gapMarkers
|| (gapMarkers
= [])).push({from: null, to
: null, marker
: first
[i
].marker
});
4143 for (var i
= 0; i
< gap
; ++i
)
4144 newMarkers
.push(gapMarkers
);
4145 newMarkers
.push(last
);
4150 function clearEmptySpans(spans
) {
4151 for (var i
= 0; i
< spans
.length
; ++i
) {
4152 var span
= spans
[i
];
4153 if (span
.from != null && span
.from == span
.to
&& span
.marker
.clearWhenEmpty
!== false)
4154 spans
.splice(i
--, 1);
4156 if (!spans
.length
) return null;
4160 function mergeOldSpans(doc
, change
) {
4161 var old
= getOldSpans(doc
, change
);
4162 var stretched
= stretchSpansOverChange(doc
, change
);
4163 if (!old
) return stretched
;
4164 if (!stretched
) return old
;
4166 for (var i
= 0; i
< old
.length
; ++i
) {
4167 var oldCur
= old
[i
], stretchCur
= stretched
[i
];
4168 if (oldCur
&& stretchCur
) {
4169 spans
: for (var j
= 0; j
< stretchCur
.length
; ++j
) {
4170 var span
= stretchCur
[j
];
4171 for (var k
= 0; k
< oldCur
.length
; ++k
)
4172 if (oldCur
[k
].marker
== span
.marker
) continue spans
;
4175 } else if (stretchCur
) {
4176 old
[i
] = stretchCur
;
4182 function removeReadOnlyRanges(doc
, from, to
) {
4184 doc
.iter(from.line
, to
.line
+ 1, function(line
) {
4185 if (line
.markedSpans
) for (var i
= 0; i
< line
.markedSpans
.length
; ++i
) {
4186 var mark
= line
.markedSpans
[i
].marker
;
4187 if (mark
.readOnly
&& (!markers
|| indexOf(markers
, mark
) == -1))
4188 (markers
|| (markers
= [])).push(mark
);
4191 if (!markers
) return null;
4192 var parts
= [{from: from, to
: to
}];
4193 for (var i
= 0; i
< markers
.length
; ++i
) {
4194 var mk
= markers
[i
], m
= mk
.find();
4195 for (var j
= 0; j
< parts
.length
; ++j
) {
4197 if (posLess(p
.to
, m
.from) || posLess(m
.to
, p
.from)) continue;
4198 var newParts
= [j
, 1];
4199 if (posLess(p
.from, m
.from) || !mk
.inclusiveLeft
&& posEq(p
.from, m
.from))
4200 newParts
.push({from: p
.from, to
: m
.from});
4201 if (posLess(m
.to
, p
.to
) || !mk
.inclusiveRight
&& posEq(p
.to
, m
.to
))
4202 newParts
.push({from: m
.to
, to
: p
.to
});
4203 parts
.splice
.apply(parts
, newParts
);
4204 j
+= newParts
.length
- 1;
4210 function extraLeft(marker
) { return marker
.inclusiveLeft
? -1 : 0; }
4211 function extraRight(marker
) { return marker
.inclusiveRight
? 1 : 0; }
4213 function compareCollapsedMarkers(a
, b
) {
4214 var lenDiff
= a
.lines
.length
- b
.lines
.length
;
4215 if (lenDiff
!= 0) return lenDiff
;
4216 var aPos
= a
.find(), bPos
= b
.find();
4217 var fromCmp
= cmp(aPos
.from, bPos
.from) || extraLeft(a
) - extraLeft(b
);
4218 if (fromCmp
) return -fromCmp
;
4219 var toCmp
= cmp(aPos
.to
, bPos
.to
) || extraRight(a
) - extraRight(b
);
4220 if (toCmp
) return toCmp
;
4224 function collapsedSpanAtSide(line
, start
) {
4225 var sps
= sawCollapsedSpans
&& line
.markedSpans
, found
;
4226 if (sps
) for (var sp
, i
= 0; i
< sps
.length
; ++i
) {
4228 if (sp
.marker
.collapsed
&& (start
? sp
.from : sp
.to
) == null &&
4229 (!found
|| compareCollapsedMarkers(found
, sp
.marker
) < 0))
4234 function collapsedSpanAtStart(line
) { return collapsedSpanAtSide(line
, true); }
4235 function collapsedSpanAtEnd(line
) { return collapsedSpanAtSide(line
, false); }
4237 function conflictingCollapsedRange(doc
, lineNo
, from, to
, marker
) {
4238 var line
= getLine(doc
, lineNo
);
4239 var sps
= sawCollapsedSpans
&& line
.markedSpans
;
4240 if (sps
) for (var i
= 0; i
< sps
.length
; ++i
) {
4242 if (!sp
.marker
.collapsed
) continue;
4243 var found
= sp
.marker
.find(true);
4244 var fromCmp
= cmp(found
.from, from) || extraLeft(sp
.marker
) - extraLeft(marker
);
4245 var toCmp
= cmp(found
.to
, to
) || extraRight(sp
.marker
) - extraRight(marker
);
4246 if (fromCmp
>= 0 && toCmp
<= 0 || fromCmp
<= 0 && toCmp
>= 0) continue;
4247 if (fromCmp
<= 0 && (cmp(found
.to
, from) || extraRight(sp
.marker
) - extraLeft(marker
)) > 0 ||
4248 fromCmp
>= 0 && (cmp(found
.from, to
) || extraLeft(sp
.marker
) - extraRight(marker
)) < 0)
4253 function visualLine(doc
, line
) {
4255 while (merged
= collapsedSpanAtStart(line
))
4256 line
= getLine(doc
, merged
.find().from.line
);
4260 function lineIsHidden(doc
, line
) {
4261 var sps
= sawCollapsedSpans
&& line
.markedSpans
;
4262 if (sps
) for (var sp
, i
= 0; i
< sps
.length
; ++i
) {
4264 if (!sp
.marker
.collapsed
) continue;
4265 if (sp
.from == null) return true;
4266 if (sp
.marker
.replacedWith
) continue;
4267 if (sp
.from == 0 && sp
.marker
.inclusiveLeft
&& lineIsHiddenInner(doc
, line
, sp
))
4271 function lineIsHiddenInner(doc
, line
, span
) {
4272 if (span
.to
== null) {
4273 var end
= span
.marker
.find().to
, endLine
= getLine(doc
, end
.line
);
4274 return lineIsHiddenInner(doc
, endLine
, getMarkedSpanFor(endLine
.markedSpans
, span
.marker
));
4276 if (span
.marker
.inclusiveRight
&& span
.to
== line
.text
.length
)
4278 for (var sp
, i
= 0; i
< line
.markedSpans
.length
; ++i
) {
4279 sp
= line
.markedSpans
[i
];
4280 if (sp
.marker
.collapsed
&& !sp
.marker
.replacedWith
&& sp
.from == span
.to
&&
4281 (sp
.to
== null || sp
.to
!= span
.from) &&
4282 (sp
.marker
.inclusiveLeft
|| span
.marker
.inclusiveRight
) &&
4283 lineIsHiddenInner(doc
, line
, sp
)) return true;
4287 function detachMarkedSpans(line
) {
4288 var spans
= line
.markedSpans
;
4290 for (var i
= 0; i
< spans
.length
; ++i
)
4291 spans
[i
].marker
.detachLine(line
);
4292 line
.markedSpans
= null;
4295 function attachMarkedSpans(line
, spans
) {
4297 for (var i
= 0; i
< spans
.length
; ++i
)
4298 spans
[i
].marker
.attachLine(line
);
4299 line
.markedSpans
= spans
;
4304 var LineWidget
= CodeMirror
.LineWidget = function(cm
, node
, options
) {
4305 if (options
) for (var opt
in options
) if (options
.hasOwnProperty(opt
))
4306 this[opt
] = options
[opt
];
4310 eventMixin(LineWidget
);
4311 function widgetOperation(f
) {
4313 var withOp
= !this.cm
.curOp
;
4314 if (withOp
) startOperation(this.cm
);
4315 try {var result
= f
.apply(this, arguments
);}
4316 finally {if (withOp
) endOperation(this.cm
);}
4320 LineWidget
.prototype.clear
= widgetOperation(function() {
4321 var ws
= this.line
.widgets
, no
= lineNo(this.line
);
4322 if (no
== null || !ws
) return;
4323 for (var i
= 0; i
< ws
.length
; ++i
) if (ws
[i
] == this) ws
.splice(i
--, 1);
4324 if (!ws
.length
) this.line
.widgets
= null;
4325 var aboveVisible
= heightAtLine(this.cm
, this.line
) < this.cm
.doc
.scrollTop
;
4326 updateLineHeight(this.line
, Math
.max(0, this.line
.height
- widgetHeight(this)));
4327 if (aboveVisible
) addToScrollPos(this.cm
, 0, -this.height
);
4328 regChange(this.cm
, no
, no
+ 1);
4330 LineWidget
.prototype.changed
= widgetOperation(function() {
4331 var oldH
= this.height
;
4333 var diff
= widgetHeight(this) - oldH
;
4335 updateLineHeight(this.line
, this.line
.height
+ diff
);
4336 var no
= lineNo(this.line
);
4337 regChange(this.cm
, no
, no
+ 1);
4340 function widgetHeight(widget
) {
4341 if (widget
.height
!= null) return widget
.height
;
4342 if (!widget
.node
.parentNode
|| widget
.node
.parentNode
.nodeType
!= 1)
4343 removeChildrenAndAdd(widget
.cm
.display
.measure
, elt("div", [widget
.node
], null, "position: relative"));
4344 return widget
.height
= widget
.node
.offsetHeight
;
4347 function addLineWidget(cm
, handle
, node
, options
) {
4348 var widget
= new LineWidget(cm
, node
, options
);
4349 if (widget
.noHScroll
) cm
.display
.alignWidgets
= true;
4350 changeLine(cm
, handle
, function(line
) {
4351 var widgets
= line
.widgets
|| (line
.widgets
= []);
4352 if (widget
.insertAt
== null) widgets
.push(widget
);
4353 else widgets
.splice(Math
.min(widgets
.length
- 1, Math
.max(0, widget
.insertAt
)), 0, widget
);
4355 if (!lineIsHidden(cm
.doc
, line
) || widget
.showIfHidden
) {
4356 var aboveVisible
= heightAtLine(cm
, line
) < cm
.doc
.scrollTop
;
4357 updateLineHeight(line
, line
.height
+ widgetHeight(widget
));
4358 if (aboveVisible
) addToScrollPos(cm
, 0, widget
.height
);
4365 // LINE DATA STRUCTURE
4367 // Line objects. These hold state related to a line, including
4368 // highlighting info (the styles array).
4369 var Line
= CodeMirror
.Line = function(text
, markedSpans
, estimateHeight
) {
4371 attachMarkedSpans(this, markedSpans
);
4372 this.height
= estimateHeight
? estimateHeight(this) : 1;
4375 Line
.prototype.lineNo = function() { return lineNo(this); };
4377 function updateLine(line
, text
, markedSpans
, estimateHeight
) {
4379 if (line
.stateAfter
) line
.stateAfter
= null;
4380 if (line
.styles
) line
.styles
= null;
4381 if (line
.order
!= null) line
.order
= null;
4382 detachMarkedSpans(line
);
4383 attachMarkedSpans(line
, markedSpans
);
4384 var estHeight
= estimateHeight
? estimateHeight(line
) : 1;
4385 if (estHeight
!= line
.height
) updateLineHeight(line
, estHeight
);
4388 function cleanUpLine(line
) {
4390 detachMarkedSpans(line
);
4393 // Run the given mode's parser over a line, update the styles
4394 // array, which contains alternating fragments of text and CSS
4396 function runMode(cm
, text
, mode
, state
, f
, forceToEnd
) {
4397 var flattenSpans
= mode
.flattenSpans
;
4398 if (flattenSpans
== null) flattenSpans
= cm
.options
.flattenSpans
;
4399 var curStart
= 0, curStyle
= null;
4400 var stream
= new StringStream(text
, cm
.options
.tabSize
), style
;
4401 if (text
== "" && mode
.blankLine
) mode
.blankLine(state
);
4402 while (!stream
.eol()) {
4403 if (stream
.pos
> cm
.options
.maxHighlightLength
) {
4404 flattenSpans
= false;
4405 if (forceToEnd
) processLine(cm
, text
, state
, stream
.pos
);
4406 stream
.pos
= text
.length
;
4409 style
= mode
.token(stream
, state
);
4411 if (cm
.options
.addModeClass
) {
4412 var mName
= CodeMirror
.innerMode(mode
, state
).mode
.name
;
4413 if (mName
) style
= "m-" + (style
? mName
+ " " + style
: mName
);
4415 if (!flattenSpans
|| curStyle
!= style
) {
4416 if (curStart
< stream
.start
) f(stream
.start
, curStyle
);
4417 curStart
= stream
.start
; curStyle
= style
;
4419 stream
.start
= stream
.pos
;
4421 while (curStart
< stream
.pos
) {
4422 // Webkit seems to refuse to render text nodes longer than 57444 characters
4423 var pos
= Math
.min(stream
.pos
, curStart
+ 50000);
4429 function highlightLine(cm
, line
, state
, forceToEnd
) {
4430 // A styles array always starts with a number identifying the
4431 // mode/overlays that it is based on (for easy invalidation).
4432 var st
= [cm
.state
.modeGen
];
4433 // Compute the base array of styles
4434 runMode(cm
, line
.text
, cm
.doc
.mode
, state
, function(end
, style
) {
4435 st
.push(end
, style
);
4438 // Run overlays, adjust style array.
4439 for (var o
= 0; o
< cm
.state
.overlays
.length
; ++o
) {
4440 var overlay
= cm
.state
.overlays
[o
], i
= 1, at
= 0;
4441 runMode(cm
, line
.text
, overlay
.mode
, true, function(end
, style
) {
4443 // Ensure there's a token end at the current position, and that i points at it
4447 st
.splice(i
, 1, end
, st
[i
+1], i_end
);
4449 at
= Math
.min(end
, i_end
);
4452 if (overlay
.opaque
) {
4453 st
.splice(start
, i
- start
, end
, style
);
4456 for (; start
< i
; start
+= 2) {
4457 var cur
= st
[start
+1];
4458 st
[start
+1] = cur
? cur
+ " " + style
: style
;
4467 function getLineStyles(cm
, line
) {
4468 if (!line
.styles
|| line
.styles
[0] != cm
.state
.modeGen
)
4469 line
.styles
= highlightLine(cm
, line
, line
.stateAfter
= getStateBefore(cm
, lineNo(line
)));
4473 // Lightweight form of highlight -- proceed over this line and
4474 // update state, but don't save a style array.
4475 function processLine(cm
, text
, state
, startAt
) {
4476 var mode
= cm
.doc
.mode
;
4477 var stream
= new StringStream(text
, cm
.options
.tabSize
);
4478 stream
.start
= stream
.pos
= startAt
|| 0;
4479 if (text
== "" && mode
.blankLine
) mode
.blankLine(state
);
4480 while (!stream
.eol() && stream
.pos
<= cm
.options
.maxHighlightLength
) {
4481 mode
.token(stream
, state
);
4482 stream
.start
= stream
.pos
;
4486 var styleToClassCache
= {}, styleToClassCacheWithMode
= {};
4487 function interpretTokenStyle(style
, builder
) {
4488 if (!style
) return null;
4490 var lineClass
= style
.match(/(?:^|\s+)line-(background-)?(\S+)/);
4491 if (!lineClass
) break;
4492 style
= style
.slice(0, lineClass
.index
) + style
.slice(lineClass
.index
+ lineClass
[0].length
);
4493 var prop
= lineClass
[1] ? "bgClass" : "textClass";
4494 if (builder
[prop
] == null)
4495 builder
[prop
] = lineClass
[2];
4496 else if (!(new RegExp("(?:^|\s)" + lineClass
[2] + "(?:$|\s)")).test(builder
[prop
]))
4497 builder
[prop
] += " " + lineClass
[2];
4499 if (/^\s*$/.test(style
)) return null;
4500 var cache
= builder
.cm
.options
.addModeClass
? styleToClassCacheWithMode
: styleToClassCache
;
4501 return cache
[style
] ||
4502 (cache
[style
] = style
.replace(/\S+/g, "cm-$&"));
4505 function buildLineContent(cm
, realLine
, measure
, copyWidgets
) {
4506 var merged
, line
= realLine
, empty
= true;
4507 while (merged
= collapsedSpanAtStart(line
))
4508 line
= getLine(cm
.doc
, merged
.find().from.line
);
4510 var builder
= {pre
: elt("pre"), col
: 0, pos
: 0,
4511 measure
: null, measuredSomething
: false, cm
: cm
,
4512 copyWidgets
: copyWidgets
};
4515 if (line
.text
) empty
= false;
4516 builder
.measure
= line
== realLine
&& measure
;
4518 builder
.addToken
= builder
.measure
? buildTokenMeasure
: buildToken
;
4519 if ((ie
|| webkit
) && cm
.getOption("lineWrapping"))
4520 builder
.addToken
= buildTokenSplitSpaces(builder
.addToken
);
4521 var next
= insertLineContent(line
, builder
, getLineStyles(cm
, line
));
4522 if (measure
&& line
== realLine
&& !builder
.measuredSomething
) {
4523 measure
[0] = builder
.pre
.appendChild(zeroWidthElement(cm
.display
.measure
));
4524 builder
.measuredSomething
= true;
4526 if (next
) line
= getLine(cm
.doc
, next
.to
.line
);
4529 if (measure
&& !builder
.measuredSomething
&& !measure
[0])
4530 measure
[0] = builder
.pre
.appendChild(empty
? elt("span", "\u00a0") : zeroWidthElement(cm
.display
.measure
));
4531 if (!builder
.pre
.firstChild
&& !lineIsHidden(cm
.doc
, realLine
))
4532 builder
.pre
.appendChild(document
.createTextNode("\u00a0"));
4535 // Work around problem with the reported dimensions of single-char
4536 // direction spans on IE (issue #1129). See also the comment in
4538 if (measure
&& ie
&& (order
= getOrder(line
))) {
4539 var l
= order
.length
- 1;
4540 if (order
[l
].from == order
[l
].to
) --l
;
4541 var last
= order
[l
], prev
= order
[l
- 1];
4542 if (last
.from + 1 == last
.to
&& prev
&& last
.level
< prev
.level
) {
4543 var span
= measure
[builder
.pos
- 1];
4544 if (span
) span
.parentNode
.insertBefore(span
.measureRight
= zeroWidthElement(cm
.display
.measure
),
4549 var textClass
= builder
.textClass
? builder
.textClass
+ " " + (realLine
.textClass
|| "") : realLine
.textClass
;
4550 if (textClass
) builder
.pre
.className
= textClass
;
4552 signal(cm
, "renderLine", cm
, realLine
, builder
.pre
);
4556 function defaultSpecialCharPlaceholder(ch
) {
4557 var token
= elt("span", "\u2022", "cm-invalidchar");
4558 token
.title
= "\\u" + ch
.charCodeAt(0).toString(16);
4562 function buildToken(builder
, text
, style
, startStyle
, endStyle
, title
) {
4564 var special
= builder
.cm
.options
.specialChars
;
4565 if (!special
.test(text
)) {
4566 builder
.col
+= text
.length
;
4567 var content
= document
.createTextNode(text
);
4569 var content
= document
.createDocumentFragment(), pos
= 0;
4571 special
.lastIndex
= pos
;
4572 var m
= special
.exec(text
);
4573 var skipped
= m
? m
.index
- pos
: text
.length
- pos
;
4575 content
.appendChild(document
.createTextNode(text
.slice(pos
, pos
+ skipped
)));
4576 builder
.col
+= skipped
;
4581 var tabSize
= builder
.cm
.options
.tabSize
, tabWidth
= tabSize
- builder
.col
% tabSize
;
4582 content
.appendChild(elt("span", spaceStr(tabWidth
), "cm-tab"));
4583 builder
.col
+= tabWidth
;
4585 var token
= builder
.cm
.options
.specialCharPlaceholder(m
[0]);
4586 content
.appendChild(token
);
4591 if (style
|| startStyle
|| endStyle
|| builder
.measure
) {
4592 var fullStyle
= style
|| "";
4593 if (startStyle
) fullStyle
+= startStyle
;
4594 if (endStyle
) fullStyle
+= endStyle
;
4595 var token
= elt("span", [content
], fullStyle
);
4596 if (title
) token
.title
= title
;
4597 return builder
.pre
.appendChild(token
);
4599 builder
.pre
.appendChild(content
);
4602 function buildTokenMeasure(builder
, text
, style
, startStyle
, endStyle
) {
4603 var wrapping
= builder
.cm
.options
.lineWrapping
;
4604 for (var i
= 0; i
< text
.length
; ++i
) {
4605 var start
= i
== 0, to
= i
+ 1;
4606 while (to
< text
.length
&& isExtendingChar(text
.charAt(to
))) ++to
;
4607 var ch
= text
.slice(i
, to
);
4609 if (i
&& wrapping
&& spanAffectsWrapping(text
, i
))
4610 builder
.pre
.appendChild(elt("wbr"));
4611 var old
= builder
.measure
[builder
.pos
];
4612 var span
= builder
.measure
[builder
.pos
] =
4613 buildToken(builder
, ch
, style
,
4614 start
&& startStyle
, i
== text
.length
- 1 && endStyle
);
4615 if (old
) span
.leftSide
= old
.leftSide
|| old
;
4616 // In IE single-space nodes wrap differently than spaces
4617 // embedded in larger text nodes, except when set to
4618 // white-space: normal (issue #1268).
4619 if (old_ie
&& wrapping
&& ch
== " " && i
&& !/\s/.test(text
.charAt(i
- 1)) &&
4620 i
< text
.length
- 1 && !/\s/.test(text
.charAt(i
+ 1)))
4621 span
.style
.whiteSpace
= "normal";
4622 builder
.pos
+= ch
.length
;
4624 if (text
.length
) builder
.measuredSomething
= true;
4627 function buildTokenSplitSpaces(inner
) {
4628 function split(old
) {
4630 for (var i
= 0; i
< old
.length
- 2; ++i
) out
+= i
% 2 ? " " : "\u00a0";
4634 return function(builder
, text
, style
, startStyle
, endStyle
, title
) {
4635 return inner(builder
, text
.replace(/ {3,}/g
, split
), style
, startStyle
, endStyle
, title
);
4639 function buildCollapsedSpan(builder
, size
, marker
, ignoreWidget
) {
4640 var widget
= !ignoreWidget
&& marker
.replacedWith
;
4642 if (builder
.copyWidgets
) widget
= widget
.cloneNode(true);
4643 builder
.pre
.appendChild(widget
);
4644 if (builder
.measure
) {
4646 builder
.measure
[builder
.pos
] = widget
;
4648 var elt
= zeroWidthElement(builder
.cm
.display
.measure
);
4649 if (marker
.type
== "bookmark" && !marker
.insertLeft
)
4650 builder
.measure
[builder
.pos
] = builder
.pre
.appendChild(elt
);
4651 else if (builder
.measure
[builder
.pos
])
4654 builder
.measure
[builder
.pos
] = builder
.pre
.insertBefore(elt
, widget
);
4656 builder
.measuredSomething
= true;
4659 builder
.pos
+= size
;
4662 // Outputs a number of spans to make up a line, taking highlighting
4663 // and marked text into account.
4664 function insertLineContent(line
, builder
, styles
) {
4665 var spans
= line
.markedSpans
, allText
= line
.text
, at
= 0;
4667 for (var i
= 1; i
< styles
.length
; i
+=2)
4668 builder
.addToken(builder
, allText
.slice(at
, at
= styles
[i
]), interpretTokenStyle(styles
[i
+1], builder
));
4672 var len
= allText
.length
, pos
= 0, i
= 1, text
= "", style
;
4673 var nextChange
= 0, spanStyle
, spanEndStyle
, spanStartStyle
, title
, collapsed
;
4675 if (nextChange
== pos
) { // Update current marker set
4676 spanStyle
= spanEndStyle
= spanStartStyle
= title
= "";
4677 collapsed
= null; nextChange
= Infinity
;
4678 var foundBookmarks
= [];
4679 for (var j
= 0; j
< spans
.length
; ++j
) {
4680 var sp
= spans
[j
], m
= sp
.marker
;
4681 if (sp
.from <= pos
&& (sp
.to
== null || sp
.to
> pos
)) {
4682 if (sp
.to
!= null && nextChange
> sp
.to
) { nextChange
= sp
.to
; spanEndStyle
= ""; }
4683 if (m
.className
) spanStyle
+= " " + m
.className
;
4684 if (m
.startStyle
&& sp
.from == pos
) spanStartStyle
+= " " + m
.startStyle
;
4685 if (m
.endStyle
&& sp
.to
== nextChange
) spanEndStyle
+= " " + m
.endStyle
;
4686 if (m
.title
&& !title
) title
= m
.title
;
4687 if (m
.collapsed
&& (!collapsed
|| compareCollapsedMarkers(collapsed
.marker
, m
) < 0))
4689 } else if (sp
.from > pos
&& nextChange
> sp
.from) {
4690 nextChange
= sp
.from;
4692 if (m
.type
== "bookmark" && sp
.from == pos
&& m
.replacedWith
) foundBookmarks
.push(m
);
4694 if (collapsed
&& (collapsed
.from || 0) == pos
) {
4695 buildCollapsedSpan(builder
, (collapsed
.to
== null ? len
: collapsed
.to
) - pos
,
4696 collapsed
.marker
, collapsed
.from == null);
4697 if (collapsed
.to
== null) return collapsed
.marker
.find();
4699 if (!collapsed
&& foundBookmarks
.length
) for (var j
= 0; j
< foundBookmarks
.length
; ++j
)
4700 buildCollapsedSpan(builder
, 0, foundBookmarks
[j
]);
4702 if (pos
>= len
) break;
4704 var upto
= Math
.min(len
, nextChange
);
4707 var end
= pos
+ text
.length
;
4709 var tokenText
= end
> upto
? text
.slice(0, upto
- pos
) : text
;
4710 builder
.addToken(builder
, tokenText
, style
? style
+ spanStyle
: spanStyle
,
4711 spanStartStyle
, pos
+ tokenText
.length
== nextChange
? spanEndStyle
: "", title
);
4713 if (end
>= upto
) {text
= text
.slice(upto
- pos
); pos
= upto
; break;}
4715 spanStartStyle
= "";
4717 text
= allText
.slice(at
, at
= styles
[i
++]);
4718 style
= interpretTokenStyle(styles
[i
++], builder
);
4723 // DOCUMENT DATA STRUCTURE
4725 function updateDoc(doc
, change
, markedSpans
, selAfter
, estimateHeight
) {
4726 function spansFor(n
) {return markedSpans
? markedSpans
[n
] : null;}
4727 function update(line
, text
, spans
) {
4728 updateLine(line
, text
, spans
, estimateHeight
);
4729 signalLater(line
, "change", line
, change
);
4732 var from = change
.from, to
= change
.to
, text
= change
.text
;
4733 var firstLine
= getLine(doc
, from.line
), lastLine
= getLine(doc
, to
.line
);
4734 var lastText
= lst(text
), lastSpans
= spansFor(text
.length
- 1), nlines
= to
.line
- from.line
;
4736 // First adjust the line structure
4737 if (from.ch
== 0 && to
.ch
== 0 && lastText
== "" &&
4738 (!doc
.cm
|| doc
.cm
.options
.wholeLineUpdateBefore
)) {
4739 // This is a whole-line replace. Treated specially to make
4740 // sure line objects move the way they are supposed to.
4741 for (var i
= 0, e
= text
.length
- 1, added
= []; i
< e
; ++i
)
4742 added
.push(new Line(text
[i
], spansFor(i
), estimateHeight
));
4743 update(lastLine
, lastLine
.text
, lastSpans
);
4744 if (nlines
) doc
.remove(from.line
, nlines
);
4745 if (added
.length
) doc
.insert(from.line
, added
);
4746 } else if (firstLine
== lastLine
) {
4747 if (text
.length
== 1) {
4748 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + lastText
+ firstLine
.text
.slice(to
.ch
), lastSpans
);
4750 for (var added
= [], i
= 1, e
= text
.length
- 1; i
< e
; ++i
)
4751 added
.push(new Line(text
[i
], spansFor(i
), estimateHeight
));
4752 added
.push(new Line(lastText
+ firstLine
.text
.slice(to
.ch
), lastSpans
, estimateHeight
));
4753 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + text
[0], spansFor(0));
4754 doc
.insert(from.line
+ 1, added
);
4756 } else if (text
.length
== 1) {
4757 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + text
[0] + lastLine
.text
.slice(to
.ch
), spansFor(0));
4758 doc
.remove(from.line
+ 1, nlines
);
4760 update(firstLine
, firstLine
.text
.slice(0, from.ch
) + text
[0], spansFor(0));
4761 update(lastLine
, lastText
+ lastLine
.text
.slice(to
.ch
), lastSpans
);
4762 for (var i
= 1, e
= text
.length
- 1, added
= []; i
< e
; ++i
)
4763 added
.push(new Line(text
[i
], spansFor(i
), estimateHeight
));
4764 if (nlines
> 1) doc
.remove(from.line
+ 1, nlines
- 1);
4765 doc
.insert(from.line
+ 1, added
);
4768 signalLater(doc
, "change", doc
, change
);
4769 setSelection(doc
, selAfter
.anchor
, selAfter
.head
, null, true);
4772 function LeafChunk(lines
) {
4775 for (var i
= 0, e
= lines
.length
, height
= 0; i
< e
; ++i
) {
4776 lines
[i
].parent
= this;
4777 height
+= lines
[i
].height
;
4779 this.height
= height
;
4782 LeafChunk
.prototype = {
4783 chunkSize: function() { return this.lines
.length
; },
4784 removeInner: function(at
, n
) {
4785 for (var i
= at
, e
= at
+ n
; i
< e
; ++i
) {
4786 var line
= this.lines
[i
];
4787 this.height
-= line
.height
;
4789 signalLater(line
, "delete");
4791 this.lines
.splice(at
, n
);
4793 collapse: function(lines
) {
4794 lines
.splice
.apply(lines
, [lines
.length
, 0].concat(this.lines
));
4796 insertInner: function(at
, lines
, height
) {
4797 this.height
+= height
;
4798 this.lines
= this.lines
.slice(0, at
).concat(lines
).concat(this.lines
.slice(at
));
4799 for (var i
= 0, e
= lines
.length
; i
< e
; ++i
) lines
[i
].parent
= this;
4801 iterN: function(at
, n
, op
) {
4802 for (var e
= at
+ n
; at
< e
; ++at
)
4803 if (op(this.lines
[at
])) return true;
4807 function BranchChunk(children
) {
4808 this.children
= children
;
4809 var size
= 0, height
= 0;
4810 for (var i
= 0, e
= children
.length
; i
< e
; ++i
) {
4811 var ch
= children
[i
];
4812 size
+= ch
.chunkSize(); height
+= ch
.height
;
4816 this.height
= height
;
4820 BranchChunk
.prototype = {
4821 chunkSize: function() { return this.size
; },
4822 removeInner: function(at
, n
) {
4824 for (var i
= 0; i
< this.children
.length
; ++i
) {
4825 var child
= this.children
[i
], sz
= child
.chunkSize();
4827 var rm
= Math
.min(n
, sz
- at
), oldHeight
= child
.height
;
4828 child
.removeInner(at
, rm
);
4829 this.height
-= oldHeight
- child
.height
;
4830 if (sz
== rm
) { this.children
.splice(i
--, 1); child
.parent
= null; }
4831 if ((n
-= rm
) == 0) break;
4835 if (this.size
- n
< 25) {
4837 this.collapse(lines
);
4838 this.children
= [new LeafChunk(lines
)];
4839 this.children
[0].parent
= this;
4842 collapse: function(lines
) {
4843 for (var i
= 0, e
= this.children
.length
; i
< e
; ++i
) this.children
[i
].collapse(lines
);
4845 insertInner: function(at
, lines
, height
) {
4846 this.size
+= lines
.length
;
4847 this.height
+= height
;
4848 for (var i
= 0, e
= this.children
.length
; i
< e
; ++i
) {
4849 var child
= this.children
[i
], sz
= child
.chunkSize();
4851 child
.insertInner(at
, lines
, height
);
4852 if (child
.lines
&& child
.lines
.length
> 50) {
4853 while (child
.lines
.length
> 50) {
4854 var spilled
= child
.lines
.splice(child
.lines
.length
- 25, 25);
4855 var newleaf
= new LeafChunk(spilled
);
4856 child
.height
-= newleaf
.height
;
4857 this.children
.splice(i
+ 1, 0, newleaf
);
4858 newleaf
.parent
= this;
4867 maybeSpill: function() {
4868 if (this.children
.length
<= 10) return;
4871 var spilled
= me
.children
.splice(me
.children
.length
- 5, 5);
4872 var sibling
= new BranchChunk(spilled
);
4873 if (!me
.parent
) { // Become the parent node
4874 var copy
= new BranchChunk(me
.children
);
4876 me
.children
= [copy
, sibling
];
4879 me
.size
-= sibling
.size
;
4880 me
.height
-= sibling
.height
;
4881 var myIndex
= indexOf(me
.parent
.children
, me
);
4882 me
.parent
.children
.splice(myIndex
+ 1, 0, sibling
);
4884 sibling
.parent
= me
.parent
;
4885 } while (me
.children
.length
> 10);
4886 me
.parent
.maybeSpill();
4888 iterN: function(at
, n
, op
) {
4889 for (var i
= 0, e
= this.children
.length
; i
< e
; ++i
) {
4890 var child
= this.children
[i
], sz
= child
.chunkSize();
4892 var used
= Math
.min(n
, sz
- at
);
4893 if (child
.iterN(at
, used
, op
)) return true;
4894 if ((n
-= used
) == 0) break;
4902 var Doc
= CodeMirror
.Doc = function(text
, mode
, firstLine
) {
4903 if (!(this instanceof Doc
)) return new Doc(text
, mode
, firstLine
);
4904 if (firstLine
== null) firstLine
= 0;
4906 BranchChunk
.call(this, [new LeafChunk([new Line("", null)])]);
4907 this.first
= firstLine
;
4908 this.scrollTop
= this.scrollLeft
= 0;
4909 this.cantEdit
= false;
4910 this.history
= makeHistory();
4911 this.cleanGeneration
= 1;
4912 this.frontier
= firstLine
;
4913 var start
= Pos(firstLine
, 0);
4914 this.sel
= {from: start
, to
: start
, head
: start
, anchor
: start
, shift
: false, extend
: false, goalColumn
: null};
4915 this.id
= ++nextDocId
;
4916 this.modeOption
= mode
;
4918 if (typeof text
== "string") text
= splitLines(text
);
4919 updateDoc(this, {from: start
, to
: start
, text
: text
}, null, {head
: start
, anchor
: start
});
4922 Doc
.prototype = createObj(BranchChunk
.prototype, {
4924 iter: function(from, to
, op
) {
4925 if (op
) this.iterN(from - this.first
, to
- from, op
);
4926 else this.iterN(this.first
, this.first
+ this.size
, from);
4929 insert: function(at
, lines
) {
4931 for (var i
= 0, e
= lines
.length
; i
< e
; ++i
) height
+= lines
[i
].height
;
4932 this.insertInner(at
- this.first
, lines
, height
);
4934 remove: function(at
, n
) { this.removeInner(at
- this.first
, n
); },
4936 getValue: function(lineSep
) {
4937 var lines
= getLines(this, this.first
, this.first
+ this.size
);
4938 if (lineSep
=== false) return lines
;
4939 return lines
.join(lineSep
|| "\n");
4941 setValue: function(code
) {
4942 var top
= Pos(this.first
, 0), last
= this.first
+ this.size
- 1;
4943 makeChange(this, {from: top
, to
: Pos(last
, getLine(this, last
).text
.length
),
4944 text
: splitLines(code
), origin
: "setValue"},
4945 {head
: top
, anchor
: top
}, true);
4947 replaceRange: function(code
, from, to
, origin
) {
4948 from = clipPos(this, from);
4949 to
= to
? clipPos(this, to
) : from;
4950 replaceRange(this, code
, from, to
, origin
);
4952 getRange: function(from, to
, lineSep
) {
4953 var lines
= getBetween(this, clipPos(this, from), clipPos(this, to
));
4954 if (lineSep
=== false) return lines
;
4955 return lines
.join(lineSep
|| "\n");
4958 getLine: function(line
) {var l
= this.getLineHandle(line
); return l
&& l
.text
;},
4959 setLine: function(line
, text
) {
4960 if (isLine(this, line
))
4961 replaceRange(this, text
, Pos(line
, 0), clipPos(this, Pos(line
)));
4963 removeLine: function(line
) {
4964 if (line
) replaceRange(this, "", clipPos(this, Pos(line
- 1)), clipPos(this, Pos(line
)));
4965 else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0)));
4968 getLineHandle: function(line
) {if (isLine(this, line
)) return getLine(this, line
);},
4969 getLineNumber: function(line
) {return lineNo(line
);},
4971 getLineHandleVisualStart: function(line
) {
4972 if (typeof line
== "number") line
= getLine(this, line
);
4973 return visualLine(this, line
);
4976 lineCount: function() {return this.size
;},
4977 firstLine: function() {return this.first
;},
4978 lastLine: function() {return this.first
+ this.size
- 1;},
4980 clipPos: function(pos
) {return clipPos(this, pos
);},
4982 getCursor: function(start
) {
4983 var sel
= this.sel
, pos
;
4984 if (start
== null || start
== "head") pos
= sel
.head
;
4985 else if (start
== "anchor") pos
= sel
.anchor
;
4986 else if (start
== "end" || start
=== false) pos
= sel
.to
;
4987 else pos
= sel
.from;
4988 return copyPos(pos
);
4990 somethingSelected: function() {return !posEq(this.sel
.head
, this.sel
.anchor
);},
4992 setCursor
: docOperation(function(line
, ch
, extend
) {
4993 var pos
= clipPos(this, typeof line
== "number" ? Pos(line
, ch
|| 0) : line
);
4994 if (extend
) extendSelection(this, pos
);
4995 else setSelection(this, pos
, pos
);
4997 setSelection
: docOperation(function(anchor
, head
, bias
) {
4998 setSelection(this, clipPos(this, anchor
), clipPos(this, head
|| anchor
), bias
);
5000 extendSelection
: docOperation(function(from, to
, bias
) {
5001 extendSelection(this, clipPos(this, from), to
&& clipPos(this, to
), bias
);
5004 getSelection: function(lineSep
) {return this.getRange(this.sel
.from, this.sel
.to
, lineSep
);},
5005 replaceSelection: function(code
, collapse
, origin
) {
5006 makeChange(this, {from: this.sel
.from, to
: this.sel
.to
, text
: splitLines(code
), origin
: origin
}, collapse
|| "around");
5008 undo
: docOperation(function() {makeChangeFromHistory(this, "undo");}),
5009 redo
: docOperation(function() {makeChangeFromHistory(this, "redo");}),
5011 setExtending: function(val
) {this.sel
.extend
= val
;},
5013 historySize: function() {
5014 var hist
= this.history
;
5015 return {undo
: hist
.done
.length
, redo
: hist
.undone
.length
};
5017 clearHistory: function() {this.history
= makeHistory(this.history
.maxGeneration
);},
5019 markClean: function() {
5020 this.cleanGeneration
= this.changeGeneration(true);
5022 changeGeneration: function(forceSplit
) {
5024 this.history
.lastOp
= this.history
.lastOrigin
= null;
5025 return this.history
.generation
;
5027 isClean: function (gen
) {
5028 return this.history
.generation
== (gen
|| this.cleanGeneration
);
5031 getHistory: function() {
5032 return {done
: copyHistoryArray(this.history
.done
),
5033 undone
: copyHistoryArray(this.history
.undone
)};
5035 setHistory: function(histData
) {
5036 var hist
= this.history
= makeHistory(this.history
.maxGeneration
);
5037 hist
.done
= histData
.done
.slice(0);
5038 hist
.undone
= histData
.undone
.slice(0);
5041 markText: function(from, to
, options
) {
5042 return markText(this, clipPos(this, from), clipPos(this, to
), options
, "range");
5044 setBookmark: function(pos
, options
) {
5045 var realOpts
= {replacedWith
: options
&& (options
.nodeType
== null ? options
.widget
: options
),
5046 insertLeft
: options
&& options
.insertLeft
,
5047 clearWhenEmpty
: false};
5048 pos
= clipPos(this, pos
);
5049 return markText(this, pos
, pos
, realOpts
, "bookmark");
5051 findMarksAt: function(pos
) {
5052 pos
= clipPos(this, pos
);
5053 var markers
= [], spans
= getLine(this, pos
.line
).markedSpans
;
5054 if (spans
) for (var i
= 0; i
< spans
.length
; ++i
) {
5055 var span
= spans
[i
];
5056 if ((span
.from == null || span
.from <= pos
.ch
) &&
5057 (span
.to
== null || span
.to
>= pos
.ch
))
5058 markers
.push(span
.marker
.parent
|| span
.marker
);
5062 getAllMarks: function() {
5064 this.iter(function(line
) {
5065 var sps
= line
.markedSpans
;
5066 if (sps
) for (var i
= 0; i
< sps
.length
; ++i
)
5067 if (sps
[i
].from != null) markers
.push(sps
[i
].marker
);
5072 posFromIndex: function(off
) {
5073 var ch
, lineNo
= this.first
;
5074 this.iter(function(line
) {
5075 var sz
= line
.text
.length
+ 1;
5076 if (sz
> off
) { ch
= off
; return true; }
5080 return clipPos(this, Pos(lineNo
, ch
));
5082 indexFromPos: function (coords
) {
5083 coords
= clipPos(this, coords
);
5084 var index
= coords
.ch
;
5085 if (coords
.line
< this.first
|| coords
.ch
< 0) return 0;
5086 this.iter(this.first
, coords
.line
, function (line
) {
5087 index
+= line
.text
.length
+ 1;
5092 copy: function(copyHistory
) {
5093 var doc
= new Doc(getLines(this, this.first
, this.first
+ this.size
), this.modeOption
, this.first
);
5094 doc
.scrollTop
= this.scrollTop
; doc
.scrollLeft
= this.scrollLeft
;
5095 doc
.sel
= {from: this.sel
.from, to
: this.sel
.to
, head
: this.sel
.head
, anchor
: this.sel
.anchor
,
5096 shift
: this.sel
.shift
, extend
: false, goalColumn
: this.sel
.goalColumn
};
5098 doc
.history
.undoDepth
= this.history
.undoDepth
;
5099 doc
.setHistory(this.getHistory());
5104 linkedDoc: function(options
) {
5105 if (!options
) options
= {};
5106 var from = this.first
, to
= this.first
+ this.size
;
5107 if (options
.from != null && options
.from > from) from = options
.from;
5108 if (options
.to
!= null && options
.to
< to
) to
= options
.to
;
5109 var copy
= new Doc(getLines(this, from, to
), options
.mode
|| this.modeOption
, from);
5110 if (options
.sharedHist
) copy
.history
= this.history
;
5111 (this.linked
|| (this.linked
= [])).push({doc
: copy
, sharedHist
: options
.sharedHist
});
5112 copy
.linked
= [{doc
: this, isParent
: true, sharedHist
: options
.sharedHist
}];
5115 unlinkDoc: function(other
) {
5116 if (other
instanceof CodeMirror
) other
= other
.doc
;
5117 if (this.linked
) for (var i
= 0; i
< this.linked
.length
; ++i
) {
5118 var link
= this.linked
[i
];
5119 if (link
.doc
!= other
) continue;
5120 this.linked
.splice(i
, 1);
5121 other
.unlinkDoc(this);
5124 // If the histories were shared, split them again
5125 if (other
.history
== this.history
) {
5126 var splitIds
= [other
.id
];
5127 linkedDocs(other
, function(doc
) {splitIds
.push(doc
.id
);}, true);
5128 other
.history
= makeHistory();
5129 other
.history
.done
= copyHistoryArray(this.history
.done
, splitIds
);
5130 other
.history
.undone
= copyHistoryArray(this.history
.undone
, splitIds
);
5133 iterLinkedDocs: function(f
) {linkedDocs(this, f
);},
5135 getMode: function() {return this.mode
;},
5136 getEditor: function() {return this.cm
;}
5139 Doc
.prototype.eachLine
= Doc
.prototype.iter
;
5141 // The Doc methods that should be available on CodeMirror instances
5142 var dontDelegate
= "iter insert remove copy getEditor".split(" ");
5143 for (var prop
in Doc
.prototype) if (Doc
.prototype.hasOwnProperty(prop
) && indexOf(dontDelegate
, prop
) < 0)
5144 CodeMirror
.prototype[prop
] = (function(method
) {
5145 return function() {return method
.apply(this.doc
, arguments
);};
5146 })(Doc
.prototype[prop
]);
5150 function linkedDocs(doc
, f
, sharedHistOnly
) {
5151 function propagate(doc
, skip
, sharedHist
) {
5152 if (doc
.linked
) for (var i
= 0; i
< doc
.linked
.length
; ++i
) {
5153 var rel
= doc
.linked
[i
];
5154 if (rel
.doc
== skip
) continue;
5155 var shared
= sharedHist
&& rel
.sharedHist
;
5156 if (sharedHistOnly
&& !shared
) continue;
5158 propagate(rel
.doc
, doc
, shared
);
5161 propagate(doc
, null, true);
5164 function attachDoc(cm
, doc
) {
5165 if (doc
.cm
) throw new Error("This document is already in use.");
5168 estimateLineHeights(cm
);
5170 if (!cm
.options
.lineWrapping
) computeMaxLength(cm
);
5171 cm
.options
.mode
= doc
.modeOption
;
5177 function getLine(chunk
, n
) {
5179 while (!chunk
.lines
) {
5180 for (var i
= 0;; ++i
) {
5181 var child
= chunk
.children
[i
], sz
= child
.chunkSize();
5182 if (n
< sz
) { chunk
= child
; break; }
5186 return chunk
.lines
[n
];
5189 function getBetween(doc
, start
, end
) {
5190 var out
= [], n
= start
.line
;
5191 doc
.iter(start
.line
, end
.line
+ 1, function(line
) {
5192 var text
= line
.text
;
5193 if (n
== end
.line
) text
= text
.slice(0, end
.ch
);
5194 if (n
== start
.line
) text
= text
.slice(start
.ch
);
5200 function getLines(doc
, from, to
) {
5202 doc
.iter(from, to
, function(line
) { out
.push(line
.text
); });
5206 function updateLineHeight(line
, height
) {
5207 var diff
= height
- line
.height
;
5208 for (var n
= line
; n
; n
= n
.parent
) n
.height
+= diff
;
5211 function lineNo(line
) {
5212 if (line
.parent
== null) return null;
5213 var cur
= line
.parent
, no
= indexOf(cur
.lines
, line
);
5214 for (var chunk
= cur
.parent
; chunk
; cur
= chunk
, chunk
= chunk
.parent
) {
5215 for (var i
= 0;; ++i
) {
5216 if (chunk
.children
[i
] == cur
) break;
5217 no
+= chunk
.children
[i
].chunkSize();
5220 return no
+ cur
.first
;
5223 function lineAtHeight(chunk
, h
) {
5224 var n
= chunk
.first
;
5226 for (var i
= 0, e
= chunk
.children
.length
; i
< e
; ++i
) {
5227 var child
= chunk
.children
[i
], ch
= child
.height
;
5228 if (h
< ch
) { chunk
= child
; continue outer
; }
5230 n
+= child
.chunkSize();
5233 } while (!chunk
.lines
);
5234 for (var i
= 0, e
= chunk
.lines
.length
; i
< e
; ++i
) {
5235 var line
= chunk
.lines
[i
], lh
= line
.height
;
5242 function heightAtLine(cm
, lineObj
) {
5243 lineObj
= visualLine(cm
.doc
, lineObj
);
5245 var h
= 0, chunk
= lineObj
.parent
;
5246 for (var i
= 0; i
< chunk
.lines
.length
; ++i
) {
5247 var line
= chunk
.lines
[i
];
5248 if (line
== lineObj
) break;
5249 else h
+= line
.height
;
5251 for (var p
= chunk
.parent
; p
; chunk
= p
, p
= chunk
.parent
) {
5252 for (var i
= 0; i
< p
.children
.length
; ++i
) {
5253 var cur
= p
.children
[i
];
5254 if (cur
== chunk
) break;
5255 else h
+= cur
.height
;
5261 function getOrder(line
) {
5262 var order
= line
.order
;
5263 if (order
== null) order
= line
.order
= bidiOrdering(line
.text
);
5269 function makeHistory(startGen
) {
5271 // Arrays of history events. Doing something adds an event to
5272 // done and clears undo. Undoing moves events from done to
5273 // undone, redoing moves them in the other direction.
5274 done
: [], undone
: [], undoDepth
: Infinity
,
5275 // Used to track when changes can be merged into a single undo
5277 lastTime
: 0, lastOp
: null, lastOrigin
: null,
5278 // Used by the isClean() method
5279 generation
: startGen
|| 1, maxGeneration
: startGen
|| 1
5283 function attachLocalSpans(doc
, change
, from, to
) {
5284 var existing
= change
["spans_" + doc
.id
], n
= 0;
5285 doc
.iter(Math
.max(doc
.first
, from), Math
.min(doc
.first
+ doc
.size
, to
), function(line
) {
5286 if (line
.markedSpans
)
5287 (existing
|| (existing
= change
["spans_" + doc
.id
] = {}))[n
] = line
.markedSpans
;
5292 function historyChangeFromChange(doc
, change
) {
5293 var from = { line
: change
.from.line
, ch
: change
.from.ch
};
5294 var histChange
= {from: from, to
: changeEnd(change
), text
: getBetween(doc
, change
.from, change
.to
)};
5295 attachLocalSpans(doc
, histChange
, change
.from.line
, change
.to
.line
+ 1);
5296 linkedDocs(doc
, function(doc
) {attachLocalSpans(doc
, histChange
, change
.from.line
, change
.to
.line
+ 1);}, true);
5300 function addToHistory(doc
, change
, selAfter
, opId
) {
5301 var hist
= doc
.history
;
5302 hist
.undone
.length
= 0;
5303 var time
= +new Date
, cur
= lst(hist
.done
);
5306 (hist
.lastOp
== opId
||
5307 hist
.lastOrigin
== change
.origin
&& change
.origin
&&
5308 ((change
.origin
.charAt(0) == "+" && doc
.cm
&& hist
.lastTime
> time
- doc
.cm
.options
.historyEventDelay
) ||
5309 change
.origin
.charAt(0) == "*"))) {
5310 // Merge this change into the last event
5311 var last
= lst(cur
.changes
);
5312 if (posEq(change
.from, change
.to
) && posEq(change
.from, last
.to
)) {
5313 // Optimized case for simple insertion -- don't want to add
5314 // new changesets for every character typed
5315 last
.to
= changeEnd(change
);
5317 // Add new sub-event
5318 cur
.changes
.push(historyChangeFromChange(doc
, change
));
5320 cur
.anchorAfter
= selAfter
.anchor
; cur
.headAfter
= selAfter
.head
;
5322 // Can not be merged, start a new event.
5323 cur
= {changes
: [historyChangeFromChange(doc
, change
)],
5324 generation
: hist
.generation
,
5325 anchorBefore
: doc
.sel
.anchor
, headBefore
: doc
.sel
.head
,
5326 anchorAfter
: selAfter
.anchor
, headAfter
: selAfter
.head
};
5327 hist
.done
.push(cur
);
5328 while (hist
.done
.length
> hist
.undoDepth
)
5331 hist
.generation
= ++hist
.maxGeneration
;
5332 hist
.lastTime
= time
;
5334 hist
.lastOrigin
= change
.origin
;
5336 if (!last
) signal(doc
, "historyAdded");
5339 function removeClearedSpans(spans
) {
5340 if (!spans
) return null;
5341 for (var i
= 0, out
; i
< spans
.length
; ++i
) {
5342 if (spans
[i
].marker
.explicitlyCleared
) { if (!out
) out
= spans
.slice(0, i
); }
5343 else if (out
) out
.push(spans
[i
]);
5345 return !out
? spans
: out
.length
? out
: null;
5348 function getOldSpans(doc
, change
) {
5349 var found
= change
["spans_" + doc
.id
];
5350 if (!found
) return null;
5351 for (var i
= 0, nw
= []; i
< change
.text
.length
; ++i
)
5352 nw
.push(removeClearedSpans(found
[i
]));
5356 // Used both to provide a JSON-safe object in .getHistory, and, when
5357 // detaching a document, to split the history in two
5358 function copyHistoryArray(events
, newGroup
) {
5359 for (var i
= 0, copy
= []; i
< events
.length
; ++i
) {
5360 var event
= events
[i
], changes
= event
.changes
, newChanges
= [];
5361 copy
.push({changes
: newChanges
, anchorBefore
: event
.anchorBefore
, headBefore
: event
.headBefore
,
5362 anchorAfter
: event
.anchorAfter
, headAfter
: event
.headAfter
});
5363 for (var j
= 0; j
< changes
.length
; ++j
) {
5364 var change
= changes
[j
], m
;
5365 newChanges
.push({from: change
.from, to
: change
.to
, text
: change
.text
});
5366 if (newGroup
) for (var prop
in change
) if (m
= prop
.match(/^spans_(\d+)$/)) {
5367 if (indexOf(newGroup
, Number(m
[1])) > -1) {
5368 lst(newChanges
)[prop
] = change
[prop
];
5369 delete change
[prop
];
5377 // Rebasing/resetting history to deal with externally-sourced changes
5379 function rebaseHistSel(pos
, from, to
, diff
) {
5380 if (to
< pos
.line
) {
5382 } else if (from < pos
.line
) {
5388 // Tries to rebase an array of history events given a change in the
5389 // document. If the change touches the same lines as the event, the
5390 // event, and everything 'behind' it, is discarded. If the change is
5391 // before the event, the event's positions are updated. Uses a
5392 // copy-on-write scheme for the positions, to avoid having to
5393 // reallocate them all on every rebase, but also avoid problems with
5394 // shared position objects being unsafely updated.
5395 function rebaseHistArray(array
, from, to
, diff
) {
5396 for (var i
= 0; i
< array
.length
; ++i
) {
5397 var sub
= array
[i
], ok
= true;
5398 for (var j
= 0; j
< sub
.changes
.length
; ++j
) {
5399 var cur
= sub
.changes
[j
];
5400 if (!sub
.copied
) { cur
.from = copyPos(cur
.from); cur
.to
= copyPos(cur
.to
); }
5401 if (to
< cur
.from.line
) {
5402 cur
.from.line
+= diff
;
5403 cur
.to
.line
+= diff
;
5404 } else if (from <= cur
.to
.line
) {
5410 sub
.anchorBefore
= copyPos(sub
.anchorBefore
); sub
.headBefore
= copyPos(sub
.headBefore
);
5411 sub
.anchorAfter
= copyPos(sub
.anchorAfter
); sub
.readAfter
= copyPos(sub
.headAfter
);
5415 array
.splice(0, i
+ 1);
5418 rebaseHistSel(sub
.anchorBefore
); rebaseHistSel(sub
.headBefore
);
5419 rebaseHistSel(sub
.anchorAfter
); rebaseHistSel(sub
.headAfter
);
5424 function rebaseHist(hist
, change
) {
5425 var from = change
.from.line
, to
= change
.to
.line
, diff
= change
.text
.length
- (to
- from) - 1;
5426 rebaseHistArray(hist
.done
, from, to
, diff
);
5427 rebaseHistArray(hist
.undone
, from, to
, diff
);
5432 function stopMethod() {e_stop(this);}
5433 // Ensure an event has a stop method.
5434 function addStop(event
) {
5435 if (!event
.stop
) event
.stop
= stopMethod
;
5439 function e_preventDefault(e
) {
5440 if (e
.preventDefault
) e
.preventDefault();
5441 else e
.returnValue
= false;
5443 function e_stopPropagation(e
) {
5444 if (e
.stopPropagation
) e
.stopPropagation();
5445 else e
.cancelBubble
= true;
5447 function e_defaultPrevented(e
) {
5448 return e
.defaultPrevented
!= null ? e
.defaultPrevented
: e
.returnValue
== false;
5450 function e_stop(e
) {e_preventDefault(e
); e_stopPropagation(e
);}
5451 CodeMirror
.e_stop
= e_stop
;
5452 CodeMirror
.e_preventDefault
= e_preventDefault
;
5453 CodeMirror
.e_stopPropagation
= e_stopPropagation
;
5455 function e_target(e
) {return e
.target
|| e
.srcElement
;}
5456 function e_button(e
) {
5459 if (e
.button
& 1) b
= 1;
5460 else if (e
.button
& 2) b
= 3;
5461 else if (e
.button
& 4) b
= 2;
5463 if (mac
&& e
.ctrlKey
&& b
== 1) b
= 3;
5469 function on(emitter
, type
, f
) {
5470 if (emitter
.addEventListener
)
5471 emitter
.addEventListener(type
, f
, false);
5472 else if (emitter
.attachEvent
)
5473 emitter
.attachEvent("on" + type
, f
);
5475 var map
= emitter
._handlers
|| (emitter
._handlers
= {});
5476 var arr
= map
[type
] || (map
[type
] = []);
5481 function off(emitter
, type
, f
) {
5482 if (emitter
.removeEventListener
)
5483 emitter
.removeEventListener(type
, f
, false);
5484 else if (emitter
.detachEvent
)
5485 emitter
.detachEvent("on" + type
, f
);
5487 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
5489 for (var i
= 0; i
< arr
.length
; ++i
)
5490 if (arr
[i
] == f
) { arr
.splice(i
, 1); break; }
5494 function signal(emitter
, type
/*, values...*/) {
5495 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
5497 var args
= Array
.prototype.slice
.call(arguments
, 2);
5498 for (var i
= 0; i
< arr
.length
; ++i
) arr
[i
].apply(null, args
);
5501 var delayedCallbacks
, delayedCallbackDepth
= 0;
5502 function signalLater(emitter
, type
/*, values...*/) {
5503 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
5505 var args
= Array
.prototype.slice
.call(arguments
, 2);
5506 if (!delayedCallbacks
) {
5507 ++delayedCallbackDepth
;
5508 delayedCallbacks
= [];
5509 setTimeout(fireDelayed
, 0);
5511 function bnd(f
) {return function(){f
.apply(null, args
);};};
5512 for (var i
= 0; i
< arr
.length
; ++i
)
5513 delayedCallbacks
.push(bnd(arr
[i
]));
5516 function signalDOMEvent(cm
, e
, override
) {
5517 signal(cm
, override
|| e
.type
, cm
, e
);
5518 return e_defaultPrevented(e
) || e
.codemirrorIgnore
;
5521 function fireDelayed() {
5522 --delayedCallbackDepth
;
5523 var delayed
= delayedCallbacks
;
5524 delayedCallbacks
= null;
5525 for (var i
= 0; i
< delayed
.length
; ++i
) delayed
[i
]();
5528 function hasHandler(emitter
, type
) {
5529 var arr
= emitter
._handlers
&& emitter
._handlers
[type
];
5530 return arr
&& arr
.length
> 0;
5533 CodeMirror
.on
= on
; CodeMirror
.off
= off
; CodeMirror
.signal
= signal
;
5535 function eventMixin(ctor
) {
5536 ctor
.prototype.on = function(type
, f
) {on(this, type
, f
);};
5537 ctor
.prototype.off = function(type
, f
) {off(this, type
, f
);};
5542 // Number of pixels added to scroller and sizer to hide scrollbar
5543 var scrollerCutOff
= 30;
5545 // Returned or thrown by various protocols to signal 'I'm not
5547 var Pass
= CodeMirror
.Pass
= {toString: function(){return "CodeMirror.Pass";}};
5549 function Delayed() {this.id
= null;}
5550 Delayed
.prototype = {set: function(ms
, f
) {clearTimeout(this.id
); this.id
= setTimeout(f
, ms
);}};
5552 // Counts the column offset in a string, taking tabs into account.
5553 // Used mostly to find indentation.
5554 function countColumn(string
, end
, tabSize
, startIndex
, startValue
) {
5556 end
= string
.search(/[^\s\u00a0]/);
5557 if (end
== -1) end
= string
.length
;
5559 for (var i
= startIndex
|| 0, n
= startValue
|| 0; i
< end
; ++i
) {
5560 if (string
.charAt(i
) == "\t") n
+= tabSize
- (n
% tabSize
);
5565 CodeMirror
.countColumn
= countColumn
;
5567 var spaceStrs
= [""];
5568 function spaceStr(n
) {
5569 while (spaceStrs
.length
<= n
)
5570 spaceStrs
.push(lst(spaceStrs
) + " ");
5571 return spaceStrs
[n
];
5574 function lst(arr
) { return arr
[arr
.length
-1]; }
5576 function selectInput(node
) {
5577 if (ios
) { // Mobile Safari apparently has a bug where select() is broken.
5578 node
.selectionStart
= 0;
5579 node
.selectionEnd
= node
.value
.length
;
5581 // Suppress mysterious IE10 errors
5582 try { node
.select(); }
5587 function indexOf(collection
, elt
) {
5588 if (collection
.indexOf
) return collection
.indexOf(elt
);
5589 for (var i
= 0, e
= collection
.length
; i
< e
; ++i
)
5590 if (collection
[i
] == elt
) return i
;
5594 function createObj(base
, props
) {
5596 Obj
.prototype = base
;
5597 var inst
= new Obj();
5598 if (props
) copyObj(props
, inst
);
5602 function copyObj(obj
, target
) {
5603 if (!target
) target
= {};
5604 for (var prop
in obj
) if (obj
.hasOwnProperty(prop
)) target
[prop
] = obj
[prop
];
5608 function emptyArray(size
) {
5609 for (var a
= [], i
= 0; i
< size
; ++i
) a
.push(undefined);
5614 var args
= Array
.prototype.slice
.call(arguments
, 1);
5615 return function(){return f
.apply(null, args
);};
5618 var nonASCIISingleCaseWordChar
= /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
5619 function isWordChar(ch
) {
5620 return /\w/.test(ch
) || ch
> "\x80" &&
5621 (ch
.toUpperCase() != ch
.toLowerCase() || nonASCIISingleCaseWordChar
.test(ch
));
5624 function isEmpty(obj
) {
5625 for (var n
in obj
) if (obj
.hasOwnProperty(n
) && obj
[n
]) return false;
5629 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]/;
5630 function isExtendingChar(ch
) { return ch
.charCodeAt(0) >= 768 && extendingChars
.test(ch
); }
5634 function elt(tag
, content
, className
, style
) {
5635 var e
= document
.createElement(tag
);
5636 if (className
) e
.className
= className
;
5637 if (style
) e
.style
.cssText
= style
;
5638 if (typeof content
== "string") setTextContent(e
, content
);
5639 else if (content
) for (var i
= 0; i
< content
.length
; ++i
) e
.appendChild(content
[i
]);
5643 function removeChildren(e
) {
5644 for (var count
= e
.childNodes
.length
; count
> 0; --count
)
5645 e
.removeChild(e
.firstChild
);
5649 function removeChildrenAndAdd(parent
, e
) {
5650 return removeChildren(parent
).appendChild(e
);
5653 function setTextContent(e
, str
) {
5656 e
.appendChild(document
.createTextNode(str
));
5657 } else e
.textContent
= str
;
5660 function getRect(node
) {
5661 return node
.getBoundingClientRect();
5663 CodeMirror
.replaceGetRect = function(f
) { getRect
= f
; };
5665 // FEATURE DETECTION
5667 // Detect drag-and-drop
5668 var dragAndDrop = function() {
5669 // There is *some* kind of drag-and-drop support in IE6-8, but I
5670 // couldn't get it to work yet.
5671 if (ie_lt9
) return false;
5672 var div
= elt('div');
5673 return "draggable" in div
|| "dragDrop" in div
;
5676 // For a reason I have yet to figure out, some browsers disallow
5677 // word wrapping between certain characters *only* if a new inline
5678 // element is started between them. This makes it hard to reliably
5679 // measure the position of things, since that requires inserting an
5680 // extra span. This terribly fragile set of tests matches the
5681 // character combinations that suffer from this phenomenon on the
5682 // various browsers.
5683 function spanAffectsWrapping() { return false; }
5684 if (gecko
) // Only for "$'"
5685 spanAffectsWrapping = function(str
, i
) {
5686 return str
.charCodeAt(i
- 1) == 36 && str
.charCodeAt(i
) == 39;
5688 else if (safari
&& !/Version\/([6-9]|\d\d)\b/.test(navigator
.userAgent
))
5689 spanAffectsWrapping = function(str
, i
) {
5690 return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str
.slice(i
- 1, i
+ 1));
5692 else if (webkit
&& /Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator
.userAgent
))
5693 spanAffectsWrapping = function(str
, i
) {
5694 var code
= str
.charCodeAt(i
- 1);
5695 return code
>= 8208 && code
<= 8212;
5698 spanAffectsWrapping = function(str
, i
) {
5699 if (i
> 1 && str
.charCodeAt(i
- 1) == 45) {
5700 if (/\w/.test(str
.charAt(i
- 2)) && /[^\-?\.]/.test(str
.charAt(i
))) return true;
5701 if (i
> 2 && /[\d\.,]/.test(str
.charAt(i
- 2)) && /[\d\.,]/.test(str
.charAt(i
))) return false;
5703 return /[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|\u2026[\w~`@#$%\^&*(_=+{[><]/.test(str
.slice(i
- 1, i
+ 1));
5706 var knownScrollbarWidth
;
5707 function scrollbarWidth(measure
) {
5708 if (knownScrollbarWidth
!= null) return knownScrollbarWidth
;
5709 var test
= elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
5710 removeChildrenAndAdd(measure
, test
);
5711 if (test
.offsetWidth
)
5712 knownScrollbarWidth
= test
.offsetHeight
- test
.clientHeight
;
5713 return knownScrollbarWidth
|| 0;
5717 function zeroWidthElement(measure
) {
5718 if (zwspSupported
== null) {
5719 var test
= elt("span", "\u200b");
5720 removeChildrenAndAdd(measure
, elt("span", [test
, document
.createTextNode("x")]));
5721 if (measure
.firstChild
.offsetHeight
!= 0)
5722 zwspSupported
= test
.offsetWidth
<= 1 && test
.offsetHeight
> 2 && !ie_lt8
;
5724 if (zwspSupported
) return elt("span", "\u200b");
5725 else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
5728 // See if "".split is the broken IE version, if so, provide an
5729 // alternative way to split lines.
5730 var splitLines
= "\n\nb".split(/\n/).length
!= 3 ? function(string
) {
5731 var pos
= 0, result
= [], l
= string
.length
;
5733 var nl
= string
.indexOf("\n", pos
);
5734 if (nl
== -1) nl
= string
.length
;
5735 var line
= string
.slice(pos
, string
.charAt(nl
- 1) == "\r" ? nl
- 1 : nl
);
5736 var rt
= line
.indexOf("\r");
5738 result
.push(line
.slice(0, rt
));
5746 } : function(string
){return string
.split(/\r\n?|\n/);};
5747 CodeMirror
.splitLines
= splitLines
;
5749 var hasSelection
= window
.getSelection
? function(te
) {
5750 try { return te
.selectionStart
!= te
.selectionEnd
; }
5751 catch(e
) { return false; }
5753 try {var range
= te
.ownerDocument
.selection
.createRange();}
5755 if (!range
|| range
.parentElement() != te
) return false;
5756 return range
.compareEndPoints("StartToEnd", range
) != 0;
5759 var hasCopyEvent
= (function() {
5761 if ("oncopy" in e
) return true;
5762 e
.setAttribute("oncopy", "return;");
5763 return typeof e
.oncopy
== 'function';
5768 var keyNames
= {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
5769 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
5770 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
5771 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete",
5772 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
5773 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
5774 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"};
5775 CodeMirror
.keyNames
= keyNames
;
5778 for (var i
= 0; i
< 10; i
++) keyNames
[i
+ 48] = keyNames
[i
+ 96] = String(i
);
5780 for (var i
= 65; i
<= 90; i
++) keyNames
[i
] = String
.fromCharCode(i
);
5782 for (var i
= 1; i
<= 12; i
++) keyNames
[i
+ 111] = keyNames
[i
+ 63235] = "F" + i
;
5787 function iterateBidiSections(order
, from, to
, f
) {
5788 if (!order
) return f(from, to
, "ltr");
5790 for (var i
= 0; i
< order
.length
; ++i
) {
5791 var part
= order
[i
];
5792 if (part
.from < to
&& part
.to
> from || from == to
&& part
.to
== from) {
5793 f(Math
.max(part
.from, from), Math
.min(part
.to
, to
), part
.level
== 1 ? "rtl" : "ltr");
5797 if (!found
) f(from, to
, "ltr");
5800 function bidiLeft(part
) { return part
.level
% 2 ? part
.to
: part
.from; }
5801 function bidiRight(part
) { return part
.level
% 2 ? part
.from : part
.to
; }
5803 function lineLeft(line
) { var order
= getOrder(line
); return order
? bidiLeft(order
[0]) : 0; }
5804 function lineRight(line
) {
5805 var order
= getOrder(line
);
5806 if (!order
) return line
.text
.length
;
5807 return bidiRight(lst(order
));
5810 function lineStart(cm
, lineN
) {
5811 var line
= getLine(cm
.doc
, lineN
);
5812 var visual
= visualLine(cm
.doc
, line
);
5813 if (visual
!= line
) lineN
= lineNo(visual
);
5814 var order
= getOrder(visual
);
5815 var ch
= !order
? 0 : order
[0].level
% 2 ? lineRight(visual
) : lineLeft(visual
);
5816 return Pos(lineN
, ch
);
5818 function lineEnd(cm
, lineN
) {
5820 while (merged
= collapsedSpanAtEnd(line
= getLine(cm
.doc
, lineN
)))
5821 lineN
= merged
.find().to
.line
;
5822 var order
= getOrder(line
);
5823 var ch
= !order
? line
.text
.length
: order
[0].level
% 2 ? lineLeft(line
) : lineRight(line
);
5824 return Pos(lineN
, ch
);
5827 function compareBidiLevel(order
, a
, b
) {
5828 var linedir
= order
[0].level
;
5829 if (a
== linedir
) return true;
5830 if (b
== linedir
) return false;
5834 function getBidiPartAt(order
, pos
) {
5836 for (var i
= 0, found
; i
< order
.length
; ++i
) {
5838 if (cur
.from < pos
&& cur
.to
> pos
) return i
;
5839 if ((cur
.from == pos
|| cur
.to
== pos
)) {
5840 if (found
== null) {
5842 } else if (compareBidiLevel(order
, cur
.level
, order
[found
].level
)) {
5843 if (cur
.from != cur
.to
) bidiOther
= found
;
5846 if (cur
.from != cur
.to
) bidiOther
= i
;
5854 function moveInLine(line
, pos
, dir
, byUnit
) {
5855 if (!byUnit
) return pos
+ dir
;
5857 while (pos
> 0 && isExtendingChar(line
.text
.charAt(pos
)));
5861 // This is somewhat involved. It is needed in order to move
5862 // 'visually' through bi-directional text -- i.e., pressing left
5863 // should make the cursor go left, even when in RTL text. The
5864 // tricky part is the 'jumps', where RTL and LTR text touch each
5865 // other. This often requires the cursor offset to move more than
5866 // one unit, in order to visually move one unit.
5867 function moveVisually(line
, start
, dir
, byUnit
) {
5868 var bidi
= getOrder(line
);
5869 if (!bidi
) return moveLogically(line
, start
, dir
, byUnit
);
5870 var pos
= getBidiPartAt(bidi
, start
), part
= bidi
[pos
];
5871 var target
= moveInLine(line
, start
, part
.level
% 2 ? -dir
: dir
, byUnit
);
5874 if (target
> part
.from && target
< part
.to
) return target
;
5875 if (target
== part
.from || target
== part
.to
) {
5876 if (getBidiPartAt(bidi
, target
) == pos
) return target
;
5877 part
= bidi
[pos
+= dir
];
5878 return (dir
> 0) == part
.level
% 2 ? part
.to
: part
.from;
5880 part
= bidi
[pos
+= dir
];
5881 if (!part
) return null;
5882 if ((dir
> 0) == part
.level
% 2)
5883 target
= moveInLine(line
, part
.to
, -1, byUnit
);
5885 target
= moveInLine(line
, part
.from, 1, byUnit
);
5890 function moveLogically(line
, start
, dir
, byUnit
) {
5891 var target
= start
+ dir
;
5892 if (byUnit
) while (target
> 0 && isExtendingChar(line
.text
.charAt(target
))) target
+= dir
;
5893 return target
< 0 || target
> line
.text
.length
? null : target
;
5896 // Bidirectional ordering algorithm
5897 // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
5898 // that this (partially) implements.
5900 // One-char codes used for character types:
5901 // L (L): Left-to-Right
5902 // R (R): Right-to-Left
5903 // r (AL): Right-to-Left Arabic
5904 // 1 (EN): European Number
5905 // + (ES): European Number Separator
5906 // % (ET): European Number Terminator
5907 // n (AN): Arabic Number
5908 // , (CS): Common Number Separator
5909 // m (NSM): Non-Spacing Mark
5910 // b (BN): Boundary Neutral
5911 // s (B): Paragraph Separator
5912 // t (S): Segment Separator
5913 // w (WS): Whitespace
5914 // N (ON): Other Neutrals
5916 // Returns null if characters are ordered as they appear
5917 // (left-to-right), or an array of sections ({from, to, level}
5918 // objects) in the order in which they occur visually.
5919 var bidiOrdering
= (function() {
5920 // Character types for codepoints 0 to 0xff
5921 var lowTypes
= "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL";
5922 // Character types for codepoints 0x600 to 0x6ff
5923 var arabicTypes
= "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr";
5924 function charType(code
) {
5925 if (code
<= 0xff) return lowTypes
.charAt(code
);
5926 else if (0x590 <= code
&& code
<= 0x5f4) return "R";
5927 else if (0x600 <= code
&& code
<= 0x6ff) return arabicTypes
.charAt(code
- 0x600);
5928 else if (0x700 <= code
&& code
<= 0x8ac) return "r";
5932 var bidiRE
= /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
5933 var isNeutral
= /[stwN]/, isStrong
= /[LRr]/, countsAsLeft
= /[Lb1n]/, countsAsNum
= /[1n]/;
5934 // Browsers seem to always treat the boundaries of block elements as being L.
5935 var outerType
= "L";
5937 return function(str
) {
5938 if (!bidiRE
.test(str
)) return false;
5939 var len
= str
.length
, types
= [];
5940 for (var i
= 0, type
; i
< len
; ++i
)
5941 types
.push(type
= charType(str
.charCodeAt(i
)));
5943 // W1. Examine each non-spacing mark (NSM) in the level run, and
5944 // change the type of the NSM to the type of the previous
5945 // character. If the NSM is at the start of the level run, it will
5946 // get the type of sor.
5947 for (var i
= 0, prev
= outerType
; i
< len
; ++i
) {
5948 var type
= types
[i
];
5949 if (type
== "m") types
[i
] = prev
;
5953 // W2. Search backwards from each instance of a European number
5954 // until the first strong type (R, L, AL, or sor) is found. If an
5955 // AL is found, change the type of the European number to Arabic
5957 // W3. Change all ALs to R.
5958 for (var i
= 0, cur
= outerType
; i
< len
; ++i
) {
5959 var type
= types
[i
];
5960 if (type
== "1" && cur
== "r") types
[i
] = "n";
5961 else if (isStrong
.test(type
)) { cur
= type
; if (type
== "r") types
[i
] = "R"; }
5964 // W4. A single European separator between two European numbers
5965 // changes to a European number. A single common separator between
5966 // two numbers of the same type changes to that type.
5967 for (var i
= 1, prev
= types
[0]; i
< len
- 1; ++i
) {
5968 var type
= types
[i
];
5969 if (type
== "+" && prev
== "1" && types
[i
+1] == "1") types
[i
] = "1";
5970 else if (type
== "," && prev
== types
[i
+1] &&
5971 (prev
== "1" || prev
== "n")) types
[i
] = prev
;
5975 // W5. A sequence of European terminators adjacent to European
5976 // numbers changes to all European numbers.
5977 // W6. Otherwise, separators and terminators change to Other
5979 for (var i
= 0; i
< len
; ++i
) {
5980 var type
= types
[i
];
5981 if (type
== ",") types
[i
] = "N";
5982 else if (type
== "%") {
5983 for (var end
= i
+ 1; end
< len
&& types
[end
] == "%"; ++end
) {}
5984 var replace
= (i
&& types
[i
-1] == "!") || (end
< len
&& types
[end
] == "1") ? "1" : "N";
5985 for (var j
= i
; j
< end
; ++j
) types
[j
] = replace
;
5990 // W7. Search backwards from each instance of a European number
5991 // until the first strong type (R, L, or sor) is found. If an L is
5992 // found, then change the type of the European number to L.
5993 for (var i
= 0, cur
= outerType
; i
< len
; ++i
) {
5994 var type
= types
[i
];
5995 if (cur
== "L" && type
== "1") types
[i
] = "L";
5996 else if (isStrong
.test(type
)) cur
= type
;
5999 // N1. A sequence of neutrals takes the direction of the
6000 // surrounding strong text if the text on both sides has the same
6001 // direction. European and Arabic numbers act as if they were R in
6002 // terms of their influence on neutrals. Start-of-level-run (sor)
6003 // and end-of-level-run (eor) are used at level run boundaries.
6004 // N2. Any remaining neutrals take the embedding direction.
6005 for (var i
= 0; i
< len
; ++i
) {
6006 if (isNeutral
.test(types
[i
])) {
6007 for (var end
= i
+ 1; end
< len
&& isNeutral
.test(types
[end
]); ++end
) {}
6008 var before
= (i
? types
[i
-1] : outerType
) == "L";
6009 var after
= (end
< len
? types
[end
] : outerType
) == "L";
6010 var replace
= before
|| after
? "L" : "R";
6011 for (var j
= i
; j
< end
; ++j
) types
[j
] = replace
;
6016 // Here we depart from the documented algorithm, in order to avoid
6017 // building up an actual levels array. Since there are only three
6018 // levels (0, 1, 2) in an implementation that doesn't take
6019 // explicit embedding into account, we can build up the order on
6020 // the fly, without following the level-based algorithm.
6022 for (var i
= 0; i
< len
;) {
6023 if (countsAsLeft
.test(types
[i
])) {
6025 for (++i
; i
< len
&& countsAsLeft
.test(types
[i
]); ++i
) {}
6026 order
.push({from: start
, to
: i
, level
: 0});
6028 var pos
= i
, at
= order
.length
;
6029 for (++i
; i
< len
&& types
[i
] != "L"; ++i
) {}
6030 for (var j
= pos
; j
< i
;) {
6031 if (countsAsNum
.test(types
[j
])) {
6032 if (pos
< j
) order
.splice(at
, 0, {from: pos
, to
: j
, level
: 1});
6034 for (++j
; j
< i
&& countsAsNum
.test(types
[j
]); ++j
) {}
6035 order
.splice(at
, 0, {from: nstart
, to
: j
, level
: 2});
6039 if (pos
< i
) order
.splice(at
, 0, {from: pos
, to
: i
, level
: 1});
6042 if (order
[0].level
== 1 && (m
= str
.match(/^\s+/))) {
6043 order
[0].from = m
[0].length
;
6044 order
.unshift({from: 0, to
: m
[0].length
, level
: 0});
6046 if (lst(order
).level
== 1 && (m
= str
.match(/\s+$/))) {
6047 lst(order
).to
-= m
[0].length
;
6048 order
.push({from: len
- m
[0].length
, to
: len
, level
: 0});
6050 if (order
[0].level
!= lst(order
).level
)
6051 order
.push({from: len
, to
: len
, level
: order
[0].level
});
6059 CodeMirror
.version
= "3.21.1";
6065 * Java parser for codemirror
6067 * @author Patrick Wied
6070 var JavaParser
= Editor
.Parser
= (function() {
6071 // Token types that can be considered to be atoms.
6072 var atomicTypes
= {"atom": true, "number": true, "string": true, "regexp": true};
6073 // Setting that can be used to have JSON data indent properly.
6075 // Constructor for the lexical context objects.
6076 function JavaLexical(indented
, column
, type
, align
, prev
, info
) {
6077 // indentation at start of this line
6078 this.indented
= indented
;
6079 // column at which this scope was opened
6080 this.column
= column
;
6081 // type of scope ( 'stat' (statement), 'form' (special form), '[', '{', or '(')
6083 // '[', '{', or '(' blocks that have any text after their opening
6084 // character are said to be 'aligned' -- any lines below are
6085 // indented all the way to the opening character.
6088 // Parent scope, if any.
6093 // java indentation rules.
6094 function indentJava(lexical
) {
6095 return function(firstChars
) {
6096 var firstChar
= firstChars
&& firstChars
.charAt(0), type
= lexical
.type
;
6097 var closing
= firstChar
== type
;
6098 if (type
== "form" && firstChar
== "{")
6099 return lexical
.indented
;
6100 else if (type
== "stat" || type
== "form")
6101 return lexical
.indented
+ indentUnit
;
6102 else if (lexical
.info
== "switch" && !closing
)
6103 return lexical
.indented
+ (/^(?:case|default)\b/.test(firstChars
) ? indentUnit
: 2 * indentUnit
);
6104 else if (lexical
.align
)
6105 return lexical
.column
- (closing
? 1 : 0);
6107 return lexical
.indented
+ (closing
? 0 : indentUnit
);
6111 // The parser-iterator-producing function itself.
6112 function parseJava(input
, basecolumn
) {
6113 // Wrap the input in a token stream
6114 var tokens
= tokenizeJava(input
);
6115 // The parser state. cc is a stack of actions that have to be
6116 // performed to finish the current statement. For example we might
6117 // know that we still need to find a closing parenthesis and a
6118 // semicolon. Actions at the end of the stack go first. It is
6119 // initialized with an infinitely looping action that consumes
6120 // whole statements.
6121 var cc
= [statements
];
6122 // The lexical scope, used mostly for indentation.
6123 var lexical
= new JavaLexical(basecolumn
|| 0, 0, "block", false);
6124 // Current column, and the indentation at the start of the current
6125 // line. Used to create lexical scope objects.
6128 // Variables which are used by the mark, cont, and pass functions
6129 // below to communicate with the driver loop in the 'next'
6131 var consume
, marked
;
6133 // The iterator object.
6134 var parser
= {next
: next
, copy
: copy
};
6137 // Start by performing any 'lexical' actions (adjusting the
6138 // lexical variable), or the operations below will be working
6139 // with the wrong lexical state.
6140 while(cc
[cc
.length
- 1].lex
)
6144 var token
= tokens
.next();
6146 // Adjust column and indented.
6147 if (token
.type
== "whitespace" && column
== 0)
6148 indented
= token
.value
.length
;
6149 column
+= token
.value
.length
;
6150 if (token
.content
== "\n"){
6151 indented
= column
= 0;
6152 // If the lexical scope's align property is still undefined at
6153 // the end of the line, it is an un-aligned scope.
6154 if (!("align" in lexical
))
6155 lexical
.align
= false;
6156 // Newline tokens get an indentation function associated with
6158 token
.indentation
= indentJava(lexical
);
6161 // No more processing for meaningless tokens.
6162 if (token
.type
== "whitespace" || token
.type
== "comment" || token
.type
== "javadoc" || token
.type
== "annotation")
6165 // When a meaningful token is found and the lexical scope's
6166 // align is undefined, it is an aligned scope.
6167 if (!("align" in lexical
))
6168 lexical
.align
= true;
6170 // Execute actions until one 'consumes' the token and we can
6173 consume
= marked
= false;
6174 // Take and execute the topmost action.
6175 cc
.pop()(token
.type
, token
.content
);
6177 // Marked is used to change the style of the current token.
6179 token
.style
= marked
;
6185 // This makes a copy of the parser state. It stores all the
6186 // stateful variables in a closure, and returns a function that
6187 // will restore them when called with a new input stream. Note
6188 // that the cc array has to be copied, because it is contantly
6189 // being modified. Lexical objects are not mutated, and context
6190 // objects are not mutated in a harmful way, so they can be shared
6191 // between runs of the parser.
6193 var _lexical
= lexical
, _cc
= cc
.concat([]), _tokenState
= tokens
.state
;
6195 return function copyParser(input
){
6197 cc
= _cc
.concat([]); // copies the array
6198 column
= indented
= 0;
6199 tokens
= tokenizeJava(input
, _tokenState
);
6204 // Helper function for pushing a number of actions onto the cc
6205 // stack in reverse order.
6207 for (var i
= fs
.length
- 1; i
>= 0; i
--)
6210 // cont and pass are used by the action functions to add other
6211 // actions to the stack. cont will cause the current token to be
6212 // consumed, pass will leave it for the next action.
6221 // Used to change the style of the current token.
6222 function mark(style
){
6226 // Push a new lexical context of the given type.
6227 function pushlex(type
, info
) {
6228 var result = function(){
6229 lexical
= new JavaLexical(indented
, column
, type
, null, lexical
, info
)
6234 // Pop off the current lexical context.
6236 lexical
= lexical
.prev
;
6239 // The 'lex' flag on these actions is used by the 'next' function
6240 // to know they can (and have to) be ran before moving on to the
6243 // Creates an action that discards tokens until it finds one of
6245 function expect(wanted
){
6246 return function expecting(type
){
6247 if (type
== wanted
) cont();
6248 else cont(arguments
.callee
);
6252 // Looks for a statement, and then calls itself.
6253 function statements(type
){
6254 return pass(statement
, statements
);
6256 // Dispatches various types of statements based on the type of the
6258 function statement(type
){
6259 if (type
== "keyword a") cont(pushlex("form"), expression
, statement
, poplex
);
6260 else if (type
== "keyword b") cont(pushlex("form"), statement
, poplex
);
6261 else if (type
== "{") cont(pushlex("}"), block
, poplex
);
6262 else if (type
== "for") cont(pushlex("form"), expect("("), pushlex(")"), forspec1
, expect(")"), poplex
, statement
, poplex
);
6263 else if (type
== "variable") cont(pushlex("stat"), maybelabel
);
6264 else if (type
== "switch") cont(pushlex("form"), expression
, pushlex("}", "switch"), expect("{"), block
, poplex
, poplex
);
6265 else if (type
== "case") cont(expression
, expect(":"));
6266 else if (type
== "default") cont(expect(":"));
6267 else if (type
== "catch") cont(pushlex("form"), expect("("), function(){}, expect(")"), statement
, poplex
);
6268 else if (type
== "class") cont();
6269 else if (type
== "interface") cont();
6270 else if (type
== "keyword c") cont(statement
);
6271 else pass(pushlex("stat"), expression
, expect(";"), poplex
);
6273 // Dispatch expression types.
6274 function expression(type
){
6275 if (atomicTypes
.hasOwnProperty(type
)) cont(maybeoperator
);
6276 //else if (type == "function") cont(functiondef);
6277 else if (type
== "keyword c") cont(expression
);
6278 else if (type
== "(") cont(pushlex(")"), expression
, expect(")"), poplex
, maybeoperator
);
6279 else if (type
== "operator") cont(expression
);
6280 else if (type
== "[") cont(pushlex("]"), commasep(expression
, "]"), poplex
, maybeoperator
);
6282 // Called for places where operators, function calls, or
6283 // subscripts are valid. Will skip on to the next action if none
6285 function maybeoperator(type
){
6286 if (type
== "operator") cont(expression
);
6287 else if (type
== "(") cont(pushlex(")"), expression
, commasep(expression
, ")"), poplex
, maybeoperator
);
6288 else if (type
== "[") cont(pushlex("]"), expression
, expect("]"), poplex
, maybeoperator
);
6290 // When a statement starts with a variable name, it might be a
6291 // label. If no colon follows, it's a regular statement.
6293 function maybelabel(type
){
6294 if (type
== "(") cont(commasep(function(){}, ")"), poplex
, statement
); // method definition
6295 else if (type
== "{") cont(poplex
, pushlex("}"), block
, poplex
); // property definition
6296 else pass(maybeoperator
, expect(";"), poplex
);
6299 // Parses a comma-separated list of the things that are recognized
6300 // by the 'what' argument.
6301 function commasep(what
, end
){
6302 function proceed(type
) {
6303 if (type
== ",") cont(what
, proceed
);
6304 else if (type
== end
) cont();
6305 else cont(expect(end
));
6307 return function commaSeparated(type
) {
6308 if (type
== end
) cont();
6309 else pass(what
, proceed
);
6312 // Look for statements until a closing brace is found.
6313 function block(type
){
6314 if (type
== "}") cont();
6315 else pass(statement
, block
);
6320 function forspec1(type
){
6321 if (type
== ";") pass(forspec2
);
6322 else pass(forspec2
);
6324 function formaybein(type
, value
){
6325 if (value
== "in") cont(expression
);
6326 else cont(maybeoperator
, forspec2
);
6328 function forspec2(type
, value
){
6329 if (type
== ";") cont(forspec3
);
6330 else if (value
== "in") cont(expression
);
6331 else cont(expression
, expect(";"), forspec3
);
6333 function forspec3(type
) {
6334 if (type
== ")") pass();
6335 else cont(expression
);
6343 electricChars
: "{}:",
6344 configure: function(obj
) {
6345 if (obj
.json
!= null) json
= obj
.json
;
6350 * Java tokenizer for codemirror
6352 * @author Patrick Wied
6353 * @version 2010-10-07
6355 var tokenizeJava
= (function() {
6356 // Advance the stream until the given character (not preceded by a
6357 // backslash) is encountered, or the end of the line is reached.
6358 function nextUntilUnescaped(source
, end
) {
6359 var escaped
= false;
6361 while (!source
.endOfLine()) {
6362 var next
= source
.next();
6363 if (next
== end
&& !escaped
)
6365 escaped
= !escaped
&& next
== "\\";
6370 // A map of Java's keywords. The a/b/c keyword distinction is
6371 // very rough, but it gives the parser enough information to parse
6372 // correct code correctly (we don't care that much how we parse
6373 // incorrect code). The style information included in these objects
6374 // is used by the highlighter to pick the correct CSS style for a
6376 var keywords = function(){
6377 function result(type
, style
){
6378 return {type
: type
, style
: "java-" + style
};
6380 // keywords that take a parenthised expression, and then a
6382 var keywordA
= result("keyword a", "keyword");
6383 // keywords that take just a statement (else)
6384 var keywordB
= result("keyword b", "keyword");
6385 // keywords that optionally take an expression, and form a
6386 // statement (return)
6387 var keywordC
= result("keyword c", "keyword");
6388 var operator
= result("operator", "keyword");
6389 var atom
= result("atom", "atom");
6392 "if": keywordA
, "while": keywordA
, "with": keywordA
,
6393 "else": keywordB
, "do": keywordB
, "try": keywordB
, "finally": keywordB
,
6394 "return": keywordC
, "break": keywordC
, "continue": keywordC
, "new": keywordC
, "throw": keywordC
, "throws": keywordB
,
6395 "in": operator
, "typeof": operator
, "instanceof": operator
,
6396 "catch": result("catch", "keyword"), "for": result("for", "keyword"), "switch": result("switch", "keyword"),
6397 "case": result("case", "keyword"), "default": result("default", "keyword"),
6398 "true": atom
, "false": atom
, "null": atom
,
6400 "class": result("class", "keyword"), "interface": result("interface", "keyword"), "package": keywordC
, "import": keywordC
,
6401 "implements": keywordC
, "extends": keywordC
, "super": keywordC
,
6403 "public": keywordC
, "private": keywordC
, "protected": keywordC
, "transient": keywordC
, "this": keywordC
,
6404 "static": keywordC
, "final": keywordC
, "const": keywordC
, "abstract": keywordC
, "static": keywordC
,
6406 "int": keywordC
, "double": keywordC
, "long": keywordC
, "boolean": keywordC
, "char": keywordC
,
6407 "void": keywordC
, "byte": keywordC
, "float": keywordC
, "short": keywordC
6411 // Some helper regexps
6412 var isOperatorChar
= /[+\-*&%=<>!?|]/;
6413 var isHexDigit
= /[0-9A-Fa-f]/;
6414 var isWordChar
= /[\w\$_]/;
6415 // Wrapper around javaToken that helps maintain parser state (whether
6416 // we are inside of a multi-line comment and whether the next token
6417 // could be a regular expression).
6418 function javaTokenState(inside
, regexp
) {
6419 return function(source
, setState
) {
6420 var newInside
= inside
;
6421 var type
= javaToken(inside
, regexp
, source
, function(c
) {newInside
= c
;});
6422 var newRegexp
= type
.type
== "operator" || type
.type
== "keyword c" || type
.type
.match(/^[\[{}\(,;:]$/);
6423 if (newRegexp
!= regexp
|| newInside
!= inside
)
6424 setState(javaTokenState(newInside
, newRegexp
));
6429 // The token reader, inteded to be used by the tokenizer from
6430 // tokenize.js (through jsTokenState). Advances the source stream
6431 // over a token, and returns an object containing the type and style
6433 function javaToken(inside
, regexp
, source
, setInside
) {
6434 function readHexNumber(){
6435 source
.next(); // skip the 'x'
6436 source
.nextWhileMatches(isHexDigit
);
6437 return {type
: "number", style
: "java-atom"};
6440 function readNumber() {
6441 source
.nextWhileMatches(/[0-9]/);
6442 if (source
.equals(".")){
6444 source
.nextWhileMatches(/[0-9]/);
6446 if (source
.equals("e") || source
.equals("E")){
6448 if (source
.equals("-"))
6450 source
.nextWhileMatches(/[0-9]/);
6452 return {type
: "number", style
: "java-atom"};
6454 // Read a word, look it up in keywords. If not found, it is a
6455 // variable, otherwise it is a keyword of the type found.
6456 function readWord() {
6457 source
.nextWhileMatches(isWordChar
);
6458 var word
= source
.get();
6459 var known
= keywords
.hasOwnProperty(word
) && keywords
.propertyIsEnumerable(word
) && keywords
[word
];
6460 return known
? {type
: known
.type
, style
: known
.style
, content
: word
} :
6461 {type
: "variable", style
: "java-variable", content
: word
};
6463 function readRegexp() {
6464 nextUntilUnescaped(source
, "/");
6465 source
.nextWhileMatches(/[gi]/);
6466 return {type
: "regexp", style
: "java-string"};
6468 // Mutli-line comments are tricky. We want to return the newlines
6469 // embedded in them as regular newline tokens, and then continue
6470 // returning a comment token for every line of the comment. So
6471 // some state has to be saved (inside) to indicate whether we are
6472 // inside a /* */ sequence.
6473 function readMultilineComment(start
){
6474 var newInside
= "/*";
6475 var maybeEnd
= (start
== "*");
6477 if (source
.endOfLine())
6479 var next
= source
.next();
6480 if (next
== "/" && maybeEnd
){
6484 maybeEnd
= (next
== "*");
6486 setInside(newInside
);
6487 return {type
: "comment", style
: "java-comment"};
6490 // for reading javadoc
6491 function readJavaDocComment(start
){
6492 var newInside
= "/**";
6493 var maybeEnd
= (start
== "*");
6495 if (source
.endOfLine())
6497 var next
= source
.next();
6498 if (next
== "/" && maybeEnd
){
6502 maybeEnd
= (next
== "*");
6504 setInside(newInside
);
6505 return {type
: "javadoc", style
: "javadoc-comment"};
6507 // for reading annotations (word based)
6508 function readAnnotation(){
6509 source
.nextWhileMatches(isWordChar
);
6510 var word
= source
.get();
6511 return {type
: "annotation", style
: "java-annotation", content
:word
};
6514 function readOperator() {
6515 source
.nextWhileMatches(isOperatorChar
);
6516 return {type
: "operator", style
: "java-operator"};
6518 function readString(quote
) {
6519 var endBackSlash
= nextUntilUnescaped(source
, quote
);
6520 setInside(endBackSlash
? quote
: null);
6521 return {type
: "string", style
: "java-string"};
6524 // Fetch the next token. Dispatches on first character in the
6525 // stream, or first two characters when the first is a slash.
6526 if (inside
== "\"" || inside
== "'")
6527 return readString(inside
);
6528 var ch
= source
.next();
6530 return readMultilineComment(ch
);
6531 else if(inside
== "/**")
6532 return readJavaDocComment(ch
);
6533 else if (ch
== "\"" || ch
== "'")
6534 return readString(ch
);
6535 // with punctuation, the type of the token is the symbol itself
6536 else if (/[\[\]{}\(\),;\:\.]/.test(ch
))
6537 return {type
: ch
, style
: "java-punctuation"};
6538 else if (ch
== "0" && (source
.equals("x") || source
.equals("X")))
6539 return readHexNumber();
6540 else if (/[0-9]/.test(ch
))
6541 return readNumber();
6542 else if (ch
== "@"){
6543 return readAnnotation();
6544 }else if (ch
== "/"){
6545 if (source
.equals("*")){
6548 if(source
.equals("*"))
6549 return readJavaDocComment(ch
);
6551 return readMultilineComment(ch
);
6553 else if (source
.equals("/"))
6554 { nextUntilUnescaped(source
, null); return {type
: "comment", style
: "java-comment"};}
6556 return readRegexp();
6558 return readOperator();
6560 else if (isOperatorChar
.test(ch
))
6561 return readOperator();
6566 // The external interface to the tokenizer.
6567 return function(source
, startState
) {
6568 return tokenizer(source
, startState
|| javaTokenState(false, true));