##// END OF EJS Templates
patch deletion in codemirror
Matthias BUSSONNIER -
Show More
@@ -1,3229 +1,3243 b''
1 1 // All functions that need access to the editor's state live inside
2 2 // the CodeMirror function. Below that, at the bottom of the file,
3 3 // some utilities are defined.
4 4
5 5 // CodeMirror is the only global var we claim
6 6 var CodeMirror = (function() {
7 7 // This is the function that produces an editor instance. Its
8 8 // closure is used to store the editor state.
9 9 function CodeMirror(place, givenOptions) {
10 10 // Determine effective options based on given values and defaults.
11 11 var options = {}, defaults = CodeMirror.defaults;
12 12 for (var opt in defaults)
13 13 if (defaults.hasOwnProperty(opt))
14 14 options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
15 15
16 16 // The element in which the editor lives.
17 17 var wrapper = document.createElement("div");
18 18 wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
19 19 // This mess creates the base DOM structure for the editor.
20 20 wrapper.innerHTML =
21 21 '<div style="overflow: hidden; position: relative; width: 3px; height: 0px;">' + // Wraps and hides input textarea
22 22 '<textarea style="position: absolute; padding: 0; width: 1px; height: 1em" wrap="off" ' +
23 23 'autocorrect="off" autocapitalize="off"></textarea></div>' +
24 24 '<div class="CodeMirror-scrollbar">' + // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself.
25 25 '<div class="CodeMirror-scrollbar-inner">' + // The empty scrollbar content, used solely for managing the scrollbar thumb.
26 26 '</div></div>' + // This must be before the scroll area because it's float-right.
27 27 '<div class="CodeMirror-scroll" tabindex="-1">' +
28 28 '<div style="position: relative">' + // Set to the height of the text, causes scrolling
29 29 '<div style="position: relative">' + // Moved around its parent to cover visible view
30 30 '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
31 31 // Provides positioning relative to (visible) text origin
32 32 '<div class="CodeMirror-lines"><div style="position: relative; z-index: 0">' +
33 33 // Used to measure text size
34 34 '<div style="position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;"></div>' +
35 35 '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
36 36 '<pre class="CodeMirror-cursor" style="visibility: hidden">&#160;</pre>' + // Used to force a width
37 37 '<div style="position: relative; z-index: -1"></div><div></div>' + // DIVs containing the selection and the actual code
38 38 '</div></div></div></div></div>';
39 39 if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
40 40 // I've never seen more elegant code in my life.
41 41 var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
42 42 scroller = wrapper.lastChild, code = scroller.firstChild,
43 43 mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
44 44 lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
45 45 cursor = measure.nextSibling, widthForcer = cursor.nextSibling,
46 46 selectionDiv = widthForcer.nextSibling, lineDiv = selectionDiv.nextSibling,
47 47 scrollbar = inputDiv.nextSibling, scrollbarInner = scrollbar.firstChild;
48 48 themeChanged(); keyMapChanged();
49 49 // Needed to hide big blue blinking cursor on Mobile Safari
50 50 if (ios) input.style.width = "0px";
51 51 if (!webkit) scroller.draggable = true;
52 52 lineSpace.style.outline = "none";
53 53 if (options.tabindex != null) input.tabIndex = options.tabindex;
54 54 if (options.autofocus) focusInput();
55 55 if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
56 56 // Needed to handle Tab key in KHTML
57 57 if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute";
58 58
59 59 // Check for OS X >= 10.7. If so, we need to force a width on the scrollbar, and
60 60 // make it overlap the content. (But we only do this if the scrollbar doesn't already
61 61 // have a natural width. If the mouse is plugged in or the user sets the system pref
62 62 // to always show scrollbars, the scrollbar shouldn't overlap.)
63 63 if (mac_geLion) {
64 64 scrollbar.className += (overlapScrollbars() ? " cm-sb-overlap" : " cm-sb-nonoverlap");
65 65 } else if (ie_lt8) {
66 66 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
67 67 scrollbar.className += " cm-sb-ie7";
68 68 }
69 69
70 70 // Check for problem with IE innerHTML not working when we have a
71 71 // P (or similar) parent node.
72 72 try { stringWidth("x"); }
73 73 catch (e) {
74 74 if (e.message.match(/runtime/i))
75 75 e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
76 76 throw e;
77 77 }
78 78
79 79 // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
80 80 var poll = new Delayed(), highlight = new Delayed(), blinker;
81 81
82 82 // mode holds a mode API object. doc is the tree of Line objects,
83 83 // work an array of lines that should be parsed, and history the
84 84 // undo history (instance of History constructor).
85 85 var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
86 86 loadMode();
87 87 // The selection. These are always maintained to point at valid
88 88 // positions. Inverted is used to remember that the user is
89 89 // selecting bottom-to-top.
90 90 var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
91 91 // Selection-related flags. shiftSelecting obviously tracks
92 92 // whether the user is holding shift.
93 93 var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, lastScrollLeft = 0, draggingText,
94 94 overwrite = false, suppressEdits = false;
95 95 // Variables used by startOperation/endOperation to track what
96 96 // happened during the operation.
97 97 var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,
98 98 gutterDirty, callbacks;
99 99 // Current visible range (may be bigger than the view window).
100 100 var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
101 101 // bracketHighlighted is used to remember that a bracket has been
102 102 // marked.
103 103 var bracketHighlighted;
104 104 // Tracks the maximum line length so that the horizontal scrollbar
105 105 // can be kept static when scrolling.
106 106 var maxLine = "", updateMaxLine = false, maxLineChanged = true;
107 107 var tabCache = {};
108 108
109 109 // Initialize the content.
110 110 operation(function(){setValue(options.value || ""); updateInput = false;})();
111 111 var history = new History();
112 112
113 113 // Register our event handlers.
114 114 connect(scroller, "mousedown", operation(onMouseDown));
115 115 connect(scroller, "dblclick", operation(onDoubleClick));
116 116 connect(lineSpace, "selectstart", e_preventDefault);
117 117 // Gecko browsers fire contextmenu *after* opening the menu, at
118 118 // which point we can't mess with it anymore. Context menu is
119 119 // handled in onMouseDown for Gecko.
120 120 if (!gecko) connect(scroller, "contextmenu", onContextMenu);
121 121 connect(scroller, "scroll", onScroll);
122 122 connect(scrollbar, "scroll", onScroll);
123 123 connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);});
124 124 connect(scroller, "mousewheel", onMouseWheel);
125 125 connect(scroller, "DOMMouseScroll", onMouseWheel);
126 126 connect(window, "resize", function() {updateDisplay(true);});
127 127 connect(input, "keyup", operation(onKeyUp));
128 128 connect(input, "input", fastPoll);
129 129 connect(input, "keydown", operation(onKeyDown));
130 130 connect(input, "keypress", operation(onKeyPress));
131 131 connect(input, "focus", onFocus);
132 132 connect(input, "blur", onBlur);
133 133
134 134 if (options.dragDrop) {
135 135 connect(scroller, "dragstart", onDragStart);
136 136 function drag_(e) {
137 137 if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
138 138 e_stop(e);
139 139 }
140 140 connect(scroller, "dragenter", drag_);
141 141 connect(scroller, "dragover", drag_);
142 142 connect(scroller, "drop", operation(onDrop));
143 143 }
144 144 connect(scroller, "paste", function(){focusInput(); fastPoll();});
145 145 connect(input, "paste", fastPoll);
146 146 connect(input, "cut", operation(function(){
147 147 if (!options.readOnly) replaceSelection("");
148 148 }));
149 149
150 150 // Needed to handle Tab key in KHTML
151 151 if (khtml) connect(code, "mouseup", function() {
152 152 if (document.activeElement == input) input.blur();
153 153 focusInput();
154 154 });
155 155
156 156 // IE throws unspecified error in certain cases, when
157 157 // trying to access activeElement before onload
158 158 var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { }
159 159 if (hasFocus || options.autofocus) setTimeout(onFocus, 20);
160 160 else onBlur();
161 161
162 162 function isLine(l) {return l >= 0 && l < doc.size;}
163 163 // The instance object that we'll return. Mostly calls out to
164 164 // local functions in the CodeMirror function. Some do some extra
165 165 // range checking and/or clipping. operation is used to wrap the
166 166 // call so that changes it makes are tracked, and the display is
167 167 // updated afterwards.
168 168 var instance = wrapper.CodeMirror = {
169 169 getValue: getValue,
170 170 setValue: operation(setValue),
171 171 getSelection: getSelection,
172 172 replaceSelection: operation(replaceSelection),
173 173 focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();},
174 174 setOption: function(option, value) {
175 175 var oldVal = options[option];
176 176 options[option] = value;
177 177 if (option == "mode" || option == "indentUnit") loadMode();
178 178 else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();}
179 179 else if (option == "readOnly" && !value) {resetInput(true);}
180 180 else if (option == "theme") themeChanged();
181 181 else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
182 182 else if (option == "tabSize") updateDisplay(true);
183 183 else if (option == "keyMap") keyMapChanged();
184 184 if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") {
185 185 gutterChanged();
186 186 updateDisplay(true);
187 187 }
188 188 },
189 189 getOption: function(option) {return options[option];},
190 190 undo: operation(undo),
191 191 redo: operation(redo),
192 192 indentLine: operation(function(n, dir) {
193 193 if (typeof dir != "string") {
194 194 if (dir == null) dir = options.smartIndent ? "smart" : "prev";
195 195 else dir = dir ? "add" : "subtract";
196 196 }
197 197 if (isLine(n)) indentLine(n, dir);
198 198 }),
199 199 indentSelection: operation(indentSelected),
200 200 historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
201 201 clearHistory: function() {history = new History();},
202 202 setHistory: function(histData) {
203 203 history = new History();
204 204 history.done = histData.done;
205 205 history.undone = histData.undone;
206 206 },
207 207 getHistory: function() {
208 208 history.time = 0;
209 209 return {done: history.done.concat([]), undone: history.undone.concat([])};
210 210 },
211 211 matchBrackets: operation(function(){matchBrackets(true);}),
212 212 getTokenAt: operation(function(pos) {
213 213 pos = clipPos(pos);
214 214 return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);
215 215 }),
216 216 getStateAfter: function(line) {
217 217 line = clipLine(line == null ? doc.size - 1: line);
218 218 return getStateBefore(line + 1);
219 219 },
220 220 cursorCoords: function(start, mode) {
221 221 if (start == null) start = sel.inverted;
222 222 return this.charCoords(start ? sel.from : sel.to, mode);
223 223 },
224 224 charCoords: function(pos, mode) {
225 225 pos = clipPos(pos);
226 226 if (mode == "local") return localCoords(pos, false);
227 227 if (mode == "div") return localCoords(pos, true);
228 228 return pageCoords(pos);
229 229 },
230 230 coordsChar: function(coords) {
231 231 var off = eltOffset(lineSpace);
232 232 return coordsChar(coords.x - off.left, coords.y - off.top);
233 233 },
234 234 markText: operation(markText),
235 235 setBookmark: setBookmark,
236 236 findMarksAt: findMarksAt,
237 237 setMarker: operation(addGutterMarker),
238 238 clearMarker: operation(removeGutterMarker),
239 239 setLineClass: operation(setLineClass),
240 240 hideLine: operation(function(h) {return setLineHidden(h, true);}),
241 241 showLine: operation(function(h) {return setLineHidden(h, false);}),
242 242 onDeleteLine: function(line, f) {
243 243 if (typeof line == "number") {
244 244 if (!isLine(line)) return null;
245 245 line = getLine(line);
246 246 }
247 247 (line.handlers || (line.handlers = [])).push(f);
248 248 return line;
249 249 },
250 250 lineInfo: lineInfo,
251 251 addWidget: function(pos, node, scroll, vert, horiz) {
252 252 pos = localCoords(clipPos(pos));
253 253 var top = pos.yBot, left = pos.x;
254 254 node.style.position = "absolute";
255 255 code.appendChild(node);
256 256 if (vert == "over") top = pos.y;
257 257 else if (vert == "near") {
258 258 var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
259 259 hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
260 260 if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
261 261 top = pos.y - node.offsetHeight;
262 262 if (left + node.offsetWidth > hspace)
263 263 left = hspace - node.offsetWidth;
264 264 }
265 265 node.style.top = (top + paddingTop()) + "px";
266 266 node.style.left = node.style.right = "";
267 267 if (horiz == "right") {
268 268 left = code.clientWidth - node.offsetWidth;
269 269 node.style.right = "0px";
270 270 } else {
271 271 if (horiz == "left") left = 0;
272 272 else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2;
273 273 node.style.left = (left + paddingLeft()) + "px";
274 274 }
275 275 if (scroll)
276 276 scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
277 277 },
278 278
279 279 lineCount: function() {return doc.size;},
280 280 clipPos: clipPos,
281 281 getCursor: function(start) {
282 282 if (start == null) start = sel.inverted;
283 283 return copyPos(start ? sel.from : sel.to);
284 284 },
285 285 somethingSelected: function() {return !posEq(sel.from, sel.to);},
286 286 setCursor: operation(function(line, ch, user) {
287 287 if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
288 288 else setCursor(line, ch, user);
289 289 }),
290 290 setSelection: operation(function(from, to, user) {
291 291 (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
292 292 }),
293 293 getLine: function(line) {if (isLine(line)) return getLine(line).text;},
294 294 getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
295 295 setLine: operation(function(line, text) {
296 296 if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
297 297 }),
298 298 removeLine: operation(function(line) {
299 299 if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
300 300 }),
301 301 replaceRange: operation(replaceRange),
302 302 getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);},
303 303
304 304 triggerOnKeyDown: operation(onKeyDown),
305 305 execCommand: function(cmd) {return commands[cmd](instance);},
306 306 // Stuff used by commands, probably not much use to outside code.
307 307 moveH: operation(moveH),
308 308 deleteH: operation(deleteH),
309 309 moveV: operation(moveV),
310 310 toggleOverwrite: function() {
311 311 if(overwrite){
312 312 overwrite = false;
313 313 cursor.className = cursor.className.replace(" CodeMirror-overwrite", "");
314 314 } else {
315 315 overwrite = true;
316 316 cursor.className += " CodeMirror-overwrite";
317 317 }
318 318 },
319 319
320 320 posFromIndex: function(off) {
321 321 var lineNo = 0, ch;
322 322 doc.iter(0, doc.size, function(line) {
323 323 var sz = line.text.length + 1;
324 324 if (sz > off) { ch = off; return true; }
325 325 off -= sz;
326 326 ++lineNo;
327 327 });
328 328 return clipPos({line: lineNo, ch: ch});
329 329 },
330 330 indexFromPos: function (coords) {
331 331 if (coords.line < 0 || coords.ch < 0) return 0;
332 332 var index = coords.ch;
333 333 doc.iter(0, coords.line, function (line) {
334 334 index += line.text.length + 1;
335 335 });
336 336 return index;
337 337 },
338 338 scrollTo: function(x, y) {
339 339 if (x != null) scroller.scrollLeft = x;
340 340 if (y != null) scrollbar.scrollTop = y;
341 341 updateDisplay([]);
342 342 },
343 343 getScrollInfo: function() {
344 344 return {x: scroller.scrollLeft, y: scrollbar.scrollTop,
345 345 height: scrollbar.scrollHeight, width: scroller.scrollWidth};
346 346 },
347 347 setSize: function(width, height) {
348 348 function interpret(val) {
349 349 val = String(val);
350 350 return /^\d+$/.test(val) ? val + "px" : val;
351 351 }
352 352 if (width != null) wrapper.style.width = interpret(width);
353 353 if (height != null) scroller.style.height = interpret(height);
354 354 },
355 355
356 356 operation: function(f){return operation(f)();},
357 357 compoundChange: function(f){return compoundChange(f);},
358 358 refresh: function(){
359 359 updateDisplay(true, null, lastScrollTop);
360 360 if (scrollbar.scrollHeight > lastScrollTop)
361 361 scrollbar.scrollTop = lastScrollTop;
362 362 },
363 363 getInputField: function(){return input;},
364 364 getWrapperElement: function(){return wrapper;},
365 365 getScrollerElement: function(){return scroller;},
366 366 getGutterElement: function(){return gutter;}
367 367 };
368 368
369 369 function getLine(n) { return getLineAt(doc, n); }
370 370 function updateLineHeight(line, height) {
371 371 gutterDirty = true;
372 372 var diff = height - line.height;
373 373 for (var n = line; n; n = n.parent) n.height += diff;
374 374 }
375 375
376 376 function setValue(code) {
377 377 var top = {line: 0, ch: 0};
378 378 updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
379 379 splitLines(code), top, top);
380 380 updateInput = true;
381 381 }
382 382 function getValue(lineSep) {
383 383 var text = [];
384 384 doc.iter(0, doc.size, function(line) { text.push(line.text); });
385 385 return text.join(lineSep || "\n");
386 386 }
387 387
388 388 function onScroll(e) {
389 389 if (scroller.scrollTop) {
390 390 scrollbar.scrollTop += scroller.scrollTop;
391 391 scroller.scrollTop = 0;
392 392 }
393 393 if (lastScrollTop != scrollbar.scrollTop || lastScrollLeft != scroller.scrollLeft) {
394 394 lastScrollTop = scrollbar.scrollTop;
395 395 lastScrollLeft = scroller.scrollLeft;
396 396 updateDisplay([]);
397 397 if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
398 398 if (options.onScroll) options.onScroll(instance);
399 399 }
400 400 }
401 401
402 402 function onMouseDown(e) {
403 403 setShift(e_prop(e, "shiftKey"));
404 404 // Check whether this is a click in a widget
405 405 for (var n = e_target(e); n != wrapper; n = n.parentNode)
406 406 if (n.parentNode == code && n != mover) return;
407 407
408 408 // See if this is a click in the gutter
409 409 for (var n = e_target(e); n != wrapper; n = n.parentNode)
410 410 if (n.parentNode == gutterText) {
411 411 if (options.onGutterClick)
412 412 options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
413 413 return e_preventDefault(e);
414 414 }
415 415
416 416 var start = posFromMouse(e);
417 417
418 418 switch (e_button(e)) {
419 419 case 3:
420 420 if (gecko) onContextMenu(e);
421 421 return;
422 422 case 2:
423 423 if (start) setCursor(start.line, start.ch, true);
424 424 setTimeout(focusInput, 20);
425 425 e_preventDefault(e);
426 426 return;
427 427 }
428 428 // For button 1, if it was clicked inside the editor
429 429 // (posFromMouse returning non-null), we have to adjust the
430 430 // selection.
431 431 if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
432 432
433 433 if (!focused) onFocus();
434 434
435 435 var now = +new Date, type = "single";
436 436 if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
437 437 type = "triple";
438 438 e_preventDefault(e);
439 439 setTimeout(focusInput, 20);
440 440 selectLine(start.line);
441 441 } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
442 442 type = "double";
443 443 lastDoubleClick = {time: now, pos: start};
444 444 e_preventDefault(e);
445 445 var word = findWordAt(start);
446 446 setSelectionUser(word.from, word.to);
447 447 } else { lastClick = {time: now, pos: start}; }
448 448
449 449 var last = start, going;
450 450 if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
451 451 !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
452 452 // Let the drag handler handle this.
453 453 if (webkit) scroller.draggable = true;
454 454 function dragEnd(e2) {
455 455 if (webkit) scroller.draggable = false;
456 456 draggingText = false;
457 457 up(); drop();
458 458 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
459 459 e_preventDefault(e2);
460 460 setCursor(start.line, start.ch, true);
461 461 focusInput();
462 462 }
463 463 }
464 464 var up = connect(document, "mouseup", operation(dragEnd), true);
465 465 var drop = connect(scroller, "drop", operation(dragEnd), true);
466 466 draggingText = true;
467 467 // IE's approach to draggable
468 468 if (scroller.dragDrop) scroller.dragDrop();
469 469 return;
470 470 }
471 471 e_preventDefault(e);
472 472 if (type == "single") setCursor(start.line, start.ch, true);
473 473
474 474 var startstart = sel.from, startend = sel.to;
475 475
476 476 function doSelect(cur) {
477 477 if (type == "single") {
478 478 setSelectionUser(start, cur);
479 479 } else if (type == "double") {
480 480 var word = findWordAt(cur);
481 481 if (posLess(cur, startstart)) setSelectionUser(word.from, startend);
482 482 else setSelectionUser(startstart, word.to);
483 483 } else if (type == "triple") {
484 484 if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0}));
485 485 else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0}));
486 486 }
487 487 }
488 488
489 489 function extend(e) {
490 490 var cur = posFromMouse(e, true);
491 491 if (cur && !posEq(cur, last)) {
492 492 if (!focused) onFocus();
493 493 last = cur;
494 494 doSelect(cur);
495 495 updateInput = false;
496 496 var visible = visibleLines();
497 497 if (cur.line >= visible.to || cur.line < visible.from)
498 498 going = setTimeout(operation(function(){extend(e);}), 150);
499 499 }
500 500 }
501 501
502 502 function done(e) {
503 503 clearTimeout(going);
504 504 var cur = posFromMouse(e);
505 505 if (cur) doSelect(cur);
506 506 e_preventDefault(e);
507 507 focusInput();
508 508 updateInput = true;
509 509 move(); up();
510 510 }
511 511 var move = connect(document, "mousemove", operation(function(e) {
512 512 clearTimeout(going);
513 513 e_preventDefault(e);
514 514 if (!ie && !e_button(e)) done(e);
515 515 else extend(e);
516 516 }), true);
517 517 var up = connect(document, "mouseup", operation(done), true);
518 518 }
519 519 function onDoubleClick(e) {
520 520 for (var n = e_target(e); n != wrapper; n = n.parentNode)
521 521 if (n.parentNode == gutterText) return e_preventDefault(e);
522 522 e_preventDefault(e);
523 523 }
524 524 function onDrop(e) {
525 525 if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
526 526 e.preventDefault();
527 527 var pos = posFromMouse(e, true), files = e.dataTransfer.files;
528 528 if (!pos || options.readOnly) return;
529 529 if (files && files.length && window.FileReader && window.File) {
530 530 function loadFile(file, i) {
531 531 var reader = new FileReader;
532 532 reader.onload = function() {
533 533 text[i] = reader.result;
534 534 if (++read == n) {
535 535 pos = clipPos(pos);
536 536 operation(function() {
537 537 var end = replaceRange(text.join(""), pos, pos);
538 538 setSelectionUser(pos, end);
539 539 })();
540 540 }
541 541 };
542 542 reader.readAsText(file);
543 543 }
544 544 var n = files.length, text = Array(n), read = 0;
545 545 for (var i = 0; i < n; ++i) loadFile(files[i], i);
546 546 } else {
547 547 // Don't do a replace if the drop happened inside of the selected text.
548 548 if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return;
549 549 try {
550 550 var text = e.dataTransfer.getData("Text");
551 551 if (text) {
552 552 compoundChange(function() {
553 553 var curFrom = sel.from, curTo = sel.to;
554 554 setSelectionUser(pos, pos);
555 555 if (draggingText) replaceRange("", curFrom, curTo);
556 556 replaceSelection(text);
557 557 focusInput();
558 558 });
559 559 }
560 560 }
561 561 catch(e){}
562 562 }
563 563 }
564 564 function onDragStart(e) {
565 565 var txt = getSelection();
566 566 e.dataTransfer.setData("Text", txt);
567 567
568 568 // Use dummy image instead of default browsers image.
569 569 if (gecko || chrome || opera) {
570 570 var img = document.createElement('img');
571 571 img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image
572 572 e.dataTransfer.setDragImage(img, 0, 0);
573 573 }
574 574 }
575 575
576 576 function doHandleBinding(bound, dropShift) {
577 577 if (typeof bound == "string") {
578 578 bound = commands[bound];
579 579 if (!bound) return false;
580 580 }
581 581 var prevShift = shiftSelecting;
582 582 try {
583 583 if (options.readOnly) suppressEdits = true;
584 584 if (dropShift) shiftSelecting = null;
585 585 bound(instance);
586 586 } catch(e) {
587 587 if (e != Pass) throw e;
588 588 return false;
589 589 } finally {
590 590 shiftSelecting = prevShift;
591 591 suppressEdits = false;
592 592 }
593 593 return true;
594 594 }
595 595 function handleKeyBinding(e) {
596 596 // Handle auto keymap transitions
597 597 var startMap = getKeyMap(options.keyMap), next = startMap.auto;
598 598 clearTimeout(maybeTransition);
599 599 if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
600 600 if (getKeyMap(options.keyMap) == startMap) {
601 601 options.keyMap = (next.call ? next.call(null, instance) : next);
602 602 }
603 603 }, 50);
604 604
605 605 var name = keyNames[e_prop(e, "keyCode")], handled = false;
606 606 if (name == null || e.altGraphKey) return false;
607 607 if (e_prop(e, "altKey")) name = "Alt-" + name;
608 608 if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name;
609 609 if (e_prop(e, "metaKey")) name = "Cmd-" + name;
610 610
611 611 var stopped = false;
612 612 function stop() { stopped = true; }
613 613
614 614 if (e_prop(e, "shiftKey")) {
615 615 handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap,
616 616 function(b) {return doHandleBinding(b, true);}, stop)
617 617 || lookupKey(name, options.extraKeys, options.keyMap, function(b) {
618 618 if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b);
619 619 }, stop);
620 620 } else {
621 621 handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop);
622 622 }
623 623 if (stopped) handled = false;
624 624 if (handled) {
625 625 e_preventDefault(e);
626 626 restartBlink();
627 627 if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
628 628 }
629 629 return handled;
630 630 }
631 631 function handleCharBinding(e, ch) {
632 632 var handled = lookupKey("'" + ch + "'", options.extraKeys,
633 633 options.keyMap, function(b) { return doHandleBinding(b, true); });
634 634 if (handled) {
635 635 e_preventDefault(e);
636 636 restartBlink();
637 637 }
638 638 return handled;
639 639 }
640 640
641 641 var lastStoppedKey = null, maybeTransition;
642 642 function onKeyDown(e) {
643 643 if (!focused) onFocus();
644 644 if (ie && e.keyCode == 27) { e.returnValue = false; }
645 645 if (pollingFast) { if (readInput()) pollingFast = false; }
646 646 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
647 647 var code = e_prop(e, "keyCode");
648 648 // IE does strange things with escape.
649 649 setShift(code == 16 || e_prop(e, "shiftKey"));
650 650 // First give onKeyEvent option a chance to handle this.
651 651 var handled = handleKeyBinding(e);
652 652 if (opera) {
653 653 lastStoppedKey = handled ? code : null;
654 654 // Opera has no cut event... we try to at least catch the key combo
655 655 if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey"))
656 656 replaceSelection("");
657 657 }
658 658 }
659 659 function onKeyPress(e) {
660 660 if (pollingFast) readInput();
661 661 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
662 662 var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
663 663 if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
664 664 if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return;
665 665 var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
666 666 if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) {
667 667 if (mode.electricChars.indexOf(ch) > -1)
668 668 setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
669 669 }
670 670 if (handleCharBinding(e, ch)) return;
671 671 fastPoll();
672 672 }
673 673 function onKeyUp(e) {
674 674 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
675 675 if (e_prop(e, "keyCode") == 16) shiftSelecting = null;
676 676 }
677 677
678 678 function onFocus() {
679 679 if (options.readOnly == "nocursor") return;
680 680 if (!focused) {
681 681 if (options.onFocus) options.onFocus(instance);
682 682 focused = true;
683 683 if (scroller.className.search(/\bCodeMirror-focused\b/) == -1)
684 684 scroller.className += " CodeMirror-focused";
685 685 if (!leaveInputAlone) resetInput(true);
686 686 }
687 687 slowPoll();
688 688 restartBlink();
689 689 }
690 690 function onBlur() {
691 691 if (focused) {
692 692 if (options.onBlur) options.onBlur(instance);
693 693 focused = false;
694 694 if (bracketHighlighted)
695 695 operation(function(){
696 696 if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; }
697 697 })();
698 698 scroller.className = scroller.className.replace(" CodeMirror-focused", "");
699 699 }
700 700 clearInterval(blinker);
701 701 setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
702 702 }
703 703
704 704 function chopDelta(delta) {
705 705 // Make sure we always scroll a little bit for any nonzero delta.
706 706 if (delta > 0.0 && delta < 1.0) return 1;
707 707 else if (delta > -1.0 && delta < 0.0) return -1;
708 708 else return Math.round(delta);
709 709 }
710 710
711 711 function onMouseWheel(e) {
712 712 var deltaX = 0, deltaY = 0;
713 713 if (e.type == "DOMMouseScroll") { // Firefox
714 714 var delta = -e.detail * 8.0;
715 715 if (e.axis == e.HORIZONTAL_AXIS) deltaX = delta;
716 716 else if (e.axis == e.VERTICAL_AXIS) deltaY = delta;
717 717 } else if (e.wheelDeltaX !== undefined && e.wheelDeltaY !== undefined) { // WebKit
718 718 deltaX = e.wheelDeltaX / 3.0;
719 719 deltaY = e.wheelDeltaY / 3.0;
720 720 } else if (e.wheelDelta !== undefined) { // IE or Opera
721 721 deltaY = e.wheelDelta / 3.0;
722 722 }
723 723
724 724 var scrolled = false;
725 725 deltaX = chopDelta(deltaX);
726 726 deltaY = chopDelta(deltaY);
727 727 if ((deltaX > 0 && scroller.scrollLeft > 0) ||
728 728 (deltaX < 0 && scroller.scrollLeft + scroller.clientWidth < scroller.scrollWidth)) {
729 729 scroller.scrollLeft -= deltaX;
730 730 scrolled = true;
731 731 }
732 732 if ((deltaY > 0 && scrollbar.scrollTop > 0) ||
733 733 (deltaY < 0 && scrollbar.scrollTop + scrollbar.clientHeight < scrollbar.scrollHeight)) {
734 734 scrollbar.scrollTop -= deltaY;
735 735 scrolled = true;
736 736 }
737 737 if (scrolled) e_stop(e);
738 738 }
739 739
740 740 // Replace the range from from to to by the strings in newText.
741 741 // Afterwards, set the selection to selFrom, selTo.
742 742 function updateLines(from, to, newText, selFrom, selTo) {
743 743 if (suppressEdits) return;
744 744 if (history) {
745 745 var old = [];
746 746 doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
747 747 history.addChange(from.line, newText.length, old);
748 748 while (history.done.length > options.undoDepth) history.done.shift();
749 749 }
750 750 updateLinesNoUndo(from, to, newText, selFrom, selTo);
751 751 }
752 752 function unredoHelper(from, to) {
753 753 if (!from.length) return;
754 754 var set = from.pop(), out = [];
755 755 for (var i = set.length - 1; i >= 0; i -= 1) {
756 756 var change = set[i];
757 757 var replaced = [], end = change.start + change.added;
758 758 doc.iter(change.start, end, function(line) { replaced.push(line.text); });
759 759 out.push({start: change.start, added: change.old.length, old: replaced});
760 760 var pos = {line: change.start + change.old.length - 1,
761 761 ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])};
762 762 updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
763 763 }
764 764 updateInput = true;
765 765 to.push(out);
766 766 }
767 767 function undo() {unredoHelper(history.done, history.undone);}
768 768 function redo() {unredoHelper(history.undone, history.done);}
769 769
770 770 function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
771 771 if (suppressEdits) return;
772 772 var recomputeMaxLength = false, maxLineLength = maxLine.length;
773 773 if (!options.lineWrapping)
774 774 doc.iter(from.line, to.line + 1, function(line) {
775 775 if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
776 776 });
777 777 if (from.line != to.line || newText.length > 1) gutterDirty = true;
778 778
779 779 var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
780 780 // First adjust the line structure, taking some care to leave highlighting intact.
781 781 if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") {
782 782 // This is a whole-line replace. Treated specially to make
783 783 // sure line objects move the way they are supposed to.
784 784 var added = [], prevLine = null;
785 785 if (from.line) {
786 786 prevLine = getLine(from.line - 1);
787 787 prevLine.fixMarkEnds(lastLine);
788 788 } else lastLine.fixMarkStarts();
789 789 for (var i = 0, e = newText.length - 1; i < e; ++i)
790 790 added.push(Line.inheritMarks(newText[i], prevLine));
791 791 if (nlines) doc.remove(from.line, nlines, callbacks);
792 792 if (added.length) doc.insert(from.line, added);
793 793 } else if (firstLine == lastLine) {
794 794 if (newText.length == 1)
795 795 firstLine.replace(from.ch, to.ch, newText[0]);
796 796 else {
797 797 lastLine = firstLine.split(to.ch, newText[newText.length-1]);
798 798 firstLine.replace(from.ch, null, newText[0]);
799 799 firstLine.fixMarkEnds(lastLine);
800 800 var added = [];
801 801 for (var i = 1, e = newText.length - 1; i < e; ++i)
802 802 added.push(Line.inheritMarks(newText[i], firstLine));
803 803 added.push(lastLine);
804 804 doc.insert(from.line + 1, added);
805 805 }
806 806 } else if (newText.length == 1) {
807 807 firstLine.replace(from.ch, null, newText[0]);
808 808 lastLine.replace(null, to.ch, "");
809 809 firstLine.append(lastLine);
810 810 doc.remove(from.line + 1, nlines, callbacks);
811 811 } else {
812 812 var added = [];
813 813 firstLine.replace(from.ch, null, newText[0]);
814 814 lastLine.replace(null, to.ch, newText[newText.length-1]);
815 815 firstLine.fixMarkEnds(lastLine);
816 816 for (var i = 1, e = newText.length - 1; i < e; ++i)
817 817 added.push(Line.inheritMarks(newText[i], firstLine));
818 818 if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
819 819 doc.insert(from.line + 1, added);
820 820 }
821 821 if (options.lineWrapping) {
822 822 var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3);
823 823 doc.iter(from.line, from.line + newText.length, function(line) {
824 824 if (line.hidden) return;
825 825 var guess = Math.ceil(line.text.length / perLine) || 1;
826 826 if (guess != line.height) updateLineHeight(line, guess);
827 827 });
828 828 } else {
829 829 doc.iter(from.line, from.line + newText.length, function(line) {
830 830 var l = line.text;
831 831 if (!line.hidden && l.length > maxLineLength) {
832 832 maxLine = l; maxLineLength = l.length; maxLineChanged = true;
833 833 recomputeMaxLength = false;
834 834 }
835 835 });
836 836 if (recomputeMaxLength) updateMaxLine = true;
837 837 }
838 838
839 839 // Add these lines to the work array, so that they will be
840 840 // highlighted. Adjust work lines if lines were added/removed.
841 841 var newWork = [], lendiff = newText.length - nlines - 1;
842 842 for (var i = 0, l = work.length; i < l; ++i) {
843 843 var task = work[i];
844 844 if (task < from.line) newWork.push(task);
845 845 else if (task > to.line) newWork.push(task + lendiff);
846 846 }
847 847 var hlEnd = from.line + Math.min(newText.length, 500);
848 848 highlightLines(from.line, hlEnd);
849 849 newWork.push(hlEnd);
850 850 work = newWork;
851 851 startWorker(100);
852 852 // Remember that these lines changed, for updating the display
853 853 changes.push({from: from.line, to: to.line + 1, diff: lendiff});
854 854 var changeObj = {from: from, to: to, text: newText};
855 855 if (textChanged) {
856 856 for (var cur = textChanged; cur.next; cur = cur.next) {}
857 857 cur.next = changeObj;
858 858 } else textChanged = changeObj;
859 859
860 860 // Update the selection
861 861 function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
862 862 setSelection(clipPos(selFrom), clipPos(selTo),
863 863 updateLine(sel.from.line), updateLine(sel.to.line));
864 864 }
865 865
866 866 function needsScrollbar() {
867 867 var realHeight = doc.height * textHeight() + 2 * paddingTop();
868 868 return realHeight - 1 > scroller.offsetHeight ? realHeight : false;
869 869 }
870 870
871 871 function updateVerticalScroll(scrollTop) {
872 872 var scrollHeight = needsScrollbar();
873 873 scrollbar.style.display = scrollHeight ? "block" : "none";
874 874 if (scrollHeight) {
875 875 scrollbarInner.style.height = scrollHeight + "px";
876 876 scrollbar.style.height = scroller.offsetHeight + "px";
877 877 if (scrollTop != null) scrollbar.scrollTop = scrollTop;
878 878 }
879 879 // Position the mover div to align with the current virtual scroll position
880 880 mover.style.top = (displayOffset * textHeight() - scrollbar.scrollTop) + "px";
881 881 }
882 882
883 883 // On Mac OS X Lion and up, detect whether the mouse is plugged in by measuring
884 884 // the width of a div with a scrollbar in it. If the width is <= 1, then
885 885 // the mouse isn't plugged in and scrollbars should overlap the content.
886 886 function overlapScrollbars() {
887 887 var tmpSb = document.createElement('div'),
888 888 tmpSbInner = document.createElement('div');
889 889 tmpSb.className = "CodeMirror-scrollbar";
890 890 tmpSb.style.cssText = "position: absolute; left: -9999px; height: 100px;";
891 891 tmpSbInner.className = "CodeMirror-scrollbar-inner";
892 892 tmpSbInner.style.height = "200px";
893 893 tmpSb.appendChild(tmpSbInner);
894 894
895 895 document.body.appendChild(tmpSb);
896 896 var result = (tmpSb.offsetWidth <= 1);
897 897 document.body.removeChild(tmpSb);
898 898 return result;
899 899 }
900 900
901 901 function computeMaxLength() {
902 902 var maxLineLength = 0;
903 903 maxLine = ""; maxLineChanged = true;
904 904 doc.iter(0, doc.size, function(line) {
905 905 var l = line.text;
906 906 if (!line.hidden && l.length > maxLineLength) {
907 907 maxLineLength = l.length; maxLine = l;
908 908 }
909 909 });
910 910 updateMaxLine = false;
911 911 }
912 912
913 913 function replaceRange(code, from, to) {
914 914 from = clipPos(from);
915 915 if (!to) to = from; else to = clipPos(to);
916 916 code = splitLines(code);
917 917 function adjustPos(pos) {
918 918 if (posLess(pos, from)) return pos;
919 919 if (!posLess(to, pos)) return end;
920 920 var line = pos.line + code.length - (to.line - from.line) - 1;
921 921 var ch = pos.ch;
922 922 if (pos.line == to.line)
923 923 ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0));
924 924 return {line: line, ch: ch};
925 925 }
926 926 var end;
927 927 replaceRange1(code, from, to, function(end1) {
928 928 end = end1;
929 929 return {from: adjustPos(sel.from), to: adjustPos(sel.to)};
930 930 });
931 931 return end;
932 932 }
933 933 function replaceSelection(code, collapse) {
934 934 replaceRange1(splitLines(code), sel.from, sel.to, function(end) {
935 935 if (collapse == "end") return {from: end, to: end};
936 936 else if (collapse == "start") return {from: sel.from, to: sel.from};
937 937 else return {from: sel.from, to: end};
938 938 });
939 939 }
940 940 function replaceRange1(code, from, to, computeSel) {
941 941 var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length;
942 942 var newSel = computeSel({line: from.line + code.length - 1, ch: endch});
943 943 updateLines(from, to, code, newSel.from, newSel.to);
944 944 }
945 945
946 946 function getRange(from, to, lineSep) {
947 947 var l1 = from.line, l2 = to.line;
948 948 if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
949 949 var code = [getLine(l1).text.slice(from.ch)];
950 950 doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
951 951 code.push(getLine(l2).text.slice(0, to.ch));
952 952 return code.join(lineSep || "\n");
953 953 }
954 954 function getSelection(lineSep) {
955 955 return getRange(sel.from, sel.to, lineSep);
956 956 }
957 957
958 958 var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
959 959 function slowPoll() {
960 960 if (pollingFast) return;
961 961 poll.set(options.pollInterval, function() {
962 962 startOperation();
963 963 readInput();
964 964 if (focused) slowPoll();
965 965 endOperation();
966 966 });
967 967 }
968 968 function fastPoll() {
969 969 var missed = false;
970 970 pollingFast = true;
971 971 function p() {
972 972 startOperation();
973 973 var changed = readInput();
974 974 if (!changed && !missed) {missed = true; poll.set(60, p);}
975 975 else {pollingFast = false; slowPoll();}
976 976 endOperation();
977 977 }
978 978 poll.set(20, p);
979 979 }
980 980
981 981 // Previnput is a hack to work with IME. If we reset the textarea
982 982 // on every change, that breaks IME. So we look for changes
983 983 // compared to the previous content instead. (Modern browsers have
984 984 // events that indicate IME taking place, but these are not widely
985 985 // supported or compatible enough yet to rely on.)
986 986 var prevInput = "";
987 987 function readInput() {
988 988 if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false;
989 989 var text = input.value;
990 990 if (text == prevInput) return false;
991 991 shiftSelecting = null;
992 992 var same = 0, l = Math.min(prevInput.length, text.length);
993 993 while (same < l && prevInput[same] == text[same]) ++same;
994 994 if (same < prevInput.length)
995 995 sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};
996 996 else if (overwrite && posEq(sel.from, sel.to))
997 997 sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};
998 998 replaceSelection(text.slice(same), "end");
999 999 if (text.length > 1000) { input.value = prevInput = ""; }
1000 1000 else prevInput = text;
1001 1001 return true;
1002 1002 }
1003 1003 function resetInput(user) {
1004 1004 if (!posEq(sel.from, sel.to)) {
1005 1005 prevInput = "";
1006 1006 input.value = getSelection();
1007 1007 selectInput(input);
1008 1008 } else if (user) prevInput = input.value = "";
1009 1009 }
1010 1010
1011 1011 function focusInput() {
1012 1012 if (options.readOnly != "nocursor") input.focus();
1013 1013 }
1014 1014
1015 1015 function scrollEditorIntoView() {
1016 1016 var rect = cursor.getBoundingClientRect();
1017 1017 // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden
1018 1018 if (ie && rect.top == rect.bottom) return;
1019 1019 var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
1020 1020 if (rect.top < 0 || rect.bottom > winH) scrollCursorIntoView();
1021 1021 }
1022 1022 function scrollCursorIntoView() {
1023 1023 var coords = calculateCursorCoords();
1024 1024 return scrollIntoView(coords.x, coords.y, coords.x, coords.yBot);
1025 1025 }
1026 1026 function calculateCursorCoords() {
1027 1027 var cursor = localCoords(sel.inverted ? sel.from : sel.to);
1028 1028 var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
1029 1029 return {x: x, y: cursor.y, yBot: cursor.yBot};
1030 1030 }
1031 1031 function scrollIntoView(x1, y1, x2, y2) {
1032 1032 var scrollPos = calculateScrollPos(x1, y1, x2, y2), scrolled = false;
1033 1033 if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft; scrolled = true;}
1034 1034 if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scrollPos.scrollTop; scrolled = true;}
1035 1035 if (scrolled && options.onScroll) options.onScroll(instance);
1036 1036 }
1037 1037 function calculateScrollPos(x1, y1, x2, y2) {
1038 1038 var pl = paddingLeft(), pt = paddingTop();
1039 1039 y1 += pt; y2 += pt; x1 += pl; x2 += pl;
1040 1040 var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {};
1041 1041 var docBottom = scroller.scrollHeight;
1042 1042 var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;;
1043 1043 if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
1044 1044 else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
1045 1045
1046 1046 var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
1047 1047 var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
1048 1048 var atLeft = x1 < gutterw + pl + 10;
1049 1049 if (x1 < screenleft + gutterw || atLeft) {
1050 1050 if (atLeft) x1 = 0;
1051 1051 result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
1052 1052 } else if (x2 > screenw + screenleft - 3) {
1053 1053 result.scrollLeft = x2 + 10 - screenw;
1054 1054 }
1055 1055 return result;
1056 1056 }
1057 1057
1058 1058 function visibleLines(scrollTop) {
1059 1059 var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop();
1060 1060 var fromHeight = Math.max(0, Math.floor(top / lh));
1061 1061 var toHeight = Math.ceil((top + scroller.clientHeight) / lh);
1062 1062 return {from: lineAtHeight(doc, fromHeight),
1063 1063 to: lineAtHeight(doc, toHeight)};
1064 1064 }
1065 1065 // Uses a set of changes plus the current scroll position to
1066 1066 // determine which DOM updates have to be made, and makes the
1067 1067 // updates.
1068 1068 function updateDisplay(changes, suppressCallback, scrollTop) {
1069 1069 if (!scroller.clientWidth) {
1070 1070 showingFrom = showingTo = displayOffset = 0;
1071 1071 return;
1072 1072 }
1073 1073 // Compute the new visible window
1074 1074 // If scrollTop is specified, use that to determine which lines
1075 1075 // to render instead of the current scrollbar position.
1076 1076 var visible = visibleLines(scrollTop);
1077 1077 // Bail out if the visible area is already rendered and nothing changed.
1078 1078 if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) {
1079 1079 updateVerticalScroll(scrollTop);
1080 1080 return;
1081 1081 }
1082 1082 var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
1083 1083 if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
1084 1084 if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
1085 1085
1086 1086 // Create a range of theoretically intact lines, and punch holes
1087 1087 // in that using the change info.
1088 1088 var intact = changes === true ? [] :
1089 1089 computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
1090 1090 // Clip off the parts that won't be visible
1091 1091 var intactLines = 0;
1092 1092 for (var i = 0; i < intact.length; ++i) {
1093 1093 var range = intact[i];
1094 1094 if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
1095 1095 if (range.to > to) range.to = to;
1096 1096 if (range.from >= range.to) intact.splice(i--, 1);
1097 1097 else intactLines += range.to - range.from;
1098 1098 }
1099 1099 if (intactLines == to - from && from == showingFrom && to == showingTo) {
1100 1100 updateVerticalScroll(scrollTop);
1101 1101 return;
1102 1102 }
1103 1103 intact.sort(function(a, b) {return a.domStart - b.domStart;});
1104 1104
1105 1105 var th = textHeight(), gutterDisplay = gutter.style.display;
1106 1106 lineDiv.style.display = "none";
1107 1107 patchDisplay(from, to, intact);
1108 1108 lineDiv.style.display = gutter.style.display = "";
1109 1109
1110 1110 var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;
1111 1111 // This is just a bogus formula that detects when the editor is
1112 1112 // resized or the font size changes.
1113 1113 if (different) lastSizeC = scroller.clientHeight + th;
1114 1114 showingFrom = from; showingTo = to;
1115 1115 displayOffset = heightAtLine(doc, from);
1116 1116
1117 1117 // Since this is all rather error prone, it is honoured with the
1118 1118 // only assertion in the whole file.
1119 1119 if (lineDiv.childNodes.length != showingTo - showingFrom)
1120 1120 throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
1121 1121 " nodes=" + lineDiv.childNodes.length);
1122 1122
1123 1123 function checkHeights() {
1124 1124 var curNode = lineDiv.firstChild, heightChanged = false;
1125 1125 doc.iter(showingFrom, showingTo, function(line) {
1126 1126 if (!line.hidden) {
1127 1127 var height = Math.round(curNode.offsetHeight / th) || 1;
1128 1128 if (line.height != height) {
1129 1129 updateLineHeight(line, height);
1130 1130 gutterDirty = heightChanged = true;
1131 1131 }
1132 1132 }
1133 1133 curNode = curNode.nextSibling;
1134 1134 });
1135 1135 return heightChanged;
1136 1136 }
1137 1137
1138 1138 if (options.lineWrapping) {
1139 1139 checkHeights();
1140 1140 var scrollHeight = needsScrollbar();
1141 1141 var shouldHaveScrollbar = scrollHeight ? "block" : "none";
1142 1142 if (scrollbar.style.display != shouldHaveScrollbar) {
1143 1143 scrollbar.style.display = shouldHaveScrollbar;
1144 1144 if (scrollHeight) scrollbarInner.style.height = scrollHeight + "px";
1145 1145 checkHeights();
1146 1146 }
1147 1147 }
1148 1148
1149 1149 gutter.style.display = gutterDisplay;
1150 1150 if (different || gutterDirty) {
1151 1151 // If the gutter grew in size, re-check heights. If those changed, re-draw gutter.
1152 1152 updateGutter() && options.lineWrapping && checkHeights() && updateGutter();
1153 1153 }
1154 1154 updateVerticalScroll(scrollTop);
1155 1155 updateSelection();
1156 1156 if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
1157 1157 return true;
1158 1158 }
1159 1159
1160 1160 function computeIntact(intact, changes) {
1161 1161 for (var i = 0, l = changes.length || 0; i < l; ++i) {
1162 1162 var change = changes[i], intact2 = [], diff = change.diff || 0;
1163 1163 for (var j = 0, l2 = intact.length; j < l2; ++j) {
1164 1164 var range = intact[j];
1165 1165 if (change.to <= range.from && change.diff)
1166 1166 intact2.push({from: range.from + diff, to: range.to + diff,
1167 1167 domStart: range.domStart});
1168 1168 else if (change.to <= range.from || change.from >= range.to)
1169 1169 intact2.push(range);
1170 1170 else {
1171 1171 if (change.from > range.from)
1172 1172 intact2.push({from: range.from, to: change.from, domStart: range.domStart});
1173 1173 if (change.to < range.to)
1174 1174 intact2.push({from: change.to + diff, to: range.to + diff,
1175 1175 domStart: range.domStart + (change.to - range.from)});
1176 1176 }
1177 1177 }
1178 1178 intact = intact2;
1179 1179 }
1180 1180 return intact;
1181 1181 }
1182 1182
1183 1183 function patchDisplay(from, to, intact) {
1184 1184 // The first pass removes the DOM nodes that aren't intact.
1185 1185 if (!intact.length) lineDiv.innerHTML = "";
1186 1186 else {
1187 1187 function killNode(node) {
1188 1188 var tmp = node.nextSibling;
1189 1189 node.parentNode.removeChild(node);
1190 1190 return tmp;
1191 1191 }
1192 1192 var domPos = 0, curNode = lineDiv.firstChild, n;
1193 1193 for (var i = 0; i < intact.length; ++i) {
1194 1194 var cur = intact[i];
1195 1195 while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
1196 1196 for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
1197 1197 }
1198 1198 while (curNode) curNode = killNode(curNode);
1199 1199 }
1200 1200 // This pass fills in the lines that actually changed.
1201 1201 var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
1202 1202 var scratch = document.createElement("div");
1203 1203 doc.iter(from, to, function(line) {
1204 1204 if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
1205 1205 if (!nextIntact || nextIntact.from > j) {
1206 1206 if (line.hidden) var html = scratch.innerHTML = "<pre></pre>";
1207 1207 else {
1208 1208 var html = '<pre' + (line.className ? ' class="' + line.className + '"' : '') + '>'
1209 1209 + line.getHTML(makeTab) + '</pre>';
1210 1210 // Kludge to make sure the styled element lies behind the selection (by z-index)
1211 1211 if (line.bgClassName)
1212 1212 html = '<div style="position: relative"><pre class="' + line.bgClassName +
1213 1213 '" style="position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2">&#160;</pre>' + html + "</div>";
1214 1214 }
1215 1215 scratch.innerHTML = html;
1216 1216 lineDiv.insertBefore(scratch.firstChild, curNode);
1217 1217 } else {
1218 1218 curNode = curNode.nextSibling;
1219 1219 }
1220 1220 ++j;
1221 1221 });
1222 1222 }
1223 1223
1224 1224 function updateGutter() {
1225 1225 if (!options.gutter && !options.lineNumbers) return;
1226 1226 var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
1227 1227 gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
1228 1228 var html = [], i = showingFrom, normalNode;
1229 1229 doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
1230 1230 if (line.hidden) {
1231 1231 html.push("<pre></pre>");
1232 1232 } else {
1233 1233 var marker = line.gutterMarker;
1234 1234 var text = options.lineNumbers ? options.lineNumberFormatter(i + options.firstLineNumber) : null;
1235 1235 if (marker && marker.text)
1236 1236 text = marker.text.replace("%N%", text != null ? text : "");
1237 1237 else if (text == null)
1238 1238 text = "\u00a0";
1239 1239 html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
1240 1240 for (var j = 1; j < line.height; ++j) html.push("<br/>&#160;");
1241 1241 html.push("</pre>");
1242 1242 if (!marker) normalNode = i;
1243 1243 }
1244 1244 ++i;
1245 1245 });
1246 1246 gutter.style.display = "none";
1247 1247 gutterText.innerHTML = html.join("");
1248 1248 // Make sure scrolling doesn't cause number gutter size to pop
1249 1249 if (normalNode != null && options.lineNumbers) {
1250 1250 var node = gutterText.childNodes[normalNode - showingFrom];
1251 1251 var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = "";
1252 1252 while (val.length + pad.length < minwidth) pad += "\u00a0";
1253 1253 if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild);
1254 1254 }
1255 1255 gutter.style.display = "";
1256 1256 var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2;
1257 1257 lineSpace.style.marginLeft = gutter.offsetWidth + "px";
1258 1258 gutterDirty = false;
1259 1259 return resized;
1260 1260 }
1261 1261 function updateSelection() {
1262 1262 var collapsed = posEq(sel.from, sel.to);
1263 1263 var fromPos = localCoords(sel.from, true);
1264 1264 var toPos = collapsed ? fromPos : localCoords(sel.to, true);
1265 1265 var headPos = sel.inverted ? fromPos : toPos, th = textHeight();
1266 1266 var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
1267 1267 inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px";
1268 1268 inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px";
1269 1269 if (collapsed) {
1270 1270 cursor.style.top = headPos.y + "px";
1271 1271 cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px";
1272 1272 cursor.style.display = "";
1273 1273 selectionDiv.style.display = "none";
1274 1274 } else {
1275 1275 var sameLine = fromPos.y == toPos.y, html = "";
1276 1276 var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth;
1277 1277 var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight;
1278 1278 function add(left, top, right, height) {
1279 1279 var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px"
1280 1280 : "right: " + right + "px";
1281 1281 html += '<div class="CodeMirror-selected" style="position: absolute; left: ' + left +
1282 1282 'px; top: ' + top + 'px; ' + rstyle + '; height: ' + height + 'px"></div>';
1283 1283 }
1284 1284 if (sel.from.ch && fromPos.y >= 0) {
1285 1285 var right = sameLine ? clientWidth - toPos.x : 0;
1286 1286 add(fromPos.x, fromPos.y, right, th);
1287 1287 }
1288 1288 var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0));
1289 1289 var middleHeight = Math.min(toPos.y, clientHeight) - middleStart;
1290 1290 if (middleHeight > 0.2 * th)
1291 1291 add(0, middleStart, 0, middleHeight);
1292 1292 if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th)
1293 1293 add(0, toPos.y, clientWidth - toPos.x, th);
1294 1294 selectionDiv.innerHTML = html;
1295 1295 cursor.style.display = "none";
1296 1296 selectionDiv.style.display = "";
1297 1297 }
1298 1298 }
1299 1299
1300 1300 function setShift(val) {
1301 1301 if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
1302 1302 else shiftSelecting = null;
1303 1303 }
1304 1304 function setSelectionUser(from, to) {
1305 1305 var sh = shiftSelecting && clipPos(shiftSelecting);
1306 1306 if (sh) {
1307 1307 if (posLess(sh, from)) from = sh;
1308 1308 else if (posLess(to, sh)) to = sh;
1309 1309 }
1310 1310 setSelection(from, to);
1311 1311 userSelChange = true;
1312 1312 }
1313 1313 // Update the selection. Last two args are only used by
1314 1314 // updateLines, since they have to be expressed in the line
1315 1315 // numbers before the update.
1316 1316 function setSelection(from, to, oldFrom, oldTo) {
1317 1317 goalColumn = null;
1318 1318 if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
1319 1319 if (posEq(sel.from, from) && posEq(sel.to, to)) return;
1320 1320 if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
1321 1321
1322 1322 // Skip over hidden lines.
1323 1323 if (from.line != oldFrom) {
1324 1324 var from1 = skipHidden(from, oldFrom, sel.from.ch);
1325 1325 // If there is no non-hidden line left, force visibility on current line
1326 1326 if (!from1) setLineHidden(from.line, false);
1327 1327 else from = from1;
1328 1328 }
1329 1329 if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
1330 1330
1331 1331 if (posEq(from, to)) sel.inverted = false;
1332 1332 else if (posEq(from, sel.to)) sel.inverted = false;
1333 1333 else if (posEq(to, sel.from)) sel.inverted = true;
1334 1334
1335 1335 if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) {
1336 1336 var head = sel.inverted ? from : to;
1337 1337 if (head.line != sel.from.line && sel.from.line < doc.size) {
1338 1338 var oldLine = getLine(sel.from.line);
1339 1339 if (/^\s+$/.test(oldLine.text))
1340 1340 setTimeout(operation(function() {
1341 1341 if (oldLine.parent && /^\s+$/.test(oldLine.text)) {
1342 1342 var no = lineNo(oldLine);
1343 1343 replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length});
1344 1344 }
1345 1345 }, 10));
1346 1346 }
1347 1347 }
1348 1348
1349 1349 sel.from = from; sel.to = to;
1350 1350 selectionChanged = true;
1351 1351 }
1352 1352 function skipHidden(pos, oldLine, oldCh) {
1353 1353 function getNonHidden(dir) {
1354 1354 var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
1355 1355 while (lNo != end) {
1356 1356 var line = getLine(lNo);
1357 1357 if (!line.hidden) {
1358 1358 var ch = pos.ch;
1359 1359 if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length;
1360 1360 return {line: lNo, ch: ch};
1361 1361 }
1362 1362 lNo += dir;
1363 1363 }
1364 1364 }
1365 1365 var line = getLine(pos.line);
1366 1366 var toEnd = pos.ch == line.text.length && pos.ch != oldCh;
1367 1367 if (!line.hidden) return pos;
1368 1368 if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
1369 1369 else return getNonHidden(-1) || getNonHidden(1);
1370 1370 }
1371 1371 function setCursor(line, ch, user) {
1372 1372 var pos = clipPos({line: line, ch: ch || 0});
1373 1373 (user ? setSelectionUser : setSelection)(pos, pos);
1374 1374 }
1375 1375
1376 1376 function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
1377 1377 function clipPos(pos) {
1378 1378 if (pos.line < 0) return {line: 0, ch: 0};
1379 1379 if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
1380 1380 var ch = pos.ch, linelen = getLine(pos.line).text.length;
1381 1381 if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
1382 1382 else if (ch < 0) return {line: pos.line, ch: 0};
1383 1383 else return pos;
1384 1384 }
1385 1385
1386 1386 function findPosH(dir, unit) {
1387 1387 var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
1388 1388 var lineObj = getLine(line);
1389 1389 function findNextLine() {
1390 1390 for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {
1391 1391 var lo = getLine(l);
1392 1392 if (!lo.hidden) { line = l; lineObj = lo; return true; }
1393 1393 }
1394 1394 }
1395 1395 function moveOnce(boundToLine) {
1396 1396 if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
1397 1397 if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
1398 1398 else return false;
1399 1399 } else ch += dir;
1400 1400 return true;
1401 1401 }
1402 1402 if (unit == "char") moveOnce();
1403 1403 else if (unit == "column") moveOnce(true);
1404 1404 else if (unit == "word") {
1405 1405 var sawWord = false;
1406 1406 for (;;) {
1407 1407 if (dir < 0) if (!moveOnce()) break;
1408 1408 if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
1409 1409 else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
1410 1410 if (dir > 0) if (!moveOnce()) break;
1411 1411 }
1412 1412 }
1413 1413 return {line: line, ch: ch};
1414 1414 }
1415 1415 function moveH(dir, unit) {
1416 1416 var pos = dir < 0 ? sel.from : sel.to;
1417 1417 if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
1418 1418 setCursor(pos.line, pos.ch, true);
1419 1419 }
1420 1420 function deleteH(dir, unit) {
1421 1421 if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to);
1422 1422 else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to);
1423 1423 else replaceRange("", sel.from, findPosH(dir, unit));
1424 1424 userSelChange = true;
1425 1425 }
1426 1426 var goalColumn = null;
1427 1427 function moveV(dir, unit) {
1428 1428 var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
1429 1429 if (goalColumn != null) pos.x = goalColumn;
1430 1430 if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight);
1431 1431 else if (unit == "line") dist = textHeight();
1432 1432 var target = coordsChar(pos.x, pos.y + dist * dir + 2);
1433 1433 if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y;
1434 1434 setCursor(target.line, target.ch, true);
1435 1435 goalColumn = pos.x;
1436 1436 }
1437 1437
1438 1438 function findWordAt(pos) {
1439 1439 var line = getLine(pos.line).text;
1440 1440 var start = pos.ch, end = pos.ch;
1441 1441 var check = isWordChar(line.charAt(start < line.length ? start : start - 1)) ?
1442 1442 isWordChar : function(ch) {return !isWordChar(ch);};
1443 1443 while (start > 0 && check(line.charAt(start - 1))) --start;
1444 1444 while (end < line.length && check(line.charAt(end))) ++end;
1445 1445 return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}};
1446 1446 }
1447 1447 function selectLine(line) {
1448 1448 setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0}));
1449 1449 }
1450 1450 function indentSelected(mode) {
1451 1451 if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
1452 1452 var e = sel.to.line - (sel.to.ch ? 0 : 1);
1453 1453 for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
1454 1454 }
1455 1455
1456 1456 function indentLine(n, how) {
1457 1457 if (!how) how = "add";
1458 1458 if (how == "smart") {
1459 1459 if (!mode.indent) how = "prev";
1460 1460 else var state = getStateBefore(n);
1461 1461 }
1462 1462
1463 1463 var line = getLine(n), curSpace = line.indentation(options.tabSize),
1464 1464 curSpaceString = line.text.match(/^\s*/)[0], indentation;
1465 1465 if (how == "smart") {
1466 1466 indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);
1467 1467 if (indentation == Pass) how = "prev";
1468 1468 }
1469 1469 if (how == "prev") {
1470 1470 if (n) indentation = getLine(n-1).indentation(options.tabSize);
1471 1471 else indentation = 0;
1472 1472 }
1473 1473 else if (how == "add") indentation = curSpace + options.indentUnit;
1474 1474 else if (how == "subtract") indentation = curSpace - options.indentUnit;
1475 1475 indentation = Math.max(0, indentation);
1476 1476 var diff = indentation - curSpace;
1477 1477
1478 1478 var indentString = "", pos = 0;
1479 1479 if (options.indentWithTabs)
1480 1480 for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";}
1481 1481 while (pos < indentation) {++pos; indentString += " ";}
1482 1482
1483 1483 replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});
1484 1484 }
1485 1485
1486 1486 function loadMode() {
1487 1487 mode = CodeMirror.getMode(options, options.mode);
1488 1488 doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
1489 1489 work = [0];
1490 1490 startWorker();
1491 1491 }
1492 1492 function gutterChanged() {
1493 1493 var visible = options.gutter || options.lineNumbers;
1494 1494 gutter.style.display = visible ? "" : "none";
1495 1495 if (visible) gutterDirty = true;
1496 1496 else lineDiv.parentNode.style.marginLeft = 0;
1497 1497 }
1498 1498 function wrappingChanged(from, to) {
1499 1499 if (options.lineWrapping) {
1500 1500 wrapper.className += " CodeMirror-wrap";
1501 1501 var perLine = scroller.clientWidth / charWidth() - 3;
1502 1502 doc.iter(0, doc.size, function(line) {
1503 1503 if (line.hidden) return;
1504 1504 var guess = Math.ceil(line.text.length / perLine) || 1;
1505 1505 if (guess != 1) updateLineHeight(line, guess);
1506 1506 });
1507 1507 lineSpace.style.width = code.style.width = "";
1508 1508 widthForcer.style.left = "";
1509 1509 } else {
1510 1510 wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
1511 1511 maxLine = ""; maxLineChanged = true;
1512 1512 doc.iter(0, doc.size, function(line) {
1513 1513 if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
1514 1514 if (line.text.length > maxLine.length) maxLine = line.text;
1515 1515 });
1516 1516 }
1517 1517 changes.push({from: 0, to: doc.size});
1518 1518 }
1519 1519 function makeTab(col) {
1520 1520 var w = options.tabSize - col % options.tabSize, cached = tabCache[w];
1521 1521 if (cached) return cached;
1522 1522 for (var str = '<span class="cm-tab">', i = 0; i < w; ++i) str += " ";
1523 1523 return (tabCache[w] = {html: str + "</span>", width: w});
1524 1524 }
1525 1525 function themeChanged() {
1526 1526 scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") +
1527 1527 options.theme.replace(/(^|\s)\s*/g, " cm-s-");
1528 1528 }
1529 1529 function keyMapChanged() {
1530 1530 var style = keyMap[options.keyMap].style;
1531 1531 wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
1532 1532 (style ? " cm-keymap-" + style : "");
1533 1533 }
1534 1534
1535 1535 function TextMarker() { this.set = []; }
1536 1536 TextMarker.prototype.clear = operation(function() {
1537 1537 var min = Infinity, max = -Infinity;
1538 1538 for (var i = 0, e = this.set.length; i < e; ++i) {
1539 1539 var line = this.set[i], mk = line.marked;
1540 1540 if (!mk || !line.parent) continue;
1541 1541 var lineN = lineNo(line);
1542 1542 min = Math.min(min, lineN); max = Math.max(max, lineN);
1543 1543 for (var j = 0; j < mk.length; ++j)
1544 1544 if (mk[j].marker == this) mk.splice(j--, 1);
1545 1545 }
1546 1546 if (min != Infinity)
1547 1547 changes.push({from: min, to: max + 1});
1548 1548 });
1549 1549 TextMarker.prototype.find = function() {
1550 1550 var from, to;
1551 1551 for (var i = 0, e = this.set.length; i < e; ++i) {
1552 1552 var line = this.set[i], mk = line.marked;
1553 1553 for (var j = 0; j < mk.length; ++j) {
1554 1554 var mark = mk[j];
1555 1555 if (mark.marker == this) {
1556 1556 if (mark.from != null || mark.to != null) {
1557 1557 var found = lineNo(line);
1558 1558 if (found != null) {
1559 1559 if (mark.from != null) from = {line: found, ch: mark.from};
1560 1560 if (mark.to != null) to = {line: found, ch: mark.to};
1561 1561 }
1562 1562 }
1563 1563 }
1564 1564 }
1565 1565 }
1566 1566 return {from: from, to: to};
1567 1567 };
1568 1568
1569 1569 function markText(from, to, className) {
1570 1570 from = clipPos(from); to = clipPos(to);
1571 1571 var tm = new TextMarker();
1572 1572 if (!posLess(from, to)) return tm;
1573 1573 function add(line, from, to, className) {
1574 1574 getLine(line).addMark(new MarkedText(from, to, className, tm));
1575 1575 }
1576 1576 if (from.line == to.line) add(from.line, from.ch, to.ch, className);
1577 1577 else {
1578 1578 add(from.line, from.ch, null, className);
1579 1579 for (var i = from.line + 1, e = to.line; i < e; ++i)
1580 1580 add(i, null, null, className);
1581 1581 add(to.line, null, to.ch, className);
1582 1582 }
1583 1583 changes.push({from: from.line, to: to.line + 1});
1584 1584 return tm;
1585 1585 }
1586 1586
1587 1587 function setBookmark(pos) {
1588 1588 pos = clipPos(pos);
1589 1589 var bm = new Bookmark(pos.ch);
1590 1590 getLine(pos.line).addMark(bm);
1591 1591 return bm;
1592 1592 }
1593 1593
1594 1594 function findMarksAt(pos) {
1595 1595 pos = clipPos(pos);
1596 1596 var markers = [], marked = getLine(pos.line).marked;
1597 1597 if (!marked) return markers;
1598 1598 for (var i = 0, e = marked.length; i < e; ++i) {
1599 1599 var m = marked[i];
1600 1600 if ((m.from == null || m.from <= pos.ch) &&
1601 1601 (m.to == null || m.to >= pos.ch))
1602 1602 markers.push(m.marker || m);
1603 1603 }
1604 1604 return markers;
1605 1605 }
1606 1606
1607 1607 function addGutterMarker(line, text, className) {
1608 1608 if (typeof line == "number") line = getLine(clipLine(line));
1609 1609 line.gutterMarker = {text: text, style: className};
1610 1610 gutterDirty = true;
1611 1611 return line;
1612 1612 }
1613 1613 function removeGutterMarker(line) {
1614 1614 if (typeof line == "number") line = getLine(clipLine(line));
1615 1615 line.gutterMarker = null;
1616 1616 gutterDirty = true;
1617 1617 }
1618 1618
1619 1619 function changeLine(handle, op) {
1620 1620 var no = handle, line = handle;
1621 1621 if (typeof handle == "number") line = getLine(clipLine(handle));
1622 1622 else no = lineNo(handle);
1623 1623 if (no == null) return null;
1624 1624 if (op(line, no)) changes.push({from: no, to: no + 1});
1625 1625 else return null;
1626 1626 return line;
1627 1627 }
1628 1628 function setLineClass(handle, className, bgClassName) {
1629 1629 return changeLine(handle, function(line) {
1630 1630 if (line.className != className || line.bgClassName != bgClassName) {
1631 1631 line.className = className;
1632 1632 line.bgClassName = bgClassName;
1633 1633 return true;
1634 1634 }
1635 1635 });
1636 1636 }
1637 1637 function setLineHidden(handle, hidden) {
1638 1638 return changeLine(handle, function(line, no) {
1639 1639 if (line.hidden != hidden) {
1640 1640 line.hidden = hidden;
1641 1641 if (!options.lineWrapping) {
1642 1642 var l = line.text;
1643 1643 if (hidden && l.length == maxLine.length) {
1644 1644 updateMaxLine = true;
1645 1645 } else if (!hidden && l.length > maxLine.length) {
1646 1646 maxLine = l; updateMaxLine = false;
1647 1647 }
1648 1648 }
1649 1649 updateLineHeight(line, hidden ? 0 : 1);
1650 1650 var fline = sel.from.line, tline = sel.to.line;
1651 1651 if (hidden && (fline == no || tline == no)) {
1652 1652 var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from;
1653 1653 var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to;
1654 1654 // Can't hide the last visible line, we'd have no place to put the cursor
1655 1655 if (!to) return;
1656 1656 setSelection(from, to);
1657 1657 }
1658 1658 return (gutterDirty = true);
1659 1659 }
1660 1660 });
1661 1661 }
1662 1662
1663 1663 function lineInfo(line) {
1664 1664 if (typeof line == "number") {
1665 1665 if (!isLine(line)) return null;
1666 1666 var n = line;
1667 1667 line = getLine(line);
1668 1668 if (!line) return null;
1669 1669 } else {
1670 1670 var n = lineNo(line);
1671 1671 if (n == null) return null;
1672 1672 }
1673 1673 var marker = line.gutterMarker;
1674 1674 return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
1675 1675 markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName};
1676 1676 }
1677 1677
1678 1678 function stringWidth(str) {
1679 1679 measure.innerHTML = "<pre><span>x</span></pre>";
1680 1680 measure.firstChild.firstChild.firstChild.nodeValue = str;
1681 1681 return measure.firstChild.firstChild.offsetWidth || 10;
1682 1682 }
1683 1683 // These are used to go from pixel positions to character
1684 1684 // positions, taking varying character widths into account.
1685 1685 function charFromX(line, x) {
1686 1686 if (x <= 0) return 0;
1687 1687 var lineObj = getLine(line), text = lineObj.text;
1688 1688 function getX(len) {
1689 1689 return measureLine(lineObj, len).left;
1690 1690 }
1691 1691 var from = 0, fromX = 0, to = text.length, toX;
1692 1692 // Guess a suitable upper bound for our search.
1693 1693 var estimated = Math.min(to, Math.ceil(x / charWidth()));
1694 1694 for (;;) {
1695 1695 var estX = getX(estimated);
1696 1696 if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1697 1697 else {toX = estX; to = estimated; break;}
1698 1698 }
1699 1699 if (x > toX) return to;
1700 1700 // Try to guess a suitable lower bound as well.
1701 1701 estimated = Math.floor(to * 0.8); estX = getX(estimated);
1702 1702 if (estX < x) {from = estimated; fromX = estX;}
1703 1703 // Do a binary search between these bounds.
1704 1704 for (;;) {
1705 1705 if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
1706 1706 var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1707 1707 if (middleX > x) {to = middle; toX = middleX;}
1708 1708 else {from = middle; fromX = middleX;}
1709 1709 }
1710 1710 }
1711 1711
1712 1712 var tempId = "CodeMirror-temp-" + Math.floor(Math.random() * 0xffffff).toString(16);
1713 1713 function measureLine(line, ch) {
1714 1714 if (ch == 0) return {top: 0, left: 0};
1715 1715 var wbr = options.lineWrapping && ch < line.text.length &&
1716 1716 spanAffectsWrapping.test(line.text.slice(ch - 1, ch + 1));
1717 1717 measure.innerHTML = "<pre>" + line.getHTML(makeTab, ch, tempId, wbr) + "</pre>";
1718 1718 var elt = document.getElementById(tempId);
1719 1719 var top = elt.offsetTop, left = elt.offsetLeft;
1720 1720 // Older IEs report zero offsets for spans directly after a wrap
1721 1721 if (ie && top == 0 && left == 0) {
1722 1722 var backup = document.createElement("span");
1723 1723 backup.innerHTML = "x";
1724 1724 elt.parentNode.insertBefore(backup, elt.nextSibling);
1725 1725 top = backup.offsetTop;
1726 1726 }
1727 1727 return {top: top, left: left};
1728 1728 }
1729 1729 function localCoords(pos, inLineWrap) {
1730 1730 var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
1731 1731 if (pos.ch == 0) x = 0;
1732 1732 else {
1733 1733 var sp = measureLine(getLine(pos.line), pos.ch);
1734 1734 x = sp.left;
1735 1735 if (options.lineWrapping) y += Math.max(0, sp.top);
1736 1736 }
1737 1737 return {x: x, y: y, yBot: y + lh};
1738 1738 }
1739 1739 // Coords must be lineSpace-local
1740 1740 function coordsChar(x, y) {
1741 1741 if (y < 0) y = 0;
1742 1742 var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
1743 1743 var lineNo = lineAtHeight(doc, heightPos);
1744 1744 if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};
1745 1745 var lineObj = getLine(lineNo), text = lineObj.text;
1746 1746 var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
1747 1747 if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
1748 1748 function getX(len) {
1749 1749 var sp = measureLine(lineObj, len);
1750 1750 if (tw) {
1751 1751 var off = Math.round(sp.top / th);
1752 1752 return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
1753 1753 }
1754 1754 return sp.left;
1755 1755 }
1756 1756 var from = 0, fromX = 0, to = text.length, toX;
1757 1757 // Guess a suitable upper bound for our search.
1758 1758 var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));
1759 1759 for (;;) {
1760 1760 var estX = getX(estimated);
1761 1761 if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1762 1762 else {toX = estX; to = estimated; break;}
1763 1763 }
1764 1764 if (x > toX) return {line: lineNo, ch: to};
1765 1765 // Try to guess a suitable lower bound as well.
1766 1766 estimated = Math.floor(to * 0.8); estX = getX(estimated);
1767 1767 if (estX < x) {from = estimated; fromX = estX;}
1768 1768 // Do a binary search between these bounds.
1769 1769 for (;;) {
1770 1770 if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to};
1771 1771 var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1772 1772 if (middleX > x) {to = middle; toX = middleX;}
1773 1773 else {from = middle; fromX = middleX;}
1774 1774 }
1775 1775 }
1776 1776 function pageCoords(pos) {
1777 1777 var local = localCoords(pos, true), off = eltOffset(lineSpace);
1778 1778 return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
1779 1779 }
1780 1780
1781 1781 var cachedHeight, cachedHeightFor, measureText;
1782 1782 function textHeight() {
1783 1783 if (measureText == null) {
1784 1784 measureText = "<pre>";
1785 1785 for (var i = 0; i < 49; ++i) measureText += "x<br/>";
1786 1786 measureText += "x</pre>";
1787 1787 }
1788 1788 var offsetHeight = lineDiv.clientHeight;
1789 1789 if (offsetHeight == cachedHeightFor) return cachedHeight;
1790 1790 cachedHeightFor = offsetHeight;
1791 1791 measure.innerHTML = measureText;
1792 1792 cachedHeight = measure.firstChild.offsetHeight / 50 || 1;
1793 1793 measure.innerHTML = "";
1794 1794 return cachedHeight;
1795 1795 }
1796 1796 var cachedWidth, cachedWidthFor = 0;
1797 1797 function charWidth() {
1798 1798 if (scroller.clientWidth == cachedWidthFor) return cachedWidth;
1799 1799 cachedWidthFor = scroller.clientWidth;
1800 1800 return (cachedWidth = stringWidth("x"));
1801 1801 }
1802 1802 function paddingTop() {return lineSpace.offsetTop;}
1803 1803 function paddingLeft() {return lineSpace.offsetLeft;}
1804 1804
1805 1805 function posFromMouse(e, liberal) {
1806 1806 var offW = eltOffset(scroller, true), x, y;
1807 1807 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1808 1808 try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1809 1809 // This is a mess of a heuristic to try and determine whether a
1810 1810 // scroll-bar was clicked or not, and to return null if one was
1811 1811 // (and !liberal).
1812 1812 if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
1813 1813 return null;
1814 1814 var offL = eltOffset(lineSpace, true);
1815 1815 return coordsChar(x - offL.left, y - offL.top);
1816 1816 }
1817 1817 function onContextMenu(e) {
1818 1818 var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop;
1819 1819 if (!pos || opera) return; // Opera is difficult.
1820 1820 if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1821 1821 operation(setCursor)(pos.line, pos.ch);
1822 1822
1823 1823 var oldCSS = input.style.cssText;
1824 1824 inputDiv.style.position = "absolute";
1825 1825 input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1826 1826 "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
1827 1827 "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1828 1828 leaveInputAlone = true;
1829 1829 var val = input.value = getSelection();
1830 1830 focusInput();
1831 1831 selectInput(input);
1832 1832 function rehide() {
1833 1833 var newVal = splitLines(input.value).join("\n");
1834 1834 if (newVal != val && !options.readOnly) operation(replaceSelection)(newVal, "end");
1835 1835 inputDiv.style.position = "relative";
1836 1836 input.style.cssText = oldCSS;
1837 1837 if (ie_lt9) scrollbar.scrollTop = scrollPos;
1838 1838 leaveInputAlone = false;
1839 1839 resetInput(true);
1840 1840 slowPoll();
1841 1841 }
1842 1842
1843 1843 if (gecko) {
1844 1844 e_stop(e);
1845 1845 var mouseup = connect(window, "mouseup", function() {
1846 1846 mouseup();
1847 1847 setTimeout(rehide, 20);
1848 1848 }, true);
1849 1849 } else {
1850 1850 setTimeout(rehide, 50);
1851 1851 }
1852 1852 }
1853 1853
1854 1854 // Cursor-blinking
1855 1855 function restartBlink() {
1856 1856 clearInterval(blinker);
1857 1857 var on = true;
1858 1858 cursor.style.visibility = "";
1859 1859 blinker = setInterval(function() {
1860 1860 cursor.style.visibility = (on = !on) ? "" : "hidden";
1861 1861 }, 650);
1862 1862 }
1863 1863
1864 1864 var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
1865 1865 function matchBrackets(autoclear) {
1866 1866 var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
1867 1867 var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
1868 1868 if (!match) return;
1869 1869 var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
1870 1870 for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)
1871 1871 if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}
1872 1872
1873 1873 var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
1874 1874 function scan(line, from, to) {
1875 1875 if (!line.text) return;
1876 1876 var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;
1877 1877 for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) {
1878 1878 var text = st[i];
1879 1879 if (st[i+1] != style) {pos += d * text.length; continue;}
1880 1880 for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) {
1881 1881 if (pos >= from && pos < to && re.test(cur = text.charAt(j))) {
1882 1882 var match = matching[cur];
1883 1883 if (match.charAt(1) == ">" == forward) stack.push(cur);
1884 1884 else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
1885 1885 else if (!stack.length) return {pos: pos, match: true};
1886 1886 }
1887 1887 }
1888 1888 }
1889 1889 }
1890 1890 for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
1891 1891 var line = getLine(i), first = i == head.line;
1892 1892 var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
1893 1893 if (found) break;
1894 1894 }
1895 1895 if (!found) found = {pos: null, match: false};
1896 1896 var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
1897 1897 var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
1898 1898 two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
1899 1899 var clear = operation(function(){one.clear(); two && two.clear();});
1900 1900 if (autoclear) setTimeout(clear, 800);
1901 1901 else bracketHighlighted = clear;
1902 1902 }
1903 1903
1904 1904 // Finds the line to start with when starting a parse. Tries to
1905 1905 // find a line with a stateAfter, so that it can start with a
1906 1906 // valid state. If that fails, it returns the line with the
1907 1907 // smallest indentation, which tends to need the least context to
1908 1908 // parse correctly.
1909 1909 function findStartLine(n) {
1910 1910 var minindent, minline;
1911 1911 for (var search = n, lim = n - 40; search > lim; --search) {
1912 1912 if (search == 0) return 0;
1913 1913 var line = getLine(search-1);
1914 1914 if (line.stateAfter) return search;
1915 1915 var indented = line.indentation(options.tabSize);
1916 1916 if (minline == null || minindent > indented) {
1917 1917 minline = search - 1;
1918 1918 minindent = indented;
1919 1919 }
1920 1920 }
1921 1921 return minline;
1922 1922 }
1923 1923 function getStateBefore(n) {
1924 1924 var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
1925 1925 if (!state) state = startState(mode);
1926 1926 else state = copyState(mode, state);
1927 1927 doc.iter(start, n, function(line) {
1928 1928 line.highlight(mode, state, options.tabSize);
1929 1929 line.stateAfter = copyState(mode, state);
1930 1930 });
1931 1931 if (start < n) changes.push({from: start, to: n});
1932 1932 if (n < doc.size && !getLine(n).stateAfter) work.push(n);
1933 1933 return state;
1934 1934 }
1935 1935 function highlightLines(start, end) {
1936 1936 var state = getStateBefore(start);
1937 1937 doc.iter(start, end, function(line) {
1938 1938 line.highlight(mode, state, options.tabSize);
1939 1939 line.stateAfter = copyState(mode, state);
1940 1940 });
1941 1941 }
1942 1942 function highlightWorker() {
1943 1943 var end = +new Date + options.workTime;
1944 1944 var foundWork = work.length;
1945 1945 while (work.length) {
1946 1946 if (!getLine(showingFrom).stateAfter) var task = showingFrom;
1947 1947 else var task = work.pop();
1948 1948 if (task >= doc.size) continue;
1949 1949 var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
1950 1950 if (state) state = copyState(mode, state);
1951 1951 else state = startState(mode);
1952 1952
1953 1953 var unchanged = 0, compare = mode.compareStates, realChange = false,
1954 1954 i = start, bail = false;
1955 1955 doc.iter(i, doc.size, function(line) {
1956 1956 var hadState = line.stateAfter;
1957 1957 if (+new Date > end) {
1958 1958 work.push(i);
1959 1959 startWorker(options.workDelay);
1960 1960 if (realChange) changes.push({from: task, to: i + 1});
1961 1961 return (bail = true);
1962 1962 }
1963 1963 var changed = line.highlight(mode, state, options.tabSize);
1964 1964 if (changed) realChange = true;
1965 1965 line.stateAfter = copyState(mode, state);
1966 1966 var done = null;
1967 1967 if (compare) {
1968 1968 var same = hadState && compare(hadState, state);
1969 1969 if (same != Pass) done = !!same;
1970 1970 }
1971 1971 if (done == null) {
1972 1972 if (changed !== false || !hadState) unchanged = 0;
1973 1973 else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, "")))
1974 1974 done = true;
1975 1975 }
1976 1976 if (done) return true;
1977 1977 ++i;
1978 1978 });
1979 1979 if (bail) return;
1980 1980 if (realChange) changes.push({from: task, to: i + 1});
1981 1981 }
1982 1982 if (foundWork && options.onHighlightComplete)
1983 1983 options.onHighlightComplete(instance);
1984 1984 }
1985 1985 function startWorker(time) {
1986 1986 if (!work.length) return;
1987 1987 highlight.set(time, operation(highlightWorker));
1988 1988 }
1989 1989
1990 1990 // Operations are used to wrap changes in such a way that each
1991 1991 // change won't have to update the cursor and display (which would
1992 1992 // be awkward, slow, and error-prone), but instead updates are
1993 1993 // batched and then all combined and executed at once.
1994 1994 function startOperation() {
1995 1995 updateInput = userSelChange = textChanged = null;
1996 1996 changes = []; selectionChanged = false; callbacks = [];
1997 1997 }
1998 1998 function endOperation() {
1999 1999 if (updateMaxLine) computeMaxLength();
2000 2000 if (maxLineChanged && !options.lineWrapping) {
2001 2001 var cursorWidth = widthForcer.offsetWidth, left = stringWidth(maxLine);
2002 2002 widthForcer.style.left = left + "px";
2003 2003 lineSpace.style.minWidth = (left + cursorWidth) + "px";
2004 2004 maxLineChanged = false;
2005 2005 }
2006 2006 var newScrollPos, updated;
2007 2007 if (selectionChanged) {
2008 2008 var coords = calculateCursorCoords();
2009 2009 newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot);
2010 2010 }
2011 2011 if (changes.length) updated = updateDisplay(changes, true, (newScrollPos ? newScrollPos.scrollTop : null));
2012 2012 else {
2013 2013 if (selectionChanged) updateSelection();
2014 2014 if (gutterDirty) updateGutter();
2015 2015 }
2016 2016 if (newScrollPos) scrollCursorIntoView();
2017 2017 if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
2018 2018
2019 2019 if (focused && !leaveInputAlone &&
2020 2020 (updateInput === true || (updateInput !== false && selectionChanged)))
2021 2021 resetInput(userSelChange);
2022 2022
2023 2023 if (selectionChanged && options.matchBrackets)
2024 2024 setTimeout(operation(function() {
2025 2025 if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
2026 2026 if (posEq(sel.from, sel.to)) matchBrackets(false);
2027 2027 }), 20);
2028 2028 var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks
2029 2029 if (textChanged && options.onChange && instance)
2030 2030 options.onChange(instance, textChanged);
2031 2031 if (sc && options.onCursorActivity)
2032 2032 options.onCursorActivity(instance);
2033 2033 for (var i = 0; i < cbs.length; ++i) cbs[i](instance);
2034 2034 if (updated && options.onUpdate) options.onUpdate(instance);
2035 2035 }
2036 2036 var nestedOperation = 0;
2037 2037 function operation(f) {
2038 2038 return function() {
2039 2039 if (!nestedOperation++) startOperation();
2040 2040 try {var result = f.apply(this, arguments);}
2041 2041 finally {if (!--nestedOperation) endOperation();}
2042 2042 return result;
2043 2043 };
2044 2044 }
2045 2045
2046 2046 function compoundChange(f) {
2047 2047 history.startCompound();
2048 2048 try { return f(); } finally { history.endCompound(); }
2049 2049 }
2050 2050
2051 2051 for (var ext in extensions)
2052 2052 if (extensions.propertyIsEnumerable(ext) &&
2053 2053 !instance.propertyIsEnumerable(ext))
2054 2054 instance[ext] = extensions[ext];
2055 2055 return instance;
2056 2056 } // (end of function CodeMirror)
2057 2057
2058 2058 // The default configuration options.
2059 2059 CodeMirror.defaults = {
2060 2060 value: "",
2061 2061 mode: null,
2062 2062 theme: "default",
2063 2063 indentUnit: 2,
2064 2064 indentWithTabs: false,
2065 2065 smartIndent: true,
2066 2066 tabSize: 4,
2067 2067 keyMap: "default",
2068 2068 extraKeys: null,
2069 2069 electricChars: true,
2070 2070 autoClearEmptyLines: false,
2071 2071 onKeyEvent: null,
2072 2072 onDragEvent: null,
2073 2073 lineWrapping: false,
2074 2074 lineNumbers: false,
2075 2075 gutter: false,
2076 2076 fixedGutter: false,
2077 2077 firstLineNumber: 1,
2078 2078 readOnly: false,
2079 2079 dragDrop: true,
2080 2080 onChange: null,
2081 2081 onCursorActivity: null,
2082 2082 onGutterClick: null,
2083 2083 onHighlightComplete: null,
2084 2084 onUpdate: null,
2085 2085 onFocus: null, onBlur: null, onScroll: null,
2086 2086 matchBrackets: false,
2087 2087 workTime: 100,
2088 2088 workDelay: 200,
2089 2089 pollInterval: 100,
2090 2090 undoDepth: 40,
2091 2091 tabindex: null,
2092 2092 autofocus: null,
2093 2093 lineNumberFormatter: function(integer) { return integer; }
2094 2094 };
2095 2095
2096 2096 var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
2097 2097 var mac = ios || /Mac/.test(navigator.platform);
2098 2098 var win = /Win/.test(navigator.platform);
2099 2099
2100 2100 // Known modes, by name and by MIME
2101 2101 var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
2102 2102 CodeMirror.defineMode = function(name, mode) {
2103 2103 if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
2104 2104 if (arguments.length > 2) {
2105 2105 mode.dependencies = [];
2106 2106 for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
2107 2107 }
2108 2108 modes[name] = mode;
2109 2109 };
2110 2110 CodeMirror.defineMIME = function(mime, spec) {
2111 2111 mimeModes[mime] = spec;
2112 2112 };
2113 2113 CodeMirror.resolveMode = function(spec) {
2114 2114 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
2115 2115 spec = mimeModes[spec];
2116 2116 else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
2117 2117 return CodeMirror.resolveMode("application/xml");
2118 2118 if (typeof spec == "string") return {name: spec};
2119 2119 else return spec || {name: "null"};
2120 2120 };
2121 2121 CodeMirror.getMode = function(options, spec) {
2122 2122 var spec = CodeMirror.resolveMode(spec);
2123 2123 var mfactory = modes[spec.name];
2124 2124 if (!mfactory) return CodeMirror.getMode(options, "text/plain");
2125 2125 return mfactory(options, spec);
2126 2126 };
2127 2127 CodeMirror.listModes = function() {
2128 2128 var list = [];
2129 2129 for (var m in modes)
2130 2130 if (modes.propertyIsEnumerable(m)) list.push(m);
2131 2131 return list;
2132 2132 };
2133 2133 CodeMirror.listMIMEs = function() {
2134 2134 var list = [];
2135 2135 for (var m in mimeModes)
2136 2136 if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
2137 2137 return list;
2138 2138 };
2139 2139
2140 2140 var extensions = CodeMirror.extensions = {};
2141 2141 CodeMirror.defineExtension = function(name, func) {
2142 2142 extensions[name] = func;
2143 2143 };
2144 2144
2145 2145 var commands = CodeMirror.commands = {
2146 2146 selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
2147 2147 killLine: function(cm) {
2148 2148 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
2149 2149 if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0});
2150 2150 else cm.replaceRange("", from, sel ? to : {line: from.line});
2151 2151 },
2152 2152 deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});},
2153 2153 undo: function(cm) {cm.undo();},
2154 2154 redo: function(cm) {cm.redo();},
2155 2155 goDocStart: function(cm) {cm.setCursor(0, 0, true);},
2156 2156 goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
2157 2157 goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
2158 2158 goLineStartSmart: function(cm) {
2159 2159 var cur = cm.getCursor();
2160 2160 var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
2161 2161 cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
2162 2162 },
2163 2163 goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
2164 2164 goLineUp: function(cm) {cm.moveV(-1, "line");},
2165 2165 goLineDown: function(cm) {cm.moveV(1, "line");},
2166 2166 goPageUp: function(cm) {cm.moveV(-1, "page");},
2167 2167 goPageDown: function(cm) {cm.moveV(1, "page");},
2168 2168 goCharLeft: function(cm) {cm.moveH(-1, "char");},
2169 2169 goCharRight: function(cm) {cm.moveH(1, "char");},
2170 2170 goColumnLeft: function(cm) {cm.moveH(-1, "column");},
2171 2171 goColumnRight: function(cm) {cm.moveH(1, "column");},
2172 2172 goWordLeft: function(cm) {cm.moveH(-1, "word");},
2173 2173 goWordRight: function(cm) {cm.moveH(1, "word");},
2174 2174 delCharLeft: function(cm) {cm.deleteH(-1, "char");},
2175 2175 delCharRight: function(cm) {cm.deleteH(1, "char");},
2176 2176 delWordLeft: function(cm) {cm.deleteH(-1, "word");},
2177 2177 delWordRight: function(cm) {cm.deleteH(1, "word");},
2178 2178 indentAuto: function(cm) {cm.indentSelection("smart");},
2179 2179 indentMore: function(cm) {cm.indentSelection("add");},
2180 2180 indentLess: function(cm) {cm.indentSelection("subtract");},
2181 2181 insertTab: function(cm) {cm.replaceSelection("\t", "end");},
2182 2182 defaultTab: function(cm) {
2183 2183 if (cm.somethingSelected()) cm.indentSelection("add");
2184 2184 else cm.replaceSelection("\t", "end");
2185 2185 },
2186 2186 transposeChars: function(cm) {
2187 2187 var cur = cm.getCursor(), line = cm.getLine(cur.line);
2188 2188 if (cur.ch > 0 && cur.ch < line.length - 1)
2189 2189 cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
2190 2190 {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
2191 2191 },
2192 2192 newlineAndIndent: function(cm) {
2193 2193 cm.replaceSelection("\n", "end");
2194 2194 cm.indentLine(cm.getCursor().line);
2195 2195 },
2196 2196 toggleOverwrite: function(cm) {cm.toggleOverwrite();}
2197 ,delSpaceToPrevTabStop : function(cm){
2198 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
2199 if (!posEq(from, to)) {cm.replaceRange("", from, to); return}
2200 var cur = cm.getCursor(), line = cm.getLine(cur.line);
2201 var tabsize = cm.getOption('tabSize');
2202 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
2203 var from = {ch:cur.ch-chToPrevTabStop,line:cur.line}
2204 var select = cm.getRange(from,cur)
2205 if( select.match(/^\ +$/) != null){
2206 cm.replaceRange("",from,cur)
2207 } else {
2208 cm.deleteH(-1,"char")
2209 }
2210 }
2197 2211 };
2198 2212
2199 2213 var keyMap = CodeMirror.keyMap = {};
2200 2214 keyMap.basic = {
2201 2215 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
2202 2216 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
2203 2217 "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
2204 2218 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
2205 2219 };
2206 2220 // Note that the save and find-related commands aren't defined by
2207 2221 // default. Unknown commands are simply ignored.
2208 2222 keyMap.pcDefault = {
2209 2223 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
2210 2224 "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
2211 2225 "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
2212 2226 "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find",
2213 2227 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
2214 2228 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
2215 2229 fallthrough: "basic"
2216 2230 };
2217 2231 keyMap.macDefault = {
2218 2232 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
2219 2233 "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
2220 2234 "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft",
2221 2235 "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find",
2222 2236 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
2223 2237 "Cmd-[": "indentLess", "Cmd-]": "indentMore",
2224 2238 fallthrough: ["basic", "emacsy"]
2225 2239 };
2226 2240 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
2227 2241 keyMap.emacsy = {
2228 2242 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
2229 2243 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
2230 2244 "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
2231 2245 "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
2232 2246 };
2233 2247
2234 2248 function getKeyMap(val) {
2235 2249 if (typeof val == "string") return keyMap[val];
2236 2250 else return val;
2237 2251 }
2238 2252 function lookupKey(name, extraMap, map, handle, stop) {
2239 2253 function lookup(map) {
2240 2254 map = getKeyMap(map);
2241 2255 var found = map[name];
2242 2256 if (found != null && handle(found)) return true;
2243 2257 if (map.nofallthrough) {
2244 2258 if (stop) stop();
2245 2259 return true;
2246 2260 }
2247 2261 var fallthrough = map.fallthrough;
2248 2262 if (fallthrough == null) return false;
2249 2263 if (Object.prototype.toString.call(fallthrough) != "[object Array]")
2250 2264 return lookup(fallthrough);
2251 2265 for (var i = 0, e = fallthrough.length; i < e; ++i) {
2252 2266 if (lookup(fallthrough[i])) return true;
2253 2267 }
2254 2268 return false;
2255 2269 }
2256 2270 if (extraMap && lookup(extraMap)) return true;
2257 2271 return lookup(map);
2258 2272 }
2259 2273 function isModifierKey(event) {
2260 2274 var name = keyNames[e_prop(event, "keyCode")];
2261 2275 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
2262 2276 }
2263 2277
2264 2278 CodeMirror.fromTextArea = function(textarea, options) {
2265 2279 if (!options) options = {};
2266 2280 options.value = textarea.value;
2267 2281 if (!options.tabindex && textarea.tabindex)
2268 2282 options.tabindex = textarea.tabindex;
2269 2283 if (options.autofocus == null && textarea.getAttribute("autofocus") != null)
2270 2284 options.autofocus = true;
2271 2285
2272 2286 function save() {textarea.value = instance.getValue();}
2273 2287 if (textarea.form) {
2274 2288 // Deplorable hack to make the submit method do the right thing.
2275 2289 var rmSubmit = connect(textarea.form, "submit", save, true);
2276 2290 if (typeof textarea.form.submit == "function") {
2277 2291 var realSubmit = textarea.form.submit;
2278 2292 function wrappedSubmit() {
2279 2293 save();
2280 2294 textarea.form.submit = realSubmit;
2281 2295 textarea.form.submit();
2282 2296 textarea.form.submit = wrappedSubmit;
2283 2297 }
2284 2298 textarea.form.submit = wrappedSubmit;
2285 2299 }
2286 2300 }
2287 2301
2288 2302 textarea.style.display = "none";
2289 2303 var instance = CodeMirror(function(node) {
2290 2304 textarea.parentNode.insertBefore(node, textarea.nextSibling);
2291 2305 }, options);
2292 2306 instance.save = save;
2293 2307 instance.getTextArea = function() { return textarea; };
2294 2308 instance.toTextArea = function() {
2295 2309 save();
2296 2310 textarea.parentNode.removeChild(instance.getWrapperElement());
2297 2311 textarea.style.display = "";
2298 2312 if (textarea.form) {
2299 2313 rmSubmit();
2300 2314 if (typeof textarea.form.submit == "function")
2301 2315 textarea.form.submit = realSubmit;
2302 2316 }
2303 2317 };
2304 2318 return instance;
2305 2319 };
2306 2320
2307 2321 // Utility functions for working with state. Exported because modes
2308 2322 // sometimes need to do this.
2309 2323 function copyState(mode, state) {
2310 2324 if (state === true) return state;
2311 2325 if (mode.copyState) return mode.copyState(state);
2312 2326 var nstate = {};
2313 2327 for (var n in state) {
2314 2328 var val = state[n];
2315 2329 if (val instanceof Array) val = val.concat([]);
2316 2330 nstate[n] = val;
2317 2331 }
2318 2332 return nstate;
2319 2333 }
2320 2334 CodeMirror.copyState = copyState;
2321 2335 function startState(mode, a1, a2) {
2322 2336 return mode.startState ? mode.startState(a1, a2) : true;
2323 2337 }
2324 2338 CodeMirror.startState = startState;
2325 2339
2326 2340 // The character stream used by a mode's parser.
2327 2341 function StringStream(string, tabSize) {
2328 2342 this.pos = this.start = 0;
2329 2343 this.string = string;
2330 2344 this.tabSize = tabSize || 8;
2331 2345 }
2332 2346 StringStream.prototype = {
2333 2347 eol: function() {return this.pos >= this.string.length;},
2334 2348 sol: function() {return this.pos == 0;},
2335 2349 peek: function() {return this.string.charAt(this.pos);},
2336 2350 next: function() {
2337 2351 if (this.pos < this.string.length)
2338 2352 return this.string.charAt(this.pos++);
2339 2353 },
2340 2354 eat: function(match) {
2341 2355 var ch = this.string.charAt(this.pos);
2342 2356 if (typeof match == "string") var ok = ch == match;
2343 2357 else var ok = ch && (match.test ? match.test(ch) : match(ch));
2344 2358 if (ok) {++this.pos; return ch;}
2345 2359 },
2346 2360 eatWhile: function(match) {
2347 2361 var start = this.pos;
2348 2362 while (this.eat(match)){}
2349 2363 return this.pos > start;
2350 2364 },
2351 2365 eatSpace: function() {
2352 2366 var start = this.pos;
2353 2367 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
2354 2368 return this.pos > start;
2355 2369 },
2356 2370 skipToEnd: function() {this.pos = this.string.length;},
2357 2371 skipTo: function(ch) {
2358 2372 var found = this.string.indexOf(ch, this.pos);
2359 2373 if (found > -1) {this.pos = found; return true;}
2360 2374 },
2361 2375 backUp: function(n) {this.pos -= n;},
2362 2376 column: function() {return countColumn(this.string, this.start, this.tabSize);},
2363 2377 indentation: function() {return countColumn(this.string, null, this.tabSize);},
2364 2378 match: function(pattern, consume, caseInsensitive) {
2365 2379 if (typeof pattern == "string") {
2366 2380 function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
2367 2381 if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
2368 2382 if (consume !== false) this.pos += pattern.length;
2369 2383 return true;
2370 2384 }
2371 2385 } else {
2372 2386 var match = this.string.slice(this.pos).match(pattern);
2373 2387 if (match && consume !== false) this.pos += match[0].length;
2374 2388 return match;
2375 2389 }
2376 2390 },
2377 2391 current: function(){return this.string.slice(this.start, this.pos);}
2378 2392 };
2379 2393 CodeMirror.StringStream = StringStream;
2380 2394
2381 2395 function MarkedText(from, to, className, marker) {
2382 2396 this.from = from; this.to = to; this.style = className; this.marker = marker;
2383 2397 }
2384 2398 MarkedText.prototype = {
2385 2399 attach: function(line) { this.marker.set.push(line); },
2386 2400 detach: function(line) {
2387 2401 var ix = indexOf(this.marker.set, line);
2388 2402 if (ix > -1) this.marker.set.splice(ix, 1);
2389 2403 },
2390 2404 split: function(pos, lenBefore) {
2391 2405 if (this.to <= pos && this.to != null) return null;
2392 2406 var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
2393 2407 var to = this.to == null ? null : this.to - pos + lenBefore;
2394 2408 return new MarkedText(from, to, this.style, this.marker);
2395 2409 },
2396 2410 dup: function() { return new MarkedText(null, null, this.style, this.marker); },
2397 2411 clipTo: function(fromOpen, from, toOpen, to, diff) {
2398 2412 if (fromOpen && to > this.from && (to < this.to || this.to == null))
2399 2413 this.from = null;
2400 2414 else if (this.from != null && this.from >= from)
2401 2415 this.from = Math.max(to, this.from) + diff;
2402 2416 if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
2403 2417 this.to = null;
2404 2418 else if (this.to != null && this.to > from)
2405 2419 this.to = to < this.to ? this.to + diff : from;
2406 2420 },
2407 2421 isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
2408 2422 sameSet: function(x) { return this.marker == x.marker; }
2409 2423 };
2410 2424
2411 2425 function Bookmark(pos) {
2412 2426 this.from = pos; this.to = pos; this.line = null;
2413 2427 }
2414 2428 Bookmark.prototype = {
2415 2429 attach: function(line) { this.line = line; },
2416 2430 detach: function(line) { if (this.line == line) this.line = null; },
2417 2431 split: function(pos, lenBefore) {
2418 2432 if (pos < this.from) {
2419 2433 this.from = this.to = (this.from - pos) + lenBefore;
2420 2434 return this;
2421 2435 }
2422 2436 },
2423 2437 isDead: function() { return this.from > this.to; },
2424 2438 clipTo: function(fromOpen, from, toOpen, to, diff) {
2425 2439 if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
2426 2440 this.from = 0; this.to = -1;
2427 2441 } else if (this.from > from) {
2428 2442 this.from = this.to = Math.max(to, this.from) + diff;
2429 2443 }
2430 2444 },
2431 2445 sameSet: function(x) { return false; },
2432 2446 find: function() {
2433 2447 if (!this.line || !this.line.parent) return null;
2434 2448 return {line: lineNo(this.line), ch: this.from};
2435 2449 },
2436 2450 clear: function() {
2437 2451 if (this.line) {
2438 2452 var found = indexOf(this.line.marked, this);
2439 2453 if (found != -1) this.line.marked.splice(found, 1);
2440 2454 this.line = null;
2441 2455 }
2442 2456 }
2443 2457 };
2444 2458
2445 2459 // Line objects. These hold state related to a line, including
2446 2460 // highlighting info (the styles array).
2447 2461 function Line(text, styles) {
2448 2462 this.styles = styles || [text, null];
2449 2463 this.text = text;
2450 2464 this.height = 1;
2451 2465 this.marked = this.gutterMarker = this.className = this.bgClassName = this.handlers = null;
2452 2466 this.stateAfter = this.parent = this.hidden = null;
2453 2467 }
2454 2468 Line.inheritMarks = function(text, orig) {
2455 2469 var ln = new Line(text), mk = orig && orig.marked;
2456 2470 if (mk) {
2457 2471 for (var i = 0; i < mk.length; ++i) {
2458 2472 if (mk[i].to == null && mk[i].style) {
2459 2473 var newmk = ln.marked || (ln.marked = []), mark = mk[i];
2460 2474 var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
2461 2475 }
2462 2476 }
2463 2477 }
2464 2478 return ln;
2465 2479 }
2466 2480 Line.prototype = {
2467 2481 // Replace a piece of a line, keeping the styles around it intact.
2468 2482 replace: function(from, to_, text) {
2469 2483 var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
2470 2484 copyStyles(0, from, this.styles, st);
2471 2485 if (text) st.push(text, null);
2472 2486 copyStyles(to, this.text.length, this.styles, st);
2473 2487 this.styles = st;
2474 2488 this.text = this.text.slice(0, from) + text + this.text.slice(to);
2475 2489 this.stateAfter = null;
2476 2490 if (mk) {
2477 2491 var diff = text.length - (to - from);
2478 2492 for (var i = 0; i < mk.length; ++i) {
2479 2493 var mark = mk[i];
2480 2494 mark.clipTo(from == null, from || 0, to_ == null, to, diff);
2481 2495 if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
2482 2496 }
2483 2497 }
2484 2498 },
2485 2499 // Split a part off a line, keeping styles and markers intact.
2486 2500 split: function(pos, textBefore) {
2487 2501 var st = [textBefore, null], mk = this.marked;
2488 2502 copyStyles(pos, this.text.length, this.styles, st);
2489 2503 var taken = new Line(textBefore + this.text.slice(pos), st);
2490 2504 if (mk) {
2491 2505 for (var i = 0; i < mk.length; ++i) {
2492 2506 var mark = mk[i];
2493 2507 var newmark = mark.split(pos, textBefore.length);
2494 2508 if (newmark) {
2495 2509 if (!taken.marked) taken.marked = [];
2496 2510 taken.marked.push(newmark); newmark.attach(taken);
2497 2511 if (newmark == mark) mk.splice(i--, 1);
2498 2512 }
2499 2513 }
2500 2514 }
2501 2515 return taken;
2502 2516 },
2503 2517 append: function(line) {
2504 2518 var mylen = this.text.length, mk = line.marked, mymk = this.marked;
2505 2519 this.text += line.text;
2506 2520 copyStyles(0, line.text.length, line.styles, this.styles);
2507 2521 if (mymk) {
2508 2522 for (var i = 0; i < mymk.length; ++i)
2509 2523 if (mymk[i].to == null) mymk[i].to = mylen;
2510 2524 }
2511 2525 if (mk && mk.length) {
2512 2526 if (!mymk) this.marked = mymk = [];
2513 2527 outer: for (var i = 0; i < mk.length; ++i) {
2514 2528 var mark = mk[i];
2515 2529 if (!mark.from) {
2516 2530 for (var j = 0; j < mymk.length; ++j) {
2517 2531 var mymark = mymk[j];
2518 2532 if (mymark.to == mylen && mymark.sameSet(mark)) {
2519 2533 mymark.to = mark.to == null ? null : mark.to + mylen;
2520 2534 if (mymark.isDead()) {
2521 2535 mymark.detach(this);
2522 2536 mk.splice(i--, 1);
2523 2537 }
2524 2538 continue outer;
2525 2539 }
2526 2540 }
2527 2541 }
2528 2542 mymk.push(mark);
2529 2543 mark.attach(this);
2530 2544 mark.from += mylen;
2531 2545 if (mark.to != null) mark.to += mylen;
2532 2546 }
2533 2547 }
2534 2548 },
2535 2549 fixMarkEnds: function(other) {
2536 2550 var mk = this.marked, omk = other.marked;
2537 2551 if (!mk) return;
2538 2552 outer: for (var i = 0; i < mk.length; ++i) {
2539 2553 var mark = mk[i], close = mark.to == null;
2540 2554 if (close && omk) {
2541 2555 for (var j = 0; j < omk.length; ++j) {
2542 2556 var om = omk[j];
2543 2557 if (!om.sameSet(mark) || om.from != null) continue
2544 2558 if (mark.from == this.text.length && om.to == 0) {
2545 2559 omk.splice(j, 1);
2546 2560 mk.splice(i--, 1);
2547 2561 continue outer;
2548 2562 } else {
2549 2563 close = false; break;
2550 2564 }
2551 2565 }
2552 2566 }
2553 2567 if (close) mark.to = this.text.length;
2554 2568 }
2555 2569 },
2556 2570 fixMarkStarts: function() {
2557 2571 var mk = this.marked;
2558 2572 if (!mk) return;
2559 2573 for (var i = 0; i < mk.length; ++i)
2560 2574 if (mk[i].from == null) mk[i].from = 0;
2561 2575 },
2562 2576 addMark: function(mark) {
2563 2577 mark.attach(this);
2564 2578 if (this.marked == null) this.marked = [];
2565 2579 this.marked.push(mark);
2566 2580 this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
2567 2581 },
2568 2582 // Run the given mode's parser over a line, update the styles
2569 2583 // array, which contains alternating fragments of text and CSS
2570 2584 // classes.
2571 2585 highlight: function(mode, state, tabSize) {
2572 2586 var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;
2573 2587 var changed = false, curWord = st[0], prevWord;
2574 2588 if (this.text == "" && mode.blankLine) mode.blankLine(state);
2575 2589 while (!stream.eol()) {
2576 2590 var style = mode.token(stream, state);
2577 2591 var substr = this.text.slice(stream.start, stream.pos);
2578 2592 stream.start = stream.pos;
2579 2593 if (pos && st[pos-1] == style)
2580 2594 st[pos-2] += substr;
2581 2595 else if (substr) {
2582 2596 if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;
2583 2597 st[pos++] = substr; st[pos++] = style;
2584 2598 prevWord = curWord; curWord = st[pos];
2585 2599 }
2586 2600 // Give up when line is ridiculously long
2587 2601 if (stream.pos > 5000) {
2588 2602 st[pos++] = this.text.slice(stream.pos); st[pos++] = null;
2589 2603 break;
2590 2604 }
2591 2605 }
2592 2606 if (st.length != pos) {st.length = pos; changed = true;}
2593 2607 if (pos && st[pos-2] != prevWord) changed = true;
2594 2608 // Short lines with simple highlights return null, and are
2595 2609 // counted as changed by the driver because they are likely to
2596 2610 // highlight the same way in various contexts.
2597 2611 return changed || (st.length < 5 && this.text.length < 10 ? null : false);
2598 2612 },
2599 2613 // Fetch the parser token for a given character. Useful for hacks
2600 2614 // that want to inspect the mode state (say, for completion).
2601 2615 getTokenAt: function(mode, state, ch) {
2602 2616 var txt = this.text, stream = new StringStream(txt);
2603 2617 while (stream.pos < ch && !stream.eol()) {
2604 2618 stream.start = stream.pos;
2605 2619 var style = mode.token(stream, state);
2606 2620 }
2607 2621 return {start: stream.start,
2608 2622 end: stream.pos,
2609 2623 string: stream.current(),
2610 2624 className: style || null,
2611 2625 state: state};
2612 2626 },
2613 2627 indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
2614 2628 // Produces an HTML fragment for the line, taking selection,
2615 2629 // marking, and highlighting into account.
2616 2630 getHTML: function(makeTab, wrapAt, wrapId, wrapWBR) {
2617 2631 var html = [], first = true, col = 0;
2618 2632 function span_(text, style) {
2619 2633 if (!text) return;
2620 2634 // Work around a bug where, in some compat modes, IE ignores leading spaces
2621 2635 if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
2622 2636 first = false;
2623 2637 if (text.indexOf("\t") == -1) {
2624 2638 col += text.length;
2625 2639 var escaped = htmlEscape(text);
2626 2640 } else {
2627 2641 var escaped = "";
2628 2642 for (var pos = 0;;) {
2629 2643 var idx = text.indexOf("\t", pos);
2630 2644 if (idx == -1) {
2631 2645 escaped += htmlEscape(text.slice(pos));
2632 2646 col += text.length - pos;
2633 2647 break;
2634 2648 } else {
2635 2649 col += idx - pos;
2636 2650 var tab = makeTab(col);
2637 2651 escaped += htmlEscape(text.slice(pos, idx)) + tab.html;
2638 2652 col += tab.width;
2639 2653 pos = idx + 1;
2640 2654 }
2641 2655 }
2642 2656 }
2643 2657 if (style) html.push('<span class="', style, '">', escaped, "</span>");
2644 2658 else html.push(escaped);
2645 2659 }
2646 2660 var span = span_;
2647 2661 if (wrapAt != null) {
2648 2662 var outPos = 0, open = "<span id=\"" + wrapId + "\">";
2649 2663 span = function(text, style) {
2650 2664 var l = text.length;
2651 2665 if (wrapAt >= outPos && wrapAt < outPos + l) {
2652 2666 if (wrapAt > outPos) {
2653 2667 span_(text.slice(0, wrapAt - outPos), style);
2654 2668 // See comment at the definition of spanAffectsWrapping
2655 2669 if (wrapWBR) html.push("<wbr>");
2656 2670 }
2657 2671 html.push(open);
2658 2672 var cut = wrapAt - outPos;
2659 2673 span_(opera ? text.slice(cut, cut + 1) : text.slice(cut), style);
2660 2674 html.push("</span>");
2661 2675 if (opera) span_(text.slice(cut + 1), style);
2662 2676 wrapAt--;
2663 2677 outPos += l;
2664 2678 } else {
2665 2679 outPos += l;
2666 2680 span_(text, style);
2667 2681 // Output empty wrapper when at end of line
2668 2682 // (Gecko and IE8+ do strange wrapping when adding a space
2669 2683 // to the end of the line. Other browsers don't react well
2670 2684 // to zero-width spaces. So we do hideous browser sniffing
2671 2685 // to determine which to use.)
2672 2686 if (outPos == wrapAt && outPos == len)
2673 2687 html.push(open + (gecko || (ie && !ie_lt8) ? "&#x200b;" : " ") + "</span>");
2674 2688 // Stop outputting HTML when gone sufficiently far beyond measure
2675 2689 else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){};
2676 2690 }
2677 2691 }
2678 2692 }
2679 2693
2680 2694 var st = this.styles, allText = this.text, marked = this.marked;
2681 2695 var len = allText.length;
2682 2696 function styleToClass(style) {
2683 2697 if (!style) return null;
2684 2698 return "cm-" + style.replace(/ +/g, " cm-");
2685 2699 }
2686 2700
2687 2701 if (!allText && wrapAt == null) {
2688 2702 span(" ");
2689 2703 } else if (!marked || !marked.length) {
2690 2704 for (var i = 0, ch = 0; ch < len; i+=2) {
2691 2705 var str = st[i], style = st[i+1], l = str.length;
2692 2706 if (ch + l > len) str = str.slice(0, len - ch);
2693 2707 ch += l;
2694 2708 span(str, styleToClass(style));
2695 2709 }
2696 2710 } else {
2697 2711 var pos = 0, i = 0, text = "", style, sg = 0;
2698 2712 var nextChange = marked[0].from || 0, marks = [], markpos = 0;
2699 2713 function advanceMarks() {
2700 2714 var m;
2701 2715 while (markpos < marked.length &&
2702 2716 ((m = marked[markpos]).from == pos || m.from == null)) {
2703 2717 if (m.style != null) marks.push(m);
2704 2718 ++markpos;
2705 2719 }
2706 2720 nextChange = markpos < marked.length ? marked[markpos].from : Infinity;
2707 2721 for (var i = 0; i < marks.length; ++i) {
2708 2722 var to = marks[i].to;
2709 2723 if (to == null) to = Infinity;
2710 2724 if (to == pos) marks.splice(i--, 1);
2711 2725 else nextChange = Math.min(to, nextChange);
2712 2726 }
2713 2727 }
2714 2728 var m = 0;
2715 2729 while (pos < len) {
2716 2730 if (nextChange == pos) advanceMarks();
2717 2731 var upto = Math.min(len, nextChange);
2718 2732 while (true) {
2719 2733 if (text) {
2720 2734 var end = pos + text.length;
2721 2735 var appliedStyle = style;
2722 2736 for (var j = 0; j < marks.length; ++j)
2723 2737 appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style;
2724 2738 span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
2725 2739 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
2726 2740 pos = end;
2727 2741 }
2728 2742 text = st[i++]; style = styleToClass(st[i++]);
2729 2743 }
2730 2744 }
2731 2745 }
2732 2746 return html.join("");
2733 2747 },
2734 2748 cleanUp: function() {
2735 2749 this.parent = null;
2736 2750 if (this.marked)
2737 2751 for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
2738 2752 }
2739 2753 };
2740 2754 // Utility used by replace and split above
2741 2755 function copyStyles(from, to, source, dest) {
2742 2756 for (var i = 0, pos = 0, state = 0; pos < to; i+=2) {
2743 2757 var part = source[i], end = pos + part.length;
2744 2758 if (state == 0) {
2745 2759 if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]);
2746 2760 if (end >= from) state = 1;
2747 2761 } else if (state == 1) {
2748 2762 if (end > to) dest.push(part.slice(0, to - pos), source[i+1]);
2749 2763 else dest.push(part, source[i+1]);
2750 2764 }
2751 2765 pos = end;
2752 2766 }
2753 2767 }
2754 2768
2755 2769 // Data structure that holds the sequence of lines.
2756 2770 function LeafChunk(lines) {
2757 2771 this.lines = lines;
2758 2772 this.parent = null;
2759 2773 for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
2760 2774 lines[i].parent = this;
2761 2775 height += lines[i].height;
2762 2776 }
2763 2777 this.height = height;
2764 2778 }
2765 2779 LeafChunk.prototype = {
2766 2780 chunkSize: function() { return this.lines.length; },
2767 2781 remove: function(at, n, callbacks) {
2768 2782 for (var i = at, e = at + n; i < e; ++i) {
2769 2783 var line = this.lines[i];
2770 2784 this.height -= line.height;
2771 2785 line.cleanUp();
2772 2786 if (line.handlers)
2773 2787 for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);
2774 2788 }
2775 2789 this.lines.splice(at, n);
2776 2790 },
2777 2791 collapse: function(lines) {
2778 2792 lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
2779 2793 },
2780 2794 insertHeight: function(at, lines, height) {
2781 2795 this.height += height;
2782 2796 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
2783 2797 for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
2784 2798 },
2785 2799 iterN: function(at, n, op) {
2786 2800 for (var e = at + n; at < e; ++at)
2787 2801 if (op(this.lines[at])) return true;
2788 2802 }
2789 2803 };
2790 2804 function BranchChunk(children) {
2791 2805 this.children = children;
2792 2806 var size = 0, height = 0;
2793 2807 for (var i = 0, e = children.length; i < e; ++i) {
2794 2808 var ch = children[i];
2795 2809 size += ch.chunkSize(); height += ch.height;
2796 2810 ch.parent = this;
2797 2811 }
2798 2812 this.size = size;
2799 2813 this.height = height;
2800 2814 this.parent = null;
2801 2815 }
2802 2816 BranchChunk.prototype = {
2803 2817 chunkSize: function() { return this.size; },
2804 2818 remove: function(at, n, callbacks) {
2805 2819 this.size -= n;
2806 2820 for (var i = 0; i < this.children.length; ++i) {
2807 2821 var child = this.children[i], sz = child.chunkSize();
2808 2822 if (at < sz) {
2809 2823 var rm = Math.min(n, sz - at), oldHeight = child.height;
2810 2824 child.remove(at, rm, callbacks);
2811 2825 this.height -= oldHeight - child.height;
2812 2826 if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
2813 2827 if ((n -= rm) == 0) break;
2814 2828 at = 0;
2815 2829 } else at -= sz;
2816 2830 }
2817 2831 if (this.size - n < 25) {
2818 2832 var lines = [];
2819 2833 this.collapse(lines);
2820 2834 this.children = [new LeafChunk(lines)];
2821 2835 this.children[0].parent = this;
2822 2836 }
2823 2837 },
2824 2838 collapse: function(lines) {
2825 2839 for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
2826 2840 },
2827 2841 insert: function(at, lines) {
2828 2842 var height = 0;
2829 2843 for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
2830 2844 this.insertHeight(at, lines, height);
2831 2845 },
2832 2846 insertHeight: function(at, lines, height) {
2833 2847 this.size += lines.length;
2834 2848 this.height += height;
2835 2849 for (var i = 0, e = this.children.length; i < e; ++i) {
2836 2850 var child = this.children[i], sz = child.chunkSize();
2837 2851 if (at <= sz) {
2838 2852 child.insertHeight(at, lines, height);
2839 2853 if (child.lines && child.lines.length > 50) {
2840 2854 while (child.lines.length > 50) {
2841 2855 var spilled = child.lines.splice(child.lines.length - 25, 25);
2842 2856 var newleaf = new LeafChunk(spilled);
2843 2857 child.height -= newleaf.height;
2844 2858 this.children.splice(i + 1, 0, newleaf);
2845 2859 newleaf.parent = this;
2846 2860 }
2847 2861 this.maybeSpill();
2848 2862 }
2849 2863 break;
2850 2864 }
2851 2865 at -= sz;
2852 2866 }
2853 2867 },
2854 2868 maybeSpill: function() {
2855 2869 if (this.children.length <= 10) return;
2856 2870 var me = this;
2857 2871 do {
2858 2872 var spilled = me.children.splice(me.children.length - 5, 5);
2859 2873 var sibling = new BranchChunk(spilled);
2860 2874 if (!me.parent) { // Become the parent node
2861 2875 var copy = new BranchChunk(me.children);
2862 2876 copy.parent = me;
2863 2877 me.children = [copy, sibling];
2864 2878 me = copy;
2865 2879 } else {
2866 2880 me.size -= sibling.size;
2867 2881 me.height -= sibling.height;
2868 2882 var myIndex = indexOf(me.parent.children, me);
2869 2883 me.parent.children.splice(myIndex + 1, 0, sibling);
2870 2884 }
2871 2885 sibling.parent = me.parent;
2872 2886 } while (me.children.length > 10);
2873 2887 me.parent.maybeSpill();
2874 2888 },
2875 2889 iter: function(from, to, op) { this.iterN(from, to - from, op); },
2876 2890 iterN: function(at, n, op) {
2877 2891 for (var i = 0, e = this.children.length; i < e; ++i) {
2878 2892 var child = this.children[i], sz = child.chunkSize();
2879 2893 if (at < sz) {
2880 2894 var used = Math.min(n, sz - at);
2881 2895 if (child.iterN(at, used, op)) return true;
2882 2896 if ((n -= used) == 0) break;
2883 2897 at = 0;
2884 2898 } else at -= sz;
2885 2899 }
2886 2900 }
2887 2901 };
2888 2902
2889 2903 function getLineAt(chunk, n) {
2890 2904 while (!chunk.lines) {
2891 2905 for (var i = 0;; ++i) {
2892 2906 var child = chunk.children[i], sz = child.chunkSize();
2893 2907 if (n < sz) { chunk = child; break; }
2894 2908 n -= sz;
2895 2909 }
2896 2910 }
2897 2911 return chunk.lines[n];
2898 2912 }
2899 2913 function lineNo(line) {
2900 2914 if (line.parent == null) return null;
2901 2915 var cur = line.parent, no = indexOf(cur.lines, line);
2902 2916 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
2903 2917 for (var i = 0, e = chunk.children.length; ; ++i) {
2904 2918 if (chunk.children[i] == cur) break;
2905 2919 no += chunk.children[i].chunkSize();
2906 2920 }
2907 2921 }
2908 2922 return no;
2909 2923 }
2910 2924 function lineAtHeight(chunk, h) {
2911 2925 var n = 0;
2912 2926 outer: do {
2913 2927 for (var i = 0, e = chunk.children.length; i < e; ++i) {
2914 2928 var child = chunk.children[i], ch = child.height;
2915 2929 if (h < ch) { chunk = child; continue outer; }
2916 2930 h -= ch;
2917 2931 n += child.chunkSize();
2918 2932 }
2919 2933 return n;
2920 2934 } while (!chunk.lines);
2921 2935 for (var i = 0, e = chunk.lines.length; i < e; ++i) {
2922 2936 var line = chunk.lines[i], lh = line.height;
2923 2937 if (h < lh) break;
2924 2938 h -= lh;
2925 2939 }
2926 2940 return n + i;
2927 2941 }
2928 2942 function heightAtLine(chunk, n) {
2929 2943 var h = 0;
2930 2944 outer: do {
2931 2945 for (var i = 0, e = chunk.children.length; i < e; ++i) {
2932 2946 var child = chunk.children[i], sz = child.chunkSize();
2933 2947 if (n < sz) { chunk = child; continue outer; }
2934 2948 n -= sz;
2935 2949 h += child.height;
2936 2950 }
2937 2951 return h;
2938 2952 } while (!chunk.lines);
2939 2953 for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
2940 2954 return h;
2941 2955 }
2942 2956
2943 2957 // The history object 'chunks' changes that are made close together
2944 2958 // and at almost the same time into bigger undoable units.
2945 2959 function History() {
2946 2960 this.time = 0;
2947 2961 this.done = []; this.undone = [];
2948 2962 this.compound = 0;
2949 2963 this.closed = false;
2950 2964 }
2951 2965 History.prototype = {
2952 2966 addChange: function(start, added, old) {
2953 2967 this.undone.length = 0;
2954 2968 var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1];
2955 2969 var dtime = time - this.time;
2956 2970
2957 2971 if (this.compound && cur && !this.closed) {
2958 2972 cur.push({start: start, added: added, old: old});
2959 2973 } else if (dtime > 400 || !last || this.closed ||
2960 2974 last.start > start + old.length || last.start + last.added < start) {
2961 2975 this.done.push([{start: start, added: added, old: old}]);
2962 2976 this.closed = false;
2963 2977 } else {
2964 2978 var startBefore = Math.max(0, last.start - start),
2965 2979 endAfter = Math.max(0, (start + old.length) - (last.start + last.added));
2966 2980 for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]);
2967 2981 for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]);
2968 2982 if (startBefore) last.start = start;
2969 2983 last.added += added - (old.length - startBefore - endAfter);
2970 2984 }
2971 2985 this.time = time;
2972 2986 },
2973 2987 startCompound: function() {
2974 2988 if (!this.compound++) this.closed = true;
2975 2989 },
2976 2990 endCompound: function() {
2977 2991 if (!--this.compound) this.closed = true;
2978 2992 }
2979 2993 };
2980 2994
2981 2995 function stopMethod() {e_stop(this);}
2982 2996 // Ensure an event has a stop method.
2983 2997 function addStop(event) {
2984 2998 if (!event.stop) event.stop = stopMethod;
2985 2999 return event;
2986 3000 }
2987 3001
2988 3002 function e_preventDefault(e) {
2989 3003 if (e.preventDefault) e.preventDefault();
2990 3004 else e.returnValue = false;
2991 3005 }
2992 3006 function e_stopPropagation(e) {
2993 3007 if (e.stopPropagation) e.stopPropagation();
2994 3008 else e.cancelBubble = true;
2995 3009 }
2996 3010 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
2997 3011 CodeMirror.e_stop = e_stop;
2998 3012 CodeMirror.e_preventDefault = e_preventDefault;
2999 3013 CodeMirror.e_stopPropagation = e_stopPropagation;
3000 3014
3001 3015 function e_target(e) {return e.target || e.srcElement;}
3002 3016 function e_button(e) {
3003 3017 var b = e.which;
3004 3018 if (b == null) {
3005 3019 if (e.button & 1) b = 1;
3006 3020 else if (e.button & 2) b = 3;
3007 3021 else if (e.button & 4) b = 2;
3008 3022 }
3009 3023 if (mac && e.ctrlKey && b == 1) b = 3;
3010 3024 return b;
3011 3025 }
3012 3026
3013 3027 // Allow 3rd-party code to override event properties by adding an override
3014 3028 // object to an event object.
3015 3029 function e_prop(e, prop) {
3016 3030 var overridden = e.override && e.override.hasOwnProperty(prop);
3017 3031 return overridden ? e.override[prop] : e[prop];
3018 3032 }
3019 3033
3020 3034 // Event handler registration. If disconnect is true, it'll return a
3021 3035 // function that unregisters the handler.
3022 3036 function connect(node, type, handler, disconnect) {
3023 3037 if (typeof node.addEventListener == "function") {
3024 3038 node.addEventListener(type, handler, false);
3025 3039 if (disconnect) return function() {node.removeEventListener(type, handler, false);};
3026 3040 } else {
3027 3041 var wrapHandler = function(event) {handler(event || window.event);};
3028 3042 node.attachEvent("on" + type, wrapHandler);
3029 3043 if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
3030 3044 }
3031 3045 }
3032 3046 CodeMirror.connect = connect;
3033 3047
3034 3048 function Delayed() {this.id = null;}
3035 3049 Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
3036 3050
3037 3051 var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
3038 3052
3039 3053 var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
3040 3054 var ie = /MSIE \d/.test(navigator.userAgent);
3041 3055 var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
3042 3056 var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
3043 3057 var quirksMode = ie && document.documentMode == 5;
3044 3058 var webkit = /WebKit\//.test(navigator.userAgent);
3045 3059 var chrome = /Chrome\//.test(navigator.userAgent);
3046 3060 var opera = /Opera\//.test(navigator.userAgent);
3047 3061 var safari = /Apple Computer/.test(navigator.vendor);
3048 3062 var khtml = /KHTML\//.test(navigator.userAgent);
3049 3063 var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent);
3050 3064
3051 3065 // Detect drag-and-drop
3052 3066 var dragAndDrop = function() {
3053 3067 // There is *some* kind of drag-and-drop support in IE6-8, but I
3054 3068 // couldn't get it to work yet.
3055 3069 if (ie_lt9) return false;
3056 3070 var div = document.createElement('div');
3057 3071 return "draggable" in div || "dragDrop" in div;
3058 3072 }();
3059 3073
3060 3074 // Feature-detect whether newlines in textareas are converted to \r\n
3061 3075 var lineSep = function () {
3062 3076 var te = document.createElement("textarea");
3063 3077 te.value = "foo\nbar";
3064 3078 if (te.value.indexOf("\r") > -1) return "\r\n";
3065 3079 return "\n";
3066 3080 }();
3067 3081
3068 3082 // For a reason I have yet to figure out, some browsers disallow
3069 3083 // word wrapping between certain characters *only* if a new inline
3070 3084 // element is started between them. This makes it hard to reliably
3071 3085 // measure the position of things, since that requires inserting an
3072 3086 // extra span. This terribly fragile set of regexps matches the
3073 3087 // character combinations that suffer from this phenomenon on the
3074 3088 // various browsers.
3075 3089 var spanAffectsWrapping = /^$/; // Won't match any two-character string
3076 3090 if (gecko) spanAffectsWrapping = /$'/;
3077 3091 else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
3078 3092 else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
3079 3093
3080 3094 // Counts the column offset in a string, taking tabs into account.
3081 3095 // Used mostly to find indentation.
3082 3096 function countColumn(string, end, tabSize) {
3083 3097 if (end == null) {
3084 3098 end = string.search(/[^\s\u00a0]/);
3085 3099 if (end == -1) end = string.length;
3086 3100 }
3087 3101 for (var i = 0, n = 0; i < end; ++i) {
3088 3102 if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
3089 3103 else ++n;
3090 3104 }
3091 3105 return n;
3092 3106 }
3093 3107
3094 3108 function computedStyle(elt) {
3095 3109 if (elt.currentStyle) return elt.currentStyle;
3096 3110 return window.getComputedStyle(elt, null);
3097 3111 }
3098 3112
3099 3113 function eltOffset(node, screen) {
3100 3114 // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
3101 3115 // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
3102 3116 try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
3103 3117 catch(e) { box = {top: 0, left: 0}; }
3104 3118 if (!screen) {
3105 3119 // Get the toplevel scroll, working around browser differences.
3106 3120 if (window.pageYOffset == null) {
3107 3121 var t = document.documentElement || document.body.parentNode;
3108 3122 if (t.scrollTop == null) t = document.body;
3109 3123 box.top += t.scrollTop; box.left += t.scrollLeft;
3110 3124 } else {
3111 3125 box.top += window.pageYOffset; box.left += window.pageXOffset;
3112 3126 }
3113 3127 }
3114 3128 return box;
3115 3129 }
3116 3130
3117 3131 // Get a node's text content.
3118 3132 function eltText(node) {
3119 3133 return node.textContent || node.innerText || node.nodeValue || "";
3120 3134 }
3121 3135 function selectInput(node) {
3122 3136 if (ios) { // Mobile Safari apparently has a bug where select() is broken.
3123 3137 node.selectionStart = 0;
3124 3138 node.selectionEnd = node.value.length;
3125 3139 } else node.select();
3126 3140 }
3127 3141
3128 3142 // Operations on {line, ch} objects.
3129 3143 function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
3130 3144 function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
3131 3145 function copyPos(x) {return {line: x.line, ch: x.ch};}
3132 3146
3133 3147 var escapeElement = document.createElement("pre");
3134 3148 function htmlEscape(str) {
3135 3149 escapeElement.textContent = str;
3136 3150 return escapeElement.innerHTML;
3137 3151 }
3138 3152 // Recent (late 2011) Opera betas insert bogus newlines at the start
3139 3153 // of the textContent, so we strip those.
3140 3154 if (htmlEscape("a") == "\na") {
3141 3155 htmlEscape = function(str) {
3142 3156 escapeElement.textContent = str;
3143 3157 return escapeElement.innerHTML.slice(1);
3144 3158 };
3145 3159 // Some IEs don't preserve tabs through innerHTML
3146 3160 } else if (htmlEscape("\t") != "\t") {
3147 3161 htmlEscape = function(str) {
3148 3162 escapeElement.innerHTML = "";
3149 3163 escapeElement.appendChild(document.createTextNode(str));
3150 3164 return escapeElement.innerHTML;
3151 3165 };
3152 3166 }
3153 3167 CodeMirror.htmlEscape = htmlEscape;
3154 3168
3155 3169 // Used to position the cursor after an undo/redo by finding the
3156 3170 // last edited character.
3157 3171 function editEnd(from, to) {
3158 3172 if (!to) return 0;
3159 3173 if (!from) return to.length;
3160 3174 for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
3161 3175 if (from.charAt(i) != to.charAt(j)) break;
3162 3176 return j + 1;
3163 3177 }
3164 3178
3165 3179 function indexOf(collection, elt) {
3166 3180 if (collection.indexOf) return collection.indexOf(elt);
3167 3181 for (var i = 0, e = collection.length; i < e; ++i)
3168 3182 if (collection[i] == elt) return i;
3169 3183 return -1;
3170 3184 }
3171 3185 function isWordChar(ch) {
3172 3186 return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();
3173 3187 }
3174 3188
3175 3189 // See if "".split is the broken IE version, if so, provide an
3176 3190 // alternative way to split lines.
3177 3191 var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
3178 3192 var pos = 0, result = [], l = string.length;
3179 3193 while (pos <= l) {
3180 3194 var nl = string.indexOf("\n", pos);
3181 3195 if (nl == -1) nl = string.length;
3182 3196 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
3183 3197 var rt = line.indexOf("\r");
3184 3198 if (rt != -1) {
3185 3199 result.push(line.slice(0, rt));
3186 3200 pos += rt + 1;
3187 3201 } else {
3188 3202 result.push(line);
3189 3203 pos = nl + 1;
3190 3204 }
3191 3205 }
3192 3206 return result;
3193 3207 } : function(string){return string.split(/\r\n?|\n/);};
3194 3208 CodeMirror.splitLines = splitLines;
3195 3209
3196 3210 var hasSelection = window.getSelection ? function(te) {
3197 3211 try { return te.selectionStart != te.selectionEnd; }
3198 3212 catch(e) { return false; }
3199 3213 } : function(te) {
3200 3214 try {var range = te.ownerDocument.selection.createRange();}
3201 3215 catch(e) {}
3202 3216 if (!range || range.parentElement() != te) return false;
3203 3217 return range.compareEndPoints("StartToEnd", range) != 0;
3204 3218 };
3205 3219
3206 3220 CodeMirror.defineMode("null", function() {
3207 3221 return {token: function(stream) {stream.skipToEnd();}};
3208 3222 });
3209 3223 CodeMirror.defineMIME("text/plain", "null");
3210 3224
3211 3225 var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
3212 3226 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
3213 3227 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
3214 3228 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
3215 3229 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
3216 3230 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
3217 3231 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
3218 3232 CodeMirror.keyNames = keyNames;
3219 3233 (function() {
3220 3234 // Number keys
3221 3235 for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
3222 3236 // Alphabetic keys
3223 3237 for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
3224 3238 // Function keys
3225 3239 for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
3226 3240 })();
3227 3241
3228 3242 return CodeMirror;
3229 3243 })();
General Comments 0
You need to be logged in to leave comments. Login now