1 // All functions that need access to the editor's state live inside
2 // the CodeMirror function. Below that, at the bottom of the file,
3 // some utilities are defined.
5 // CodeMirror is the only global var we claim
6 var CodeMirror
= (function() {
7 // This is the function that produces an editor instance. It's
8 // closure is used to store the editor state.
9 function CodeMirror(place
, givenOptions
) {
10 // Determine effective options based on given values and defaults.
11 var options
= {}, defaults
= CodeMirror
.defaults
;
12 for (var opt
in defaults
)
13 if (defaults
.hasOwnProperty(opt
))
14 options
[opt
] = (givenOptions
&& givenOptions
.hasOwnProperty(opt
) ? givenOptions
: defaults
)[opt
];
16 var targetDocument
= options
["document"];
17 // The element in which the editor lives.
18 var wrapper
= targetDocument
.createElement("div");
19 wrapper
.className
= "CodeMirror";
20 // This mess creates the base DOM structure for the editor.
22 '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea
23 '<textarea style="position: absolute; width: 2px;" wrap="off"></textarea></div>' +
24 '<div class="CodeMirror-scroll cm-s-' + options
.theme
+ '">' +
25 '<div style="position: relative">' + // Set to the height of the text, causes scrolling
26 '<div style="position: absolute; height: 0; width: 0; overflow: hidden;"></div>' +
27 '<div style="position: relative">' + // Moved around its parent to cover visible view
28 '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
29 // Provides positioning relative to (visible) text origin
30 '<div class="CodeMirror-lines"><div style="position: relative">' +
31 '<pre class="CodeMirror-cursor"> </pre>' + // Absolutely positioned blinky cursor
32 '<div></div>' + // This DIV contains the actual code
33 '</div></div></div></div></div>';
34 if (place
.appendChild
) place
.appendChild(wrapper
); else place(wrapper
);
35 // I've never seen more elegant code in my life.
36 var inputDiv
= wrapper
.firstChild
, input
= inputDiv
.firstChild
,
37 scroller
= wrapper
.lastChild
, code
= scroller
.firstChild
,
38 measure
= code
.firstChild
, mover
= measure
.nextSibling
,
39 gutter
= mover
.firstChild
, gutterText
= gutter
.firstChild
,
40 lineSpace
= gutter
.nextSibling
.firstChild
,
41 cursor
= lineSpace
.firstChild
, lineDiv
= cursor
.nextSibling
;
42 if (options
.tabindex
!= null) input
.tabindex
= options
.tabindex
;
43 if (!options
.gutter
&& !options
.lineNumbers
) gutter
.style
.display
= "none";
45 // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
46 var poll
= new Delayed(), highlight
= new Delayed(), blinker
;
48 // mode holds a mode API object. lines an array of Line objects
49 // (see Line constructor), work an array of lines that should be
50 // parsed, and history the undo history (instance of History
52 var mode
, lines
= [new Line("")], work
, history
= new History(), focused
;
54 // The selection. These are always maintained to point at valid
55 // positions. Inverted is used to remember that the user is
56 // selecting bottom-to-top.
57 var sel
= {from: {line
: 0, ch
: 0}, to
: {line
: 0, ch
: 0}, inverted
: false};
58 // Selection-related flags. shiftSelecting obviously tracks
59 // whether the user is holding shift. reducedSelection is a hack
60 // to get around the fact that we can't create inverted
61 // selections. See below.
62 var shiftSelecting
, reducedSelection
, lastDoubleClick
;
63 // Variables used by startOperation/endOperation to track what
64 // happened during the operation.
65 var updateInput
, changes
, textChanged
, selectionChanged
, leaveInputAlone
;
66 // Current visible range (may be bigger than the view window).
67 var showingFrom
= 0, showingTo
= 0, lastHeight
= 0, curKeyId
= null;
68 // editing will hold an object describing the things we put in the
69 // textarea, to help figure out whether something changed.
70 // bracketHighlighted is used to remember that a backet has been
72 var editing
, bracketHighlighted
;
73 // Tracks the maximum line length so that the horizontal scrollbar
74 // can be kept static when scrolling.
75 var maxLine
= "", maxWidth
;
77 // Initialize the content.
78 operation(function(){setValue(options
.value
|| ""); updateInput
= false;})();
80 // Register our event handlers.
81 connect(scroller
, "mousedown", operation(onMouseDown
));
82 // Gecko browsers fire contextmenu *after* opening the menu, at
83 // which point we can't mess with it anymore. Context menu is
84 // handled in onMouseDown for Gecko.
85 if (!gecko
) connect(scroller
, "contextmenu", onContextMenu
);
86 connect(code
, "dblclick", operation(onDblClick
));
87 connect(scroller
, "scroll", function() {updateDisplay([]); if (options
.onScroll
) options
.onScroll(instance
);});
88 connect(window
, "resize", function() {updateDisplay(true);});
89 connect(input
, "keyup", operation(onKeyUp
));
90 connect(input
, "keydown", operation(onKeyDown
));
91 connect(input
, "keypress", operation(onKeyPress
));
92 connect(input
, "focus", onFocus
);
93 connect(input
, "blur", onBlur
);
95 connect(scroller
, "dragenter", e_stop
);
96 connect(scroller
, "dragover", e_stop
);
97 connect(scroller
, "drop", operation(onDrop
));
98 connect(scroller
, "paste", function(){focusInput(); fastPoll();});
99 connect(input
, "paste", function(){fastPoll();});
100 connect(input
, "cut", function(){fastPoll();});
102 // IE throws unspecified error in certain cases, when
103 // trying to access activeElement before onload
104 var hasFocus
; try { hasFocus
= (targetDocument
.activeElement
== input
); } catch(e
) { }
105 if (hasFocus
) setTimeout(onFocus
, 20);
108 function isLine(l
) {return l
>= 0 && l
< lines
.length
;}
109 // The instance object that we'll return. Mostly calls out to
110 // local functions in the CodeMirror function. Some do some extra
111 // range checking and/or clipping. operation is used to wrap the
112 // call so that changes it makes are tracked, and the display is
113 // updated afterwards.
116 setValue
: operation(setValue
),
117 getSelection
: getSelection
,
118 replaceSelection
: operation(replaceSelection
),
119 focus: function(){focusInput(); onFocus(); fastPoll();},
120 setOption: function(option
, value
) {
121 options
[option
] = value
;
122 if (option
== "lineNumbers" || option
== "gutter") gutterChanged();
123 else if (option
== "mode" || option
== "indentUnit") loadMode();
124 else if (option
== "readOnly" && value
== "nocursor") input
.blur();
125 else if (option
== "theme") scroller
.className
= scroller
.className
.replace(/cm-s-\w+/, "cm-s-" + value
);
127 getOption: function(option
) {return options
[option
];},
128 undo
: operation(undo
),
129 redo
: operation(redo
),
130 indentLine
: operation(function(n
) {if (isLine(n
)) indentLine(n
, "smart");}),
131 historySize: function() {return {undo
: history
.done
.length
, redo
: history
.undone
.length
};},
132 matchBrackets
: operation(function(){matchBrackets(true);}),
133 getTokenAt: function(pos
) {
135 return lines
[pos
.line
].getTokenAt(mode
, getStateBefore(pos
.line
), pos
.ch
);
137 getStateAfter: function(line
) {
138 line
= clipLine(line
== null ? lines
.length
- 1: line
);
139 return getStateBefore(line
+ 1);
141 cursorCoords: function(start
){
142 if (start
== null) start
= sel
.inverted
;
143 return pageCoords(start
? sel
.from : sel
.to
);
145 charCoords: function(pos
){return pageCoords(clipPos(pos
));},
146 coordsChar: function(coords
) {
147 var off
= eltOffset(lineSpace
);
148 var line
= clipLine(Math
.min(lines
.length
- 1, showingFrom
+ Math
.floor((coords
.y
- off
.top
) / lineHeight())));
149 return clipPos({line
: line
, ch
: charFromX(clipLine(line
), coords
.x
- off
.left
)});
151 getSearchCursor: function(query
, pos
, caseFold
) {return new SearchCursor(query
, pos
, caseFold
);},
152 markText
: operation(function(a
, b
, c
){return operation(markText(a
, b
, c
));}),
153 setMarker
: addGutterMarker
,
154 clearMarker
: removeGutterMarker
,
155 setLineClass
: operation(setLineClass
),
157 addWidget: function(pos
, node
, scroll
, where
) {
158 pos
= localCoords(clipPos(pos
));
159 var top
= pos
.yBot
, left
= pos
.x
;
160 node
.style
.position
= "absolute";
161 code
.appendChild(node
);
162 node
.style
.left
= left
+ "px";
163 if (where
== "over") top
= pos
.y
;
164 else if (where
== "near") {
165 var vspace
= Math
.max(scroller
.offsetHeight
, lines
.length
* lineHeight()),
166 hspace
= Math
.max(code
.clientWidth
, lineSpace
.clientWidth
) - paddingLeft();
167 if (pos
.yBot
+ node
.offsetHeight
> vspace
&& pos
.y
> node
.offsetHeight
)
168 top
= pos
.y
- node
.offsetHeight
;
169 if (left
+ node
.offsetWidth
> hspace
)
170 left
= hspace
- node
.offsetWidth
;
172 node
.style
.top
= (top
+ paddingTop()) + "px";
173 node
.style
.left
= (left
+ paddingLeft()) + "px";
175 scrollIntoView(left
, top
, left
+ node
.offsetWidth
, top
+ node
.offsetHeight
);
178 lineCount: function() {return lines
.length
;},
179 getCursor: function(start
) {
180 if (start
== null) start
= sel
.inverted
;
181 return copyPos(start
? sel
.from : sel
.to
);
183 somethingSelected: function() {return !posEq(sel
.from, sel
.to
);},
184 setCursor
: operation(function(line
, ch
) {
185 if (ch
== null && typeof line
.line
== "number") setCursor(line
.line
, line
.ch
);
186 else setCursor(line
, ch
);
188 setSelection
: operation(function(from, to
) {setSelection(clipPos(from), clipPos(to
|| from));}),
189 getLine: function(line
) {if (isLine(line
)) return lines
[line
].text
;},
190 setLine
: operation(function(line
, text
) {
191 if (isLine(line
)) replaceRange(text
, {line
: line
, ch
: 0}, {line
: line
, ch
: lines
[line
].text
.length
});
193 removeLine
: operation(function(line
) {
194 if (isLine(line
)) replaceRange("", {line
: line
, ch
: 0}, clipPos({line
: line
+1, ch
: 0}));
196 replaceRange
: operation(replaceRange
),
197 getRange: function(from, to
) {return getRange(clipPos(from), clipPos(to
));},
199 operation: function(f
){return operation(f
)();},
200 refresh: function(){updateDisplay(true);},
201 getInputField: function(){return input
;},
202 getWrapperElement: function(){return wrapper
;},
203 getScrollerElement: function(){return scroller
;}
206 function setValue(code
) {
208 var top
= {line
: 0, ch
: 0};
209 updateLines(top
, {line
: lines
.length
- 1, ch
: lines
[lines
.length
-1].text
.length
},
210 splitLines(code
), top
, top
);
211 history
= new History();
213 function getValue(code
) {
215 for (var i
= 0, l
= lines
.length
; i
< l
; ++i
)
216 text
.push(lines
[i
].text
);
217 return text
.join("\n");
220 function onMouseDown(e
) {
221 // Check whether this is a click in a widget
222 for (var n
= e_target(e
); n
!= wrapper
; n
= n
.parentNode
)
223 if (n
.parentNode
== code
&& n
!= mover
) return;
224 var ld
= lastDoubleClick
; lastDoubleClick
= null;
225 // First, see if this is a click in the gutter
226 for (var n
= e_target(e
); n
!= wrapper
; n
= n
.parentNode
)
227 if (n
.parentNode
== gutterText
) {
228 if (options
.onGutterClick
)
229 options
.onGutterClick(instance
, indexOf(gutterText
.childNodes
, n
) + showingFrom
);
230 return e_preventDefault(e
);
233 var start
= posFromMouse(e
);
235 switch (e_button(e
)) {
237 if (gecko
&& !mac
) onContextMenu(e
);
240 if (start
) setCursor(start
.line
, start
.ch
, true);
243 // For button 1, if it was clicked inside the editor
244 // (posFromMouse returning non-null), we have to adjust the
246 if (!start
) {if (e_target(e
) == scroller
) e_preventDefault(e
); return;}
248 if (!focused
) onFocus();
250 if (ld
&& +new Date
- ld
< 400) return selectLine(start
.line
);
252 setCursor(start
.line
, start
.ch
, true);
253 var last
= start
, going
;
254 // And then we have to see if it's a drag event, in which case
255 // the dragged-over text must be selected.
262 var cur
= posFromMouse(e
, true);
263 if (cur
&& !posEq(cur
, last
)) {
264 if (!focused
) onFocus();
266 setSelectionUser(start
, cur
);
268 var visible
= visibleLines();
269 if (cur
.line
>= visible
.to
|| cur
.line
< visible
.from)
270 going
= setTimeout(operation(function(){extend(e
);}), 150);
274 var move = connect(targetDocument
, "mousemove", operation(function(e
) {
279 var up
= connect(targetDocument
, "mouseup", operation(function(e
) {
281 var cur
= posFromMouse(e
);
282 if (cur
) setSelectionUser(start
, cur
);
287 function onDblClick(e
) {
288 var pos
= posFromMouse(e
);
292 lastDoubleClick
= +new Date
;
296 var pos
= posFromMouse(e
, true), files
= e
.dataTransfer
.files
;
297 if (!pos
|| options
.readOnly
) return;
298 if (files
&& files
.length
&& window
.FileReader
&& window
.File
) {
299 function loadFile(file
, i
) {
300 var reader
= new FileReader
;
301 reader
.onload = function() {
302 text
[i
] = reader
.result
;
303 if (++read
== n
) replaceRange(text
.join(""), clipPos(pos
), clipPos(pos
));
305 reader
.readAsText(file
);
307 var n
= files
.length
, text
= Array(n
), read
= 0;
308 for (var i
= 0; i
< n
; ++i
) loadFile(files
[i
], i
);
312 var text
= e
.dataTransfer
.getData("Text");
313 if (text
) replaceRange(text
, pos
, pos
);
318 function onKeyDown(e
) {
319 if (!focused
) onFocus();
321 var code
= e
.keyCode
;
322 // IE does strange things with escape.
323 if (ie
&& code
== 27) { e
.returnValue
= false; }
324 // Tries to detect ctrl on non-mac, cmd on mac.
325 var mod
= (mac
? e
.metaKey
: e
.ctrlKey
) && !e
.altKey
, anyMod
= e
.ctrlKey
|| e
.altKey
|| e
.metaKey
;
326 if (code
== 16 || e
.shiftKey
) shiftSelecting
= shiftSelecting
|| (sel
.inverted
? sel
.to
: sel
.from);
327 else shiftSelecting
= null;
328 // First give onKeyEvent option a chance to handle this.
329 if (options
.onKeyEvent
&& options
.onKeyEvent(instance
, addStop(e
))) return;
331 if (code
== 33 || code
== 34) {scrollPage(code
== 34); return e_preventDefault(e
);} // page up/down
332 if (mod
&& ((code
== 36 || code
== 35) || // ctrl-home/end
333 mac
&& (code
== 38 || code
== 40))) { // cmd-up/down
334 scrollEnd(code
== 36 || code
== 38); return e_preventDefault(e
);
336 if (mod
&& code
== 65) {selectAll(); return e_preventDefault(e
);} // ctrl-a
337 if (!options
.readOnly
) {
338 if (!anyMod
&& code
== 13) {return;} // enter
339 if (!anyMod
&& code
== 9 && handleTab(e
.shiftKey
)) return e_preventDefault(e
); // tab
340 if (mod
&& code
== 90) {undo(); return e_preventDefault(e
);} // ctrl-z
341 if (mod
&& ((e
.shiftKey
&& code
== 90) || code
== 89)) {redo(); return e_preventDefault(e
);} // ctrl-shift-z, ctrl-y
344 // Key id to use in the movementKeys map. We also pass it to
345 // fastPoll in order to 'self learn'. We need this because
346 // reducedSelection, the hack where we collapse the selection to
347 // its start when it is inverted and a movement key is pressed
348 // (and later restore it again), shouldn't be used for
349 // non-movement keys.
350 curKeyId
= (mod
? "c" : "") + code
;
351 if (sel
.inverted
&& movementKeys
.hasOwnProperty(curKeyId
)) {
352 var range
= selRange(input
);
354 reducedSelection
= {anchor
: range
.start
};
355 setSelRange(input
, range
.start
, range
.start
);
360 function onKeyUp(e
) {
361 if (options
.onKeyEvent
&& options
.onKeyEvent(instance
, addStop(e
))) return;
362 if (reducedSelection
) {
363 reducedSelection
= null;
366 if (e
.keyCode
== 16) shiftSelecting
= null;
368 function onKeyPress(e
) {
369 if (options
.onKeyEvent
&& options
.onKeyEvent(instance
, addStop(e
))) return;
370 if (options
.electricChars
&& mode
.electricChars
) {
371 var ch
= String
.fromCharCode(e
.charCode
== null ? e
.keyCode
: e
.charCode
);
372 if (mode
.electricChars
.indexOf(ch
) > -1)
373 setTimeout(operation(function() {indentLine(sel
.to
.line
, "smart");}), 50);
375 var code
= e
.keyCode
;
376 // Re-stop tab and enter. Necessary on some browsers.
377 if (code
== 13) {if (!options
.readOnly
) handleEnter(); e_preventDefault(e
);}
378 else if (!e
.ctrlKey
&& !e
.altKey
&& !e
.metaKey
&& code
== 9 && options
.tabMode
!= "default") e_preventDefault(e
);
379 else fastPoll(curKeyId
);
383 if (options
.readOnly
== "nocursor") return;
385 if (options
.onFocus
) options
.onFocus(instance
);
387 if (wrapper
.className
.search(/\bCodeMirror-focused\b/) == -1)
388 wrapper
.className
+= " CodeMirror-focused";
389 if (!leaveInputAlone
) prepareInput();
396 if (options
.onBlur
) options
.onBlur(instance
);
398 wrapper
.className
= wrapper
.className
.replace(" CodeMirror-focused", "");
400 clearInterval(blinker
);
401 setTimeout(function() {if (!focused
) shiftSelecting
= null;}, 150);
404 // Replace the range from from to to by the strings in newText.
405 // Afterwards, set the selection to selFrom, selTo.
406 function updateLines(from, to
, newText
, selFrom
, selTo
) {
409 for (var i
= from.line
, e
= to
.line
+ 1; i
< e
; ++i
) old
.push(lines
[i
].text
);
410 history
.addChange(from.line
, newText
.length
, old
);
411 while (history
.done
.length
> options
.undoDepth
) history
.done
.shift();
413 updateLinesNoUndo(from, to
, newText
, selFrom
, selTo
);
415 function unredoHelper(from, to
) {
416 var change
= from.pop();
418 var replaced
= [], end
= change
.start
+ change
.added
;
419 for (var i
= change
.start
; i
< end
; ++i
) replaced
.push(lines
[i
].text
);
420 to
.push({start
: change
.start
, added
: change
.old
.length
, old
: replaced
});
421 var pos
= clipPos({line
: change
.start
+ change
.old
.length
- 1,
422 ch
: editEnd(replaced
[replaced
.length
-1], change
.old
[change
.old
.length
-1])});
423 updateLinesNoUndo({line
: change
.start
, ch
: 0}, {line
: end
- 1, ch
: lines
[end
-1].text
.length
}, change
.old
, pos
, pos
);
427 function undo() {unredoHelper(history
.done
, history
.undone
);}
428 function redo() {unredoHelper(history
.undone
, history
.done
);}
430 function updateLinesNoUndo(from, to
, newText
, selFrom
, selTo
) {
431 var recomputeMaxLength
= false, maxLineLength
= maxLine
.length
;
432 for (var i
= from.line
; i
<= to
.line
; ++i
) {
433 if (lines
[i
].text
.length
== maxLineLength
) {recomputeMaxLength
= true; break;}
436 var nlines
= to
.line
- from.line
, firstLine
= lines
[from.line
], lastLine
= lines
[to
.line
];
437 // First adjust the line structure, taking some care to leave highlighting intact.
438 if (firstLine
== lastLine
) {
439 if (newText
.length
== 1)
440 firstLine
.replace(from.ch
, to
.ch
, newText
[0]);
442 lastLine
= firstLine
.split(to
.ch
, newText
[newText
.length
-1]);
443 var spliceargs
= [from.line
+ 1, nlines
];
444 firstLine
.replace(from.ch
, firstLine
.text
.length
, newText
[0]);
445 for (var i
= 1, e
= newText
.length
- 1; i
< e
; ++i
) spliceargs
.push(new Line(newText
[i
]));
446 spliceargs
.push(lastLine
);
447 lines
.splice
.apply(lines
, spliceargs
);
450 else if (newText
.length
== 1) {
451 firstLine
.replace(from.ch
, firstLine
.text
.length
, newText
[0] + lastLine
.text
.slice(to
.ch
));
452 lines
.splice(from.line
+ 1, nlines
);
455 var spliceargs
= [from.line
+ 1, nlines
- 1];
456 firstLine
.replace(from.ch
, firstLine
.text
.length
, newText
[0]);
457 lastLine
.replace(0, to
.ch
, newText
[newText
.length
-1]);
458 for (var i
= 1, e
= newText
.length
- 1; i
< e
; ++i
) spliceargs
.push(new Line(newText
[i
]));
459 lines
.splice
.apply(lines
, spliceargs
);
463 for (var i
= from.line
, e
= i
+ newText
.length
; i
< e
; ++i
) {
464 var l
= lines
[i
].text
;
465 if (l
.length
> maxLineLength
) {
466 maxLine
= l
; maxLineLength
= l
.length
; maxWidth
= null;
467 recomputeMaxLength
= false;
470 if (recomputeMaxLength
) {
471 maxLineLength
= 0; maxLine
= ""; maxWidth
= null;
472 for (var i
= 0, e
= lines
.length
; i
< e
; ++i
) {
473 var l
= lines
[i
].text
;
474 if (l
.length
> maxLineLength
) {
475 maxLineLength
= l
.length
; maxLine
= l
;
480 // Add these lines to the work array, so that they will be
481 // highlighted. Adjust work lines if lines were added/removed.
482 var newWork
= [], lendiff
= newText
.length
- nlines
- 1;
483 for (var i
= 0, l
= work
.length
; i
< l
; ++i
) {
485 if (task
< from.line
) newWork
.push(task
);
486 else if (task
> to
.line
) newWork
.push(task
+ lendiff
);
488 if (newText
.length
< 5) {
489 highlightLines(from.line
, from.line
+ newText
.length
);
490 newWork
.push(from.line
+ newText
.length
);
492 newWork
.push(from.line
);
496 // Remember that these lines changed, for updating the display
497 changes
.push({from: from.line
, to
: to
.line
+ 1, diff
: lendiff
});
498 textChanged
= {from: from, to
: to
, text
: newText
};
500 // Update the selection
501 function updateLine(n
) {return n
<= Math
.min(to
.line
, to
.line
+ lendiff
) ? n
: n
+ lendiff
;}
502 setSelection(selFrom
, selTo
, updateLine(sel
.from.line
), updateLine(sel
.to
.line
));
504 // Make sure the scroll-size div has the correct height.
505 code
.style
.height
= (lines
.length
* lineHeight() + 2 * paddingTop()) + "px";
508 function replaceRange(code
, from, to
) {
509 from = clipPos(from);
510 if (!to
) to
= from; else to
= clipPos(to
);
511 code
= splitLines(code
);
512 function adjustPos(pos
) {
513 if (posLess(pos
, from)) return pos
;
514 if (!posLess(to
, pos
)) return end
;
515 var line
= pos
.line
+ code
.length
- (to
.line
- from.line
) - 1;
517 if (pos
.line
== to
.line
)
518 ch
+= code
[code
.length
-1].length
- (to
.ch
- (to
.line
== from.line
? from.ch
: 0));
519 return {line
: line
, ch
: ch
};
522 replaceRange1(code
, from, to
, function(end1
) {
524 return {from: adjustPos(sel
.from), to
: adjustPos(sel
.to
)};
528 function replaceSelection(code
, collapse
) {
529 replaceRange1(splitLines(code
), sel
.from, sel
.to
, function(end
) {
530 if (collapse
== "end") return {from: end
, to
: end
};
531 else if (collapse
== "start") return {from: sel
.from, to
: sel
.from};
532 else return {from: sel
.from, to
: end
};
535 function replaceRange1(code
, from, to
, computeSel
) {
536 var endch
= code
.length
== 1 ? code
[0].length
+ from.ch
: code
[code
.length
-1].length
;
537 var newSel
= computeSel({line
: from.line
+ code
.length
- 1, ch
: endch
});
538 updateLines(from, to
, code
, newSel
.from, newSel
.to
);
541 function getRange(from, to
) {
542 var l1
= from.line
, l2
= to
.line
;
543 if (l1
== l2
) return lines
[l1
].text
.slice(from.ch
, to
.ch
);
544 var code
= [lines
[l1
].text
.slice(from.ch
)];
545 for (var i
= l1
+ 1; i
< l2
; ++i
) code
.push(lines
[i
].text
);
546 code
.push(lines
[l2
].text
.slice(0, to
.ch
));
547 return code
.join("\n");
549 function getSelection() {
550 return getRange(sel
.from, sel
.to
);
553 var pollingFast
= false; // Ensures slowPoll doesn't cancel fastPoll
554 function slowPoll() {
555 if (pollingFast
) return;
556 poll
.set(2000, function() {
559 if (focused
) slowPoll();
563 function fastPoll(keyId
) {
568 var changed
= readInput();
569 if (changed
== "moved" && keyId
) movementKeys
[keyId
] = true;
570 if (!changed
&& !missed
) {missed
= true; poll
.set(80, p
);}
571 else {pollingFast
= false; slowPoll();}
577 // Inspects the textarea, compares its state (content, selection)
578 // to the data in the editing variable, and updates the editor
579 // content or cursor if something changed.
580 function readInput() {
581 if (leaveInputAlone
|| !focused
) return;
582 var changed
= false, text
= input
.value
, sr
= selRange(input
);
583 if (!sr
) return false;
584 var changed
= editing
.text
!= text
, rs
= reducedSelection
;
585 var moved
= changed
|| sr
.start
!= editing
.start
|| sr
.end
!= (rs
? editing
.start
: editing
.end
);
586 if (!moved
&& !rs
) return false;
588 shiftSelecting
= reducedSelection
= null;
589 if (options
.readOnly
) {updateInput
= true; return "changed";}
592 // Compute selection start and end based on start/end offsets in textarea
593 function computeOffset(n
, startLine
) {
596 var found
= text
.indexOf("\n", pos
);
597 if (found
== -1 || (text
.charAt(found
-1) == "\r" ? found
- 1 : found
) >= n
)
598 return {line
: startLine
, ch
: n
- pos
};
603 var from = computeOffset(sr
.start
, editing
.from),
604 to
= computeOffset(sr
.end
, editing
.from);
605 // Here we have to take the reducedSelection hack into account,
606 // so that you can, for example, press shift-up at the start of
607 // your selection and have the right thing happen.
609 var head
= sr
.start
== rs
.anchor
? to
: from;
610 var tail
= shiftSelecting
? sel
.to
: sr
.start
== rs
.anchor
? from : to
;
611 if (sel
.inverted
= posLess(head
, tail
)) { from = head
; to
= tail
; }
612 else { reducedSelection
= null; from = tail
; to
= head
; }
615 // In some cases (cursor on same line as before), we don't have
616 // to update the textarea content at all.
617 if (from.line
== to
.line
&& from.line
== sel
.from.line
&& from.line
== sel
.to
.line
&& !shiftSelecting
)
620 // Magic mess to extract precise edited range from the changed
623 var start
= 0, end
= text
.length
, len
= Math
.min(end
, editing
.text
.length
);
624 var c
, line
= editing
.from, nl
= -1;
625 while (start
< len
&& (c
= text
.charAt(start
)) == editing
.text
.charAt(start
)) {
627 if (c
== "\n") {line
++; nl
= start
;}
629 var ch
= nl
> -1 ? start
- nl
: start
, endline
= editing
.to
- 1, edend
= editing
.text
.length
;
631 c
= editing
.text
.charAt(edend
);
632 if (text
.charAt(end
) != c
) {++end
; ++edend
; break;}
633 if (c
== "\n") endline
--;
634 if (edend
<= start
|| end
<= start
) break;
637 var nl
= editing
.text
.lastIndexOf("\n", edend
- 1), endch
= nl
== -1 ? edend
: edend
- nl
- 1;
638 updateLines({line
: line
, ch
: ch
}, {line
: endline
, ch
: endch
}, splitLines(text
.slice(start
, end
)), from, to
);
639 if (line
!= endline
|| from.line
!= line
) updateInput
= true;
641 else setSelection(from, to
);
643 editing
.text
= text
; editing
.start
= sr
.start
; editing
.end
= sr
.end
;
644 return changed
? "changed" : moved
? "moved" : false;
647 // Set the textarea content and selection range to match the
649 function prepareInput() {
651 var from = Math
.max(0, sel
.from.line
- 1), to
= Math
.min(lines
.length
, sel
.to
.line
+ 2);
652 for (var i
= from; i
< to
; ++i
) text
.push(lines
[i
].text
);
653 text
= input
.value
= text
.join(lineSep
);
654 var startch
= sel
.from.ch
, endch
= sel
.to
.ch
;
655 for (var i
= from; i
< sel
.from.line
; ++i
)
656 startch
+= lineSep
.length
+ lines
[i
].text
.length
;
657 for (var i
= from; i
< sel
.to
.line
; ++i
)
658 endch
+= lineSep
.length
+ lines
[i
].text
.length
;
659 editing
= {text
: text
, from: from, to
: to
, start
: startch
, end
: endch
};
660 setSelRange(input
, startch
, reducedSelection
? startch
: endch
);
662 function focusInput() {
663 if (options
.readOnly
!= "nocursor") input
.focus();
666 function scrollCursorIntoView() {
667 var cursor
= localCoords(sel
.inverted
? sel
.from : sel
.to
);
668 return scrollIntoView(cursor
.x
, cursor
.y
, cursor
.x
, cursor
.yBot
);
670 function scrollIntoView(x1
, y1
, x2
, y2
) {
671 var pl
= paddingLeft(), pt
= paddingTop(), lh
= lineHeight();
672 y1
+= pt
; y2
+= pt
; x1
+= pl
; x2
+= pl
;
673 var screen
= scroller
.clientHeight
, screentop
= scroller
.scrollTop
, scrolled
= false, result
= true;
674 if (y1
< screentop
) {scroller
.scrollTop
= Math
.max(0, y1
- 2*lh
); scrolled
= true;}
675 else if (y2
> screentop
+ screen
) {scroller
.scrollTop
= y2
+ lh
- screen
; scrolled
= true;}
677 var screenw
= scroller
.clientWidth
, screenleft
= scroller
.scrollLeft
;
678 if (x1
< screenleft
) {
680 scroller
.scrollLeft
= Math
.max(0, x1
- 10);
683 else if (x2
> screenw
+ screenleft
) {
684 scroller
.scrollLeft
= x2
+ 10 - screenw
;
686 if (x2
> code
.clientWidth
) result
= false;
688 if (scrolled
&& options
.onScroll
) options
.onScroll(instance
);
692 function visibleLines() {
693 var lh
= lineHeight(), top
= scroller
.scrollTop
- paddingTop();
694 return {from: Math
.min(lines
.length
, Math
.max(0, Math
.floor(top
/ lh
))),
695 to
: Math
.min(lines
.length
, Math
.ceil((top
+ scroller
.clientHeight
) / lh
))};
697 // Uses a set of changes plus the current scroll position to
698 // determine which DOM updates have to be made, and makes the
700 function updateDisplay(changes
) {
701 if (!scroller
.clientWidth
) {
702 showingFrom
= showingTo
= 0;
705 // First create a range of theoretically intact lines, and punch
706 // holes in that using the change info.
707 var intact
= changes
=== true ? [] : [{from: showingFrom
, to
: showingTo
, domStart
: 0}];
708 for (var i
= 0, l
= changes
.length
|| 0; i
< l
; ++i
) {
709 var change
= changes
[i
], intact2
= [], diff
= change
.diff
|| 0;
710 for (var j
= 0, l2
= intact
.length
; j
< l2
; ++j
) {
711 var range
= intact
[j
];
712 if (change
.to
<= range
.from)
713 intact2
.push({from: range
.from + diff
, to
: range
.to
+ diff
, domStart
: range
.domStart
});
714 else if (range
.to
<= change
.from)
717 if (change
.from > range
.from)
718 intact2
.push({from: range
.from, to
: change
.from, domStart
: range
.domStart
})
719 if (change
.to
< range
.to
)
720 intact2
.push({from: change
.to
+ diff
, to
: range
.to
+ diff
,
721 domStart
: range
.domStart
+ (change
.to
- range
.from)});
727 // Then, determine which lines we'd want to see, and which
728 // updates have to be made to get there.
729 var visible
= visibleLines();
730 var from = Math
.min(showingFrom
, Math
.max(visible
.from - 3, 0)),
731 to
= Math
.min(lines
.length
, Math
.max(showingTo
, visible
.to
+ 3)),
732 updates
= [], domPos
= 0, domEnd
= showingTo
- showingFrom
, pos
= from, changedLines
= 0;
734 for (var i
= 0, l
= intact
.length
; i
< l
; ++i
) {
735 var range
= intact
[i
];
736 if (range
.to
<= from) continue;
737 if (range
.from >= to
) break;
738 if (range
.domStart
> domPos
|| range
.from > pos
) {
739 updates
.push({from: pos
, to
: range
.from, domSize
: range
.domStart
- domPos
, domStart
: domPos
});
740 changedLines
+= range
.from - pos
;
743 domPos
= range
.domStart
+ (range
.to
- range
.from);
745 if (domPos
!= domEnd
|| pos
!= to
) {
746 changedLines
+= Math
.abs(to
- pos
);
747 updates
.push({from: pos
, to
: to
, domSize
: domEnd
- domPos
, domStart
: domPos
});
750 if (!updates
.length
) return;
751 lineDiv
.style
.display
= "none";
752 // If more than 30% of the screen needs update, just do a full
753 // redraw (which is quicker than patching)
754 if (changedLines
> (visible
.to
- visible
.from) * .3)
755 refreshDisplay(from = Math
.max(visible
.from - 10, 0), to
= Math
.min(visible
.to
+ 7, lines
.length
));
756 // Otherwise, only update the stuff that needs updating.
758 patchDisplay(updates
);
759 lineDiv
.style
.display
= "";
761 // Position the mover div to align with the lines it's supposed
762 // to be showing (which will cover the visible display)
763 var different
= from != showingFrom
|| to
!= showingTo
|| lastHeight
!= scroller
.clientHeight
;
764 showingFrom
= from; showingTo
= to
;
765 mover
.style
.top
= (from * lineHeight()) + "px";
767 lastHeight
= scroller
.clientHeight
;
768 code
.style
.height
= (lines
.length
* lineHeight() + 2 * paddingTop()) + "px";
772 if (maxWidth
== null) maxWidth
= stringWidth(maxLine
);
773 if (maxWidth
> scroller
.clientWidth
) {
774 lineSpace
.style
.width
= maxWidth
+ "px";
775 // Needed to prevent odd wrapping/hiding of widgets placed in here.
776 code
.style
.width
= "";
777 code
.style
.width
= scroller
.scrollWidth
+ "px";
779 lineSpace
.style
.width
= code
.style
.width
= "";
782 // Since this is all rather error prone, it is honoured with the
783 // only assertion in the whole file.
784 if (lineDiv
.childNodes
.length
!= showingTo
- showingFrom
)
785 throw new Error("BAD PATCH! " + JSON
.stringify(updates
) + " size=" + (showingTo
- showingFrom
) +
786 " nodes=" + lineDiv
.childNodes
.length
);
790 function refreshDisplay(from, to
) {
791 var html
= [], start
= {line
: from, ch
: 0}, inSel
= posLess(sel
.from, start
) && !posLess(sel
.to
, start
);
792 for (var i
= from; i
< to
; ++i
) {
793 var ch1
= null, ch2
= null;
796 if (sel
.to
.line
== i
) {inSel
= false; ch2
= sel
.to
.ch
;}
798 else if (sel
.from.line
== i
) {
799 if (sel
.to
.line
== i
) {ch1
= sel
.from.ch
; ch2
= sel
.to
.ch
;}
800 else {inSel
= true; ch1
= sel
.from.ch
;}
802 html
.push(lines
[i
].getHTML(ch1
, ch2
, true));
804 lineDiv
.innerHTML
= html
.join("");
806 function patchDisplay(updates
) {
807 // Slightly different algorithm for IE (badInnerHTML), since
808 // there .innerHTML on PRE nodes is dumb, and discards
810 var sfrom
= sel
.from.line
, sto
= sel
.to
.line
, off
= 0,
811 scratch
= badInnerHTML
&& targetDocument
.createElement("div");
812 for (var i
= 0, e
= updates
.length
; i
< e
; ++i
) {
813 var rec
= updates
[i
];
814 var extra
= (rec
.to
- rec
.from) - rec
.domSize
;
815 var nodeAfter
= lineDiv
.childNodes
[rec
.domStart
+ rec
.domSize
+ off
] || null;
817 for (var j
= Math
.max(-extra
, rec
.domSize
); j
> 0; --j
)
818 lineDiv
.removeChild(nodeAfter
? nodeAfter
.previousSibling
: lineDiv
.lastChild
);
820 for (var j
= Math
.max(0, extra
); j
> 0; --j
)
821 lineDiv
.insertBefore(targetDocument
.createElement("pre"), nodeAfter
);
822 for (var j
= Math
.max(0, -extra
); j
> 0; --j
)
823 lineDiv
.removeChild(nodeAfter
? nodeAfter
.previousSibling
: lineDiv
.lastChild
);
825 var node
= lineDiv
.childNodes
[rec
.domStart
+ off
], inSel
= sfrom
< rec
.from && sto
>= rec
.from;
826 for (var j
= rec
.from; j
< rec
.to
; ++j
) {
827 var ch1
= null, ch2
= null;
830 if (sto
== j
) {inSel
= false; ch2
= sel
.to
.ch
;}
832 else if (sfrom
== j
) {
833 if (sto
== j
) {ch1
= sel
.from.ch
; ch2
= sel
.to
.ch
;}
834 else {inSel
= true; ch1
= sel
.from.ch
;}
837 scratch
.innerHTML
= lines
[j
].getHTML(ch1
, ch2
, true);
838 lineDiv
.insertBefore(scratch
.firstChild
, nodeAfter
);
841 node
.innerHTML
= lines
[j
].getHTML(ch1
, ch2
, false);
842 node
.className
= lines
[j
].className
|| "";
843 node
= node
.nextSibling
;
850 function updateGutter() {
851 if (!options
.gutter
&& !options
.lineNumbers
) return;
852 var hText
= mover
.offsetHeight
, hEditor
= scroller
.clientHeight
;
853 gutter
.style
.height
= (hText
- hEditor
< 2 ? hEditor
: hText
) + "px";
855 for (var i
= showingFrom
; i
< Math
.max(showingTo
, showingFrom
+ 1); ++i
) {
856 var marker
= lines
[i
].gutterMarker
;
857 var text
= options
.lineNumbers
? i
+ options
.firstLineNumber
: null;
858 if (marker
&& marker
.text
)
859 text
= marker
.text
.replace("%N%", text
!= null ? text
: "");
860 else if (text
== null)
862 html
.push((marker
&& marker
.style
? '<pre class="' + marker
.style
+ '">' : "<pre>"), text
, "</pre>");
864 gutter
.style
.display
= "none";
865 gutterText
.innerHTML
= html
.join("");
866 var minwidth
= String(lines
.length
).length
, firstNode
= gutterText
.firstChild
, val
= eltText(firstNode
), pad
= "";
867 while (val
.length
+ pad
.length
< minwidth
) pad
+= "\u00a0";
868 if (pad
) firstNode
.insertBefore(targetDocument
.createTextNode(pad
), firstNode
.firstChild
);
869 gutter
.style
.display
= "";
870 lineSpace
.style
.marginLeft
= gutter
.offsetWidth
+ "px";
872 function updateCursor() {
873 var head
= sel
.inverted
? sel
.from : sel
.to
, lh
= lineHeight();
874 var x
= charX(head
.line
, head
.ch
) + "px", y
= (head
.line
- showingFrom
) * lh
+ "px";
875 inputDiv
.style
.top
= (head
.line
* lh
- scroller
.scrollTop
) + "px";
876 if (posEq(sel
.from, sel
.to
)) {
877 cursor
.style
.top
= y
; cursor
.style
.left
= x
;
878 cursor
.style
.display
= "";
880 else cursor
.style
.display
= "none";
883 function setSelectionUser(from, to
) {
884 var sh
= shiftSelecting
&& clipPos(shiftSelecting
);
886 if (posLess(sh
, from)) from = sh
;
887 else if (posLess(to
, sh
)) to
= sh
;
889 setSelection(from, to
);
891 // Update the selection. Last two args are only used by
892 // updateLines, since they have to be expressed in the line
893 // numbers before the update.
894 function setSelection(from, to
, oldFrom
, oldTo
) {
895 if (posEq(sel
.from, from) && posEq(sel
.to
, to
)) return;
896 if (posLess(to
, from)) {var tmp
= to
; to
= from; from = tmp
;}
898 if (posEq(from, to
)) sel
.inverted
= false;
899 else if (posEq(from, sel
.to
)) sel
.inverted
= false;
900 else if (posEq(to
, sel
.from)) sel
.inverted
= true;
902 // Some ugly logic used to only mark the lines that actually did
903 // see a change in selection as changed, rather than the whole
905 if (oldFrom
== null) {oldFrom
= sel
.from.line
; oldTo
= sel
.to
.line
;}
906 if (posEq(from, to
)) {
907 if (!posEq(sel
.from, sel
.to
))
908 changes
.push({from: oldFrom
, to
: oldTo
+ 1});
910 else if (posEq(sel
.from, sel
.to
)) {
911 changes
.push({from: from.line
, to
: to
.line
+ 1});
914 if (!posEq(from, sel
.from)) {
915 if (from.line
< oldFrom
)
916 changes
.push({from: from.line
, to
: Math
.min(to
.line
, oldFrom
) + 1});
918 changes
.push({from: oldFrom
, to
: Math
.min(oldTo
, from.line
) + 1});
920 if (!posEq(to
, sel
.to
)) {
922 changes
.push({from: Math
.max(oldFrom
, from.line
), to
: oldTo
+ 1});
924 changes
.push({from: Math
.max(from.line
, oldTo
), to
: to
.line
+ 1});
927 sel
.from = from; sel
.to
= to
;
928 selectionChanged
= true;
930 function setCursor(line
, ch
, user
) {
931 var pos
= clipPos({line
: line
, ch
: ch
|| 0});
932 (user
? setSelectionUser
: setSelection
)(pos
, pos
);
935 function clipLine(n
) {return Math
.max(0, Math
.min(n
, lines
.length
-1));}
936 function clipPos(pos
) {
937 if (pos
.line
< 0) return {line
: 0, ch
: 0};
938 if (pos
.line
>= lines
.length
) return {line
: lines
.length
-1, ch
: lines
[lines
.length
-1].text
.length
};
939 var ch
= pos
.ch
, linelen
= lines
[pos
.line
].text
.length
;
940 if (ch
== null || ch
> linelen
) return {line
: pos
.line
, ch
: linelen
};
941 else if (ch
< 0) return {line
: pos
.line
, ch
: 0};
945 function scrollPage(down
) {
946 var linesPerPage
= Math
.floor(scroller
.clientHeight
/ lineHeight()), head
= sel
.inverted
? sel
.from : sel
.to
;
947 setCursor(head
.line
+ (Math
.max(linesPerPage
- 1, 1) * (down
? 1 : -1)), head
.ch
, true);
949 function scrollEnd(top
) {
950 var pos
= top
? {line
: 0, ch
: 0} : {line
: lines
.length
- 1, ch
: lines
[lines
.length
-1].text
.length
};
951 setSelectionUser(pos
, pos
);
953 function selectAll() {
954 var endLine
= lines
.length
- 1;
955 setSelection({line
: 0, ch
: 0}, {line
: endLine
, ch
: lines
[endLine
].text
.length
});
957 function selectWordAt(pos
) {
958 var line
= lines
[pos
.line
].text
;
959 var start
= pos
.ch
, end
= pos
.ch
;
960 while (start
> 0 && /\w/.test(line
.charAt(start
- 1))) --start
;
961 while (end
< line
.length
&& /\w/.test(line
.charAt(end
))) ++end
;
962 setSelectionUser({line
: pos
.line
, ch
: start
}, {line
: pos
.line
, ch
: end
});
964 function selectLine(line
) {
965 setSelectionUser({line
: line
, ch
: 0}, {line
: line
, ch
: lines
[line
].text
.length
});
967 function handleEnter() {
968 replaceSelection("\n", "end");
969 if (options
.enterMode
!= "flat")
970 indentLine(sel
.from.line
, options
.enterMode
== "keep" ? "prev" : "smart");
972 function handleTab(shift
) {
973 function indentSelected(mode
) {
974 if (posEq(sel
.from, sel
.to
)) return indentLine(sel
.from.line
, mode
);
975 var e
= sel
.to
.line
- (sel
.to
.ch
? 0 : 1);
976 for (var i
= sel
.from.line
; i
<= e
; ++i
) indentLine(i
, mode
);
978 shiftSelecting
= null;
979 switch (options
.tabMode
) {
983 indentSelected("smart");
986 if (posEq(sel
.from, sel
.to
)) {
987 if (shift
) indentLine(sel
.from.line
, "smart");
988 else replaceSelection("\t", "end");
992 indentSelected(shift
? "subtract" : "add");
998 function indentLine(n
, how
) {
999 if (how
== "smart") {
1000 if (!mode
.indent
) how
= "prev";
1001 else var state
= getStateBefore(n
);
1004 var line
= lines
[n
], curSpace
= line
.indentation(), curSpaceString
= line
.text
.match(/^\s*/)[0], indentation
;
1005 if (how
== "prev") {
1006 if (n
) indentation
= lines
[n
-1].indentation();
1007 else indentation
= 0;
1009 else if (how
== "smart") indentation
= mode
.indent(state
, line
.text
.slice(curSpaceString
.length
));
1010 else if (how
== "add") indentation
= curSpace
+ options
.indentUnit
;
1011 else if (how
== "subtract") indentation
= curSpace
- options
.indentUnit
;
1012 indentation
= Math
.max(0, indentation
);
1013 var diff
= indentation
- curSpace
;
1016 if (sel
.from.line
!= n
&& sel
.to
.line
!= n
) return;
1017 var indentString
= curSpaceString
;
1020 var indentString
= "", pos
= 0;
1021 if (options
.indentWithTabs
)
1022 for (var i
= Math
.floor(indentation
/ tabSize
); i
; --i
) {pos
+= tabSize
; indentString
+= "\t";}
1023 while (pos
< indentation
) {++pos
; indentString
+= " ";}
1026 replaceRange(indentString
, {line
: n
, ch
: 0}, {line
: n
, ch
: curSpaceString
.length
});
1029 function loadMode() {
1030 mode
= CodeMirror
.getMode(options
, options
.mode
);
1031 for (var i
= 0, l
= lines
.length
; i
< l
; ++i
)
1032 lines
[i
].stateAfter
= null;
1036 function gutterChanged() {
1037 var visible
= options
.gutter
|| options
.lineNumbers
;
1038 gutter
.style
.display
= visible
? "" : "none";
1039 if (visible
) updateGutter();
1040 else lineDiv
.parentNode
.style
.marginLeft
= 0;
1043 function markText(from, to
, className
) {
1044 from = clipPos(from); to
= clipPos(to
);
1046 function add(line
, from, to
, className
) {
1047 var line
= lines
[line
], mark
= line
.addMark(from, to
, className
);
1051 if (from.line
== to
.line
) add(from.line
, from.ch
, to
.ch
, className
);
1053 add(from.line
, from.ch
, null, className
);
1054 for (var i
= from.line
+ 1, e
= to
.line
; i
< e
; ++i
)
1055 add(i
, 0, null, className
);
1056 add(to
.line
, 0, to
.ch
, className
);
1058 changes
.push({from: from.line
, to
: to
.line
+ 1});
1061 for (var i
= 0; i
< accum
.length
; ++i
) {
1062 var mark
= accum
[i
], found
= indexOf(lines
, mark
.line
);
1063 mark
.line
.removeMark(mark
);
1065 if (start
== null) start
= found
;
1069 if (start
!= null) changes
.push({from: start
, to
: end
+ 1});
1073 function addGutterMarker(line
, text
, className
) {
1074 if (typeof line
== "number") line
= lines
[clipLine(line
)];
1075 line
.gutterMarker
= {text
: text
, style
: className
};
1079 function removeGutterMarker(line
) {
1080 if (typeof line
== "number") line
= lines
[clipLine(line
)];
1081 line
.gutterMarker
= null;
1084 function setLineClass(line
, className
) {
1085 if (typeof line
== "number") {
1087 line
= lines
[clipLine(line
)];
1090 var no
= indexOf(lines
, line
);
1091 if (no
== -1) return null;
1093 if (line
.className
!= className
) {
1094 line
.className
= className
;
1095 changes
.push({from: no
, to
: no
+ 1});
1100 function lineInfo(line
) {
1101 if (typeof line
== "number") {
1104 if (!line
) return null;
1107 var n
= indexOf(lines
, line
);
1108 if (n
== -1) return null;
1110 var marker
= line
.gutterMarker
;
1111 return {line
: n
, text
: line
.text
, markerText
: marker
&& marker
.text
, markerClass
: marker
&& marker
.style
};
1114 function stringWidth(str
) {
1115 measure
.innerHTML
= "<pre><span>x</span></pre>";
1116 measure
.firstChild
.firstChild
.firstChild
.nodeValue
= str
;
1117 return measure
.firstChild
.firstChild
.offsetWidth
|| 10;
1119 // These are used to go from pixel positions to character
1120 // positions, taking varying character widths into account.
1121 function charX(line
, pos
) {
1122 if (pos
== 0) return 0;
1123 measure
.innerHTML
= "<pre><span>" + lines
[line
].getHTML(null, null, false, pos
) + "</span></pre>";
1124 return measure
.firstChild
.firstChild
.offsetWidth
;
1126 function charFromX(line
, x
) {
1127 if (x
<= 0) return 0;
1128 var lineObj
= lines
[line
], text
= lineObj
.text
;
1129 function getX(len
) {
1130 measure
.innerHTML
= "<pre><span>" + lineObj
.getHTML(null, null, false, len
) + "</span></pre>";
1131 return measure
.firstChild
.firstChild
.offsetWidth
;
1133 var from = 0, fromX
= 0, to
= text
.length
, toX
;
1134 // Guess a suitable upper bound for our search.
1135 var estimated
= Math
.min(to
, Math
.ceil(x
/ stringWidth("x")));
1137 var estX
= getX(estimated
);
1138 if (estX
<= x
&& estimated
< to
) estimated
= Math
.min(to
, Math
.ceil(estimated
* 1.2));
1139 else {toX
= estX
; to
= estimated
; break;}
1141 if (x
> toX
) return to
;
1142 // Try to guess a suitable lower bound as well.
1143 estimated
= Math
.floor(to
* 0.8); estX
= getX(estimated
);
1144 if (estX
< x
) {from = estimated
; fromX
= estX
;}
1145 // Do a binary search between these bounds.
1147 if (to
- from <= 1) return (toX
- x
> x
- fromX
) ? from : to
;
1148 var middle
= Math
.ceil((from + to
) / 2), middleX
= getX(middle
);
1149 if (middleX
> x
) {to
= middle
; toX
= middleX
;}
1150 else {from = middle
; fromX
= middleX
;}
1154 function localCoords(pos
, inLineWrap
) {
1155 var lh
= lineHeight(), line
= pos
.line
- (inLineWrap
? showingFrom
: 0);
1156 return {x
: charX(pos
.line
, pos
.ch
), y
: line
* lh
, yBot
: (line
+ 1) * lh
};
1158 function pageCoords(pos
) {
1159 var local
= localCoords(pos
, true), off
= eltOffset(lineSpace
);
1160 return {x
: off
.left
+ local
.x
, y
: off
.top
+ local
.y
, yBot
: off
.top
+ local
.yBot
};
1163 function lineHeight() {
1164 var nlines
= lineDiv
.childNodes
.length
;
1165 if (nlines
) return (lineDiv
.offsetHeight
/ nlines
) || 1;
1166 measure
.innerHTML
= "<pre>x</pre>";
1167 return measure
.firstChild
.offsetHeight
|| 1;
1169 function paddingTop() {return lineSpace
.offsetTop
;}
1170 function paddingLeft() {return lineSpace
.offsetLeft
;}
1172 function posFromMouse(e
, liberal
) {
1173 var offW
= eltOffset(scroller
, true), x
, y
;
1174 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1175 try { x
= e
.clientX
; y
= e
.clientY
; } catch (e
) { return null; }
1176 // This is a mess of a heuristic to try and determine whether a
1177 // scroll-bar was clicked or not, and to return null if one was
1179 if (!liberal
&& (x
- offW
.left
> scroller
.clientWidth
|| y
- offW
.top
> scroller
.clientHeight
))
1181 var offL
= eltOffset(lineSpace
, true);
1182 var line
= showingFrom
+ Math
.floor((y
- offL
.top
) / lineHeight());
1183 return clipPos({line
: line
, ch
: charFromX(clipLine(line
), x
- offL
.left
)});
1185 function onContextMenu(e
) {
1186 var pos
= posFromMouse(e
);
1187 if (!pos
|| window
.opera
) return; // Opera is difficult.
1188 if (posEq(sel
.from, sel
.to
) || posLess(pos
, sel
.from) || !posLess(pos
, sel
.to
))
1189 operation(setCursor
)(pos
.line
, pos
.ch
);
1191 var oldCSS
= input
.style
.cssText
;
1192 inputDiv
.style
.position
= "absolute";
1193 input
.style
.cssText
= "position: fixed; width: 30px; height: 30px; top: " + (e_pageY(e
) - 1) +
1194 "px; left: " + (e_pageX(e
) - 1) + "px; z-index: 1000; background: white; " +
1195 "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1196 leaveInputAlone
= true;
1197 var val
= input
.value
= getSelection();
1199 setSelRange(input
, 0, input
.value
.length
);
1201 var newVal
= splitLines(input
.value
).join("\n");
1202 if (newVal
!= val
) operation(replaceSelection
)(newVal
, "end");
1203 inputDiv
.style
.position
= "relative";
1204 input
.style
.cssText
= oldCSS
;
1205 leaveInputAlone
= false;
1212 var mouseup
= connect(window
, "mouseup", function() {
1214 setTimeout(rehide
, 20);
1218 setTimeout(rehide
, 50);
1223 function restartBlink() {
1224 clearInterval(blinker
);
1226 cursor
.style
.visibility
= "";
1227 blinker
= setInterval(function() {
1228 cursor
.style
.visibility
= (on
= !on
) ? "" : "hidden";
1232 var matching
= {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
1233 function matchBrackets(autoclear
) {
1234 var head
= sel
.inverted
? sel
.from : sel
.to
, line
= lines
[head
.line
], pos
= head
.ch
- 1;
1235 var match
= (pos
>= 0 && matching
[line
.text
.charAt(pos
)]) || matching
[line
.text
.charAt(++pos
)];
1237 var ch
= match
.charAt(0), forward
= match
.charAt(1) == ">", d
= forward
? 1 : -1, st
= line
.styles
;
1238 for (var off
= pos
+ 1, i
= 0, e
= st
.length
; i
< e
; i
+=2)
1239 if ((off
-= st
[i
].length
) <= 0) {var style
= st
[i
+1]; break;}
1241 var stack
= [line
.text
.charAt(pos
)], re
= /[(){}[\]]/;
1242 function scan(line
, from, to
) {
1243 if (!line
.text
) return;
1244 var st
= line
.styles
, pos
= forward
? 0 : line
.text
.length
- 1, cur
;
1245 for (var i
= forward
? 0 : st
.length
- 2, e
= forward
? st
.length
: -2; i
!= e
; i
+= 2*d
) {
1247 if (st
[i
+1] != null && st
[i
+1] != style
) {pos
+= d
* text
.length
; continue;}
1248 for (var j
= forward
? 0 : text
.length
- 1, te
= forward
? text
.length
: -1; j
!= te
; j
+= d
, pos
+=d
) {
1249 if (pos
>= from && pos
< to
&& re
.test(cur
= text
.charAt(j
))) {
1250 var match
= matching
[cur
];
1251 if (match
.charAt(1) == ">" == forward
) stack
.push(cur
);
1252 else if (stack
.pop() != match
.charAt(0)) return {pos
: pos
, match
: false};
1253 else if (!stack
.length
) return {pos
: pos
, match
: true};
1258 for (var i
= head
.line
, e
= forward
? Math
.min(i
+ 100, lines
.length
) : Math
.max(-1, i
- 100); i
!= e
; i
+=d
) {
1259 var line
= lines
[i
], first
= i
== head
.line
;
1260 var found
= scan(line
, first
&& forward
? pos
+ 1 : 0, first
&& !forward
? pos
: line
.text
.length
);
1263 if (!found
) found
= {pos
: null, match
: false};
1264 var style
= found
.match
? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
1265 var one
= markText({line
: head
.line
, ch
: pos
}, {line
: head
.line
, ch
: pos
+1}, style
),
1266 two
= found
.pos
!= null
1267 ? markText({line
: i
, ch
: found
.pos
}, {line
: i
, ch
: found
.pos
+ 1}, style
)
1269 var clear
= operation(function(){one(); two();});
1270 if (autoclear
) setTimeout(clear
, 800);
1271 else bracketHighlighted
= clear
;
1274 // Finds the line to start with when starting a parse. Tries to
1275 // find a line with a stateAfter, so that it can start with a
1276 // valid state. If that fails, it returns the line with the
1277 // smallest indentation, which tends to need the least context to
1279 function findStartLine(n
) {
1280 var minindent
, minline
;
1281 for (var search
= n
, lim
= n
- 40; search
> lim
; --search
) {
1282 if (search
== 0) return 0;
1283 var line
= lines
[search
-1];
1284 if (line
.stateAfter
) return search
;
1285 var indented
= line
.indentation();
1286 if (minline
== null || minindent
> indented
) {
1288 minindent
= indented
;
1293 function getStateBefore(n
) {
1294 var start
= findStartLine(n
), state
= start
&& lines
[start
-1].stateAfter
;
1295 if (!state
) state
= startState(mode
);
1296 else state
= copyState(mode
, state
);
1297 for (var i
= start
; i
< n
; ++i
) {
1298 var line
= lines
[i
];
1299 line
.highlight(mode
, state
);
1300 line
.stateAfter
= copyState(mode
, state
);
1302 if (n
< lines
.length
&& !lines
[n
].stateAfter
) work
.push(n
);
1305 function highlightLines(start
, end
) {
1306 var state
= getStateBefore(start
);
1307 for (var i
= start
; i
< end
; ++i
) {
1308 var line
= lines
[i
];
1309 line
.highlight(mode
, state
);
1310 line
.stateAfter
= copyState(mode
, state
);
1313 function highlightWorker() {
1314 var end
= +new Date
+ options
.workTime
;
1315 var foundWork
= work
.length
;
1316 while (work
.length
) {
1317 if (!lines
[showingFrom
].stateAfter
) var task
= showingFrom
;
1318 else var task
= work
.pop();
1319 if (task
>= lines
.length
) continue;
1320 var start
= findStartLine(task
), state
= start
&& lines
[start
-1].stateAfter
;
1321 if (state
) state
= copyState(mode
, state
);
1322 else state
= startState(mode
);
1324 var unchanged
= 0, compare
= mode
.compareStates
;
1325 for (var i
= start
, l
= lines
.length
; i
< l
; ++i
) {
1326 var line
= lines
[i
], hadState
= line
.stateAfter
;
1327 if (+new Date
> end
) {
1329 startWorker(options
.workDelay
);
1330 changes
.push({from: task
, to
: i
+ 1});
1333 var changed
= line
.highlight(mode
, state
);
1334 line
.stateAfter
= copyState(mode
, state
);
1336 if (hadState
&& compare(hadState
, state
)) break;
1338 if (changed
|| !hadState
) unchanged
= 0;
1339 else if (++unchanged
> 3) break;
1342 changes
.push({from: task
, to
: i
+ 1});
1344 if (foundWork
&& options
.onHighlightComplete
)
1345 options
.onHighlightComplete(instance
);
1347 function startWorker(time
) {
1348 if (!work
.length
) return;
1349 highlight
.set(time
, operation(highlightWorker
));
1352 // Operations are used to wrap changes in such a way that each
1353 // change won't have to update the cursor and display (which would
1354 // be awkward, slow, and error-prone), but instead updates are
1355 // batched and then all combined and executed at once.
1356 function startOperation() {
1357 updateInput
= null; changes
= []; textChanged
= selectionChanged
= false;
1359 function endOperation() {
1360 var reScroll
= false;
1361 if (selectionChanged
) reScroll
= !scrollCursorIntoView();
1362 if (changes
.length
) updateDisplay(changes
);
1363 else if (selectionChanged
) updateCursor();
1364 if (reScroll
) scrollCursorIntoView();
1365 if (selectionChanged
) restartBlink();
1367 // updateInput can be set to a boolean value to force/prevent an
1369 if (focused
&& !leaveInputAlone
&&
1370 (updateInput
=== true || (updateInput
!== false && selectionChanged
)))
1373 if (selectionChanged
&& options
.matchBrackets
)
1374 setTimeout(operation(function() {
1375 if (bracketHighlighted
) {bracketHighlighted(); bracketHighlighted
= null;}
1376 matchBrackets(false);
1378 var tc
= textChanged
; // textChanged can be reset by cursoractivity callback
1379 if (selectionChanged
&& options
.onCursorActivity
)
1380 options
.onCursorActivity(instance
);
1381 if (tc
&& options
.onChange
&& instance
)
1382 options
.onChange(instance
, tc
);
1384 var nestedOperation
= 0;
1385 function operation(f
) {
1387 if (!nestedOperation
++) startOperation();
1388 try {var result
= f
.apply(this, arguments
);}
1389 finally {if (!--nestedOperation
) endOperation();}
1394 function SearchCursor(query
, pos
, caseFold
) {
1395 this.atOccurrence
= false;
1396 if (caseFold
== null) caseFold
= typeof query
== "string" && query
== query
.toLowerCase();
1398 if (pos
&& typeof pos
== "object") pos
= clipPos(pos
);
1399 else pos
= {line
: 0, ch
: 0};
1400 this.pos
= {from: pos
, to
: pos
};
1402 // The matches method is filled in based on the type of query.
1403 // It takes a position and a direction, and returns an object
1404 // describing the next occurrence of the query, or null if no
1405 // more matches were found.
1406 if (typeof query
!= "string") // Regexp match
1407 this.matches = function(reverse
, pos
) {
1409 var line
= lines
[pos
.line
].text
.slice(0, pos
.ch
), match
= line
.match(query
), start
= 0;
1411 var ind
= line
.indexOf(match
[0]);
1413 line
= line
.slice(ind
+ 1);
1414 var newmatch
= line
.match(query
);
1415 if (newmatch
) match
= newmatch
;
1421 var line
= lines
[pos
.line
].text
.slice(pos
.ch
), match
= line
.match(query
),
1422 start
= match
&& pos
.ch
+ line
.indexOf(match
[0]);
1425 return {from: {line
: pos
.line
, ch
: start
},
1426 to
: {line
: pos
.line
, ch
: start
+ match
[0].length
},
1429 else { // String query
1430 if (caseFold
) query
= query
.toLowerCase();
1431 var fold
= caseFold
? function(str
){return str
.toLowerCase();} : function(str
){return str
;};
1432 var target
= query
.split("\n");
1433 // Different methods for single-line and multi-line queries
1434 if (target
.length
== 1)
1435 this.matches = function(reverse
, pos
) {
1436 var line
= fold(lines
[pos
.line
].text
), len
= query
.length
, match
;
1437 if (reverse
? (pos
.ch
>= len
&& (match
= line
.lastIndexOf(query
, pos
.ch
- len
)) != -1)
1438 : (match
= line
.indexOf(query
, pos
.ch
)) != -1)
1439 return {from: {line
: pos
.line
, ch
: match
},
1440 to
: {line
: pos
.line
, ch
: match
+ len
}};
1443 this.matches = function(reverse
, pos
) {
1444 var ln
= pos
.line
, idx
= (reverse
? target
.length
- 1 : 0), match
= target
[idx
], line
= fold(lines
[ln
].text
);
1445 var offsetA
= (reverse
? line
.indexOf(match
) + match
.length
: line
.lastIndexOf(match
));
1446 if (reverse
? offsetA
>= pos
.ch
|| offsetA
!= match
.length
1447 : offsetA
<= pos
.ch
|| offsetA
!= line
.length
- match
.length
)
1450 if (reverse
? !ln
: ln
== lines
.length
- 1) return;
1451 line
= fold(lines
[ln
+= reverse
? -1 : 1].text
);
1452 match
= target
[reverse
? --idx
: ++idx
];
1453 if (idx
> 0 && idx
< target
.length
- 1) {
1454 if (line
!= match
) return;
1457 var offsetB
= (reverse
? line
.lastIndexOf(match
) : line
.indexOf(match
) + match
.length
);
1458 if (reverse
? offsetB
!= line
.length
- match
.length
: offsetB
!= match
.length
)
1460 var start
= {line
: pos
.line
, ch
: offsetA
}, end
= {line
: ln
, ch
: offsetB
};
1461 return {from: reverse
? end
: start
, to
: reverse
? start
: end
};
1467 SearchCursor
.prototype = {
1468 findNext: function() {return this.find(false);},
1469 findPrevious: function() {return this.find(true);},
1471 find: function(reverse
) {
1472 var self
= this, pos
= clipPos(reverse
? this.pos
.from : this.pos
.to
);
1473 function savePosAndFail(line
) {
1474 var pos
= {line
: line
, ch
: 0};
1475 self
.pos
= {from: pos
, to
: pos
};
1476 self
.atOccurrence
= false;
1481 if (this.pos
= this.matches(reverse
, pos
)) {
1482 this.atOccurrence
= true;
1483 return this.pos
.match
|| true;
1486 if (!pos
.line
) return savePosAndFail(0);
1487 pos
= {line
: pos
.line
-1, ch
: lines
[pos
.line
-1].text
.length
};
1490 if (pos
.line
== lines
.length
- 1) return savePosAndFail(lines
.length
);
1491 pos
= {line
: pos
.line
+1, ch
: 0};
1496 from: function() {if (this.atOccurrence
) return copyPos(this.pos
.from);},
1497 to: function() {if (this.atOccurrence
) return copyPos(this.pos
.to
);},
1499 replace: function(newText
) {
1501 if (this.atOccurrence
)
1502 operation(function() {
1503 self
.pos
.to
= replaceRange(newText
, self
.pos
.from, self
.pos
.to
);
1508 for (var ext
in extensions
)
1509 if (extensions
.propertyIsEnumerable(ext
) &&
1510 !instance
.propertyIsEnumerable(ext
))
1511 instance
[ext
] = extensions
[ext
];
1513 } // (end of function CodeMirror)
1515 // The default configuration options.
1516 CodeMirror
.defaults
= {
1521 indentWithTabs
: false,
1523 enterMode
: "indent",
1524 electricChars
: true,
1531 onCursorActivity
: null,
1532 onGutterClick
: null,
1533 onHighlightComplete
: null,
1534 onFocus
: null, onBlur
: null, onScroll
: null,
1535 matchBrackets
: false,
1540 document
: window
.document
1543 // Known modes, by name and by MIME
1544 var modes
= {}, mimeModes
= {};
1545 CodeMirror
.defineMode = function(name
, mode
) {
1546 if (!CodeMirror
.defaults
.mode
&& name
!= "null") CodeMirror
.defaults
.mode
= name
;
1549 CodeMirror
.defineMIME = function(mime
, spec
) {
1550 mimeModes
[mime
] = spec
;
1552 CodeMirror
.getMode = function(options
, spec
) {
1553 if (typeof spec
== "string" && mimeModes
.hasOwnProperty(spec
))
1554 spec
= mimeModes
[spec
];
1555 if (typeof spec
== "string")
1556 var mname
= spec
, config
= {};
1557 else if (spec
!= null)
1558 var mname
= spec
.name
, config
= spec
;
1559 var mfactory
= modes
[mname
];
1561 if (window
.console
) console
.warn("No mode " + mname
+ " found, falling back to plain text.");
1562 return CodeMirror
.getMode(options
, "text/plain");
1564 return mfactory(options
, config
|| {});
1566 CodeMirror
.listModes = function() {
1568 for (var m
in modes
)
1569 if (modes
.propertyIsEnumerable(m
)) list
.push(m
);
1572 CodeMirror
.listMIMEs = function() {
1574 for (var m
in mimeModes
)
1575 if (mimeModes
.propertyIsEnumerable(m
)) list
.push(m
);
1579 var extensions
= {};
1580 CodeMirror
.defineExtension = function(name
, func
) {
1581 extensions
[name
] = func
;
1584 CodeMirror
.fromTextArea = function(textarea
, options
) {
1585 if (!options
) options
= {};
1586 options
.value
= textarea
.value
;
1587 if (!options
.tabindex
&& textarea
.tabindex
)
1588 options
.tabindex
= textarea
.tabindex
;
1590 function save() {textarea
.value
= instance
.getValue();}
1591 if (textarea
.form
) {
1592 // Deplorable hack to make the submit method do the right thing.
1593 var rmSubmit
= connect(textarea
.form
, "submit", save
, true);
1594 if (typeof textarea
.form
.submit
== "function") {
1595 var realSubmit
= textarea
.form
.submit
;
1596 function wrappedSubmit() {
1598 textarea
.form
.submit
= realSubmit
;
1599 textarea
.form
.submit();
1600 textarea
.form
.submit
= wrappedSubmit
;
1602 textarea
.form
.submit
= wrappedSubmit
;
1606 textarea
.style
.display
= "none";
1607 var instance
= CodeMirror(function(node
) {
1608 textarea
.parentNode
.insertBefore(node
, textarea
.nextSibling
);
1610 instance
.save
= save
;
1611 instance
.toTextArea = function() {
1613 textarea
.parentNode
.removeChild(instance
.getWrapperElement());
1614 textarea
.style
.display
= "";
1615 if (textarea
.form
) {
1617 if (typeof textarea
.form
.submit
== "function")
1618 textarea
.form
.submit
= realSubmit
;
1624 // Utility functions for working with state. Exported because modes
1625 // sometimes need to do this.
1626 function copyState(mode
, state
) {
1627 if (state
=== true) return state
;
1628 if (mode
.copyState
) return mode
.copyState(state
);
1630 for (var n
in state
) {
1632 if (val
instanceof Array
) val
= val
.concat([]);
1637 CodeMirror
.startState
= startState
;
1638 function startState(mode
, a1
, a2
) {
1639 return mode
.startState
? mode
.startState(a1
, a2
) : true;
1641 CodeMirror
.copyState
= copyState
;
1643 // The character stream used by a mode's parser.
1644 function StringStream(string
) {
1645 this.pos
= this.start
= 0;
1646 this.string
= string
;
1648 StringStream
.prototype = {
1649 eol: function() {return this.pos
>= this.string
.length
;},
1650 sol: function() {return this.pos
== 0;},
1651 peek: function() {return this.string
.charAt(this.pos
);},
1653 if (this.pos
< this.string
.length
)
1654 return this.string
.charAt(this.pos
++);
1656 eat: function(match
) {
1657 var ch
= this.string
.charAt(this.pos
);
1658 if (typeof match
== "string") var ok
= ch
== match
;
1659 else var ok
= ch
&& (match
.test
? match
.test(ch
) : match(ch
));
1660 if (ok
) {++this.pos
; return ch
;}
1662 eatWhile: function(match
) {
1663 var start
= this.start
;
1664 while (this.eat(match
)){}
1665 return this.pos
> start
;
1667 eatSpace: function() {
1668 var start
= this.pos
;
1669 while (/[\s\u00a0]/.test(this.string
.charAt(this.pos
))) ++this.pos
;
1670 return this.pos
> start
;
1672 skipToEnd: function() {this.pos
= this.string
.length
;},
1673 skipTo: function(ch
) {
1674 var found
= this.string
.indexOf(ch
, this.pos
);
1675 if (found
> -1) {this.pos
= found
; return true;}
1677 backUp: function(n
) {this.pos
-= n
;},
1678 column: function() {return countColumn(this.string
, this.start
);},
1679 indentation: function() {return countColumn(this.string
);},
1680 match: function(pattern
, consume
, caseInsensitive
) {
1681 if (typeof pattern
== "string") {
1682 function cased(str
) {return caseInsensitive
? str
.toLowerCase() : str
;}
1683 if (cased(this.string
).indexOf(cased(pattern
), this.pos
) == this.pos
) {
1684 if (consume
!== false) this.pos
+= pattern
.length
;
1689 var match
= this.string
.slice(this.pos
).match(pattern
);
1690 if (match
&& consume
!== false) this.pos
+= match
[0].length
;
1694 current: function(){return this.string
.slice(this.start
, this.pos
);}
1696 CodeMirror
.StringStream
= StringStream
;
1698 // Line objects. These hold state related to a line, including
1699 // highlighting info (the styles array).
1700 function Line(text
, styles
) {
1701 this.styles
= styles
|| [text
, null];
1702 this.stateAfter
= null;
1704 this.marked
= this.gutterMarker
= this.className
= null;
1707 // Replace a piece of a line, keeping the styles around it intact.
1708 replace: function(from, to
, text
) {
1709 var st
= [], mk
= this.marked
;
1710 copyStyles(0, from, this.styles
, st
);
1711 if (text
) st
.push(text
, null);
1712 copyStyles(to
, this.text
.length
, this.styles
, st
);
1714 this.text
= this.text
.slice(0, from) + text
+ this.text
.slice(to
);
1715 this.stateAfter
= null;
1717 var diff
= text
.length
- (to
- from), end
= this.text
.length
;
1718 function fix(n
) {return n
<= Math
.min(to
, to
+ diff
) ? n
: n
+ diff
;}
1719 for (var i
= 0; i
< mk
.length
; ++i
) {
1720 var mark
= mk
[i
], del
= false;
1721 if (mark
.from >= end
) del
= true;
1722 else {mark
.from = fix(mark
.from); if (mark
.to
!= null) mark
.to
= fix(mark
.to
);}
1723 if (del
|| mark
.from >= mark
.to
) {mk
.splice(i
, 1); i
--;}
1727 // Split a line in two, again keeping styles intact.
1728 split: function(pos
, textBefore
) {
1729 var st
= [textBefore
, null];
1730 copyStyles(pos
, this.text
.length
, this.styles
, st
);
1731 return new Line(textBefore
+ this.text
.slice(pos
), st
);
1733 addMark: function(from, to
, style
) {
1734 var mk
= this.marked
, mark
= {from: from, to
: to
, style
: style
};
1735 if (this.marked
== null) this.marked
= [];
1736 this.marked
.push(mark
);
1737 this.marked
.sort(function(a
, b
){return a
.from - b
.from;});
1740 removeMark: function(mark
) {
1741 var mk
= this.marked
;
1743 for (var i
= 0; i
< mk
.length
; ++i
)
1744 if (mk
[i
] == mark
) {mk
.splice(i
, 1); break;}
1746 // Run the given mode's parser over a line, update the styles
1747 // array, which contains alternating fragments of text and CSS
1749 highlight: function(mode
, state
) {
1750 var stream
= new StringStream(this.text
), st
= this.styles
, pos
= 0;
1751 var changed
= false, curWord
= st
[0], prevWord
;
1752 if (this.text
== "" && mode
.blankLine
) mode
.blankLine(state
);
1753 while (!stream
.eol()) {
1754 var style
= mode
.token(stream
, state
);
1755 var substr
= this.text
.slice(stream
.start
, stream
.pos
);
1756 stream
.start
= stream
.pos
;
1757 if (pos
&& st
[pos
-1] == style
)
1758 st
[pos
-2] += substr
;
1760 if (!changed
&& (st
[pos
+1] != style
|| (pos
&& st
[pos
-2] != prevWord
))) changed
= true;
1761 st
[pos
++] = substr
; st
[pos
++] = style
;
1762 prevWord
= curWord
; curWord
= st
[pos
];
1764 // Give up when line is ridiculously long
1765 if (stream
.pos
> 5000) {
1766 st
[pos
++] = this.text
.slice(stream
.pos
); st
[pos
++] = null;
1770 if (st
.length
!= pos
) {st
.length
= pos
; changed
= true;}
1771 if (pos
&& st
[pos
-2] != prevWord
) changed
= true;
1772 // Short lines with simple highlights always count as changed,
1773 // because they are likely to highlight the same way in various
1775 return changed
|| (st
.length
< 5 && this.text
.length
< 10);
1777 // Fetch the parser token for a given character. Useful for hacks
1778 // that want to inspect the mode state (say, for completion).
1779 getTokenAt: function(mode
, state
, ch
) {
1780 var txt
= this.text
, stream
= new StringStream(txt
);
1781 while (stream
.pos
< ch
&& !stream
.eol()) {
1782 stream
.start
= stream
.pos
;
1783 var style
= mode
.token(stream
, state
);
1785 return {start
: stream
.start
,
1787 string
: stream
.current(),
1788 className
: style
|| null,
1791 indentation: function() {return countColumn(this.text
);},
1792 // Produces an HTML fragment for the line, taking selection,
1793 // marking, and highlighting into account.
1794 getHTML: function(sfrom
, sto
, includePre
, endAt
) {
1797 html
.push(this.className
? '<pre class="' + this.className
+ '">': "<pre>");
1798 function span(text
, style
) {
1800 if (style
) html
.push('<span class="', style
, '">', htmlEscape(text
), "</span>");
1801 else html
.push(htmlEscape(text
));
1803 var st
= this.styles
, allText
= this.text
, marked
= this.marked
;
1804 if (sfrom
== sto
) sfrom
= null;
1805 var len
= allText
.length
;
1806 if (endAt
!= null) len
= Math
.min(endAt
, len
);
1808 if (!allText
&& endAt
== null)
1809 span(" ", sfrom
!= null && sto
== null ? "CodeMirror-selected" : null);
1810 else if (!marked
&& sfrom
== null)
1811 for (var i
= 0, ch
= 0; ch
< len
; i
+=2) {
1812 var str
= st
[i
], l
= str
.length
;
1813 if (ch
+ l
> len
) str
= str
.slice(0, len
- ch
);
1815 span(str
, "cm-" + st
[i
+1]);
1818 var pos
= 0, i
= 0, text
= "", style
, sg
= 0;
1819 var markpos
= -1, mark
= null;
1820 function nextMark() {
1823 mark
= (markpos
< marked
.length
) ? marked
[markpos
] : null;
1829 var extraStyle
= "";
1830 if (sfrom
!= null) {
1831 if (sfrom
> pos
) upto
= sfrom
;
1832 else if (sto
== null || sto
> pos
) {
1833 extraStyle
= " CodeMirror-selected";
1834 if (sto
!= null) upto
= Math
.min(upto
, sto
);
1837 while (mark
&& mark
.to
!= null && mark
.to
<= pos
) nextMark();
1839 if (mark
.from > pos
) upto
= Math
.min(upto
, mark
.from);
1841 extraStyle
+= " " + mark
.style
;
1842 if (mark
.to
!= null) upto
= Math
.min(upto
, mark
.to
);
1846 var end
= pos
+ text
.length
;
1847 var appliedStyle
= style
;
1848 if (extraStyle
) appliedStyle
= style
? style
+ extraStyle
: extraStyle
;
1849 span(end
> upto
? text
.slice(0, upto
- pos
) : text
, appliedStyle
);
1850 if (end
>= upto
) {text
= text
.slice(upto
- pos
); pos
= upto
; break;}
1852 text
= st
[i
++]; style
= "cm-" + st
[i
++];
1855 if (sfrom
!= null && sto
== null) span(" ", "CodeMirror-selected");
1857 if (includePre
) html
.push("</pre>");
1858 return html
.join("");
1861 // Utility used by replace and split above
1862 function copyStyles(from, to
, source
, dest
) {
1863 for (var i
= 0, pos
= 0, state
= 0; pos
< to
; i
+=2) {
1864 var part
= source
[i
], end
= pos
+ part
.length
;
1866 if (end
> from) dest
.push(part
.slice(from - pos
, Math
.min(part
.length
, to
- pos
)), source
[i
+1]);
1867 if (end
>= from) state
= 1;
1869 else if (state
== 1) {
1870 if (end
> to
) dest
.push(part
.slice(0, to
- pos
), source
[i
+1]);
1871 else dest
.push(part
, source
[i
+1]);
1877 // The history object 'chunks' changes that are made close together
1878 // and at almost the same time into bigger undoable units.
1879 function History() {
1881 this.done
= []; this.undone
= [];
1883 History
.prototype = {
1884 addChange: function(start
, added
, old
) {
1885 this.undone
.length
= 0;
1886 var time
= +new Date
, last
= this.done
[this.done
.length
- 1];
1887 if (time
- this.time
> 400 || !last
||
1888 last
.start
> start
+ added
|| last
.start
+ last
.added
< start
- last
.added
+ last
.old
.length
)
1889 this.done
.push({start
: start
, added
: added
, old
: old
});
1892 if (start
< last
.start
) {
1893 for (var i
= last
.start
- start
- 1; i
>= 0; --i
)
1894 last
.old
.unshift(old
[i
]);
1895 last
.added
+= last
.start
- start
;
1898 else if (last
.start
< start
) {
1899 oldoff
= start
- last
.start
;
1902 for (var i
= last
.added
- oldoff
, e
= old
.length
; i
< e
; ++i
)
1903 last
.old
.push(old
[i
]);
1904 if (last
.added
< added
) last
.added
= added
;
1910 function stopMethod() {e_stop(this);}
1911 // Ensure an event has a stop method.
1912 function addStop(event
) {
1913 if (!event
.stop
) event
.stop
= stopMethod
;
1917 function e_preventDefault(e
) {
1918 if (e
.preventDefault
) e
.preventDefault();
1919 else e
.returnValue
= false;
1921 function e_stopPropagation(e
) {
1922 if (e
.stopPropagation
) e
.stopPropagation();
1923 else e
.cancelBubble
= true;
1925 function e_stop(e
) {e_preventDefault(e
); e_stopPropagation(e
);}
1926 function e_target(e
) {return e
.target
|| e
.srcElement
;}
1927 function e_button(e
) {
1928 if (e
.which
) return e
.which
;
1929 else if (e
.button
& 1) return 1;
1930 else if (e
.button
& 2) return 3;
1931 else if (e
.button
& 4) return 2;
1933 function e_pageX(e
) {
1934 if (e
.pageX
!= null) return e
.pageX
;
1935 var doc
= e_target(e
).ownerDocument
;
1936 return e
.clientX
+ doc
.body
.scrollLeft
+ doc
.documentElement
.scrollLeft
;
1938 function e_pageY(e
) {
1939 if (e
.pageY
!= null) return e
.pageY
;
1940 var doc
= e_target(e
).ownerDocument
;
1941 return e
.clientY
+ doc
.body
.scrollTop
+ doc
.documentElement
.scrollTop
;
1944 // Event handler registration. If disconnect is true, it'll return a
1945 // function that unregisters the handler.
1946 function connect(node
, type
, handler
, disconnect
) {
1947 function wrapHandler(event
) {handler(event
|| window
.event
);}
1948 if (typeof node
.addEventListener
== "function") {
1949 node
.addEventListener(type
, wrapHandler
, false);
1950 if (disconnect
) return function() {node
.removeEventListener(type
, wrapHandler
, false);};
1953 node
.attachEvent("on" + type
, wrapHandler
);
1954 if (disconnect
) return function() {node
.detachEvent("on" + type
, wrapHandler
);};
1958 function Delayed() {this.id
= null;}
1959 Delayed
.prototype = {set: function(ms
, f
) {clearTimeout(this.id
); this.id
= setTimeout(f
, ms
);}};
1961 // Some IE versions don't preserve whitespace when setting the
1962 // innerHTML of a PRE tag.
1963 var badInnerHTML
= (function() {
1964 var pre
= document
.createElement("pre");
1965 pre
.innerHTML
= " "; return !pre
.innerHTML
;
1968 var gecko
= /gecko\/\d{7}/i.test(navigator
.userAgent
);
1969 var ie
= /MSIE \d/.test(navigator
.userAgent
);
1970 var safari
= /Apple Computer/.test(navigator
.vendor
);
1973 // Feature-detect whether newlines in textareas are converted to \r\n
1975 var te
= document
.createElement("textarea");
1976 te
.value
= "foo\nbar";
1977 if (te
.value
.indexOf("\r") > -1) lineSep
= "\r\n";
1981 var mac
= /Mac/.test(navigator
.platform
);
1982 var movementKeys
= {};
1983 for (var i
= 35; i
<= 40; ++i
)
1984 movementKeys
[i
] = movementKeys
["c" + i
] = true;
1986 // Counts the column offset in a string, taking tabs into account.
1987 // Used mostly to find indentation.
1988 function countColumn(string
, end
) {
1990 end
= string
.search(/[^\s\u00a0]/);
1991 if (end
== -1) end
= string
.length
;
1993 for (var i
= 0, n
= 0; i
< end
; ++i
) {
1994 if (string
.charAt(i
) == "\t") n
+= tabSize
- (n
% tabSize
);
2000 function computedStyle(elt
) {
2001 if (elt
.currentStyle
) return elt
.currentStyle
;
2002 return window
.getComputedStyle(elt
, null);
2004 // Find the position of an element by following the offsetParent chain.
2005 // If screen==true, it returns screen (rather than page) coordinates.
2006 function eltOffset(node
, screen
) {
2007 var doc
= node
.ownerDocument
.body
;
2008 var x
= 0, y
= 0, skipDoc
= false;
2009 for (var n
= node
; n
; n
= n
.offsetParent
) {
2010 x
+= n
.offsetLeft
; y
+= n
.offsetTop
;
2011 if (screen
&& computedStyle(n
).position
== "fixed")
2014 var e
= screen
&& !skipDoc
? null : doc
;
2015 for (var n
= node
.parentNode
; n
!= e
; n
= n
.parentNode
)
2016 if (n
.scrollLeft
!= null) { x
-= n
.scrollLeft
; y
-= n
.scrollTop
;}
2017 return {left
: x
, top
: y
};
2019 // Get a node's text content.
2020 function eltText(node
) {
2021 return node
.textContent
|| node
.innerText
|| node
.nodeValue
|| "";
2024 // Operations on {line, ch} objects.
2025 function posEq(a
, b
) {return a
.line
== b
.line
&& a
.ch
== b
.ch
;}
2026 function posLess(a
, b
) {return a
.line
< b
.line
|| (a
.line
== b
.line
&& a
.ch
< b
.ch
);}
2027 function copyPos(x
) {return {line
: x
.line
, ch
: x
.ch
};}
2029 var escapeElement
= document
.createElement("div");
2030 function htmlEscape(str
) {
2031 escapeElement
.innerText
= escapeElement
.textContent
= str
;
2032 return escapeElement
.innerHTML
;
2034 CodeMirror
.htmlEscape
= htmlEscape
;
2036 // Used to position the cursor after an undo/redo by finding the
2037 // last edited character.
2038 function editEnd(from, to
) {
2039 if (!to
) return from ? from.length
: 0;
2040 if (!from) return to
.length
;
2041 for (var i
= from.length
, j
= to
.length
; i
>= 0 && j
>= 0; --i
, --j
)
2042 if (from.charAt(i
) != to
.charAt(j
)) break;
2046 function indexOf(collection
, elt
) {
2047 if (collection
.indexOf
) return collection
.indexOf(elt
);
2048 for (var i
= 0, e
= collection
.length
; i
< e
; ++i
)
2049 if (collection
[i
] == elt
) return i
;
2053 // See if "".split is the broken IE version, if so, provide an
2054 // alternative way to split lines.
2055 var splitLines
, selRange
, setSelRange
;
2056 if ("\n\nb".split(/\n/).length
!= 3)
2057 splitLines = function(string
) {
2058 var pos
= 0, nl
, result
= [];
2059 while ((nl
= string
.indexOf("\n", pos
)) > -1) {
2060 result
.push(string
.slice(pos
, string
.charAt(nl
-1) == "\r" ? nl
- 1 : nl
));
2063 result
.push(string
.slice(pos
));
2067 splitLines = function(string
){return string
.split(/\r?\n/);};
2068 CodeMirror
.splitLines
= splitLines
;
2070 // Sane model of finding and setting the selection in a textarea
2071 if (window
.getSelection
) {
2072 selRange = function(te
) {
2073 try {return {start
: te
.selectionStart
, end
: te
.selectionEnd
};}
2074 catch(e
) {return null;}
2077 // On Safari, selection set with setSelectionRange are in a sort
2078 // of limbo wrt their anchor. If you press shift-left in them,
2079 // the anchor is put at the end, and the selection expanded to
2080 // the left. If you press shift-right, the anchor ends up at the
2081 // front. This is not what CodeMirror wants, so it does a
2082 // spurious modify() call to get out of limbo.
2083 setSelRange = function(te
, start
, end
) {
2085 te
.setSelectionRange(start
, end
);
2087 te
.setSelectionRange(start
, end
- 1);
2088 window
.getSelection().modify("extend", "forward", "character");
2092 setSelRange = function(te
, start
, end
) {
2093 try {te
.setSelectionRange(start
, end
);}
2094 catch(e
) {} // Fails on Firefox when textarea isn't part of the document
2097 // IE model. Don't ask.
2099 selRange = function(te
) {
2100 try {var range
= te
.ownerDocument
.selection
.createRange();}
2101 catch(e
) {return null;}
2102 if (!range
|| range
.parentElement() != te
) return null;
2103 var val
= te
.value
, len
= val
.length
, localRange
= te
.createTextRange();
2104 localRange
.moveToBookmark(range
.getBookmark());
2105 var endRange
= te
.createTextRange();
2106 endRange
.collapse(false);
2108 if (localRange
.compareEndPoints("StartToEnd", endRange
) > -1)
2109 return {start
: len
, end
: len
};
2111 var start
= -localRange
.moveStart("character", -len
);
2112 for (var i
= val
.indexOf("\r"); i
> -1 && i
< start
; i
= val
.indexOf("\r", i
+1), start
++) {}
2114 if (localRange
.compareEndPoints("EndToEnd", endRange
) > -1)
2115 return {start
: start
, end
: len
};
2117 var end
= -localRange
.moveEnd("character", -len
);
2118 for (var i
= val
.indexOf("\r"); i
> -1 && i
< end
; i
= val
.indexOf("\r", i
+1), end
++) {}
2119 return {start
: start
, end
: end
};
2121 setSelRange = function(te
, start
, end
) {
2122 var range
= te
.createTextRange();
2123 range
.collapse(true);
2124 var endrange
= range
.duplicate();
2125 var newlines
= 0, txt
= te
.value
;
2126 for (var pos
= txt
.indexOf("\n"); pos
> -1 && pos
< start
; pos
= txt
.indexOf("\n", pos
+ 1))
2128 range
.move("character", start
- newlines
);
2129 for (; pos
> -1 && pos
< end
; pos
= txt
.indexOf("\n", pos
+ 1))
2131 endrange
.move("character", end
- newlines
);
2132 range
.setEndPoint("EndToEnd", endrange
);
2137 CodeMirror
.defineMode("null", function() {
2138 return {token: function(stream
) {stream
.skipToEnd();}};
2140 CodeMirror
.defineMIME("text/plain", "null");