codemirror-rails 0.3.2 → 2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  module Codemirror
2
2
  module Rails
3
- VERSION = '0.3.2'
4
- CODEMIRROR_VERSION = '2.18'
3
+ VERSION = '2.2'
4
+ CODEMIRROR_VERSION = '2.2'
5
5
  end
6
6
  end
@@ -1,5 +1,5 @@
1
- // CodeMirror v2.18
2
-
1
+ // CodeMirror version 2.2
2
+ //
3
3
  // All functions that need access to the editor's state live inside
4
4
  // the CodeMirror function. Below that, at the bottom of the file,
5
5
  // some utilities are defined.
@@ -21,10 +21,10 @@ var CodeMirror = (function() {
21
21
  wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
22
22
  // This mess creates the base DOM structure for the editor.
23
23
  wrapper.innerHTML =
24
- '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea
25
- '<textarea style="position: absolute; width: 10000px;" wrap="off" ' +
24
+ '<div style="overflow: hidden; position: relative; width: 3px; height: 0px;">' + // Wraps and hides input textarea
25
+ '<textarea style="position: absolute; padding: 0; width: 1px;" wrap="off" ' +
26
26
  'autocorrect="off" autocapitalize="off"></textarea></div>' +
27
- '<div class="CodeMirror-scroll cm-s-' + options.theme + '">' +
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>' +
@@ -41,15 +41,18 @@ var CodeMirror = (function() {
41
41
  mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
42
42
  lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
43
43
  cursor = measure.nextSibling, lineDiv = cursor.nextSibling;
44
+ themeChanged();
45
+ // Needed to hide big blue blinking cursor on Mobile Safari
46
+ if (/AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent)) input.style.width = "0px";
44
47
  if (!webkit) lineSpace.draggable = true;
45
- if (options.tabindex != null) input.tabindex = options.tabindex;
48
+ if (options.tabindex != null) input.tabIndex = options.tabindex;
46
49
  if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
47
50
 
48
51
  // Check for problem with IE innerHTML not working when we have a
49
52
  // P (or similar) parent node.
50
53
  try { stringWidth("x"); }
51
54
  catch (e) {
52
- if (e.message.match(/unknown runtime/i))
55
+ if (e.message.match(/runtime/i))
53
56
  e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
54
57
  throw e;
55
58
  }
@@ -67,36 +70,25 @@ var CodeMirror = (function() {
67
70
  // selecting bottom-to-top.
68
71
  var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
69
72
  // Selection-related flags. shiftSelecting obviously tracks
70
- // whether the user is holding shift. reducedSelection is a hack
71
- // to get around the fact that we can't create inverted
72
- // selections. See below.
73
- var shiftSelecting, reducedSelection, lastClick, lastDoubleClick, draggingText;
73
+ // whether the user is holding shift.
74
+ var shiftSelecting, lastClick, lastDoubleClick, draggingText, overwrite = false;
74
75
  // Variables used by startOperation/endOperation to track what
75
76
  // happened during the operation.
76
- var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty;
77
+ var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,
78
+ gutterDirty, callbacks;
77
79
  // Current visible range (may be bigger than the view window).
78
- var displayOffset = 0, showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
79
- // editing will hold an object describing the things we put in the
80
- // textarea, to help figure out whether something changed.
80
+ var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
81
81
  // bracketHighlighted is used to remember that a backet has been
82
82
  // marked.
83
- var editing, bracketHighlighted;
83
+ var bracketHighlighted;
84
84
  // Tracks the maximum line length so that the horizontal scrollbar
85
85
  // can be kept static when scrolling.
86
- var maxLine = "", maxWidth;
86
+ var maxLine = "", maxWidth, tabText = computeTabText();
87
87
 
88
88
  // Initialize the content.
89
89
  operation(function(){setValue(options.value || ""); updateInput = false;})();
90
90
  var history = new History();
91
91
 
92
- var slowPollInterval = 2000;
93
- // Gecko and Opera Linux do not reliably fire any event when starting an IME compose
94
- var alwaysPollForIME = (!win && !mac) && (gecko || window.opera);
95
- if (options.pollForIME && alwaysPollForIME) slowPollInterval = 50;
96
- function keyMightStartIME(keyCode) {
97
- return (win && ((gecko && keyCode == 229) || (window.opera && keyCode == 197))) || (mac && gecko);
98
- }
99
-
100
92
  // Register our event handlers.
101
93
  connect(scroller, "mousedown", operation(onMouseDown));
102
94
  connect(scroller, "dblclick", operation(onDoubleClick));
@@ -113,7 +105,7 @@ var CodeMirror = (function() {
113
105
  });
114
106
  connect(window, "resize", function() {updateDisplay(true);});
115
107
  connect(input, "keyup", operation(onKeyUp));
116
- connect(input, "input", function() {fastPoll(curKeyId);});
108
+ connect(input, "input", fastPoll);
117
109
  connect(input, "keydown", operation(onKeyDown));
118
110
  connect(input, "keypress", operation(onKeyPress));
119
111
  connect(input, "focus", onFocus);
@@ -123,8 +115,8 @@ var CodeMirror = (function() {
123
115
  connect(scroller, "dragover", e_stop);
124
116
  connect(scroller, "drop", operation(onDrop));
125
117
  connect(scroller, "paste", function(){focusInput(); fastPoll();});
126
- connect(input, "paste", function(){fastPoll();});
127
- connect(input, "cut", function(){fastPoll();});
118
+ connect(input, "paste", fastPoll);
119
+ connect(input, "cut", operation(function(){replaceSelection("");}));
128
120
 
129
121
  // IE throws unspecified error in certain cases, when
130
122
  // trying to access activeElement before onload
@@ -148,10 +140,10 @@ var CodeMirror = (function() {
148
140
  var oldVal = options[option];
149
141
  options[option] = value;
150
142
  if (option == "mode" || option == "indentUnit") loadMode();
151
- else if (option == "readOnly" && value == "nocursor") input.blur();
152
- else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value);
143
+ else if (option == "readOnly" && value) {onBlur(); input.blur();}
144
+ else if (option == "theme") themeChanged();
153
145
  else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
154
- else if (option == "pollForIME" && alwaysPollForIME) slowPollInterval = value ? 50 : 2000;
146
+ else if (option == "tabSize") operation(tabsChanged)();
155
147
  if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme")
156
148
  operation(gutterChanged)();
157
149
  },
@@ -161,6 +153,7 @@ var CodeMirror = (function() {
161
153
  indentLine: operation(function(n, dir) {
162
154
  if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract");
163
155
  }),
156
+ indentSelection: operation(indentSelected),
164
157
  historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
165
158
  clearHistory: function() {history = new History();},
166
159
  matchBrackets: operation(function(){matchBrackets(true);}),
@@ -181,7 +174,6 @@ var CodeMirror = (function() {
181
174
  var off = eltOffset(lineSpace);
182
175
  return coordsChar(coords.x - off.left, coords.y - off.top);
183
176
  },
184
- getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);},
185
177
  markText: operation(markText),
186
178
  setBookmark: setBookmark,
187
179
  setMarker: operation(addGutterMarker),
@@ -189,6 +181,14 @@ var CodeMirror = (function() {
189
181
  setLineClass: operation(setLineClass),
190
182
  hideLine: operation(function(h) {return setLineHidden(h, true);}),
191
183
  showLine: operation(function(h) {return setLineHidden(h, false);}),
184
+ onDeleteLine: function(line, f) {
185
+ if (typeof line == "number") {
186
+ if (!isLine(line)) return null;
187
+ line = getLine(line);
188
+ }
189
+ (line.handlers || (line.handlers = [])).push(f);
190
+ return line;
191
+ },
192
192
  lineInfo: lineInfo,
193
193
  addWidget: function(pos, node, scroll, vert, horiz) {
194
194
  pos = localCoords(clipPos(pos));
@@ -219,17 +219,21 @@ var CodeMirror = (function() {
219
219
  },
220
220
 
221
221
  lineCount: function() {return doc.size;},
222
+ clipPos: clipPos,
222
223
  getCursor: function(start) {
223
224
  if (start == null) start = sel.inverted;
224
225
  return copyPos(start ? sel.from : sel.to);
225
226
  },
226
227
  somethingSelected: function() {return !posEq(sel.from, sel.to);},
227
- setCursor: operation(function(line, ch) {
228
- if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch);
229
- else setCursor(line, ch);
228
+ setCursor: operation(function(line, ch, user) {
229
+ if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
230
+ else setCursor(line, ch, user);
231
+ }),
232
+ setSelection: operation(function(from, to, user) {
233
+ (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
230
234
  }),
231
- setSelection: operation(function(from, to) {setSelection(clipPos(from), clipPos(to || from));}),
232
235
  getLine: function(line) {if (isLine(line)) return getLine(line).text;},
236
+ getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
233
237
  setLine: operation(function(line, text) {
234
238
  if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
235
239
  }),
@@ -239,7 +243,14 @@ var CodeMirror = (function() {
239
243
  replaceRange: operation(replaceRange),
240
244
  getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
241
245
 
242
- coordsFromIndex: function(off) {
246
+ execCommand: function(cmd) {return commands[cmd](instance);},
247
+ // Stuff used by commands, probably not much use to outside code.
248
+ moveH: operation(moveH),
249
+ deleteH: operation(deleteH),
250
+ moveV: operation(moveV),
251
+ toggleOverwrite: function() {overwrite = !overwrite;},
252
+
253
+ posFromIndex: function(off) {
243
254
  var lineNo = 0, ch;
244
255
  doc.iter(0, doc.size, function(line) {
245
256
  var sz = line.text.length + 1;
@@ -249,6 +260,14 @@ var CodeMirror = (function() {
249
260
  });
250
261
  return clipPos({line: lineNo, ch: ch});
251
262
  },
263
+ indexFromPos: function (coords) {
264
+ if (coords.line < 0 || coords.ch < 0) return 0;
265
+ var index = coords.ch;
266
+ doc.iter(0, coords.line, function (line) {
267
+ index += line.text.length + 1;
268
+ });
269
+ return index;
270
+ },
252
271
 
253
272
  operation: function(f){return operation(f)();},
254
273
  refresh: function(){updateDisplay(true);},
@@ -278,6 +297,7 @@ var CodeMirror = (function() {
278
297
  }
279
298
 
280
299
  function onMouseDown(e) {
300
+ setShift(e.shiftKey);
281
301
  // Check whether this is a click in a widget
282
302
  for (var n = e_target(e); n != wrapper; n = n.parentNode)
283
303
  if (n.parentNode == code && n != mover) return;
@@ -419,85 +439,76 @@ var CodeMirror = (function() {
419
439
  e.dataTransfer.setDragImage(escapeElement, 0, 0);
420
440
  e.dataTransfer.setData("Text", txt);
421
441
  }
442
+ function handleKeyBinding(e) {
443
+ var name = keyNames[e.keyCode], next = keyMap[options.keyMap].auto, bound, dropShift;
444
+ if (name == null || e.altGraphKey) {
445
+ if (next) options.keyMap = next;
446
+ return null;
447
+ }
448
+ if (e.altKey) name = "Alt-" + name;
449
+ if (e.ctrlKey) name = "Ctrl-" + name;
450
+ if (e.metaKey) name = "Cmd-" + name;
451
+ if (e.shiftKey && (bound = lookupKey("Shift-" + name, options.extraKeys, options.keyMap))) {
452
+ dropShift = true;
453
+ } else {
454
+ bound = lookupKey(name, options.extraKeys, options.keyMap);
455
+ }
456
+ if (typeof bound == "string") {
457
+ if (commands.propertyIsEnumerable(bound)) bound = commands[bound];
458
+ else bound = null;
459
+ }
460
+ if (next && (bound || !isModifierKey(e))) options.keyMap = next;
461
+ if (!bound) return false;
462
+ if (dropShift) {
463
+ var prevShift = shiftSelecting;
464
+ shiftSelecting = null;
465
+ bound(instance);
466
+ shiftSelecting = prevShift;
467
+ } else bound(instance);
468
+ e_preventDefault(e);
469
+ return true;
470
+ }
471
+ var lastStoppedKey = null;
422
472
  function onKeyDown(e) {
423
473
  if (!focused) onFocus();
424
-
425
474
  var code = e.keyCode;
426
475
  // IE does strange things with escape.
427
476
  if (ie && code == 27) { e.returnValue = false; }
428
- // Tries to detect ctrl on non-mac, cmd on mac.
429
- var mod = (mac ? e.metaKey : e.ctrlKey) && !e.altKey, anyMod = e.ctrlKey || e.altKey || e.metaKey;
430
- if (code == 16 || e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
431
- else shiftSelecting = null;
477
+ setShift(code == 16 || e.shiftKey);
432
478
  // First give onKeyEvent option a chance to handle this.
433
479
  if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
434
-
435
- if (code == 33 || code == 34) {scrollPage(code == 34); return e_preventDefault(e);} // page up/down
436
- if (mod && ((code == 36 || code == 35) || // ctrl-home/end
437
- mac && (code == 38 || code == 40))) { // cmd-up/down
438
- scrollEnd(code == 36 || code == 38); return e_preventDefault(e);
439
- }
440
- if (mod && code == 65) {selectAll(); return e_preventDefault(e);} // ctrl-a
441
- if (!options.readOnly) {
442
- if (!anyMod && code == 13) {return;} // enter
443
- if (!anyMod && code == 9 && handleTab(e.shiftKey)) return e_preventDefault(e); // tab
444
- if (mod && code == 90) {undo(); return e_preventDefault(e);} // ctrl-z
445
- if (mod && ((e.shiftKey && code == 90) || code == 89)) {redo(); return e_preventDefault(e);} // ctrl-shift-z, ctrl-y
446
- }
447
- if (code == 36) { if (options.smartHome) { smartHome(); return e_preventDefault(e); } }
448
-
449
- // Key id to use in the movementKeys map. We also pass it to
450
- // fastPoll in order to 'self learn'. We need this because
451
- // reducedSelection, the hack where we collapse the selection to
452
- // its start when it is inverted and a movement key is pressed
453
- // (and later restore it again), shouldn't be used for
454
- // non-movement keys.
455
- curKeyId = (mod ? "c" : "") + (e.altKey ? "a" : "") + code;
456
- if (sel.inverted && movementKeys[curKeyId] === true) {
457
- var range = selRange(input);
458
- if (range) {
459
- reducedSelection = {anchor: range.start};
460
- setSelRange(input, range.start, range.start);
461
- }
480
+ var handled = handleKeyBinding(e);
481
+ if (window.opera) {
482
+ lastStoppedKey = handled ? e.keyCode : null;
483
+ // Opera has no cut event... we try to at least catch the key combo
484
+ if (!handled && (mac ? e.metaKey : e.ctrlKey) && e.keyCode == 88)
485
+ replaceSelection("");
462
486
  }
463
- // Don't save the key as a movementkey unless it had a modifier
464
- if (!mod && !e.altKey) curKeyId = null;
465
- fastPoll(curKeyId);
466
-
467
- if (options.pollForIME && keyMightStartIME(code)) slowPollInterval = 50;
468
- }
469
- function onKeyUp(e) {
470
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
471
- if (reducedSelection) {
472
- reducedSelection = null;
473
- updateInput = true;
474
- }
475
- if (e.keyCode == 16) shiftSelecting = null;
476
-
477
- if (slowPollInterval < 2000 && !alwaysPollForIME) slowPollInterval = 2000;
478
487
  }
479
488
  function onKeyPress(e) {
489
+ if (window.opera && e.keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
480
490
  if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
491
+ if (window.opera && !e.which && handleKeyBinding(e)) return;
481
492
  if (options.electricChars && mode.electricChars) {
482
493
  var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode);
483
494
  if (mode.electricChars.indexOf(ch) > -1)
484
- setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50);
495
+ setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
485
496
  }
486
- var code = e.keyCode;
487
- // Re-stop tab and enter. Necessary on some browsers.
488
- if (code == 13) {if (!options.readOnly) handleEnter(); e_preventDefault(e);}
489
- else if (!e.ctrlKey && !e.altKey && !e.metaKey && code == 9 && options.tabMode != "default") e_preventDefault(e);
490
- else fastPoll(curKeyId);
497
+ fastPoll();
498
+ }
499
+ function onKeyUp(e) {
500
+ if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
501
+ if (e.keyCode == 16) shiftSelecting = null;
491
502
  }
492
503
 
493
504
  function onFocus() {
494
- if (options.readOnly == "nocursor") return;
505
+ if (options.readOnly) return;
495
506
  if (!focused) {
496
507
  if (options.onFocus) options.onFocus(instance);
497
508
  focused = true;
498
509
  if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
499
510
  wrapper.className += " CodeMirror-focused";
500
- if (!leaveInputAlone) prepareInput();
511
+ if (!leaveInputAlone) resetInput(true);
501
512
  }
502
513
  slowPoll();
503
514
  restartBlink();
@@ -544,10 +555,23 @@ var CodeMirror = (function() {
544
555
  doc.iter(from.line, to.line, function(line) {
545
556
  if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
546
557
  });
558
+ if (from.line != to.line || newText.length > 1) gutterDirty = true;
547
559
 
548
560
  var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
549
561
  // First adjust the line structure, taking some care to leave highlighting intact.
550
- if (firstLine == lastLine) {
562
+ if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") {
563
+ // This is a whole-line replace. Treated specially to make
564
+ // sure line objects move the way they are supposed to.
565
+ var added = [], prevLine = null;
566
+ if (from.line) {
567
+ prevLine = getLine(from.line - 1);
568
+ prevLine.fixMarkEnds(lastLine);
569
+ } else lastLine.fixMarkStarts();
570
+ for (var i = 0, e = newText.length - 1; i < e; ++i)
571
+ added.push(Line.inheritMarks(newText[i], prevLine));
572
+ if (nlines) doc.remove(from.line, nlines, callbacks);
573
+ if (added.length) doc.insert(from.line, added);
574
+ } else if (firstLine == lastLine) {
551
575
  if (newText.length == 1)
552
576
  firstLine.replace(from.ch, to.ch, newText[0]);
553
577
  else {
@@ -560,21 +584,19 @@ var CodeMirror = (function() {
560
584
  added.push(lastLine);
561
585
  doc.insert(from.line + 1, added);
562
586
  }
563
- }
564
- else if (newText.length == 1) {
587
+ } else if (newText.length == 1) {
565
588
  firstLine.replace(from.ch, null, newText[0]);
566
589
  lastLine.replace(null, to.ch, "");
567
590
  firstLine.append(lastLine);
568
- doc.remove(from.line + 1, nlines);
569
- }
570
- else {
591
+ doc.remove(from.line + 1, nlines, callbacks);
592
+ } else {
571
593
  var added = [];
572
594
  firstLine.replace(from.ch, null, newText[0]);
573
595
  lastLine.replace(null, to.ch, newText[newText.length-1]);
574
596
  firstLine.fixMarkEnds(lastLine);
575
597
  for (var i = 1, e = newText.length - 1; i < e; ++i)
576
598
  added.push(Line.inheritMarks(newText[i], firstLine));
577
- if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
599
+ if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
578
600
  doc.insert(from.line + 1, added);
579
601
  }
580
602
  if (options.lineWrapping) {
@@ -618,7 +640,11 @@ var CodeMirror = (function() {
618
640
  startWorker(100);
619
641
  // Remember that these lines changed, for updating the display
620
642
  changes.push({from: from.line, to: to.line + 1, diff: lendiff});
621
- textChanged = {from: from, to: to, text: newText};
643
+ var changeObj = {from: from, to: to, text: newText};
644
+ if (textChanged) {
645
+ for (var cur = textChanged; cur.next; cur = cur.next) {}
646
+ cur.next = changeObj;
647
+ } else textChanged = changeObj;
622
648
 
623
649
  // Update the selection
624
650
  function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
@@ -676,124 +702,64 @@ var CodeMirror = (function() {
676
702
  var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
677
703
  function slowPoll() {
678
704
  if (pollingFast) return;
679
- poll.set(slowPollInterval, function() {
705
+ poll.set(options.pollInterval, function() {
680
706
  startOperation();
681
707
  readInput();
682
708
  if (focused) slowPoll();
683
709
  endOperation();
684
710
  });
685
711
  }
686
- function fastPoll(keyId) {
712
+ function fastPoll() {
687
713
  var missed = false;
688
714
  pollingFast = true;
689
715
  function p() {
690
716
  startOperation();
691
717
  var changed = readInput();
692
- if (changed && keyId) {
693
- if (changed == "moved" && movementKeys[keyId] == null) movementKeys[keyId] = true;
694
- if (changed == "changed") movementKeys[keyId] = false;
695
- }
696
- if (!changed && !missed) {missed = true; poll.set(80, p);}
718
+ if (!changed && !missed) {missed = true; poll.set(60, p);}
697
719
  else {pollingFast = false; slowPoll();}
698
720
  endOperation();
699
721
  }
700
722
  poll.set(20, p);
701
723
  }
702
724
 
703
- // Inspects the textarea, compares its state (content, selection)
704
- // to the data in the editing variable, and updates the editor
705
- // content or cursor if something changed.
725
+ // Previnput is a hack to work with IME. If we reset the textarea
726
+ // on every change, that breaks IME. So we look for changes
727
+ // compared to the previous content instead. (Modern browsers have
728
+ // events that indicate IME taking place, but these are not widely
729
+ // supported or compatible enough yet to rely on.)
730
+ var prevInput = "";
706
731
  function readInput() {
707
- if (leaveInputAlone || !focused) return;
708
- var changed = false, text = input.value, sr = selRange(input);
709
- if (!sr) return false;
710
- var changed = editing.text != text, rs = reducedSelection;
711
- var moved = changed || sr.start != editing.start || sr.end != (rs ? editing.start : editing.end);
712
- if (!moved && !rs) return false;
713
- if (changed) {
714
- shiftSelecting = reducedSelection = null;
715
- if (options.readOnly) {updateInput = true; return "changed";}
716
- }
717
-
718
- // Compute selection start and end based on start/end offsets in textarea
719
- function computeOffset(n, startLine) {
720
- var pos = 0;
721
- for (;;) {
722
- var found = text.indexOf("\n", pos);
723
- if (found == -1 || (text.charAt(found-1) == "\r" ? found - 1 : found) >= n)
724
- return {line: startLine, ch: n - pos};
725
- ++startLine;
726
- pos = found + 1;
727
- }
728
- }
729
- var from = computeOffset(sr.start, editing.from),
730
- to = computeOffset(sr.end, editing.from);
731
- // Here we have to take the reducedSelection hack into account,
732
- // so that you can, for example, press shift-up at the start of
733
- // your selection and have the right thing happen.
734
- if (rs) {
735
- var head = sr.start == rs.anchor ? to : from;
736
- var tail = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to;
737
- if (sel.inverted = posLess(head, tail)) { from = head; to = tail; }
738
- else { reducedSelection = null; from = tail; to = head; }
739
- }
740
-
741
- // In some cases (cursor on same line as before), we don't have
742
- // to update the textarea content at all.
743
- if (from.line == to.line && from.line == sel.from.line && from.line == sel.to.line && !shiftSelecting)
744
- updateInput = false;
745
-
746
- // Magic mess to extract precise edited range from the changed
747
- // string.
748
- if (changed) {
749
- var start = 0, end = text.length, len = Math.min(end, editing.text.length);
750
- var c, line = editing.from, nl = -1;
751
- while (start < len && (c = text.charAt(start)) == editing.text.charAt(start)) {
752
- ++start;
753
- if (c == "\n") {line++; nl = start;}
754
- }
755
- var ch = nl > -1 ? start - nl : start, endline = editing.to - 1, edend = editing.text.length;
756
- for (;;) {
757
- c = editing.text.charAt(edend);
758
- if (text.charAt(end) != c) {++end; ++edend; break;}
759
- if (c == "\n") endline--;
760
- if (edend <= start || end <= start) break;
761
- --end; --edend;
762
- }
763
- var nl = editing.text.lastIndexOf("\n", edend - 1), endch = nl == -1 ? edend : edend - nl - 1;
764
- updateLines({line: line, ch: ch}, {line: endline, ch: endch}, splitLines(text.slice(start, end)), from, to);
765
- if (line != endline || from.line != line) updateInput = true;
766
- }
767
- else setSelection(from, to);
768
-
769
- editing.text = text; editing.start = sr.start; editing.end = sr.end;
770
- return changed ? "changed" : moved ? "moved" : false;
732
+ if (leaveInputAlone || !focused || hasSelection(input)) return false;
733
+ var text = input.value;
734
+ if (text == prevInput) return false;
735
+ shiftSelecting = null;
736
+ var same = 0, l = Math.min(prevInput.length, text.length);
737
+ while (same < l && prevInput[same] == text[same]) ++same;
738
+ if (same < prevInput.length)
739
+ sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};
740
+ else if (overwrite && posEq(sel.from, sel.to))
741
+ sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};
742
+ replaceSelection(text.slice(same), "end");
743
+ prevInput = text;
744
+ return true;
771
745
  }
772
-
773
- // Set the textarea content and selection range to match the
774
- // editor state.
775
- function prepareInput() {
776
- var text = [];
777
- var from = Math.max(0, sel.from.line - 1), to = Math.min(doc.size, sel.to.line + 2);
778
- doc.iter(from, to, function(line) { text.push(line.text); });
779
- text = input.value = text.join(lineSep);
780
- var startch = sel.from.ch, endch = sel.to.ch;
781
- doc.iter(from, sel.from.line, function(line) {
782
- startch += lineSep.length + line.text.length;
783
- });
784
- doc.iter(from, sel.to.line, function(line) {
785
- endch += lineSep.length + line.text.length;
786
- });
787
- editing = {text: text, from: from, to: to, start: startch, end: endch};
788
- setSelRange(input, startch, reducedSelection ? startch : endch);
746
+ function resetInput(user) {
747
+ if (!posEq(sel.from, sel.to)) {
748
+ prevInput = "";
749
+ input.value = getSelection();
750
+ input.select();
751
+ } else if (user) prevInput = input.value = "";
789
752
  }
753
+
790
754
  function focusInput() {
791
- if (options.readOnly != "nocursor") input.focus();
755
+ if (!options.readOnly) input.focus();
792
756
  }
793
757
 
794
758
  function scrollEditorIntoView() {
795
759
  if (!cursor.getBoundingClientRect) return;
796
760
  var rect = cursor.getBoundingClientRect();
761
+ // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden
762
+ if (ie && rect.top == rect.bottom) return;
797
763
  var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
798
764
  if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();
799
765
  }
@@ -816,7 +782,7 @@ var CodeMirror = (function() {
816
782
  scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
817
783
  scrolled = true;
818
784
  }
819
- else if (x2 > screenw + screenleft) {
785
+ else if (x2 > screenw + screenleft - 3) {
820
786
  scroller.scrollLeft = x2 + 10 - screenw;
821
787
  scrolled = true;
822
788
  if (x2 > code.clientWidth) result = false;
@@ -835,7 +801,7 @@ var CodeMirror = (function() {
835
801
  // Uses a set of changes plus the current scroll position to
836
802
  // determine which DOM updates have to be made, and makes the
837
803
  // updates.
838
- function updateDisplay(changes) {
804
+ function updateDisplay(changes, suppressCallback) {
839
805
  if (!scroller.clientWidth) {
840
806
  showingFrom = showingTo = displayOffset = 0;
841
807
  return;
@@ -871,8 +837,10 @@ var CodeMirror = (function() {
871
837
 
872
838
  // Position the mover div to align with the lines it's supposed
873
839
  // to be showing (which will cover the visible display)
874
- var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
875
- if (different) lastHeight = scroller.clientHeight;
840
+ var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;
841
+ // This is just a bogus formula that detects when the editor is
842
+ // resized or the font size changes.
843
+ if (different) lastSizeC = scroller.clientHeight + th;
876
844
  showingFrom = from; showingTo = to;
877
845
  displayOffset = heightAtLine(doc, from);
878
846
  mover.style.top = (displayOffset * th) + "px";
@@ -908,6 +876,8 @@ var CodeMirror = (function() {
908
876
  gutter.style.display = gutterDisplay;
909
877
  if (different || gutterDirty) updateGutter();
910
878
  updateCursor();
879
+ if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
880
+ return true;
911
881
  }
912
882
 
913
883
  function computeIntact(intact, changes) {
@@ -966,7 +936,7 @@ var CodeMirror = (function() {
966
936
  if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
967
937
  if (!nextIntact || nextIntact.from > j) {
968
938
  if (line.hidden) scratch.innerHTML = "<pre></pre>";
969
- else scratch.innerHTML = line.getHTML(ch1, ch2, true);
939
+ else scratch.innerHTML = line.getHTML(ch1, ch2, true, tabText);
970
940
  lineDiv.insertBefore(scratch.firstChild, curNode);
971
941
  } else {
972
942
  curNode = curNode.nextSibling;
@@ -991,7 +961,7 @@ var CodeMirror = (function() {
991
961
  else if (text == null)
992
962
  text = "\u00a0";
993
963
  html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
994
- for (var j = 1; j < line.height; ++j) html.push("<br>&nbsp;");
964
+ for (var j = 1; j < line.height; ++j) html.push("<br/>&#160;");
995
965
  html.push("</pre>");
996
966
  }
997
967
  ++i;
@@ -1008,9 +978,9 @@ var CodeMirror = (function() {
1008
978
  function updateCursor() {
1009
979
  var head = sel.inverted ? sel.from : sel.to, lh = textHeight();
1010
980
  var pos = localCoords(head, true);
1011
- var globalY = pos.y + displayOffset * textHeight();
1012
- inputDiv.style.top = Math.max(Math.min(globalY, scroller.offsetHeight), 0) + "px";
1013
- inputDiv.style.left = (pos.x - scroller.scrollLeft) + "px";
981
+ var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
982
+ inputDiv.style.top = (pos.y + lineOff.top - wrapOff.top) + "px";
983
+ inputDiv.style.left = (pos.x + lineOff.left - wrapOff.left) + "px";
1014
984
  if (posEq(sel.from, sel.to)) {
1015
985
  cursor.style.top = pos.y + "px";
1016
986
  cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + "px";
@@ -1019,6 +989,10 @@ var CodeMirror = (function() {
1019
989
  else cursor.style.display = "none";
1020
990
  }
1021
991
 
992
+ function setShift(val) {
993
+ if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
994
+ else shiftSelecting = null;
995
+ }
1022
996
  function setSelectionUser(from, to) {
1023
997
  var sh = shiftSelecting && clipPos(shiftSelecting);
1024
998
  if (sh) {
@@ -1026,11 +1000,13 @@ var CodeMirror = (function() {
1026
1000
  else if (posLess(to, sh)) to = sh;
1027
1001
  }
1028
1002
  setSelection(from, to);
1003
+ userSelChange = true;
1029
1004
  }
1030
1005
  // Update the selection. Last two args are only used by
1031
1006
  // updateLines, since they have to be expressed in the line
1032
1007
  // numbers before the update.
1033
1008
  function setSelection(from, to, oldFrom, oldTo) {
1009
+ goalColumn = null;
1034
1010
  if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
1035
1011
  if (posEq(sel.from, from) && posEq(sel.to, to)) return;
1036
1012
  if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
@@ -1103,76 +1079,87 @@ var CodeMirror = (function() {
1103
1079
  else return pos;
1104
1080
  }
1105
1081
 
1106
- function scrollPage(down) {
1107
- var linesPerPage = Math.floor(scroller.clientHeight / textHeight()), head = sel.inverted ? sel.from : sel.to;
1108
- var target = heightAtLine(doc, head.line) + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1));
1109
- setCursor(lineAtHeight(doc, target), head.ch, true);
1082
+ function findPosH(dir, unit) {
1083
+ var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
1084
+ var lineObj = getLine(line);
1085
+ function findNextLine() {
1086
+ for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {
1087
+ var lo = getLine(l);
1088
+ if (!lo.hidden) { line = l; lineObj = lo; return true; }
1089
+ }
1090
+ }
1091
+ function moveOnce(boundToLine) {
1092
+ if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
1093
+ if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
1094
+ else return false;
1095
+ } else ch += dir;
1096
+ return true;
1097
+ }
1098
+ if (unit == "char") moveOnce();
1099
+ else if (unit == "column") moveOnce(true);
1100
+ else if (unit == "word") {
1101
+ var sawWord = false;
1102
+ for (;;) {
1103
+ if (dir < 0) if (!moveOnce()) break;
1104
+ if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
1105
+ else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
1106
+ if (dir > 0) if (!moveOnce()) break;
1107
+ }
1108
+ }
1109
+ return {line: line, ch: ch};
1110
+ }
1111
+ function moveH(dir, unit) {
1112
+ var pos = dir < 0 ? sel.from : sel.to;
1113
+ if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
1114
+ setCursor(pos.line, pos.ch, true);
1110
1115
  }
1111
- function scrollEnd(top) {
1112
- var pos = top ? {line: 0, ch: 0} : {line: doc.size - 1, ch: getLine(doc.size-1).text.length};
1113
- setSelectionUser(pos, pos);
1116
+ function deleteH(dir, unit) {
1117
+ if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to);
1118
+ else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to);
1119
+ else replaceRange("", sel.from, findPosH(dir, unit));
1120
+ userSelChange = true;
1114
1121
  }
1115
- function selectAll() {
1116
- var endLine = doc.size - 1;
1117
- setSelection({line: 0, ch: 0}, {line: endLine, ch: getLine(endLine).text.length});
1122
+ var goalColumn = null;
1123
+ function moveV(dir, unit) {
1124
+ var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
1125
+ if (goalColumn != null) pos.x = goalColumn;
1126
+ if (unit == "page") dist = scroller.clientHeight;
1127
+ else if (unit == "line") dist = textHeight();
1128
+ var target = coordsChar(pos.x, pos.y + dist * dir + 2);
1129
+ setCursor(target.line, target.ch, true);
1130
+ goalColumn = pos.x;
1118
1131
  }
1132
+
1119
1133
  function selectWordAt(pos) {
1120
1134
  var line = getLine(pos.line).text;
1121
1135
  var start = pos.ch, end = pos.ch;
1122
- while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
1123
- while (end < line.length && /\w/.test(line.charAt(end))) ++end;
1136
+ while (start > 0 && isWordChar(line.charAt(start - 1))) --start;
1137
+ while (end < line.length && isWordChar(line.charAt(end))) ++end;
1124
1138
  setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
1125
1139
  }
1126
1140
  function selectLine(line) {
1127
1141
  setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
1128
1142
  }
1129
- function handleEnter() {
1130
- replaceSelection("\n", "end");
1131
- if (options.enterMode != "flat")
1132
- indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart");
1133
- }
1134
- function handleTab(shift) {
1135
- function indentSelected(mode) {
1136
- if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
1137
- var e = sel.to.line - (sel.to.ch ? 0 : 1);
1138
- for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
1139
- }
1140
- shiftSelecting = null;
1141
- switch (options.tabMode) {
1142
- case "default":
1143
- return false;
1144
- case "indent":
1145
- indentSelected("smart");
1146
- break;
1147
- case "classic":
1148
- if (posEq(sel.from, sel.to)) {
1149
- if (shift) indentLine(sel.from.line, "smart");
1150
- else replaceSelection("\t", "end");
1151
- break;
1152
- }
1153
- case "shift":
1154
- indentSelected(shift ? "subtract" : "add");
1155
- break;
1156
- }
1157
- return true;
1158
- }
1159
- function smartHome() {
1160
- var firstNonWS = Math.max(0, getLine(sel.from.line).text.search(/\S/));
1161
- setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true);
1143
+ function indentSelected(mode) {
1144
+ if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
1145
+ var e = sel.to.line - (sel.to.ch ? 0 : 1);
1146
+ for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
1162
1147
  }
1163
1148
 
1164
1149
  function indentLine(n, how) {
1150
+ if (!how) how = "add";
1165
1151
  if (how == "smart") {
1166
1152
  if (!mode.indent) how = "prev";
1167
1153
  else var state = getStateBefore(n);
1168
1154
  }
1169
1155
 
1170
- var line = getLine(n), curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
1156
+ var line = getLine(n), curSpace = line.indentation(options.tabSize),
1157
+ curSpaceString = line.text.match(/^\s*/)[0], indentation;
1171
1158
  if (how == "prev") {
1172
- if (n) indentation = getLine(n-1).indentation();
1159
+ if (n) indentation = getLine(n-1).indentation(options.tabSize);
1173
1160
  else indentation = 0;
1174
1161
  }
1175
- else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length));
1162
+ else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);
1176
1163
  else if (how == "add") indentation = curSpace + options.indentUnit;
1177
1164
  else if (how == "subtract") indentation = curSpace - options.indentUnit;
1178
1165
  indentation = Math.max(0, indentation);
@@ -1185,7 +1172,7 @@ var CodeMirror = (function() {
1185
1172
  else {
1186
1173
  var indentString = "", pos = 0;
1187
1174
  if (options.indentWithTabs)
1188
- for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
1175
+ for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";}
1189
1176
  while (pos < indentation) {++pos; indentString += " ";}
1190
1177
  }
1191
1178
 
@@ -1224,18 +1211,32 @@ var CodeMirror = (function() {
1224
1211
  }
1225
1212
  changes.push({from: 0, to: doc.size});
1226
1213
  }
1214
+ function computeTabText() {
1215
+ for (var str = '<span class="cm-tab">', i = 0; i < options.tabSize; ++i) str += " ";
1216
+ return str + "</span>";
1217
+ }
1218
+ function tabsChanged() {
1219
+ tabText = computeTabText();
1220
+ updateDisplay(true);
1221
+ }
1222
+ function themeChanged() {
1223
+ scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") +
1224
+ options.theme.replace(/(^|\s)\s*/g, " cm-s-");
1225
+ }
1227
1226
 
1228
1227
  function TextMarker() { this.set = []; }
1229
1228
  TextMarker.prototype.clear = operation(function() {
1229
+ var min = Infinity, max = -Infinity;
1230
1230
  for (var i = 0, e = this.set.length; i < e; ++i) {
1231
- var mk = this.set[i].marked;
1232
- if (!mk) continue;
1231
+ var line = this.set[i], mk = line.marked;
1232
+ if (!mk || !line.parent) continue;
1233
+ var lineN = lineNo(line);
1234
+ min = Math.min(min, lineN); max = Math.max(max, lineN);
1233
1235
  for (var j = 0; j < mk.length; ++j)
1234
1236
  if (mk[j].set == this.set) mk.splice(j--, 1);
1235
1237
  }
1236
- // We don't know the exact lines that changed. Refreshing is
1237
- // cheaper than finding them.
1238
- changes.push({from: 0, to: doc.size});
1238
+ if (min != Infinity)
1239
+ changes.push({from: min, to: max + 1});
1239
1240
  });
1240
1241
  TextMarker.prototype.find = function() {
1241
1242
  var from, to;
@@ -1261,7 +1262,7 @@ var CodeMirror = (function() {
1261
1262
  from = clipPos(from); to = clipPos(to);
1262
1263
  var tm = new TextMarker();
1263
1264
  function add(line, from, to, className) {
1264
- mark = getLine(line).addMark(new MarkedText(from, to, className, tm.set));
1265
+ getLine(line).addMark(new MarkedText(from, to, className, tm.set));
1265
1266
  }
1266
1267
  if (from.line == to.line) add(from.line, from.ch, to.ch, className);
1267
1268
  else {
@@ -1299,6 +1300,7 @@ var CodeMirror = (function() {
1299
1300
  else no = lineNo(handle);
1300
1301
  if (no == null) return null;
1301
1302
  if (op(line, no)) changes.push({from: no, to: no + 1});
1303
+ else return null;
1302
1304
  return line;
1303
1305
  }
1304
1306
  function setLineClass(handle, className) {
@@ -1349,7 +1351,7 @@ var CodeMirror = (function() {
1349
1351
  if (x <= 0) return 0;
1350
1352
  var lineObj = getLine(line), text = lineObj.text;
1351
1353
  function getX(len) {
1352
- measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>";
1354
+ measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, tabText, len) + "</span></pre>";
1353
1355
  return measure.firstChild.firstChild.offsetWidth;
1354
1356
  }
1355
1357
  var from = 0, fromX = 0, to = text.length, toX;
@@ -1379,10 +1381,10 @@ var CodeMirror = (function() {
1379
1381
  // Include extra text at the end to make sure the measured line is wrapped in the right way.
1380
1382
  if (options.lineWrapping) {
1381
1383
  var end = line.text.indexOf(" ", ch + 2);
1382
- extra = line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0));
1384
+ extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0)));
1383
1385
  }
1384
- measure.innerHTML = "<pre>" + line.getHTML(null, null, false, ch) +
1385
- '<span id="CodeMirror-temp-' + tempId + '">' + (line.text.charAt(ch) || " ") + "</span>" +
1386
+ measure.innerHTML = "<pre>" + line.getHTML(null, null, false, tabText, ch) +
1387
+ '<span id="CodeMirror-temp-' + tempId + '">' + htmlEscape(line.text.charAt(ch) || " ") + "</span>" +
1386
1388
  extra + "</pre>";
1387
1389
  var elt = document.getElementById("CodeMirror-temp-" + tempId);
1388
1390
  var top = elt.offsetTop, left = elt.offsetLeft;
@@ -1410,7 +1412,7 @@ var CodeMirror = (function() {
1410
1412
  if (y < 0) y = 0;
1411
1413
  var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
1412
1414
  var lineNo = lineAtHeight(doc, heightPos);
1413
- if (lineNo >= doc.size) return {line: doc.size - 1, ch: 0};
1415
+ if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};
1414
1416
  var lineObj = getLine(lineNo), text = lineObj.text;
1415
1417
  var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
1416
1418
  if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
@@ -1447,18 +1449,25 @@ var CodeMirror = (function() {
1447
1449
  return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
1448
1450
  }
1449
1451
 
1450
- var cachedHeight, cachedFor;
1452
+ var cachedHeight, cachedHeightFor, measureText;
1451
1453
  function textHeight() {
1452
- var offsetHeight = lineDiv.offsetHeight;
1453
- if (offsetHeight == cachedFor) return cachedHeight;
1454
- cachedFor = offsetHeight;
1455
- measure.innerHTML = "<pre>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x</pre>";
1456
- return (cachedHeight = measure.firstChild.offsetHeight / 10 || 1);
1457
- }
1458
- var cachedWidth, cachedFor = 0;
1454
+ if (measureText == null) {
1455
+ measureText = "<pre>";
1456
+ for (var i = 0; i < 49; ++i) measureText += "x<br/>";
1457
+ measureText += "x</pre>";
1458
+ }
1459
+ var offsetHeight = lineDiv.clientHeight;
1460
+ if (offsetHeight == cachedHeightFor) return cachedHeight;
1461
+ cachedHeightFor = offsetHeight;
1462
+ measure.innerHTML = measureText;
1463
+ cachedHeight = measure.firstChild.offsetHeight / 50 || 1;
1464
+ measure.innerHTML = "";
1465
+ return cachedHeight;
1466
+ }
1467
+ var cachedWidth, cachedWidthFor = 0;
1459
1468
  function charWidth() {
1460
- if (scroller.clientWidth == cachedFor) return cachedWidth;
1461
- cachedFor = scroller.clientWidth;
1469
+ if (scroller.clientWidth == cachedWidthFor) return cachedWidth;
1470
+ cachedWidthFor = scroller.clientWidth;
1462
1471
  return (cachedWidth = stringWidth("x"));
1463
1472
  }
1464
1473
  function paddingTop() {return lineSpace.offsetTop;}
@@ -1490,14 +1499,14 @@ var CodeMirror = (function() {
1490
1499
  leaveInputAlone = true;
1491
1500
  var val = input.value = getSelection();
1492
1501
  focusInput();
1493
- setSelRange(input, 0, input.value.length);
1502
+ input.select();
1494
1503
  function rehide() {
1495
1504
  var newVal = splitLines(input.value).join("\n");
1496
1505
  if (newVal != val) operation(replaceSelection)(newVal, "end");
1497
1506
  inputDiv.style.position = "relative";
1498
1507
  input.style.cssText = oldCSS;
1499
1508
  leaveInputAlone = false;
1500
- prepareInput();
1509
+ resetInput(true);
1501
1510
  slowPoll();
1502
1511
  }
1503
1512
 
@@ -1574,7 +1583,7 @@ var CodeMirror = (function() {
1574
1583
  if (search == 0) return 0;
1575
1584
  var line = getLine(search-1);
1576
1585
  if (line.stateAfter) return search;
1577
- var indented = line.indentation();
1586
+ var indented = line.indentation(options.tabSize);
1578
1587
  if (minline == null || minindent > indented) {
1579
1588
  minline = search - 1;
1580
1589
  minindent = indented;
@@ -1587,7 +1596,7 @@ var CodeMirror = (function() {
1587
1596
  if (!state) state = startState(mode);
1588
1597
  else state = copyState(mode, state);
1589
1598
  doc.iter(start, n, function(line) {
1590
- line.highlight(mode, state);
1599
+ line.highlight(mode, state, options.tabSize);
1591
1600
  line.stateAfter = copyState(mode, state);
1592
1601
  });
1593
1602
  if (start < n) changes.push({from: start, to: n});
@@ -1597,7 +1606,7 @@ var CodeMirror = (function() {
1597
1606
  function highlightLines(start, end) {
1598
1607
  var state = getStateBefore(start);
1599
1608
  doc.iter(start, end, function(line) {
1600
- line.highlight(mode, state);
1609
+ line.highlight(mode, state, options.tabSize);
1601
1610
  line.stateAfter = copyState(mode, state);
1602
1611
  });
1603
1612
  }
@@ -1622,14 +1631,15 @@ var CodeMirror = (function() {
1622
1631
  if (realChange) changes.push({from: task, to: i + 1});
1623
1632
  return (bail = true);
1624
1633
  }
1625
- var changed = line.highlight(mode, state);
1634
+ var changed = line.highlight(mode, state, options.tabSize);
1626
1635
  if (changed) realChange = true;
1627
1636
  line.stateAfter = copyState(mode, state);
1628
1637
  if (compare) {
1629
1638
  if (hadState && compare(hadState, state)) return true;
1630
1639
  } else {
1631
1640
  if (changed !== false || !hadState) unchanged = 0;
1632
- else if (++unchanged > 3) return true;
1641
+ else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, "")))
1642
+ return true;
1633
1643
  }
1634
1644
  ++i;
1635
1645
  });
@@ -1649,12 +1659,13 @@ var CodeMirror = (function() {
1649
1659
  // be awkward, slow, and error-prone), but instead updates are
1650
1660
  // batched and then all combined and executed at once.
1651
1661
  function startOperation() {
1652
- updateInput = null; changes = []; textChanged = selectionChanged = false;
1662
+ updateInput = userSelChange = textChanged = null;
1663
+ changes = []; selectionChanged = false; callbacks = [];
1653
1664
  }
1654
1665
  function endOperation() {
1655
- var reScroll = false;
1666
+ var reScroll = false, updated;
1656
1667
  if (selectionChanged) reScroll = !scrollCursorIntoView();
1657
- if (changes.length) updateDisplay(changes);
1668
+ if (changes.length) updated = updateDisplay(changes, true);
1658
1669
  else {
1659
1670
  if (selectionChanged) updateCursor();
1660
1671
  if (gutterDirty) updateGutter();
@@ -1662,22 +1673,22 @@ var CodeMirror = (function() {
1662
1673
  if (reScroll) scrollCursorIntoView();
1663
1674
  if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
1664
1675
 
1665
- // updateInput can be set to a boolean value to force/prevent an
1666
- // update.
1667
1676
  if (focused && !leaveInputAlone &&
1668
1677
  (updateInput === true || (updateInput !== false && selectionChanged)))
1669
- prepareInput();
1678
+ resetInput(userSelChange);
1670
1679
 
1671
1680
  if (selectionChanged && options.matchBrackets)
1672
1681
  setTimeout(operation(function() {
1673
1682
  if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
1674
- matchBrackets(false);
1683
+ if (posEq(sel.from, sel.to)) matchBrackets(false);
1675
1684
  }), 20);
1676
- var tc = textChanged; // textChanged can be reset by cursoractivity callback
1685
+ var tc = textChanged, cbs = callbacks; // these can be reset by callbacks
1677
1686
  if (selectionChanged && options.onCursorActivity)
1678
1687
  options.onCursorActivity(instance);
1679
1688
  if (tc && options.onChange && instance)
1680
1689
  options.onChange(instance, tc);
1690
+ for (var i = 0; i < cbs.length; ++i) cbs[i](instance);
1691
+ if (updated && options.onUpdate) options.onUpdate(instance);
1681
1692
  }
1682
1693
  var nestedOperation = 0;
1683
1694
  function operation(f) {
@@ -1689,120 +1700,6 @@ var CodeMirror = (function() {
1689
1700
  };
1690
1701
  }
1691
1702
 
1692
- function SearchCursor(query, pos, caseFold) {
1693
- this.atOccurrence = false;
1694
- if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase();
1695
-
1696
- if (pos && typeof pos == "object") pos = clipPos(pos);
1697
- else pos = {line: 0, ch: 0};
1698
- this.pos = {from: pos, to: pos};
1699
-
1700
- // The matches method is filled in based on the type of query.
1701
- // It takes a position and a direction, and returns an object
1702
- // describing the next occurrence of the query, or null if no
1703
- // more matches were found.
1704
- if (typeof query != "string") // Regexp match
1705
- this.matches = function(reverse, pos) {
1706
- if (reverse) {
1707
- var line = getLine(pos.line).text.slice(0, pos.ch), match = line.match(query), start = 0;
1708
- while (match) {
1709
- var ind = line.indexOf(match[0]);
1710
- start += ind;
1711
- line = line.slice(ind + 1);
1712
- var newmatch = line.match(query);
1713
- if (newmatch) match = newmatch;
1714
- else break;
1715
- start++;
1716
- }
1717
- }
1718
- else {
1719
- var line = getLine(pos.line).text.slice(pos.ch), match = line.match(query),
1720
- start = match && pos.ch + line.indexOf(match[0]);
1721
- }
1722
- if (match)
1723
- return {from: {line: pos.line, ch: start},
1724
- to: {line: pos.line, ch: start + match[0].length},
1725
- match: match};
1726
- };
1727
- else { // String query
1728
- if (caseFold) query = query.toLowerCase();
1729
- var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
1730
- var target = query.split("\n");
1731
- // Different methods for single-line and multi-line queries
1732
- if (target.length == 1)
1733
- this.matches = function(reverse, pos) {
1734
- var line = fold(getLine(pos.line).text), len = query.length, match;
1735
- if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
1736
- : (match = line.indexOf(query, pos.ch)) != -1)
1737
- return {from: {line: pos.line, ch: match},
1738
- to: {line: pos.line, ch: match + len}};
1739
- };
1740
- else
1741
- this.matches = function(reverse, pos) {
1742
- var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(getLine(ln).text);
1743
- var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
1744
- if (reverse ? offsetA >= pos.ch || offsetA != match.length
1745
- : offsetA <= pos.ch || offsetA != line.length - match.length)
1746
- return;
1747
- for (;;) {
1748
- if (reverse ? !ln : ln == doc.size - 1) return;
1749
- line = fold(getLine(ln += reverse ? -1 : 1).text);
1750
- match = target[reverse ? --idx : ++idx];
1751
- if (idx > 0 && idx < target.length - 1) {
1752
- if (line != match) return;
1753
- else continue;
1754
- }
1755
- var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
1756
- if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
1757
- return;
1758
- var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB};
1759
- return {from: reverse ? end : start, to: reverse ? start : end};
1760
- }
1761
- };
1762
- }
1763
- }
1764
-
1765
- SearchCursor.prototype = {
1766
- findNext: function() {return this.find(false);},
1767
- findPrevious: function() {return this.find(true);},
1768
-
1769
- find: function(reverse) {
1770
- var self = this, pos = clipPos(reverse ? this.pos.from : this.pos.to);
1771
- function savePosAndFail(line) {
1772
- var pos = {line: line, ch: 0};
1773
- self.pos = {from: pos, to: pos};
1774
- self.atOccurrence = false;
1775
- return false;
1776
- }
1777
-
1778
- for (;;) {
1779
- if (this.pos = this.matches(reverse, pos)) {
1780
- this.atOccurrence = true;
1781
- return this.pos.match || true;
1782
- }
1783
- if (reverse) {
1784
- if (!pos.line) return savePosAndFail(0);
1785
- pos = {line: pos.line-1, ch: getLine(pos.line-1).text.length};
1786
- }
1787
- else {
1788
- if (pos.line == doc.size - 1) return savePosAndFail(doc.size);
1789
- pos = {line: pos.line+1, ch: 0};
1790
- }
1791
- }
1792
- },
1793
-
1794
- from: function() {if (this.atOccurrence) return copyPos(this.pos.from);},
1795
- to: function() {if (this.atOccurrence) return copyPos(this.pos.to);},
1796
-
1797
- replace: function(newText) {
1798
- var self = this;
1799
- if (this.atOccurrence)
1800
- operation(function() {
1801
- self.pos.to = replaceRange(newText, self.pos.from, self.pos.to);
1802
- })();
1803
- }
1804
- };
1805
-
1806
1703
  for (var ext in extensions)
1807
1704
  if (extensions.propertyIsEnumerable(ext) &&
1808
1705
  !instance.propertyIsEnumerable(ext))
@@ -1817,8 +1714,9 @@ var CodeMirror = (function() {
1817
1714
  theme: "default",
1818
1715
  indentUnit: 2,
1819
1716
  indentWithTabs: false,
1820
- tabMode: "classic",
1821
- enterMode: "indent",
1717
+ tabSize: 4,
1718
+ keyMap: "default",
1719
+ extraKeys: null,
1822
1720
  electricChars: true,
1823
1721
  onKeyEvent: null,
1824
1722
  lineWrapping: false,
@@ -1827,21 +1725,24 @@ var CodeMirror = (function() {
1827
1725
  fixedGutter: false,
1828
1726
  firstLineNumber: 1,
1829
1727
  readOnly: false,
1830
- smartHome: true,
1831
1728
  onChange: null,
1832
1729
  onCursorActivity: null,
1833
1730
  onGutterClick: null,
1834
1731
  onHighlightComplete: null,
1732
+ onUpdate: null,
1835
1733
  onFocus: null, onBlur: null, onScroll: null,
1836
1734
  matchBrackets: false,
1837
1735
  workTime: 100,
1838
1736
  workDelay: 200,
1737
+ pollInterval: 100,
1839
1738
  undoDepth: 40,
1840
1739
  tabindex: null,
1841
- pollForIME: false,
1842
1740
  document: window.document
1843
1741
  };
1844
1742
 
1743
+ var mac = /Mac/.test(navigator.platform);
1744
+ var win = /Win/.test(navigator.platform);
1745
+
1845
1746
  // Known modes, by name and by MIME
1846
1747
  var modes = {}, mimeModes = {};
1847
1748
  CodeMirror.defineMode = function(name, mode) {
@@ -1878,11 +1779,114 @@ var CodeMirror = (function() {
1878
1779
  return list;
1879
1780
  };
1880
1781
 
1881
- var extensions = {};
1782
+ var extensions = CodeMirror.extensions = {};
1882
1783
  CodeMirror.defineExtension = function(name, func) {
1883
1784
  extensions[name] = func;
1884
1785
  };
1885
1786
 
1787
+ var commands = CodeMirror.commands = {
1788
+ selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
1789
+ killLine: function(cm) {
1790
+ var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
1791
+ if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0});
1792
+ else cm.replaceRange("", from, sel ? to : {line: from.line});
1793
+ },
1794
+ deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});},
1795
+ undo: function(cm) {cm.undo();},
1796
+ redo: function(cm) {cm.redo();},
1797
+ goDocStart: function(cm) {cm.setCursor(0, 0, true);},
1798
+ goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
1799
+ goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
1800
+ goLineStartSmart: function(cm) {
1801
+ var cur = cm.getCursor();
1802
+ var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
1803
+ cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
1804
+ },
1805
+ goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
1806
+ goLineUp: function(cm) {cm.moveV(-1, "line");},
1807
+ goLineDown: function(cm) {cm.moveV(1, "line");},
1808
+ goPageUp: function(cm) {cm.moveV(-1, "page");},
1809
+ goPageDown: function(cm) {cm.moveV(1, "page");},
1810
+ goCharLeft: function(cm) {cm.moveH(-1, "char");},
1811
+ goCharRight: function(cm) {cm.moveH(1, "char");},
1812
+ goColumnLeft: function(cm) {cm.moveH(-1, "column");},
1813
+ goColumnRight: function(cm) {cm.moveH(1, "column");},
1814
+ goWordLeft: function(cm) {cm.moveH(-1, "word");},
1815
+ goWordRight: function(cm) {cm.moveH(1, "word");},
1816
+ delCharLeft: function(cm) {cm.deleteH(-1, "char");},
1817
+ delCharRight: function(cm) {cm.deleteH(1, "char");},
1818
+ delWordLeft: function(cm) {cm.deleteH(-1, "word");},
1819
+ delWordRight: function(cm) {cm.deleteH(1, "word");},
1820
+ indentAuto: function(cm) {cm.indentSelection("smart");},
1821
+ indentMore: function(cm) {cm.indentSelection("add");},
1822
+ indentLess: function(cm) {cm.indentSelection("subtract");},
1823
+ insertTab: function(cm) {cm.replaceSelection("\t", "end");},
1824
+ transposeChars: function(cm) {
1825
+ var cur = cm.getCursor(), line = cm.getLine(cur.line);
1826
+ if (cur.ch > 0 && cur.ch < line.length - 1)
1827
+ cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
1828
+ {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
1829
+ },
1830
+ newlineAndIndent: function(cm) {
1831
+ cm.replaceSelection("\n", "end");
1832
+ cm.indentLine(cm.getCursor().line);
1833
+ },
1834
+ toggleOverwrite: function(cm) {cm.toggleOverwrite();}
1835
+ };
1836
+
1837
+ var keyMap = CodeMirror.keyMap = {};
1838
+ keyMap.basic = {
1839
+ "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
1840
+ "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
1841
+ "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "indentMore", "Shift-Tab": "indentLess",
1842
+ "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
1843
+ };
1844
+ // Note that the save and find-related commands aren't defined by
1845
+ // default. Unknown commands are simply ignored.
1846
+ keyMap.pcDefault = {
1847
+ "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
1848
+ "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
1849
+ "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
1850
+ "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find",
1851
+ "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
1852
+ fallthrough: "basic"
1853
+ };
1854
+ keyMap.macDefault = {
1855
+ "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
1856
+ "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
1857
+ "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft",
1858
+ "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find",
1859
+ "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
1860
+ fallthrough: ["basic", "emacsy"]
1861
+ };
1862
+ keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
1863
+ keyMap.emacsy = {
1864
+ "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
1865
+ "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
1866
+ "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
1867
+ "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
1868
+ };
1869
+
1870
+ function lookupKey(name, extraMap, map) {
1871
+ function lookup(name, map, ft) {
1872
+ var found = map[name];
1873
+ if (found != null) return found;
1874
+ if (ft == null) ft = map.fallthrough;
1875
+ if (ft == null) return map.catchall;
1876
+ if (typeof ft == "string") return lookup(name, keyMap[ft]);
1877
+ for (var i = 0, e = ft.length; i < e; ++i) {
1878
+ found = lookup(name, keyMap[ft[i]]);
1879
+ if (found != null) return found;
1880
+ }
1881
+ return null;
1882
+ }
1883
+ return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]);
1884
+ }
1885
+ function isModifierKey(event) {
1886
+ var name = keyNames[event.keyCode];
1887
+ return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
1888
+ }
1889
+
1886
1890
  CodeMirror.fromTextArea = function(textarea, options) {
1887
1891
  if (!options) options = {};
1888
1892
  options.value = textarea.value;
@@ -1910,6 +1914,7 @@ var CodeMirror = (function() {
1910
1914
  textarea.parentNode.insertBefore(node, textarea.nextSibling);
1911
1915
  }, options);
1912
1916
  instance.save = save;
1917
+ instance.getTextArea = function() { return textarea; };
1913
1918
  instance.toTextArea = function() {
1914
1919
  save();
1915
1920
  textarea.parentNode.removeChild(instance.getWrapperElement());
@@ -1943,9 +1948,10 @@ var CodeMirror = (function() {
1943
1948
  CodeMirror.startState = startState;
1944
1949
 
1945
1950
  // The character stream used by a mode's parser.
1946
- function StringStream(string) {
1951
+ function StringStream(string, tabSize) {
1947
1952
  this.pos = this.start = 0;
1948
1953
  this.string = string;
1954
+ this.tabSize = tabSize || 8;
1949
1955
  }
1950
1956
  StringStream.prototype = {
1951
1957
  eol: function() {return this.pos >= this.string.length;},
@@ -1977,8 +1983,8 @@ var CodeMirror = (function() {
1977
1983
  if (found > -1) {this.pos = found; return true;}
1978
1984
  },
1979
1985
  backUp: function(n) {this.pos -= n;},
1980
- column: function() {return countColumn(this.string, this.start);},
1981
- indentation: function() {return countColumn(this.string);},
1986
+ column: function() {return countColumn(this.string, this.start, this.tabSize);},
1987
+ indentation: function() {return countColumn(this.string, null, this.tabSize);},
1982
1988
  match: function(pattern, consume, caseInsensitive) {
1983
1989
  if (typeof pattern == "string") {
1984
1990
  function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
@@ -2010,7 +2016,7 @@ var CodeMirror = (function() {
2010
2016
  if (this.to <= pos && this.to != null) return null;
2011
2017
  var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
2012
2018
  var to = this.to == null ? null : this.to - pos + lenBefore;
2013
- return new MarkedText(from, to, this.style, this.set);
2019
+ return new MarkedText(from, to, this.style, this.set);
2014
2020
  },
2015
2021
  dup: function() { return new MarkedText(null, null, this.style, this.set); },
2016
2022
  clipTo: function(fromOpen, from, toOpen, to, diff) {
@@ -2067,11 +2073,11 @@ var CodeMirror = (function() {
2067
2073
  this.styles = styles || [text, null];
2068
2074
  this.text = text;
2069
2075
  this.height = 1;
2070
- this.marked = this.gutterMarker = this.className = null;
2076
+ this.marked = this.gutterMarker = this.className = this.handlers = null;
2071
2077
  this.stateAfter = this.parent = this.hidden = null;
2072
2078
  }
2073
2079
  Line.inheritMarks = function(text, orig) {
2074
- var ln = new Line(text), mk = orig.marked;
2080
+ var ln = new Line(text), mk = orig && orig.marked;
2075
2081
  if (mk) {
2076
2082
  for (var i = 0; i < mk.length; ++i) {
2077
2083
  if (mk[i].to == null && mk[i].style) {
@@ -2085,9 +2091,6 @@ var CodeMirror = (function() {
2085
2091
  Line.prototype = {
2086
2092
  // Replace a piece of a line, keeping the styles around it intact.
2087
2093
  replace: function(from, to_, text) {
2088
- // Reset line class if the whole text was replaced.
2089
- if (!from && (to_ == null || to_ == this.text.length))
2090
- this.className = this.gutterMarker = null;
2091
2094
  var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
2092
2095
  copyStyles(0, from, this.styles, st);
2093
2096
  if (text) st.push(text, null);
@@ -2164,6 +2167,12 @@ var CodeMirror = (function() {
2164
2167
  if (close) mark.to = this.text.length;
2165
2168
  }
2166
2169
  },
2170
+ fixMarkStarts: function() {
2171
+ var mk = this.marked;
2172
+ if (!mk) return;
2173
+ for (var i = 0; i < mk.length; ++i)
2174
+ if (mk[i].from == null) mk[i].from = 0;
2175
+ },
2167
2176
  addMark: function(mark) {
2168
2177
  mark.attach(this);
2169
2178
  if (this.marked == null) this.marked = [];
@@ -2173,8 +2182,8 @@ var CodeMirror = (function() {
2173
2182
  // Run the given mode's parser over a line, update the styles
2174
2183
  // array, which contains alternating fragments of text and CSS
2175
2184
  // classes.
2176
- highlight: function(mode, state) {
2177
- var stream = new StringStream(this.text), st = this.styles, pos = 0;
2185
+ highlight: function(mode, state, tabSize) {
2186
+ var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;
2178
2187
  var changed = false, curWord = st[0], prevWord;
2179
2188
  if (this.text == "" && mode.blankLine) mode.blankLine(state);
2180
2189
  while (!stream.eol()) {
@@ -2215,10 +2224,10 @@ var CodeMirror = (function() {
2215
2224
  className: style || null,
2216
2225
  state: state};
2217
2226
  },
2218
- indentation: function() {return countColumn(this.text);},
2227
+ indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
2219
2228
  // Produces an HTML fragment for the line, taking selection,
2220
2229
  // marking, and highlighting into account.
2221
- getHTML: function(sfrom, sto, includePre, endAt) {
2230
+ getHTML: function(sfrom, sto, includePre, tabText, endAt) {
2222
2231
  var html = [], first = true;
2223
2232
  if (includePre)
2224
2233
  html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
@@ -2227,8 +2236,8 @@ var CodeMirror = (function() {
2227
2236
  // Work around a bug where, in some compat modes, IE ignores leading spaces
2228
2237
  if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
2229
2238
  first = false;
2230
- if (style) html.push('<span class="', style, '">', htmlEscape(text), "</span>");
2231
- else html.push(htmlEscape(text));
2239
+ if (style) html.push('<span class="', style, '">', htmlEscape(text).replace(/\t/g, tabText), "</span>");
2240
+ else html.push(htmlEscape(text).replace(/\t/g, tabText));
2232
2241
  }
2233
2242
  var st = this.styles, allText = this.text, marked = this.marked;
2234
2243
  if (sfrom == sto) sfrom = null;
@@ -2321,11 +2330,13 @@ var CodeMirror = (function() {
2321
2330
  }
2322
2331
  LeafChunk.prototype = {
2323
2332
  chunkSize: function() { return this.lines.length; },
2324
- remove: function(at, n) {
2333
+ remove: function(at, n, callbacks) {
2325
2334
  for (var i = at, e = at + n; i < e; ++i) {
2326
2335
  var line = this.lines[i];
2327
- line.cleanUp();
2328
2336
  this.height -= line.height;
2337
+ line.cleanUp();
2338
+ if (line.handlers)
2339
+ for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);
2329
2340
  }
2330
2341
  this.lines.splice(at, n);
2331
2342
  },
@@ -2356,13 +2367,13 @@ var CodeMirror = (function() {
2356
2367
  }
2357
2368
  BranchChunk.prototype = {
2358
2369
  chunkSize: function() { return this.size; },
2359
- remove: function(at, n) {
2370
+ remove: function(at, n, callbacks) {
2360
2371
  this.size -= n;
2361
2372
  for (var i = 0; i < this.children.length; ++i) {
2362
2373
  var child = this.children[i], sz = child.chunkSize();
2363
2374
  if (at < sz) {
2364
2375
  var rm = Math.min(n, sz - at), oldHeight = child.height;
2365
- child.remove(at, rm);
2376
+ child.remove(at, rm, callbacks);
2366
2377
  this.height -= oldHeight - child.height;
2367
2378
  if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
2368
2379
  if ((n -= rm) == 0) break;
@@ -2441,14 +2452,14 @@ var CodeMirror = (function() {
2441
2452
  };
2442
2453
 
2443
2454
  function getLineAt(chunk, n) {
2444
- for (;;) {
2445
- for (var i = 0, e = chunk.children.length; i < e; ++i) {
2455
+ while (!chunk.lines) {
2456
+ for (var i = 0;; ++i) {
2446
2457
  var child = chunk.children[i], sz = child.chunkSize();
2447
2458
  if (n < sz) { chunk = child; break; }
2448
2459
  n -= sz;
2449
2460
  }
2450
- if (chunk.lines) return chunk.lines[n];
2451
2461
  }
2462
+ return chunk.lines[n];
2452
2463
  }
2453
2464
  function lineNo(line) {
2454
2465
  if (line.parent == null) return null;
@@ -2543,6 +2554,10 @@ var CodeMirror = (function() {
2543
2554
  else e.cancelBubble = true;
2544
2555
  }
2545
2556
  function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
2557
+ CodeMirror.e_stop = e_stop;
2558
+ CodeMirror.e_preventDefault = e_preventDefault;
2559
+ CodeMirror.e_stopPropagation = e_stopPropagation;
2560
+
2546
2561
  function e_target(e) {return e.target || e.srcElement;}
2547
2562
  function e_button(e) {
2548
2563
  if (e.which) return e.which;
@@ -2554,16 +2569,17 @@ var CodeMirror = (function() {
2554
2569
  // Event handler registration. If disconnect is true, it'll return a
2555
2570
  // function that unregisters the handler.
2556
2571
  function connect(node, type, handler, disconnect) {
2557
- function wrapHandler(event) {handler(event || window.event);}
2558
2572
  if (typeof node.addEventListener == "function") {
2559
- node.addEventListener(type, wrapHandler, false);
2560
- if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);};
2573
+ node.addEventListener(type, handler, false);
2574
+ if (disconnect) return function() {node.removeEventListener(type, handler, false);};
2561
2575
  }
2562
2576
  else {
2577
+ var wrapHandler = function(event) {handler(event || window.event);};
2563
2578
  node.attachEvent("on" + type, wrapHandler);
2564
2579
  if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
2565
2580
  }
2566
2581
  }
2582
+ CodeMirror.connect = connect;
2567
2583
 
2568
2584
  function Delayed() {this.id = null;}
2569
2585
  Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
@@ -2589,16 +2605,9 @@ var CodeMirror = (function() {
2589
2605
  if (te.value.indexOf("\r") > -1) lineSep = "\r\n";
2590
2606
  }());
2591
2607
 
2592
- var tabSize = 8;
2593
- var mac = /Mac/.test(navigator.platform);
2594
- var win = /Win/.test(navigator.platform);
2595
- var movementKeys = {};
2596
- for (var i = 35; i <= 40; ++i)
2597
- movementKeys[i] = movementKeys["c" + i] = true;
2598
-
2599
2608
  // Counts the column offset in a string, taking tabs into account.
2600
2609
  // Used mostly to find indentation.
2601
- function countColumn(string, end) {
2610
+ function countColumn(string, end, tabSize) {
2602
2611
  if (end == null) {
2603
2612
  end = string.search(/[^\s\u00a0]/);
2604
2613
  if (end == -1) end = string.length;
@@ -2664,15 +2673,23 @@ var CodeMirror = (function() {
2664
2673
 
2665
2674
  var escapeElement = document.createElement("pre");
2666
2675
  function htmlEscape(str) {
2667
- if (badTextContent) {
2668
- escapeElement.innerHTML = "";
2669
- escapeElement.appendChild(document.createTextNode(str));
2670
- } else {
2671
- escapeElement.textContent = str;
2672
- }
2676
+ escapeElement.textContent = str;
2673
2677
  return escapeElement.innerHTML;
2674
2678
  }
2675
- var badTextContent = htmlEscape("\t") != "\t";
2679
+ // Recent (late 2011) Opera betas insert bogus newlines at the start
2680
+ // of the textContent, so we strip those.
2681
+ if (htmlEscape("a") == "\na")
2682
+ htmlEscape = function(str) {
2683
+ escapeElement.textContent = str;
2684
+ return escapeElement.innerHTML.slice(1);
2685
+ };
2686
+ // Some IEs don't preserve tabs through innerHTML
2687
+ else if (htmlEscape("\t") != "\t")
2688
+ htmlEscape = function(str) {
2689
+ escapeElement.innerHTML = "";
2690
+ escapeElement.appendChild(document.createTextNode(str));
2691
+ return escapeElement.innerHTML;
2692
+ };
2676
2693
  CodeMirror.htmlEscape = htmlEscape;
2677
2694
 
2678
2695
  // Used to position the cursor after an undo/redo by finding the
@@ -2691,95 +2708,54 @@ var CodeMirror = (function() {
2691
2708
  if (collection[i] == elt) return i;
2692
2709
  return -1;
2693
2710
  }
2711
+ function isWordChar(ch) {
2712
+ return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();
2713
+ }
2694
2714
 
2695
2715
  // See if "".split is the broken IE version, if so, provide an
2696
2716
  // alternative way to split lines.
2697
- var splitLines, selRange, setSelRange;
2698
- if ("\n\nb".split(/\n/).length != 3)
2699
- splitLines = function(string) {
2700
- var pos = 0, nl, result = [];
2701
- while ((nl = string.indexOf("\n", pos)) > -1) {
2702
- result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
2703
- pos = nl + 1;
2704
- }
2705
- result.push(string.slice(pos));
2706
- return result;
2707
- };
2708
- else
2709
- splitLines = function(string){return string.split(/\r?\n/);};
2717
+ var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
2718
+ var pos = 0, nl, result = [];
2719
+ while ((nl = string.indexOf("\n", pos)) > -1) {
2720
+ result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
2721
+ pos = nl + 1;
2722
+ }
2723
+ result.push(string.slice(pos));
2724
+ return result;
2725
+ } : function(string){return string.split(/\r?\n/);};
2710
2726
  CodeMirror.splitLines = splitLines;
2711
2727
 
2712
- // Sane model of finding and setting the selection in a textarea
2713
- if (window.getSelection) {
2714
- selRange = function(te) {
2715
- try {return {start: te.selectionStart, end: te.selectionEnd};}
2716
- catch(e) {return null;}
2717
- };
2718
- if (webkit)
2719
- // On Safari, selection set with setSelectionRange are in a sort
2720
- // of limbo wrt their anchor. If you press shift-left in them,
2721
- // the anchor is put at the end, and the selection expanded to
2722
- // the left. If you press shift-right, the anchor ends up at the
2723
- // front. This is not what CodeMirror wants, so it does a
2724
- // spurious modify() call to get out of limbo.
2725
- setSelRange = function(te, start, end) {
2726
- if (start == end)
2727
- te.setSelectionRange(start, end);
2728
- else {
2729
- te.setSelectionRange(start, end - 1);
2730
- window.getSelection().modify("extend", "forward", "character");
2731
- }
2732
- };
2733
- else
2734
- setSelRange = function(te, start, end) {
2735
- try {te.setSelectionRange(start, end);}
2736
- catch(e) {} // Fails on Firefox when textarea isn't part of the document
2737
- };
2738
- }
2739
- // IE model. Don't ask.
2740
- else {
2741
- selRange = function(te) {
2742
- try {var range = te.ownerDocument.selection.createRange();}
2743
- catch(e) {return null;}
2744
- if (!range || range.parentElement() != te) return null;
2745
- var val = te.value, len = val.length, localRange = te.createTextRange();
2746
- localRange.moveToBookmark(range.getBookmark());
2747
- var endRange = te.createTextRange();
2748
- endRange.collapse(false);
2749
-
2750
- if (localRange.compareEndPoints("StartToEnd", endRange) > -1)
2751
- return {start: len, end: len};
2752
-
2753
- var start = -localRange.moveStart("character", -len);
2754
- for (var i = val.indexOf("\r"); i > -1 && i < start; i = val.indexOf("\r", i+1), start++) {}
2755
-
2756
- if (localRange.compareEndPoints("EndToEnd", endRange) > -1)
2757
- return {start: start, end: len};
2758
-
2759
- var end = -localRange.moveEnd("character", -len);
2760
- for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {}
2761
- return {start: start, end: end};
2762
- };
2763
- setSelRange = function(te, start, end) {
2764
- var range = te.createTextRange();
2765
- range.collapse(true);
2766
- var endrange = range.duplicate();
2767
- var newlines = 0, txt = te.value;
2768
- for (var pos = txt.indexOf("\n"); pos > -1 && pos < start; pos = txt.indexOf("\n", pos + 1))
2769
- ++newlines;
2770
- range.move("character", start - newlines);
2771
- for (; pos > -1 && pos < end; pos = txt.indexOf("\n", pos + 1))
2772
- ++newlines;
2773
- endrange.move("character", end - newlines);
2774
- range.setEndPoint("EndToEnd", endrange);
2775
- range.select();
2776
- };
2777
- }
2728
+ var hasSelection = window.getSelection ? function(te) {
2729
+ try { return te.selectionStart != te.selectionEnd; }
2730
+ catch(e) { return false; }
2731
+ } : function(te) {
2732
+ try {var range = te.ownerDocument.selection.createRange();}
2733
+ catch(e) {}
2734
+ if (!range || range.parentElement() != te) return false;
2735
+ return range.compareEndPoints("StartToEnd", range) != 0;
2736
+ };
2778
2737
 
2779
2738
  CodeMirror.defineMode("null", function() {
2780
2739
  return {token: function(stream) {stream.skipToEnd();}};
2781
2740
  });
2782
2741
  CodeMirror.defineMIME("text/plain", "null");
2783
2742
 
2743
+ var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
2744
+ 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
2745
+ 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
2746
+ 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 186: ";", 187: "=", 188: ",",
2747
+ 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63276: "PageUp",
2748
+ 63277: "PageDown", 63275: "End", 63273: "Home", 63234: "Left", 63232: "Up", 63235: "Right",
2749
+ 63233: "Down", 63302: "Insert", 63272: "Delete"};
2750
+ CodeMirror.keyNames = keyNames;
2751
+ (function() {
2752
+ // Number keys
2753
+ for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
2754
+ // Alphabetic keys
2755
+ for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
2756
+ // Function keys
2757
+ for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
2758
+ })();
2759
+
2784
2760
  return CodeMirror;
2785
2761
  })();