codemirror-rails 3.02 → 3.12

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.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +1 -1
  3. data/LICENSE +1 -1
  4. data/codemirror-rails.gemspec +1 -0
  5. data/doc/CodeMirror-LICENSE +5 -1
  6. data/lib/codemirror/rails/version.rb +2 -2
  7. data/vendor/assets/javascripts/codemirror/{utils → addons/dialog}/dialog.js +4 -0
  8. data/vendor/assets/javascripts/codemirror/addons/display/placeholder.js +54 -0
  9. data/vendor/assets/javascripts/codemirror/addons/edit/closebrackets.js +54 -0
  10. data/vendor/assets/javascripts/codemirror/{utils → addons/edit}/closetag.js +9 -8
  11. data/vendor/assets/javascripts/codemirror/{utils → addons/edit}/continuecomment.js +12 -4
  12. data/vendor/assets/javascripts/codemirror/addons/edit/continuelist.js +25 -0
  13. data/vendor/assets/javascripts/codemirror/{utils → addons/edit}/matchbrackets.js +30 -11
  14. data/vendor/assets/javascripts/codemirror/addons/fold/brace-fold.js +31 -0
  15. data/vendor/assets/javascripts/codemirror/addons/fold/foldcode.js +32 -0
  16. data/vendor/assets/javascripts/codemirror/addons/fold/indent-fold.js +11 -0
  17. data/vendor/assets/javascripts/codemirror/addons/fold/xml-fold.js +64 -0
  18. data/vendor/assets/javascripts/codemirror/addons/hint/html-hint.js +582 -0
  19. data/vendor/assets/javascripts/codemirror/{utils → addons/hint}/javascript-hint.js +15 -11
  20. data/vendor/assets/javascripts/codemirror/{utils → addons/hint}/pig-hint.js +19 -19
  21. data/vendor/assets/javascripts/codemirror/{utils → addons/hint}/python-hint.js +2 -2
  22. data/vendor/assets/javascripts/codemirror/addons/hint/show-hint.js +180 -0
  23. data/vendor/assets/javascripts/codemirror/{utils → addons/hint}/xml-hint.js +5 -18
  24. data/vendor/assets/javascripts/codemirror/addons/lint/javascript-lint.js +127 -0
  25. data/vendor/assets/javascripts/codemirror/addons/lint/json-lint.js +14 -0
  26. data/vendor/assets/javascripts/codemirror/addons/lint/lint.js +197 -0
  27. data/vendor/assets/javascripts/codemirror/{utils → addons/mode}/loadmode.js +0 -0
  28. data/vendor/assets/javascripts/codemirror/{utils → addons/mode}/multiplex.js +2 -2
  29. data/vendor/assets/javascripts/codemirror/{utils → addons/mode}/overlay.js +2 -2
  30. data/vendor/assets/javascripts/codemirror/{utils → addons/runmode}/colorize.js +0 -0
  31. data/vendor/assets/javascripts/codemirror/{utils → addons/runmode}/runmode-standalone.js +2 -3
  32. data/vendor/assets/javascripts/codemirror/{utils → addons/runmode}/runmode.js +0 -0
  33. data/vendor/assets/javascripts/codemirror/addons/runmode/runmode.node.js +89 -0
  34. data/vendor/assets/javascripts/codemirror/addons/search/match-highlighter.js +60 -0
  35. data/vendor/assets/javascripts/codemirror/{utils → addons/search}/search.js +5 -5
  36. data/vendor/assets/javascripts/codemirror/{utils → addons/search}/searchcursor.js +37 -30
  37. data/vendor/assets/javascripts/codemirror/addons/selection/active-line.js +39 -0
  38. data/vendor/assets/javascripts/codemirror/addons/selection/mark-selection.js +34 -0
  39. data/vendor/assets/javascripts/codemirror/keymaps/vim.js +721 -188
  40. data/vendor/assets/javascripts/codemirror/modes/asterisk.js +6 -6
  41. data/vendor/assets/javascripts/codemirror/modes/clike.js +14 -14
  42. data/vendor/assets/javascripts/codemirror/modes/clojure.js +23 -7
  43. data/vendor/assets/javascripts/codemirror/modes/coffeescript.js +2 -2
  44. data/vendor/assets/javascripts/codemirror/modes/css.js +337 -235
  45. data/vendor/assets/javascripts/codemirror/modes/ecl.js +12 -12
  46. data/vendor/assets/javascripts/codemirror/modes/erlang.js +1 -1
  47. data/vendor/assets/javascripts/codemirror/modes/gas.js +326 -0
  48. data/vendor/assets/javascripts/codemirror/modes/gfm.js +1 -0
  49. data/vendor/assets/javascripts/codemirror/modes/haskell.js +26 -26
  50. data/vendor/assets/javascripts/codemirror/modes/haxe.js +17 -17
  51. data/vendor/assets/javascripts/codemirror/modes/htmlembedded.js +6 -6
  52. data/vendor/assets/javascripts/codemirror/modes/htmlmixed.js +43 -23
  53. data/vendor/assets/javascripts/codemirror/modes/javascript.js +78 -33
  54. data/vendor/assets/javascripts/codemirror/modes/jinja2.js +2 -2
  55. data/vendor/assets/javascripts/codemirror/modes/less.js +38 -38
  56. data/vendor/assets/javascripts/codemirror/modes/livescript.js +267 -0
  57. data/vendor/assets/javascripts/codemirror/modes/lua.js +7 -7
  58. data/vendor/assets/javascripts/codemirror/modes/markdown.js +108 -57
  59. data/vendor/assets/javascripts/codemirror/modes/mirc.js +177 -0
  60. data/vendor/assets/javascripts/codemirror/modes/ntriples.js +22 -22
  61. data/vendor/assets/javascripts/codemirror/modes/ocaml.js +1 -1
  62. data/vendor/assets/javascripts/codemirror/modes/perl.js +791 -791
  63. data/vendor/assets/javascripts/codemirror/modes/php.js +1 -1
  64. data/vendor/assets/javascripts/codemirror/modes/pig.js +163 -163
  65. data/vendor/assets/javascripts/codemirror/modes/python.js +31 -31
  66. data/vendor/assets/javascripts/codemirror/modes/q.js +124 -0
  67. data/vendor/assets/javascripts/codemirror/modes/rst.js +486 -250
  68. data/vendor/assets/javascripts/codemirror/modes/sass.js +3 -3
  69. data/vendor/assets/javascripts/codemirror/modes/scss_test.js +80 -0
  70. data/vendor/assets/javascripts/codemirror/modes/shell.js +2 -2
  71. data/vendor/assets/javascripts/codemirror/modes/sieve.js +10 -10
  72. data/vendor/assets/javascripts/codemirror/modes/smalltalk.js +129 -129
  73. data/vendor/assets/javascripts/codemirror/modes/smarty.js +3 -3
  74. data/vendor/assets/javascripts/codemirror/modes/sparql.js +1 -1
  75. data/vendor/assets/javascripts/codemirror/modes/sql.js +23 -23
  76. data/vendor/assets/javascripts/codemirror/modes/stex.js +192 -121
  77. data/vendor/assets/javascripts/codemirror/modes/tcl.js +131 -0
  78. data/vendor/assets/javascripts/codemirror/modes/test.js +64 -0
  79. data/vendor/assets/javascripts/codemirror/modes/tiddlywiki.js +345 -345
  80. data/vendor/assets/javascripts/codemirror/modes/tiki.js +297 -298
  81. data/vendor/assets/javascripts/codemirror/modes/turtle.js +145 -0
  82. data/vendor/assets/javascripts/codemirror/modes/vb.js +31 -32
  83. data/vendor/assets/javascripts/codemirror/modes/vbscript.js +4 -4
  84. data/vendor/assets/javascripts/codemirror/modes/xml.js +10 -6
  85. data/vendor/assets/javascripts/codemirror/modes/xquery.js +55 -55
  86. data/vendor/assets/javascripts/codemirror/modes/yaml.js +90 -90
  87. data/vendor/assets/javascripts/codemirror/modes/z80.js +82 -110
  88. data/vendor/assets/javascripts/codemirror.js +1914 -1115
  89. data/vendor/assets/stylesheets/codemirror/{utils → addons/dialog}/dialog.css +0 -0
  90. data/vendor/assets/stylesheets/codemirror/addons/hint/show-hint.css +38 -0
  91. data/vendor/assets/stylesheets/codemirror/addons/lint/lint.css +96 -0
  92. data/vendor/assets/stylesheets/codemirror/modes/tiki.css +2 -2
  93. data/vendor/assets/stylesheets/codemirror/themes/ambiance-mobile.css +0 -1
  94. data/vendor/assets/stylesheets/codemirror/themes/ambiance.css +0 -1
  95. data/vendor/assets/stylesheets/codemirror/themes/eclipse.css +2 -2
  96. data/vendor/assets/stylesheets/codemirror/themes/erlang-dark.css +5 -5
  97. data/vendor/assets/stylesheets/codemirror/themes/midnight.css +52 -0
  98. data/vendor/assets/stylesheets/codemirror/themes/xq-light.css +43 -0
  99. data/vendor/assets/stylesheets/codemirror.css +16 -10
  100. metadata +60 -52
  101. data/vendor/assets/javascripts/codemirror/modes/mysql.js +0 -203
  102. data/vendor/assets/javascripts/codemirror/modes/plsql.js +0 -216
  103. data/vendor/assets/javascripts/codemirror/utils/collapserange.js +0 -68
  104. data/vendor/assets/javascripts/codemirror/utils/continuelist.js +0 -28
  105. data/vendor/assets/javascripts/codemirror/utils/foldcode.js +0 -183
  106. data/vendor/assets/javascripts/codemirror/utils/formatting.js +0 -114
  107. data/vendor/assets/javascripts/codemirror/utils/match-highlighter.js +0 -46
  108. data/vendor/assets/javascripts/codemirror/utils/simple-hint.js +0 -102
  109. data/vendor/assets/stylesheets/codemirror/utils/simple-hint.css +0 -16
@@ -1,4 +1,4 @@
1
- // CodeMirror version 3.02
1
+ // CodeMirror version 3.12
2
2
  //
3
3
  // CodeMirror is the only global var we claim
4
4
  window.CodeMirror = (function() {
@@ -32,6 +32,7 @@ window.CodeMirror = (function() {
32
32
  if (opera_version) opera_version = Number(opera_version[1]);
33
33
  // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
34
34
  var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
35
+ var captureMiddleClick = gecko || (ie && !ie_lt9);
35
36
 
36
37
  // Optimize some code when these features are not used
37
38
  var sawReadOnlySpans = false, sawCollapsedSpans = false;
@@ -40,31 +41,38 @@ window.CodeMirror = (function() {
40
41
 
41
42
  function CodeMirror(place, options) {
42
43
  if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
43
-
44
+
44
45
  this.options = options = options || {};
45
46
  // Determine effective options based on given values and defaults.
46
47
  for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
47
48
  options[opt] = defaults[opt];
48
49
  setGuttersForLineNumbers(options);
49
50
 
50
- var display = this.display = makeDisplay(place);
51
+ var docStart = typeof options.value == "string" ? 0 : options.value.first;
52
+ var display = this.display = makeDisplay(place, docStart);
51
53
  display.wrapper.CodeMirror = this;
52
54
  updateGutters(this);
53
55
  if (options.autofocus && !mobile) focusInput(this);
54
56
 
55
- this.view = makeView(new BranchChunk([new LeafChunk([makeLine("", null, textHeight(display))])]));
56
- this.nextOpId = 0;
57
- loadMode(this);
57
+ this.state = {keyMaps: [],
58
+ overlays: [],
59
+ modeGen: 0,
60
+ overwrite: false, focused: false,
61
+ suppressEdits: false, pasteIncoming: false,
62
+ draggingText: false,
63
+ highlight: new Delayed()};
64
+
58
65
  themeChanged(this);
59
66
  if (options.lineWrapping)
60
67
  this.display.wrapper.className += " CodeMirror-wrap";
61
68
 
62
- // Initialize the content.
63
- this.setValue(options.value || "");
69
+ var doc = options.value;
70
+ if (typeof doc == "string") doc = new Doc(options.value, options.mode);
71
+ operation(this, attachDoc)(this, doc);
72
+
64
73
  // Override magic textarea content restore that IE sometimes does
65
74
  // on our hidden textarea on reload
66
75
  if (ie) setTimeout(bind(resetInput, this, true), 20);
67
- this.view.history = makeHistory();
68
76
 
69
77
  registerEventHandlers(this);
70
78
  // IE throws unspecified error in certain cases, when
@@ -83,12 +91,16 @@ window.CodeMirror = (function() {
83
91
 
84
92
  // DISPLAY CONSTRUCTOR
85
93
 
86
- function makeDisplay(place) {
94
+ function makeDisplay(place, docStart) {
87
95
  var d = {};
88
- var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;");
96
+
97
+ var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");
89
98
  if (webkit) input.style.width = "1000px";
90
99
  else input.setAttribute("wrap", "off");
100
+ // if border: 0; -- iOS fails to open keyboard (issue #1287)
101
+ if (ios) input.style.border = "1px solid black";
91
102
  input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
103
+
92
104
  // Wraps and hides input textarea
93
105
  d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
94
106
  // The actual fake scrollbars.
@@ -112,7 +124,7 @@ window.CodeMirror = (function() {
112
124
  // Set to the height of the text, causes scrolling
113
125
  d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
114
126
  // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
115
- d.heightForcer = elt("div", "\u00a0", null, "position: absolute; height: " + scrollerCutOff + "px");
127
+ d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
116
128
  // Will contain the gutters, if any
117
129
  d.gutters = elt("div", null, "CodeMirror-gutters");
118
130
  d.lineGutter = null;
@@ -137,7 +149,8 @@ window.CodeMirror = (function() {
137
149
  else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
138
150
 
139
151
  // Current visible range (may be bigger than the view window).
140
- d.viewOffset = d.showingFrom = d.showingTo = d.lastSizeC = 0;
152
+ d.viewOffset = d.lastSizeC = 0;
153
+ d.showingFrom = d.showingTo = docStart;
141
154
 
142
155
  // Used to only resize the line number gutter when necessary (when
143
156
  // the amount of lines crosses a boundary that makes its width change)
@@ -153,8 +166,6 @@ window.CodeMirror = (function() {
153
166
  d.pollingFast = false;
154
167
  // Self-resetting timeout for the poller
155
168
  d.poll = new Delayed();
156
- // True when a drag from the editor is active
157
- d.draggingText = false;
158
169
 
159
170
  d.cachedCharWidth = d.cachedTextHeight = null;
160
171
  d.measureLineCache = [];
@@ -164,40 +175,16 @@ window.CodeMirror = (function() {
164
175
  // string instead of the (large) selection.
165
176
  d.inaccurateSelection = false;
166
177
 
167
- // Used to adjust overwrite behaviour when a paste has been
168
- // detected
169
- d.pasteIncoming = false;
178
+ // Tracks the maximum line length so that the horizontal scrollbar
179
+ // can be kept static when scrolling.
180
+ d.maxLine = null;
181
+ d.maxLineLength = 0;
182
+ d.maxLineChanged = false;
170
183
 
171
184
  // Used for measuring wheel scrolling granularity
172
185
  d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
173
-
174
- return d;
175
- }
176
-
177
- // VIEW CONSTRUCTOR
178
186
 
179
- function makeView(doc) {
180
- var selPos = {line: 0, ch: 0};
181
- return {
182
- doc: doc,
183
- // frontier is the point up to which the content has been parsed,
184
- frontier: 0, highlight: new Delayed(),
185
- sel: {from: selPos, to: selPos, head: selPos, anchor: selPos, shift: false, extend: false},
186
- scrollTop: 0, scrollLeft: 0,
187
- overwrite: false, focused: false,
188
- // Tracks the maximum line length so that
189
- // the horizontal scrollbar can be kept
190
- // static when scrolling.
191
- maxLine: getLine(doc, 0),
192
- maxLineLength: 0,
193
- maxLineChanged: false,
194
- suppressEdits: false,
195
- goalColumn: null,
196
- cantEdit: false,
197
- keyMaps: [],
198
- overlays: [],
199
- modeGen: 0
200
- };
187
+ return d;
201
188
  }
202
189
 
203
190
  // STATE UPDATES
@@ -205,39 +192,50 @@ window.CodeMirror = (function() {
205
192
  // Used to get the editor into a consistent state again when options change.
206
193
 
207
194
  function loadMode(cm) {
208
- var doc = cm.view.doc;
209
- cm.view.mode = CodeMirror.getMode(cm.options, cm.options.mode);
210
- doc.iter(0, doc.size, function(line) {
195
+ cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
196
+ cm.doc.iter(function(line) {
211
197
  if (line.stateAfter) line.stateAfter = null;
212
198
  if (line.styles) line.styles = null;
213
199
  });
214
- cm.view.frontier = 0;
200
+ cm.doc.frontier = cm.doc.first;
215
201
  startWorker(cm, 100);
216
- cm.view.modeGen++;
217
- if (cm.curOp) regChange(cm, 0, doc.size);
202
+ cm.state.modeGen++;
203
+ if (cm.curOp) regChange(cm);
218
204
  }
219
205
 
220
206
  function wrappingChanged(cm) {
221
- var doc = cm.view.doc, th = textHeight(cm.display);
222
207
  if (cm.options.lineWrapping) {
223
208
  cm.display.wrapper.className += " CodeMirror-wrap";
224
- var perLine = cm.display.scroller.clientWidth / charWidth(cm.display) - 3;
225
- doc.iter(0, doc.size, function(line) {
226
- if (line.height == 0) return;
227
- var guess = Math.ceil(line.text.length / perLine) || 1;
228
- if (guess != 1) updateLineHeight(line, guess * th);
229
- });
230
209
  cm.display.sizer.style.minWidth = "";
231
210
  } else {
232
211
  cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
233
- computeMaxLength(cm.view);
234
- doc.iter(0, doc.size, function(line) {
235
- if (line.height != 0) updateLineHeight(line, th);
236
- });
212
+ computeMaxLength(cm);
237
213
  }
238
- regChange(cm, 0, doc.size);
214
+ estimateLineHeights(cm);
215
+ regChange(cm);
239
216
  clearCaches(cm);
240
- setTimeout(function(){updateScrollbars(cm.display, cm.view.doc.height);}, 100);
217
+ setTimeout(function(){updateScrollbars(cm.display, cm.doc.height);}, 100);
218
+ }
219
+
220
+ function estimateHeight(cm) {
221
+ var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
222
+ var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
223
+ return function(line) {
224
+ if (lineIsHidden(cm.doc, line))
225
+ return 0;
226
+ else if (wrapping)
227
+ return (Math.ceil(line.text.length / perLine) || 1) * th;
228
+ else
229
+ return th;
230
+ };
231
+ }
232
+
233
+ function estimateLineHeights(cm) {
234
+ var doc = cm.doc, est = estimateHeight(cm);
235
+ doc.iter(function(line) {
236
+ var estHeight = est(line);
237
+ if (estHeight != line.height) updateLineHeight(line, estHeight);
238
+ });
241
239
  }
242
240
 
243
241
  function keyMapChanged(cm) {
@@ -254,7 +252,7 @@ window.CodeMirror = (function() {
254
252
 
255
253
  function guttersChanged(cm) {
256
254
  updateGutters(cm);
257
- updateDisplay(cm, true);
255
+ regChange(cm);
258
256
  }
259
257
 
260
258
  function updateGutters(cm) {
@@ -289,15 +287,16 @@ window.CodeMirror = (function() {
289
287
  return len;
290
288
  }
291
289
 
292
- function computeMaxLength(view) {
293
- view.maxLine = getLine(view.doc, 0);
294
- view.maxLineLength = lineLength(view.doc, view.maxLine);
295
- view.maxLineChanged = true;
296
- view.doc.iter(1, view.doc.size, function(line) {
297
- var len = lineLength(view.doc, line);
298
- if (len > view.maxLineLength) {
299
- view.maxLineLength = len;
300
- view.maxLine = line;
290
+ function computeMaxLength(cm) {
291
+ var d = cm.display, doc = cm.doc;
292
+ d.maxLine = getLine(doc, doc.first);
293
+ d.maxLineLength = lineLength(doc, d.maxLine);
294
+ d.maxLineChanged = true;
295
+ doc.iter(function(line) {
296
+ var len = lineLength(doc, line);
297
+ if (len > d.maxLineLength) {
298
+ d.maxLineLength = len;
299
+ d.maxLine = line;
301
300
  }
302
301
  });
303
302
  }
@@ -321,7 +320,7 @@ window.CodeMirror = (function() {
321
320
  // Re-synchronize the fake scrollbars with the actual size of the
322
321
  // content. Optionally force a scrollTop.
323
322
  function updateScrollbars(d /* display */, docHeight) {
324
- var totalHeight = docHeight + 2 * paddingTop(d);
323
+ var totalHeight = docHeight + paddingVert(d);
325
324
  d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
326
325
  var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
327
326
  var needsH = d.scroller.scrollWidth > d.scroller.clientWidth;
@@ -329,7 +328,7 @@ window.CodeMirror = (function() {
329
328
  if (needsV) {
330
329
  d.scrollbarV.style.display = "block";
331
330
  d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
332
- d.scrollbarV.firstChild.style.height =
331
+ d.scrollbarV.firstChild.style.height =
333
332
  (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
334
333
  } else d.scrollbarV.style.display = "";
335
334
  if (needsH) {
@@ -361,7 +360,7 @@ window.CodeMirror = (function() {
361
360
  function alignHorizontally(cm) {
362
361
  var display = cm.display;
363
362
  if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
364
- var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.view.scrollLeft;
363
+ var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
365
364
  var gutterW = display.gutters.offsetWidth, l = comp + "px";
366
365
  for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
367
366
  for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
@@ -372,7 +371,7 @@ window.CodeMirror = (function() {
372
371
 
373
372
  function maybeUpdateLineNumberWidth(cm) {
374
373
  if (!cm.options.lineNumbers) return false;
375
- var doc = cm.view.doc, last = lineNumberFor(cm.options, doc.size - 1), display = cm.display;
374
+ var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
376
375
  if (last.length != display.lineNumChars) {
377
376
  var test = display.measure.appendChild(elt("div", [elt("div", last)],
378
377
  "CodeMirror-linenumber CodeMirror-gutter-elt"));
@@ -391,21 +390,33 @@ window.CodeMirror = (function() {
391
390
  return String(options.lineNumberFormatter(i + options.firstLineNumber));
392
391
  }
393
392
  function compensateForHScroll(display) {
394
- return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left;
393
+ return getRect(display.scroller).left - getRect(display.sizer).left;
395
394
  }
396
395
 
397
396
  // DISPLAY DRAWING
398
397
 
399
398
  function updateDisplay(cm, changes, viewPort) {
400
- var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo;
401
- var updated = updateDisplayInner(cm, changes, viewPort);
402
- if (updated) {
403
- signalLater(cm, cm, "update", cm);
404
- if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
405
- signalLater(cm, cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
399
+ var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated;
400
+ var visible = visibleLines(cm.display, cm.doc, viewPort);
401
+ for (;;) {
402
+ if (updateDisplayInner(cm, changes, visible)) {
403
+ updated = true;
404
+ signalLater(cm, "update", cm);
405
+ if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
406
+ signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
407
+ } else break;
408
+ updateSelection(cm);
409
+ updateScrollbars(cm.display, cm.doc.height);
410
+
411
+ // Clip forced viewport to actual scrollable area
412
+ if (viewPort)
413
+ viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight,
414
+ typeof viewPort == "number" ? viewPort : viewPort.top);
415
+ visible = visibleLines(cm.display, cm.doc, viewPort);
416
+ if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo)
417
+ break;
418
+ changes = [];
406
419
  }
407
- updateSelection(cm);
408
- updateScrollbars(cm.display, cm.view.doc.height);
409
420
 
410
421
  return updated;
411
422
  }
@@ -413,58 +424,59 @@ window.CodeMirror = (function() {
413
424
  // Uses a set of changes plus the current scroll position to
414
425
  // determine which DOM updates have to be made, and makes the
415
426
  // updates.
416
- function updateDisplayInner(cm, changes, viewPort) {
417
- var display = cm.display, doc = cm.view.doc;
427
+ function updateDisplayInner(cm, changes, visible) {
428
+ var display = cm.display, doc = cm.doc;
418
429
  if (!display.wrapper.clientWidth) {
419
- display.showingFrom = display.showingTo = display.viewOffset = 0;
430
+ display.showingFrom = display.showingTo = doc.first;
431
+ display.viewOffset = 0;
420
432
  return;
421
433
  }
422
434
 
423
- // Compute the new visible window
424
- // If scrollTop is specified, use that to determine which lines
425
- // to render instead of the current scrollbar position.
426
- var visible = visibleLines(display, doc, viewPort);
427
435
  // Bail out if the visible area is already rendered and nothing changed.
428
- if (changes !== true && changes.length == 0 &&
436
+ if (changes.length == 0 &&
429
437
  visible.from > display.showingFrom && visible.to < display.showingTo)
430
438
  return;
431
439
 
432
- if (changes && maybeUpdateLineNumberWidth(cm))
433
- changes = true;
440
+ if (maybeUpdateLineNumberWidth(cm))
441
+ changes = [{from: doc.first, to: doc.first + doc.size}];
434
442
  var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
435
443
  display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
436
444
 
437
- // When merged lines are present, the line that needs to be
438
- // redrawn might not be the one that was changed.
439
- if (changes !== true && sawCollapsedSpans)
440
- for (var i = 0; i < changes.length; ++i) {
441
- var ch = changes[i], merged;
442
- while (merged = collapsedSpanAtStart(getLine(doc, ch.from))) {
443
- var from = merged.find().from.line;
444
- if (ch.diff) ch.diff -= ch.from - from;
445
- ch.from = from;
446
- }
447
- }
448
-
449
445
  // Used to determine which lines need their line numbers updated
450
- var positionsChangedFrom = changes === true ? 0 : Infinity;
451
- if (cm.options.lineNumbers && changes && changes !== true)
446
+ var positionsChangedFrom = Infinity;
447
+ if (cm.options.lineNumbers)
452
448
  for (var i = 0; i < changes.length; ++i)
453
449
  if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; }
454
450
 
455
- var from = Math.max(visible.from - cm.options.viewportMargin, 0);
456
- var to = Math.min(doc.size, visible.to + cm.options.viewportMargin);
457
- if (display.showingFrom < from && from - display.showingFrom < 20) from = display.showingFrom;
458
- if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(doc.size, display.showingTo);
451
+ var end = doc.first + doc.size;
452
+ var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
453
+ var to = Math.min(end, visible.to + cm.options.viewportMargin);
454
+ if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom);
455
+ if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo);
459
456
  if (sawCollapsedSpans) {
460
457
  from = lineNo(visualLine(doc, getLine(doc, from)));
461
- while (to < doc.size && lineIsHidden(getLine(doc, to))) ++to;
458
+ while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to;
462
459
  }
463
460
 
464
461
  // Create a range of theoretically intact lines, and punch holes
465
462
  // in that using the change info.
466
- var intact = changes === true ? [] :
467
- computeIntact([{from: display.showingFrom, to: display.showingTo}], changes);
463
+ var intact = [{from: Math.max(display.showingFrom, doc.first),
464
+ to: Math.min(display.showingTo, end)}];
465
+ if (intact[0].from >= intact[0].to) intact = [];
466
+ else intact = computeIntact(intact, changes);
467
+ // When merged lines are present, we might have to reduce the
468
+ // intact ranges because changes in continued fragments of the
469
+ // intact lines do require the lines to be redrawn.
470
+ if (sawCollapsedSpans)
471
+ for (var i = 0; i < intact.length; ++i) {
472
+ var range = intact[i], merged;
473
+ while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) {
474
+ var newTo = merged.find().from.line;
475
+ if (newTo > range.from) range.to = newTo;
476
+ else { intact.splice(i--, 1); break; }
477
+ }
478
+ }
479
+
468
480
  // Clip off the parts that won't be visible
469
481
  var intactLines = 0;
470
482
  for (var i = 0; i < intact.length; ++i) {
@@ -474,15 +486,20 @@ window.CodeMirror = (function() {
474
486
  if (range.from >= range.to) intact.splice(i--, 1);
475
487
  else intactLines += range.to - range.from;
476
488
  }
477
- if (intactLines == to - from && from == display.showingFrom && to == display.showingTo)
489
+ if (intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
490
+ updateViewOffset(cm);
478
491
  return;
492
+ }
479
493
  intact.sort(function(a, b) {return a.from - b.from;});
480
494
 
481
- var focused = document.activeElement;
495
+ // Avoid crashing on IE's "unspecified error" when in iframes
496
+ try {
497
+ var focused = document.activeElement;
498
+ } catch(e) {}
482
499
  if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
483
500
  patchDisplay(cm, from, to, intact, positionsChangedFrom);
484
501
  display.lineDiv.style.display = "";
485
- if (document.activeElement != focused && focused.offsetHeight) focused.focus();
502
+ if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus();
486
503
 
487
504
  var different = from != display.showingFrom || to != display.showingTo ||
488
505
  display.lastSizeC != display.wrapper.clientHeight;
@@ -499,7 +516,7 @@ window.CodeMirror = (function() {
499
516
  height = bot - prevBottom;
500
517
  prevBottom = bot;
501
518
  } else {
502
- var box = node.getBoundingClientRect();
519
+ var box = getRect(node);
503
520
  height = box.bottom - box.top;
504
521
  }
505
522
  var diff = node.lineObj.height - height;
@@ -511,15 +528,17 @@ window.CodeMirror = (function() {
511
528
  widgets[i].height = widgets[i].node.offsetHeight;
512
529
  }
513
530
  }
514
- display.viewOffset = heightAtLine(cm, getLine(doc, from));
515
- // Position the mover div to align with the current virtual scroll position
516
- display.mover.style.top = display.viewOffset + "px";
531
+ updateViewOffset(cm);
517
532
 
518
- if (visibleLines(display, doc, viewPort).to >= to)
519
- updateDisplayInner(cm, [], viewPort);
520
533
  return true;
521
534
  }
522
535
 
536
+ function updateViewOffset(cm) {
537
+ var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom));
538
+ // Position the mover div to align with the current virtual scroll position
539
+ cm.display.mover.style.top = off + "px";
540
+ }
541
+
523
542
  function computeIntact(intact, changes) {
524
543
  for (var i = 0, l = changes.length || 0; i < l; ++i) {
525
544
  var change = changes[i], intact2 = [], diff = change.diff || 0;
@@ -572,56 +591,101 @@ window.CodeMirror = (function() {
572
591
  return next;
573
592
  }
574
593
 
575
- var nextIntact = intact.shift(), lineNo = from;
576
- cm.view.doc.iter(from, to, function(line) {
577
- if (nextIntact && nextIntact.to == lineNo) nextIntact = intact.shift();
578
- if (lineIsHidden(line)) {
594
+ var nextIntact = intact.shift(), lineN = from;
595
+ cm.doc.iter(from, to, function(line) {
596
+ if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
597
+ if (lineIsHidden(cm.doc, line)) {
579
598
  if (line.height != 0) updateLineHeight(line, 0);
580
599
  if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i)
581
600
  if (line.widgets[i].showIfHidden) {
582
601
  var prev = cur.previousSibling;
583
- if (prev.nodeType == "pre") {
602
+ if (/pre/i.test(prev.nodeName)) {
584
603
  var wrap = elt("div", null, null, "position: relative");
585
604
  prev.parentNode.replaceChild(wrap, prev);
586
605
  wrap.appendChild(prev);
587
606
  prev = wrap;
588
607
  }
589
- prev.appendChild(buildLineWidget(line.widgets[i], prev, dims));
608
+ var wnode = prev.appendChild(elt("div", [line.widgets[i].node], "CodeMirror-linewidget"));
609
+ positionLineWidget(line.widgets[i], wnode, prev, dims);
590
610
  }
591
- } else if (nextIntact && nextIntact.from <= lineNo && nextIntact.to > lineNo) {
611
+ } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
592
612
  // This line is intact. Skip to the actual node. Update its
593
613
  // line number if needed.
594
614
  while (cur.lineObj != line) cur = rm(cur);
595
- if (lineNumbers && updateNumbersFrom <= lineNo && cur.lineNumber)
596
- setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineNo));
615
+ if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber)
616
+ setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN));
597
617
  cur = cur.nextSibling;
598
618
  } else {
619
+ // For lines with widgets, make an attempt to find and reuse
620
+ // the existing element, so that widgets aren't needlessly
621
+ // removed and re-inserted into the dom
622
+ if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling)
623
+ if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; }
599
624
  // This line needs to be generated.
600
- var lineNode = buildLineElement(cm, line, lineNo, dims);
601
- container.insertBefore(lineNode, cur);
625
+ var lineNode = buildLineElement(cm, line, lineN, dims, reuse);
626
+ if (lineNode != reuse) {
627
+ container.insertBefore(lineNode, cur);
628
+ } else {
629
+ while (cur != reuse) cur = rm(cur);
630
+ cur = cur.nextSibling;
631
+ }
632
+
602
633
  lineNode.lineObj = line;
603
634
  }
604
- ++lineNo;
635
+ ++lineN;
605
636
  });
606
637
  while (cur) cur = rm(cur);
607
638
  }
608
639
 
609
- function buildLineElement(cm, line, lineNo, dims) {
640
+ function buildLineElement(cm, line, lineNo, dims, reuse) {
610
641
  var lineElement = lineContent(cm, line);
611
- var markers = line.gutterMarkers, display = cm.display;
612
-
613
- if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass &&
614
- (!line.widgets || !line.widgets.length)) return lineElement;
615
-
616
- // Lines with gutter elements or a background class need
617
- // to be wrapped again, and have the extra elements added
618
- // to the wrapper div
619
-
620
- var wrap = elt("div", null, line.wrapClass, "position: relative");
642
+ var markers = line.gutterMarkers, display = cm.display, wrap;
643
+
644
+ if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets)
645
+ return lineElement;
646
+
647
+ // Lines with gutter elements, widgets or a background class need
648
+ // to be wrapped again, and have the extra elements added to the
649
+ // wrapper div
650
+
651
+ if (reuse) {
652
+ reuse.alignable = null;
653
+ var isOk = true, widgetsSeen = 0;
654
+ for (var n = reuse.firstChild, next; n; n = next) {
655
+ next = n.nextSibling;
656
+ if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
657
+ reuse.removeChild(n);
658
+ } else {
659
+ for (var i = 0, first = true; i < line.widgets.length; ++i) {
660
+ var widget = line.widgets[i], isFirst = false;
661
+ if (!widget.above) { isFirst = first; first = false; }
662
+ if (widget.node == n.firstChild) {
663
+ positionLineWidget(widget, n, reuse, dims);
664
+ ++widgetsSeen;
665
+ if (isFirst) reuse.insertBefore(lineElement, n);
666
+ break;
667
+ }
668
+ }
669
+ if (i == line.widgets.length) { isOk = false; break; }
670
+ }
671
+ }
672
+ if (isOk && widgetsSeen == line.widgets.length) {
673
+ wrap = reuse;
674
+ reuse.className = line.wrapClass || "";
675
+ }
676
+ }
677
+ if (!wrap) {
678
+ wrap = elt("div", null, line.wrapClass, "position: relative");
679
+ wrap.appendChild(lineElement);
680
+ }
681
+ // Kludge to make sure the styled element lies behind the selection (by z-index)
682
+ if (line.bgClass)
683
+ wrap.insertBefore(elt("div", null, line.bgClass + " CodeMirror-linebackground"), wrap.firstChild);
621
684
  if (cm.options.lineNumbers || markers) {
622
- var gutterWrap = wrap.appendChild(elt("div", null, null, "position: absolute; left: " +
623
- (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"));
624
- if (cm.options.fixedGutter) wrap.alignable = [gutterWrap];
685
+ var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " +
686
+ (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
687
+ wrap.firstChild);
688
+ if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);
625
689
  if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
626
690
  wrap.lineNumber = gutterWrap.appendChild(
627
691
  elt("div", lineNumberFor(cm.options, lineNo),
@@ -636,24 +700,20 @@ window.CodeMirror = (function() {
636
700
  dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
637
701
  }
638
702
  }
639
- // Kludge to make sure the styled element lies behind the selection (by z-index)
640
- if (line.bgClass)
641
- wrap.appendChild(elt("div", "\u00a0", line.bgClass + " CodeMirror-linebackground"));
642
- wrap.appendChild(lineElement);
643
- if (line.widgets) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
644
- var widget = ws[i], node = buildLineWidget(widget, wrap, dims);
703
+ if (ie_lt8) wrap.style.zIndex = 2;
704
+ if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
705
+ var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
706
+ positionLineWidget(widget, node, wrap, dims);
645
707
  if (widget.above)
646
708
  wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
647
709
  else
648
710
  wrap.appendChild(node);
711
+ signalLater(widget, "redraw");
649
712
  }
650
- if (ie_lt8) wrap.style.zIndex = 2;
651
713
  return wrap;
652
714
  }
653
715
 
654
- function buildLineWidget(widget, wrap, dims) {
655
- var node = elt("div", [widget.node], "CodeMirror-linewidget");
656
- node.widget = widget;
716
+ function positionLineWidget(widget, node, wrap, dims) {
657
717
  if (widget.noHScroll) {
658
718
  (wrap.alignable || (wrap.alignable = [])).push(node);
659
719
  var width = dims.wrapperWidth;
@@ -669,14 +729,13 @@ window.CodeMirror = (function() {
669
729
  node.style.position = "relative";
670
730
  if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
671
731
  }
672
- return node;
673
732
  }
674
733
 
675
734
  // SELECTION / CURSOR
676
735
 
677
736
  function updateSelection(cm) {
678
737
  var display = cm.display;
679
- var collapsed = posEq(cm.view.sel.from, cm.view.sel.to);
738
+ var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to);
680
739
  if (collapsed || cm.options.showCursorWhenSelecting)
681
740
  updateSelectionCursor(cm);
682
741
  else
@@ -687,17 +746,19 @@ window.CodeMirror = (function() {
687
746
  display.selectionDiv.style.display = "none";
688
747
 
689
748
  // Move the hidden textarea near the cursor to prevent scrolling artifacts
690
- var headPos = cursorCoords(cm, cm.view.sel.head, "div");
691
- var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
692
- display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
693
- headPos.top + lineOff.top - wrapOff.top)) + "px";
694
- display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
695
- headPos.left + lineOff.left - wrapOff.left)) + "px";
749
+ if (cm.options.moveInputWithCursor) {
750
+ var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
751
+ var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
752
+ display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
753
+ headPos.top + lineOff.top - wrapOff.top)) + "px";
754
+ display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
755
+ headPos.left + lineOff.left - wrapOff.left)) + "px";
756
+ }
696
757
  }
697
758
 
698
759
  // No selection, plain cursor
699
760
  function updateSelectionCursor(cm) {
700
- var display = cm.display, pos = cursorCoords(cm, cm.view.sel.head, "div");
761
+ var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div");
701
762
  display.cursor.style.left = pos.left + "px";
702
763
  display.cursor.style.top = pos.top + "px";
703
764
  display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
@@ -713,7 +774,7 @@ window.CodeMirror = (function() {
713
774
 
714
775
  // Highlight selection
715
776
  function updateSelectionRange(cm) {
716
- var display = cm.display, doc = cm.view.doc, sel = cm.view.sel;
777
+ var display = cm.display, doc = cm.doc, sel = cm.doc.sel;
717
778
  var fragment = document.createDocumentFragment();
718
779
  var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
719
780
 
@@ -728,13 +789,20 @@ window.CodeMirror = (function() {
728
789
  var lineObj = getLine(doc, line);
729
790
  var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity;
730
791
  function coords(ch) {
731
- return charCoords(cm, {line: line, ch: ch}, "div", lineObj);
792
+ return charCoords(cm, Pos(line, ch), "div", lineObj);
732
793
  }
733
794
 
734
795
  iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
735
- var leftPos = coords(dir == "rtl" ? to - 1 : from);
736
- var rightPos = coords(dir == "rtl" ? from : to - 1);
737
- var left = leftPos.left, right = rightPos.right;
796
+ var leftPos = coords(from), rightPos, left, right;
797
+ if (from == to) {
798
+ rightPos = leftPos;
799
+ left = right = leftPos.left;
800
+ } else {
801
+ rightPos = coords(to - 1);
802
+ if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
803
+ left = leftPos.left;
804
+ right = rightPos.right;
805
+ }
738
806
  if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
739
807
  add(left, leftPos.top, null, leftPos.bottom);
740
808
  left = pl;
@@ -793,12 +861,12 @@ window.CodeMirror = (function() {
793
861
 
794
862
  // Cursor-blinking
795
863
  function restartBlink(cm) {
864
+ if (!cm.state.focused) return;
796
865
  var display = cm.display;
797
866
  clearInterval(display.blinker);
798
867
  var on = true;
799
868
  display.cursor.style.visibility = display.otherCursor.style.visibility = "";
800
869
  display.blinker = setInterval(function() {
801
- if (!display.cursor.offsetHeight) return;
802
870
  display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
803
871
  }, cm.options.cursorBlinkRate);
804
872
  }
@@ -806,33 +874,33 @@ window.CodeMirror = (function() {
806
874
  // HIGHLIGHT WORKER
807
875
 
808
876
  function startWorker(cm, time) {
809
- if (cm.view.mode.startState && cm.view.frontier < cm.display.showingTo)
810
- cm.view.highlight.set(time, bind(highlightWorker, cm));
877
+ if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo)
878
+ cm.state.highlight.set(time, bind(highlightWorker, cm));
811
879
  }
812
880
 
813
881
  function highlightWorker(cm) {
814
- var view = cm.view, doc = view.doc;
815
- if (view.frontier >= cm.display.showingTo) return;
882
+ var doc = cm.doc;
883
+ if (doc.frontier < doc.first) doc.frontier = doc.first;
884
+ if (doc.frontier >= cm.display.showingTo) return;
816
885
  var end = +new Date + cm.options.workTime;
817
- var state = copyState(view.mode, getStateBefore(cm, view.frontier));
886
+ var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
818
887
  var changed = [], prevChange;
819
- doc.iter(view.frontier, Math.min(doc.size, cm.display.showingTo + 500), function(line) {
820
- if (view.frontier >= cm.display.showingFrom) { // Visible
888
+ doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {
889
+ if (doc.frontier >= cm.display.showingFrom) { // Visible
821
890
  var oldStyles = line.styles;
822
891
  line.styles = highlightLine(cm, line, state);
823
892
  var ischange = !oldStyles || oldStyles.length != line.styles.length;
824
- for (var i = 0; !ischange && i < oldStyles.length; ++i)
825
- ischange = oldStyles[i] != line.styles[i];
893
+ for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
826
894
  if (ischange) {
827
- if (prevChange && prevChange.end == view.frontier) prevChange.end++;
828
- else changed.push(prevChange = {start: view.frontier, end: view.frontier + 1});
895
+ if (prevChange && prevChange.end == doc.frontier) prevChange.end++;
896
+ else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1});
829
897
  }
830
- line.stateAfter = copyState(view.mode, state);
898
+ line.stateAfter = copyState(doc.mode, state);
831
899
  } else {
832
900
  processLine(cm, line, state);
833
- line.stateAfter = view.frontier % 5 == 0 ? copyState(view.mode, state) : null;
901
+ line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
834
902
  }
835
- ++view.frontier;
903
+ ++doc.frontier;
836
904
  if (+new Date > end) {
837
905
  startWorker(cm, cm.options.workDelay);
838
906
  return true;
@@ -851,10 +919,10 @@ window.CodeMirror = (function() {
851
919
  // smallest indentation, which tends to need the least context to
852
920
  // parse correctly.
853
921
  function findStartLine(cm, n) {
854
- var minindent, minline, doc = cm.view.doc;
922
+ var minindent, minline, doc = cm.doc;
855
923
  for (var search = n, lim = n - 100; search > lim; --search) {
856
- if (search == 0) return 0;
857
- var line = getLine(doc, search-1);
924
+ if (search <= doc.first) return doc.first;
925
+ var line = getLine(doc, search - 1);
858
926
  if (line.stateAfter) return search;
859
927
  var indented = countColumn(line.text, null, cm.options.tabSize);
860
928
  if (minline == null || minindent > indented) {
@@ -866,32 +934,33 @@ window.CodeMirror = (function() {
866
934
  }
867
935
 
868
936
  function getStateBefore(cm, n) {
869
- var view = cm.view;
870
- if (!view.mode.startState) return true;
871
- var pos = findStartLine(cm, n), state = pos && getLine(view.doc, pos-1).stateAfter;
872
- if (!state) state = startState(view.mode);
873
- else state = copyState(view.mode, state);
874
- view.doc.iter(pos, n, function(line) {
937
+ var doc = cm.doc, display = cm.display;
938
+ if (!doc.mode.startState) return true;
939
+ var pos = findStartLine(cm, n), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
940
+ if (!state) state = startState(doc.mode);
941
+ else state = copyState(doc.mode, state);
942
+ doc.iter(pos, n, function(line) {
875
943
  processLine(cm, line, state);
876
- var save = pos == n - 1 || pos % 5 == 0 || pos >= view.showingFrom && pos < view.showingTo;
877
- line.stateAfter = save ? copyState(view.mode, state) : null;
944
+ var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;
945
+ line.stateAfter = save ? copyState(doc.mode, state) : null;
878
946
  ++pos;
879
947
  });
880
948
  return state;
881
949
  }
882
950
 
883
951
  // POSITION MEASUREMENT
884
-
952
+
885
953
  function paddingTop(display) {return display.lineSpace.offsetTop;}
954
+ function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
886
955
  function paddingLeft(display) {
887
- var e = removeChildrenAndAdd(display.measure, elt("pre")).appendChild(elt("span", "x"));
956
+ var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x"));
888
957
  return e.offsetLeft;
889
958
  }
890
959
 
891
960
  function measureChar(cm, line, ch, data) {
892
961
  var dir = -1;
893
962
  data = data || measureLine(cm, line);
894
-
963
+
895
964
  for (var pos = ch;; pos += dir) {
896
965
  var r = data[pos];
897
966
  if (r) break;
@@ -902,22 +971,30 @@ window.CodeMirror = (function() {
902
971
  top: r.top, bottom: r.bottom};
903
972
  }
904
973
 
905
- function measureLine(cm, line) {
906
- // First look in the cache
907
- var display = cm.display, cache = cm.display.measureLineCache;
974
+ function findCachedMeasurement(cm, line) {
975
+ var cache = cm.display.measureLineCache;
908
976
  for (var i = 0; i < cache.length; ++i) {
909
977
  var memo = cache[i];
910
978
  if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
911
- display.scroller.clientWidth == memo.width)
979
+ cm.display.scroller.clientWidth == memo.width &&
980
+ memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass)
912
981
  return memo.measure;
913
982
  }
914
-
915
- var measure = measureLineInner(cm, line);
916
- // Store result in the cache
917
- var memo = {text: line.text, width: display.scroller.clientWidth,
918
- markedSpans: line.markedSpans, measure: measure};
919
- if (cache.length == 16) cache[++display.measureLineCachePos % 16] = memo;
920
- else cache.push(memo);
983
+ }
984
+
985
+ function measureLine(cm, line) {
986
+ // First look in the cache
987
+ var measure = findCachedMeasurement(cm, line);
988
+ if (!measure) {
989
+ // Failing that, recompute and store result in cache
990
+ measure = measureLineInner(cm, line);
991
+ var cache = cm.display.measureLineCache;
992
+ var memo = {text: line.text, width: cm.display.scroller.clientWidth,
993
+ markedSpans: line.markedSpans, measure: measure,
994
+ classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass};
995
+ if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;
996
+ else cache.push(memo);
997
+ }
921
998
  return measure;
922
999
  }
923
1000
 
@@ -952,10 +1029,15 @@ window.CodeMirror = (function() {
952
1029
 
953
1030
  removeChildrenAndAdd(display.measure, pre);
954
1031
 
955
- var outer = display.lineDiv.getBoundingClientRect();
1032
+ var outer = getRect(display.lineDiv);
956
1033
  var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
1034
+ // Work around an IE7/8 bug where it will sometimes have randomly
1035
+ // replaced our pre with a clone at this point.
1036
+ if (ie_lt9 && display.measure.first != pre)
1037
+ removeChildrenAndAdd(display.measure, pre);
1038
+
957
1039
  for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
958
- var size = cur.getBoundingClientRect();
1040
+ var size = getRect(cur);
959
1041
  var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot);
960
1042
  for (var j = 0; j < vranges.length; j += 2) {
961
1043
  var rtop = vranges[j], rbot = vranges[j+1];
@@ -969,19 +1051,38 @@ window.CodeMirror = (function() {
969
1051
  }
970
1052
  }
971
1053
  if (j == vranges.length) vranges.push(top, bot);
972
- data[i] = {left: size.left - outer.left, right: size.right - outer.left, top: j};
1054
+ var right = size.right;
1055
+ if (cur.measureRight) right = getRect(cur.measureRight).left;
1056
+ data[i] = {left: size.left - outer.left, right: right - outer.left, top: j};
973
1057
  }
974
1058
  for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
975
1059
  var vr = cur.top;
976
1060
  cur.top = vranges[vr]; cur.bottom = vranges[vr+1];
977
1061
  }
1062
+
978
1063
  return data;
979
1064
  }
980
1065
 
1066
+ function measureLineWidth(cm, line) {
1067
+ var hasBadSpan = false;
1068
+ if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) {
1069
+ var sp = line.markedSpans[i];
1070
+ if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
1071
+ }
1072
+ var cached = !hasBadSpan && findCachedMeasurement(cm, line);
1073
+ if (cached) return measureChar(cm, line, line.text.length, cached).right;
1074
+
1075
+ var pre = lineContent(cm, line);
1076
+ var end = pre.appendChild(zeroWidthElement(cm.display.measure));
1077
+ removeChildrenAndAdd(cm.display.measure, pre);
1078
+ return getRect(end).right - getRect(cm.display.lineDiv).left;
1079
+ }
1080
+
981
1081
  function clearCaches(cm) {
982
1082
  cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
983
1083
  cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
984
- cm.view.maxLineChanged = true;
1084
+ if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
1085
+ cm.display.lineNumChars = null;
985
1086
  }
986
1087
 
987
1088
  // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
@@ -995,7 +1096,7 @@ window.CodeMirror = (function() {
995
1096
  var yOff = heightAtLine(cm, lineObj);
996
1097
  if (context != "local") yOff -= cm.display.viewOffset;
997
1098
  if (context == "page") {
998
- var lOff = cm.display.lineSpace.getBoundingClientRect();
1099
+ var lOff = getRect(cm.display.lineSpace);
999
1100
  yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop);
1000
1101
  var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft);
1001
1102
  rect.left += xOff; rect.right += xOff;
@@ -1004,13 +1105,33 @@ window.CodeMirror = (function() {
1004
1105
  return rect;
1005
1106
  }
1006
1107
 
1108
+ // Context may be "window", "page", "div", or "local"/null
1109
+ // Result is in "div" coords
1110
+ function fromCoordSystem(cm, coords, context) {
1111
+ if (context == "div") return coords;
1112
+ var left = coords.left, top = coords.top;
1113
+ if (context == "page") {
1114
+ left -= window.pageXOffset || (document.documentElement || document.body).scrollLeft;
1115
+ top -= window.pageYOffset || (document.documentElement || document.body).scrollTop;
1116
+ }
1117
+ var lineSpaceBox = getRect(cm.display.lineSpace);
1118
+ left -= lineSpaceBox.left;
1119
+ top -= lineSpaceBox.top;
1120
+ if (context == "local" || !context) {
1121
+ var editorBox = getRect(cm.display.wrapper);
1122
+ left += editorBox.left;
1123
+ top += editorBox.top;
1124
+ }
1125
+ return {left: left, top: top};
1126
+ }
1127
+
1007
1128
  function charCoords(cm, pos, context, lineObj) {
1008
- if (!lineObj) lineObj = getLine(cm.view.doc, pos.line);
1129
+ if (!lineObj) lineObj = getLine(cm.doc, pos.line);
1009
1130
  return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context);
1010
1131
  }
1011
1132
 
1012
1133
  function cursorCoords(cm, pos, context, lineObj, measurement) {
1013
- lineObj = lineObj || getLine(cm.view.doc, pos.line);
1134
+ lineObj = lineObj || getLine(cm.doc, pos.line);
1014
1135
  if (!measurement) measurement = measureLine(cm, lineObj);
1015
1136
  function get(ch, right) {
1016
1137
  var m = measureChar(cm, lineObj, ch, measurement);
@@ -1025,10 +1146,9 @@ window.CodeMirror = (function() {
1025
1146
  if (part.from < ch && part.to > ch) return get(ch, rtl);
1026
1147
  var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to;
1027
1148
  if (left == ch) {
1028
- // Opera and IE return bogus offsets and widths for edges
1029
- // where the direction flips, but only for the side with the
1030
- // lower level. So we try to use the side with the higher
1031
- // level.
1149
+ // IE returns bogus offsets and widths for edges where the
1150
+ // direction flips, but only for the side with the lower
1151
+ // level. So we try to use the side with the higher level.
1032
1152
  if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true);
1033
1153
  else here = get(rtl && part.from != part.to ? ch - 1 : ch);
1034
1154
  if (rtl == linedir) main = here; else other = here;
@@ -1046,13 +1166,20 @@ window.CodeMirror = (function() {
1046
1166
  return main;
1047
1167
  }
1048
1168
 
1169
+ function PosMaybeOutside(line, ch, outside) {
1170
+ var pos = new Pos(line, ch);
1171
+ if (outside) pos.outside = true;
1172
+ return pos;
1173
+ }
1174
+
1049
1175
  // Coords must be lineSpace-local
1050
1176
  function coordsChar(cm, x, y) {
1051
- var doc = cm.view.doc;
1177
+ var doc = cm.doc;
1052
1178
  y += cm.display.viewOffset;
1053
- if (y < 0) return {line: 0, ch: 0, outside: true};
1054
- var lineNo = lineAtHeight(doc, y);
1055
- if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc, doc.size - 1).text.length};
1179
+ if (y < 0) return PosMaybeOutside(doc.first, 0, true);
1180
+ var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
1181
+ if (lineNo > last)
1182
+ return PosMaybeOutside(doc.first + doc.size - 1, getLine(doc, last).text.length, true);
1056
1183
  if (x < 0) x = 0;
1057
1184
 
1058
1185
  for (;;) {
@@ -1069,30 +1196,32 @@ window.CodeMirror = (function() {
1069
1196
 
1070
1197
  function coordsCharInner(cm, lineObj, lineNo, x, y) {
1071
1198
  var innerOff = y - heightAtLine(cm, lineObj);
1072
- var wrongLine = false, cWidth = cm.display.wrapper.clientWidth;
1199
+ var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
1073
1200
  var measurement = measureLine(cm, lineObj);
1074
1201
 
1075
1202
  function getX(ch) {
1076
- var sp = cursorCoords(cm, {line: lineNo, ch: ch}, "line",
1203
+ var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
1077
1204
  lineObj, measurement);
1078
1205
  wrongLine = true;
1079
- if (innerOff > sp.bottom) return Math.max(0, sp.left - cWidth);
1080
- else if (innerOff < sp.top) return sp.left + cWidth;
1206
+ if (innerOff > sp.bottom) return sp.left - adjust;
1207
+ else if (innerOff < sp.top) return sp.left + adjust;
1081
1208
  else wrongLine = false;
1082
1209
  return sp.left;
1083
1210
  }
1084
1211
 
1085
1212
  var bidi = getOrder(lineObj), dist = lineObj.text.length;
1086
1213
  var from = lineLeft(lineObj), to = lineRight(lineObj);
1087
- var fromX = paddingLeft(cm.display), toX = getX(to);
1214
+ var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
1088
1215
 
1089
- if (x > toX) return {line: lineNo, ch: to, outside: wrongLine};
1216
+ if (x > toX) return PosMaybeOutside(lineNo, to, toOutside);
1090
1217
  // Do a binary search between these bounds.
1091
1218
  for (;;) {
1092
1219
  if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1093
1220
  var after = x - fromX < toX - x, ch = after ? from : to;
1094
1221
  while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
1095
- return {line: lineNo, ch: ch, after: after, outside: wrongLine};
1222
+ var pos = PosMaybeOutside(lineNo, ch, after ? fromOutside : toOutside);
1223
+ pos.after = after;
1224
+ return pos;
1096
1225
  }
1097
1226
  var step = Math.ceil(dist / 2), middle = from + step;
1098
1227
  if (bidi) {
@@ -1100,8 +1229,8 @@ window.CodeMirror = (function() {
1100
1229
  for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1101
1230
  }
1102
1231
  var middleX = getX(middle);
1103
- if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; dist -= step;}
1104
- else {from = middle; fromX = middleX; dist = step;}
1232
+ if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
1233
+ else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
1105
1234
  }
1106
1235
  }
1107
1236
 
@@ -1142,81 +1271,120 @@ window.CodeMirror = (function() {
1142
1271
  // be awkward, slow, and error-prone), but instead updates are
1143
1272
  // batched and then all combined and executed at once.
1144
1273
 
1274
+ var nextOpId = 0;
1145
1275
  function startOperation(cm) {
1146
- if (cm.curOp) ++cm.curOp.depth;
1147
- else cm.curOp = {
1148
- // Nested operations delay update until the outermost one
1149
- // finishes.
1150
- depth: 1,
1276
+ cm.curOp = {
1151
1277
  // An array of ranges of lines that have to be updated. See
1152
1278
  // updateDisplay.
1153
1279
  changes: [],
1154
- delayedCallbacks: [],
1155
1280
  updateInput: null,
1156
1281
  userSelChange: null,
1157
1282
  textChanged: null,
1158
1283
  selectionChanged: false,
1284
+ cursorActivity: false,
1159
1285
  updateMaxLine: false,
1160
- id: ++cm.nextOpId
1286
+ updateScrollPos: false,
1287
+ id: ++nextOpId
1161
1288
  };
1289
+ if (!delayedCallbackDepth++) delayedCallbacks = [];
1162
1290
  }
1163
1291
 
1164
1292
  function endOperation(cm) {
1165
- var op = cm.curOp;
1166
- if (--op.depth) return;
1293
+ var op = cm.curOp, doc = cm.doc, display = cm.display;
1167
1294
  cm.curOp = null;
1168
- var view = cm.view, display = cm.display;
1169
- if (op.updateMaxLine) computeMaxLength(view);
1170
- if (view.maxLineChanged && !cm.options.lineWrapping) {
1171
- var width = measureChar(cm, view.maxLine, view.maxLine.text.length).right;
1172
- display.sizer.style.minWidth = (width + 3 + scrollerCutOff) + "px";
1173
- view.maxLineChanged = false;
1295
+
1296
+ if (op.updateMaxLine) computeMaxLength(cm);
1297
+ if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) {
1298
+ var width = measureLineWidth(cm, display.maxLine);
1299
+ display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
1300
+ display.maxLineChanged = false;
1174
1301
  var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
1175
- if (maxScrollLeft < view.scrollLeft)
1302
+ if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos)
1176
1303
  setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
1177
1304
  }
1178
1305
  var newScrollPos, updated;
1179
- if (op.selectionChanged) {
1180
- var coords = cursorCoords(cm, view.sel.head);
1306
+ if (op.updateScrollPos) {
1307
+ newScrollPos = op.updateScrollPos;
1308
+ } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible
1309
+ var coords = cursorCoords(cm, doc.sel.head);
1181
1310
  newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
1182
1311
  }
1183
- if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null)
1312
+ if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null) {
1184
1313
  updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop);
1314
+ if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;
1315
+ }
1185
1316
  if (!updated && op.selectionChanged) updateSelection(cm);
1186
- if (newScrollPos) scrollCursorIntoView(cm);
1317
+ if (op.updateScrollPos) {
1318
+ display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop;
1319
+ display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft;
1320
+ alignHorizontally(cm);
1321
+ if (op.scrollToPos)
1322
+ scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin);
1323
+ } else if (newScrollPos) {
1324
+ scrollCursorIntoView(cm);
1325
+ }
1187
1326
  if (op.selectionChanged) restartBlink(cm);
1188
1327
 
1189
- if (view.focused && op.updateInput)
1328
+ if (cm.state.focused && op.updateInput)
1190
1329
  resetInput(cm, op.userSelChange);
1191
1330
 
1331
+ var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
1332
+ if (hidden) for (var i = 0; i < hidden.length; ++i)
1333
+ if (!hidden[i].lines.length) signal(hidden[i], "hide");
1334
+ if (unhidden) for (var i = 0; i < unhidden.length; ++i)
1335
+ if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
1336
+
1337
+ var delayed;
1338
+ if (!--delayedCallbackDepth) {
1339
+ delayed = delayedCallbacks;
1340
+ delayedCallbacks = null;
1341
+ }
1192
1342
  if (op.textChanged)
1193
1343
  signal(cm, "change", cm, op.textChanged);
1194
- if (op.selectionChanged) signal(cm, "cursorActivity", cm);
1195
- for (var i = 0; i < op.delayedCallbacks.length; ++i) op.delayedCallbacks[i](cm);
1344
+ if (op.cursorActivity) signal(cm, "cursorActivity", cm);
1345
+ if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
1196
1346
  }
1197
1347
 
1198
1348
  // Wraps a function in an operation. Returns the wrapped function.
1199
1349
  function operation(cm1, f) {
1200
1350
  return function() {
1201
- var cm = cm1 || this;
1202
- startOperation(cm);
1203
- try {var result = f.apply(cm, arguments);}
1204
- finally {endOperation(cm);}
1351
+ var cm = cm1 || this, withOp = !cm.curOp;
1352
+ if (withOp) startOperation(cm);
1353
+ try { var result = f.apply(cm, arguments); }
1354
+ finally { if (withOp) endOperation(cm); }
1355
+ return result;
1356
+ };
1357
+ }
1358
+ function docOperation(f) {
1359
+ return function() {
1360
+ var withOp = this.cm && !this.cm.curOp, result;
1361
+ if (withOp) startOperation(this.cm);
1362
+ try { result = f.apply(this, arguments); }
1363
+ finally { if (withOp) endOperation(this.cm); }
1205
1364
  return result;
1206
1365
  };
1207
1366
  }
1367
+ function runInOp(cm, f) {
1368
+ var withOp = !cm.curOp, result;
1369
+ if (withOp) startOperation(cm);
1370
+ try { result = f(); }
1371
+ finally { if (withOp) endOperation(cm); }
1372
+ return result;
1373
+ }
1208
1374
 
1209
1375
  function regChange(cm, from, to, lendiff) {
1376
+ if (from == null) from = cm.doc.first;
1377
+ if (to == null) to = cm.doc.first + cm.doc.size;
1210
1378
  cm.curOp.changes.push({from: from, to: to, diff: lendiff});
1211
1379
  }
1212
1380
 
1213
1381
  // INPUT HANDLING
1214
1382
 
1215
1383
  function slowPoll(cm) {
1216
- if (cm.view.pollingFast) return;
1384
+ if (cm.display.pollingFast) return;
1217
1385
  cm.display.poll.set(cm.options.pollInterval, function() {
1218
1386
  readInput(cm);
1219
- if (cm.view.focused) slowPoll(cm);
1387
+ if (cm.state.focused) slowPoll(cm);
1220
1388
  });
1221
1389
  }
1222
1390
 
@@ -1237,50 +1405,61 @@ window.CodeMirror = (function() {
1237
1405
  // events that indicate IME taking place, but these are not widely
1238
1406
  // supported or compatible enough yet to rely on.)
1239
1407
  function readInput(cm) {
1240
- var input = cm.display.input, prevInput = cm.display.prevInput, view = cm.view, sel = view.sel;
1241
- if (!view.focused || hasSelection(input) || isReadOnly(cm)) return false;
1408
+ var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
1409
+ if (!cm.state.focused || hasSelection(input) || isReadOnly(cm)) return false;
1242
1410
  var text = input.value;
1243
1411
  if (text == prevInput && posEq(sel.from, sel.to)) return false;
1244
- startOperation(cm);
1245
- view.sel.shift = false;
1412
+ if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {
1413
+ resetInput(cm, true);
1414
+ return false;
1415
+ }
1416
+
1417
+ var withOp = !cm.curOp;
1418
+ if (withOp) startOperation(cm);
1419
+ sel.shift = false;
1246
1420
  var same = 0, l = Math.min(prevInput.length, text.length);
1247
- while (same < l && prevInput[same] == text[same]) ++same;
1421
+ while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
1248
1422
  var from = sel.from, to = sel.to;
1249
1423
  if (same < prevInput.length)
1250
- from = {line: from.line, ch: from.ch - (prevInput.length - same)};
1251
- else if (view.overwrite && posEq(from, to) && !cm.display.pasteIncoming)
1252
- to = {line: to.line, ch: Math.min(getLine(cm.view.doc, to.line).text.length, to.ch + (text.length - same))};
1424
+ from = Pos(from.line, from.ch - (prevInput.length - same));
1425
+ else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
1426
+ to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
1253
1427
  var updateInput = cm.curOp.updateInput;
1254
- updateDoc(cm, from, to, splitLines(text.slice(same)), "end",
1255
- cm.display.pasteIncoming ? "paste" : "input", {from: from, to: to});
1428
+ makeChange(cm.doc, {from: from, to: to, text: splitLines(text.slice(same)),
1429
+ origin: cm.state.pasteIncoming ? "paste" : "+input"}, "end");
1430
+
1256
1431
  cm.curOp.updateInput = updateInput;
1257
- if (text.length > 1000) input.value = cm.display.prevInput = "";
1432
+ if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
1258
1433
  else cm.display.prevInput = text;
1259
- endOperation(cm);
1260
- cm.display.pasteIncoming = false;
1434
+ if (withOp) endOperation(cm);
1435
+ cm.state.pasteIncoming = false;
1261
1436
  return true;
1262
1437
  }
1263
1438
 
1264
1439
  function resetInput(cm, user) {
1265
- var view = cm.view, minimal, selected;
1266
- if (!posEq(view.sel.from, view.sel.to)) {
1440
+ var minimal, selected, doc = cm.doc;
1441
+ if (!posEq(doc.sel.from, doc.sel.to)) {
1267
1442
  cm.display.prevInput = "";
1268
1443
  minimal = hasCopyEvent &&
1269
- (view.sel.to.line - view.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1270
- if (minimal) cm.display.input.value = "-";
1271
- else cm.display.input.value = selected || cm.getSelection();
1272
- if (view.focused) selectInput(cm.display.input);
1273
- } else if (user) cm.display.prevInput = cm.display.input.value = "";
1444
+ (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1445
+ var content = minimal ? "-" : selected || cm.getSelection();
1446
+ cm.display.input.value = content;
1447
+ if (cm.state.focused) selectInput(cm.display.input);
1448
+ if (ie && !ie_lt9) cm.display.inputHasSelection = content;
1449
+ } else if (user) {
1450
+ cm.display.prevInput = cm.display.input.value = "";
1451
+ if (ie && !ie_lt9) cm.display.inputHasSelection = null;
1452
+ }
1274
1453
  cm.display.inaccurateSelection = minimal;
1275
1454
  }
1276
1455
 
1277
1456
  function focusInput(cm) {
1278
- if (cm.options.readOnly != "nocursor" && (ie || document.activeElement != cm.display.input))
1457
+ if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input))
1279
1458
  cm.display.input.focus();
1280
1459
  }
1281
1460
 
1282
1461
  function isReadOnly(cm) {
1283
- return cm.options.readOnly || cm.view.cantEdit;
1462
+ return cm.options.readOnly || cm.doc.cantEdit;
1284
1463
  }
1285
1464
 
1286
1465
  // EVENT HANDLERS
@@ -1288,57 +1467,67 @@ window.CodeMirror = (function() {
1288
1467
  function registerEventHandlers(cm) {
1289
1468
  var d = cm.display;
1290
1469
  on(d.scroller, "mousedown", operation(cm, onMouseDown));
1291
- on(d.scroller, "dblclick", operation(cm, e_preventDefault));
1470
+ if (ie)
1471
+ on(d.scroller, "dblclick", operation(cm, function(e) {
1472
+ var pos = posFromMouse(cm, e);
1473
+ if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
1474
+ e_preventDefault(e);
1475
+ var word = findWordAt(getLine(cm.doc, pos.line).text, pos);
1476
+ extendSelection(cm.doc, word.from, word.to);
1477
+ }));
1478
+ else
1479
+ on(d.scroller, "dblclick", e_preventDefault);
1292
1480
  on(d.lineSpace, "selectstart", function(e) {
1293
1481
  if (!eventInWidget(d, e)) e_preventDefault(e);
1294
1482
  });
1295
1483
  // Gecko browsers fire contextmenu *after* opening the menu, at
1296
1484
  // which point we can't mess with it anymore. Context menu is
1297
1485
  // handled in onMouseDown for Gecko.
1298
- if (!gecko) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
1486
+ if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
1299
1487
 
1300
1488
  on(d.scroller, "scroll", function() {
1301
- setScrollTop(cm, d.scroller.scrollTop);
1302
- setScrollLeft(cm, d.scroller.scrollLeft, true);
1303
- signal(cm, "scroll", cm);
1489
+ if (d.scroller.clientHeight) {
1490
+ setScrollTop(cm, d.scroller.scrollTop);
1491
+ setScrollLeft(cm, d.scroller.scrollLeft, true);
1492
+ signal(cm, "scroll", cm);
1493
+ }
1304
1494
  });
1305
1495
  on(d.scrollbarV, "scroll", function() {
1306
- setScrollTop(cm, d.scrollbarV.scrollTop);
1496
+ if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
1307
1497
  });
1308
1498
  on(d.scrollbarH, "scroll", function() {
1309
- setScrollLeft(cm, d.scrollbarH.scrollLeft);
1499
+ if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
1310
1500
  });
1311
1501
 
1312
1502
  on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
1313
1503
  on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
1314
1504
 
1315
- function reFocus() { if (cm.view.focused) setTimeout(bind(focusInput, cm), 0); }
1505
+ function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
1316
1506
  on(d.scrollbarH, "mousedown", reFocus);
1317
1507
  on(d.scrollbarV, "mousedown", reFocus);
1318
1508
  // Prevent wrapper from ever scrolling
1319
1509
  on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
1320
1510
 
1321
- if (!window.registered) window.registered = 0;
1322
- ++window.registered;
1323
1511
  function onResize() {
1324
1512
  // Might be a text scaling operation, clear size caches.
1325
1513
  d.cachedCharWidth = d.cachedTextHeight = null;
1326
1514
  clearCaches(cm);
1327
- updateDisplay(cm, true);
1515
+ runInOp(cm, bind(regChange, cm));
1328
1516
  }
1329
1517
  on(window, "resize", onResize);
1330
1518
  // Above handler holds on to the editor and its data structures.
1331
1519
  // Here we poll to unregister it when the editor is no longer in
1332
1520
  // the document, so that it can be garbage-collected.
1333
- setTimeout(function unregister() {
1521
+ function unregister() {
1334
1522
  for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
1335
1523
  if (p) setTimeout(unregister, 5000);
1336
- else {--window.registered; off(window, "resize", onResize);}
1337
- }, 5000);
1524
+ else off(window, "resize", onResize);
1525
+ }
1526
+ setTimeout(unregister, 5000);
1338
1527
 
1339
1528
  on(d.input, "keyup", operation(cm, function(e) {
1340
1529
  if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1341
- if (e_prop(e, "keyCode") == 16) cm.view.sel.shift = false;
1530
+ if (e.keyCode == 16) cm.doc.sel.shift = false;
1342
1531
  }));
1343
1532
  on(d.input, "input", bind(fastPoll, cm));
1344
1533
  on(d.input, "keydown", operation(cm, onKeyDown));
@@ -1358,11 +1547,11 @@ window.CodeMirror = (function() {
1358
1547
  }
1359
1548
  on(d.scroller, "paste", function(e){
1360
1549
  if (eventInWidget(d, e)) return;
1361
- focusInput(cm);
1550
+ focusInput(cm);
1362
1551
  fastPoll(cm);
1363
1552
  });
1364
1553
  on(d.input, "paste", function() {
1365
- d.pasteIncoming = true;
1554
+ cm.state.pasteIncoming = true;
1366
1555
  fastPoll(cm);
1367
1556
  });
1368
1557
 
@@ -1400,7 +1589,7 @@ window.CodeMirror = (function() {
1400
1589
  target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1401
1590
  target == display.scrollbarFiller) return null;
1402
1591
  }
1403
- var x, y, space = display.lineSpace.getBoundingClientRect();
1592
+ var x, y, space = getRect(display.lineSpace);
1404
1593
  // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1405
1594
  try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1406
1595
  return coordsChar(cm, x - space.left, y - space.top);
@@ -1408,8 +1597,8 @@ window.CodeMirror = (function() {
1408
1597
 
1409
1598
  var lastClick, lastDoubleClick;
1410
1599
  function onMouseDown(e) {
1411
- var cm = this, display = cm.display, view = cm.view, sel = view.sel, doc = view.doc;
1412
- sel.shift = e_prop(e, "shiftKey");
1600
+ var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
1601
+ sel.shift = e.shiftKey;
1413
1602
 
1414
1603
  if (eventInWidget(display, e)) {
1415
1604
  if (!webkit) {
@@ -1423,10 +1612,10 @@ window.CodeMirror = (function() {
1423
1612
 
1424
1613
  switch (e_button(e)) {
1425
1614
  case 3:
1426
- if (gecko) onContextMenu.call(cm, cm, e);
1615
+ if (captureMiddleClick) onContextMenu.call(cm, cm, e);
1427
1616
  return;
1428
1617
  case 2:
1429
- if (start) extendSelection(cm, start);
1618
+ if (start) extendSelection(cm.doc, start);
1430
1619
  setTimeout(bind(focusInput, cm), 20);
1431
1620
  e_preventDefault(e);
1432
1621
  return;
@@ -1436,7 +1625,7 @@ window.CodeMirror = (function() {
1436
1625
  // selection.
1437
1626
  if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
1438
1627
 
1439
- if (!view.focused) onFocus(cm);
1628
+ if (!cm.state.focused) onFocus(cm);
1440
1629
 
1441
1630
  var now = +new Date, type = "single";
1442
1631
  if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
@@ -1449,7 +1638,7 @@ window.CodeMirror = (function() {
1449
1638
  lastDoubleClick = {time: now, pos: start};
1450
1639
  e_preventDefault(e);
1451
1640
  var word = findWordAt(getLine(doc, start.line).text, start);
1452
- extendSelection(cm, word.from, word.to);
1641
+ extendSelection(cm.doc, word.from, word.to);
1453
1642
  } else { lastClick = {time: now, pos: start}; }
1454
1643
 
1455
1644
  var last = start;
@@ -1457,18 +1646,18 @@ window.CodeMirror = (function() {
1457
1646
  !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
1458
1647
  var dragEnd = operation(cm, function(e2) {
1459
1648
  if (webkit) display.scroller.draggable = false;
1460
- view.draggingText = false;
1649
+ cm.state.draggingText = false;
1461
1650
  off(document, "mouseup", dragEnd);
1462
1651
  off(display.scroller, "drop", dragEnd);
1463
1652
  if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
1464
1653
  e_preventDefault(e2);
1465
- extendSelection(cm, start);
1654
+ extendSelection(cm.doc, start);
1466
1655
  focusInput(cm);
1467
1656
  }
1468
1657
  });
1469
1658
  // Let the drag handler handle this.
1470
1659
  if (webkit) display.scroller.draggable = true;
1471
- view.draggingText = dragEnd;
1660
+ cm.state.draggingText = dragEnd;
1472
1661
  // IE's approach to draggable
1473
1662
  if (display.scroller.dragDrop) display.scroller.dragDrop();
1474
1663
  on(document, "mouseup", dragEnd);
@@ -1476,13 +1665,16 @@ window.CodeMirror = (function() {
1476
1665
  return;
1477
1666
  }
1478
1667
  e_preventDefault(e);
1479
- if (type == "single") extendSelection(cm, clipPos(doc, start));
1668
+ if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
1480
1669
 
1481
- var startstart = sel.from, startend = sel.to;
1670
+ var startstart = sel.from, startend = sel.to, lastPos = start;
1482
1671
 
1483
1672
  function doSelect(cur) {
1673
+ if (posEq(lastPos, cur)) return;
1674
+ lastPos = cur;
1675
+
1484
1676
  if (type == "single") {
1485
- extendSelection(cm, clipPos(doc, start), cur);
1677
+ extendSelection(cm.doc, clipPos(doc, start), cur);
1486
1678
  return;
1487
1679
  }
1488
1680
 
@@ -1490,15 +1682,15 @@ window.CodeMirror = (function() {
1490
1682
  startend = clipPos(doc, startend);
1491
1683
  if (type == "double") {
1492
1684
  var word = findWordAt(getLine(doc, cur.line).text, cur);
1493
- if (posLess(cur, startstart)) extendSelection(cm, word.from, startend);
1494
- else extendSelection(cm, startstart, word.to);
1685
+ if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend);
1686
+ else extendSelection(cm.doc, startstart, word.to);
1495
1687
  } else if (type == "triple") {
1496
- if (posLess(cur, startstart)) extendSelection(cm, startend, clipPos(doc, {line: cur.line, ch: 0}));
1497
- else extendSelection(cm, startstart, clipPos(doc, {line: cur.line + 1, ch: 0}));
1688
+ if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0)));
1689
+ else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0)));
1498
1690
  }
1499
1691
  }
1500
1692
 
1501
- var editorSize = display.wrapper.getBoundingClientRect();
1693
+ var editorSize = getRect(display.wrapper);
1502
1694
  // Used to ensure timeout re-tries don't fire when another extend
1503
1695
  // happened in the meantime (clearTimeout isn't reliable -- at
1504
1696
  // least on Chrome, the timeouts still happen even when cleared,
@@ -1510,7 +1702,7 @@ window.CodeMirror = (function() {
1510
1702
  var cur = posFromMouse(cm, e, true);
1511
1703
  if (!cur) return;
1512
1704
  if (!posEq(cur, last)) {
1513
- if (!view.focused) onFocus(cm);
1705
+ if (!cm.state.focused) onFocus(cm);
1514
1706
  last = cur;
1515
1707
  doSelect(cur);
1516
1708
  var visible = visibleLines(display, doc);
@@ -1559,11 +1751,8 @@ window.CodeMirror = (function() {
1559
1751
  reader.onload = function() {
1560
1752
  text[i] = reader.result;
1561
1753
  if (++read == n) {
1562
- pos = clipPos(cm.view.doc, pos);
1563
- operation(cm, function() {
1564
- var end = replaceRange(cm, text.join(""), pos, pos, "paste");
1565
- setSelection(cm, pos, end);
1566
- })();
1754
+ pos = clipPos(cm.doc, pos);
1755
+ makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around");
1567
1756
  }
1568
1757
  };
1569
1758
  reader.readAsText(file);
@@ -1571,8 +1760,8 @@ window.CodeMirror = (function() {
1571
1760
  for (var i = 0; i < n; ++i) loadFile(files[i], i);
1572
1761
  } else {
1573
1762
  // Don't do a replace if the drop happened inside of the selected text.
1574
- if (cm.view.draggingText && !(posLess(pos, cm.view.sel.from) || posLess(cm.view.sel.to, pos))) {
1575
- cm.view.draggingText(e);
1763
+ if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) {
1764
+ cm.state.draggingText(e);
1576
1765
  // Ensure the editor is re-focused
1577
1766
  setTimeout(bind(focusInput, cm), 20);
1578
1767
  return;
@@ -1580,9 +1769,9 @@ window.CodeMirror = (function() {
1580
1769
  try {
1581
1770
  var text = e.dataTransfer.getData("Text");
1582
1771
  if (text) {
1583
- var curFrom = cm.view.sel.from, curTo = cm.view.sel.to;
1584
- setSelection(cm, pos, pos);
1585
- if (cm.view.draggingText) replaceRange(cm, "", curFrom, curTo, "paste");
1772
+ var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to;
1773
+ setSelection(cm.doc, pos, pos);
1774
+ if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste");
1586
1775
  cm.replaceSelection(text, null, "paste");
1587
1776
  focusInput(cm);
1588
1777
  onFocus(cm);
@@ -1597,20 +1786,20 @@ window.CodeMirror = (function() {
1597
1786
  try { var mX = e.clientX, mY = e.clientY; }
1598
1787
  catch(e) { return false; }
1599
1788
 
1600
- if (mX >= Math.floor(display.gutters.getBoundingClientRect().right)) return false;
1789
+ if (mX >= Math.floor(getRect(display.gutters).right)) return false;
1601
1790
  e_preventDefault(e);
1602
1791
  if (!hasHandler(cm, "gutterClick")) return true;
1603
1792
 
1604
- var lineBox = display.lineDiv.getBoundingClientRect();
1793
+ var lineBox = getRect(display.lineDiv);
1605
1794
  if (mY > lineBox.bottom) return true;
1606
1795
  mY -= lineBox.top - display.viewOffset;
1607
1796
 
1608
1797
  for (var i = 0; i < cm.options.gutters.length; ++i) {
1609
1798
  var g = display.gutters.childNodes[i];
1610
- if (g && g.getBoundingClientRect().right >= mX) {
1611
- var line = lineAtHeight(cm.view.doc, mY);
1799
+ if (g && getRect(g).right >= mX) {
1800
+ var line = lineAtHeight(cm.doc, mY);
1612
1801
  var gutter = cm.options.gutters[i];
1613
- signalLater(cm, cm, "gutterClick", cm, line, gutter, e);
1802
+ signalLater(cm, "gutterClick", cm, line, gutter, e);
1614
1803
  break;
1615
1804
  }
1616
1805
  }
@@ -1618,14 +1807,15 @@ window.CodeMirror = (function() {
1618
1807
  }
1619
1808
 
1620
1809
  function onDragStart(cm, e) {
1810
+ if (ie && !cm.state.draggingText) { e_stop(e); return; }
1621
1811
  if (eventInWidget(cm.display, e)) return;
1622
-
1812
+
1623
1813
  var txt = cm.getSelection();
1624
1814
  e.dataTransfer.setData("Text", txt);
1625
1815
 
1626
1816
  // Use dummy image instead of default browsers image.
1627
1817
  // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1628
- if (e.dataTransfer.setDragImage && !safari) {
1818
+ if (e.dataTransfer.setDragImage) {
1629
1819
  var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
1630
1820
  if (opera) {
1631
1821
  img.width = img.height = 1;
@@ -1633,23 +1823,32 @@ window.CodeMirror = (function() {
1633
1823
  // Force a relayout, or Opera won't use our image for some obscure reason
1634
1824
  img._top = img.offsetTop;
1635
1825
  }
1826
+ if (safari) {
1827
+ if (cm.display.dragImg) {
1828
+ img = cm.display.dragImg;
1829
+ } else {
1830
+ cm.display.dragImg = img;
1831
+ img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
1832
+ cm.display.wrapper.appendChild(img);
1833
+ }
1834
+ }
1636
1835
  e.dataTransfer.setDragImage(img, 0, 0);
1637
1836
  if (opera) img.parentNode.removeChild(img);
1638
1837
  }
1639
1838
  }
1640
1839
 
1641
1840
  function setScrollTop(cm, val) {
1642
- if (Math.abs(cm.view.scrollTop - val) < 2) return;
1643
- cm.view.scrollTop = val;
1841
+ if (Math.abs(cm.doc.scrollTop - val) < 2) return;
1842
+ cm.doc.scrollTop = val;
1644
1843
  if (!gecko) updateDisplay(cm, [], val);
1645
1844
  if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
1646
1845
  if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
1647
1846
  if (gecko) updateDisplay(cm, []);
1648
1847
  }
1649
1848
  function setScrollLeft(cm, val, isScroller) {
1650
- if (isScroller ? val == cm.view.scrollLeft : Math.abs(cm.view.scrollLeft - val) < 2) return;
1849
+ if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
1651
1850
  val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
1652
- cm.view.scrollLeft = val;
1851
+ cm.doc.scrollLeft = val;
1653
1852
  alignHorizontally(cm);
1654
1853
  if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
1655
1854
  if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
@@ -1682,6 +1881,11 @@ window.CodeMirror = (function() {
1682
1881
  if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
1683
1882
  else if (dy == null) dy = e.wheelDelta;
1684
1883
 
1884
+ var display = cm.display, scroll = display.scroller;
1885
+ // Quit if there's nothing to scroll here
1886
+ if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
1887
+ dy && scroll.scrollHeight > scroll.clientHeight)) return;
1888
+
1685
1889
  // Webkit browsers on OS X abort momentum scrolls when the target
1686
1890
  // of the scroll event is removed from the scrollable element.
1687
1891
  // This hack (see related code in patchDisplay) makes sure the
@@ -1695,7 +1899,6 @@ window.CodeMirror = (function() {
1695
1899
  }
1696
1900
  }
1697
1901
 
1698
- var display = cm.display, scroll = display.scroller;
1699
1902
  // On some browsers, horizontal scrolling will cause redraws to
1700
1903
  // happen before the gutter has been realigned, causing it to
1701
1904
  // wriggle around in a most unseemly way. When we have an
@@ -1713,9 +1916,9 @@ window.CodeMirror = (function() {
1713
1916
 
1714
1917
  if (dy && wheelPixelsPerUnit != null) {
1715
1918
  var pixels = dy * wheelPixelsPerUnit;
1716
- var top = cm.view.scrollTop, bot = top + display.wrapper.clientHeight;
1919
+ var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
1717
1920
  if (pixels < 0) top = Math.max(0, top + pixels - 50);
1718
- else bot = Math.min(cm.view.doc.height, bot + pixels + 50);
1921
+ else bot = Math.min(cm.doc.height, bot + pixels + 50);
1719
1922
  updateDisplay(cm, [], {top: top, bottom: bot});
1720
1923
  }
1721
1924
 
@@ -1748,25 +1951,22 @@ window.CodeMirror = (function() {
1748
1951
  // Ensure previous input has been read, so that the handler sees a
1749
1952
  // consistent view of the document
1750
1953
  if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
1751
- var view = cm.view, prevShift = view.sel.shift;
1954
+ var doc = cm.doc, prevShift = doc.sel.shift, done = false;
1752
1955
  try {
1753
- if (isReadOnly(cm)) view.suppressEdits = true;
1754
- if (dropShift) view.sel.shift = false;
1755
- bound(cm);
1756
- } catch(e) {
1757
- if (e != Pass) throw e;
1758
- return false;
1956
+ if (isReadOnly(cm)) cm.state.suppressEdits = true;
1957
+ if (dropShift) doc.sel.shift = false;
1958
+ done = bound(cm) != Pass;
1759
1959
  } finally {
1760
- view.sel.shift = prevShift;
1761
- view.suppressEdits = false;
1960
+ doc.sel.shift = prevShift;
1961
+ cm.state.suppressEdits = false;
1762
1962
  }
1763
- return true;
1963
+ return done;
1764
1964
  }
1765
1965
 
1766
1966
  function allKeyMaps(cm) {
1767
- var maps = cm.view.keyMaps.slice(0);
1967
+ var maps = cm.state.keyMaps.slice(0);
1968
+ if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
1768
1969
  maps.push(cm.options.keyMap);
1769
- if (cm.options.extraKeys) maps.unshift(cm.options.extraKeys);
1770
1970
  return maps;
1771
1971
  }
1772
1972
 
@@ -1780,27 +1980,23 @@ window.CodeMirror = (function() {
1780
1980
  cm.options.keyMap = (next.call ? next.call(null, cm) : next);
1781
1981
  }, 50);
1782
1982
 
1783
- var name = keyNames[e_prop(e, "keyCode")], handled = false;
1784
- if (name == null || e.altGraphKey) return false;
1785
- if (e_prop(e, "altKey")) name = "Alt-" + name;
1786
- if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name;
1787
- if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name;
1788
-
1789
- var stopped = false;
1790
- function stop() { stopped = true; }
1983
+ var name = keyName(e, true), handled = false;
1984
+ if (!name) return false;
1791
1985
  var keymaps = allKeyMaps(cm);
1792
1986
 
1793
- if (e_prop(e, "shiftKey")) {
1794
- handled = lookupKey("Shift-" + name, keymaps,
1795
- function(b) {return doHandleBinding(cm, b, true);}, stop)
1796
- || lookupKey(name, keymaps, function(b) {
1797
- if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b);
1798
- }, stop);
1987
+ if (e.shiftKey) {
1988
+ // First try to resolve full name (including 'Shift-'). Failing
1989
+ // that, see if there is a cursor-motion command (starting with
1990
+ // 'go') bound to the keyname without 'Shift-'.
1991
+ handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
1992
+ || lookupKey(name, keymaps, function(b) {
1993
+ if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b);
1994
+ });
1799
1995
  } else {
1800
- handled = lookupKey(name, keymaps,
1801
- function(b) { return doHandleBinding(cm, b); }, stop);
1996
+ handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
1802
1997
  }
1803
- if (stopped) handled = false;
1998
+ if (handled == "stop") handled = false;
1999
+
1804
2000
  if (handled) {
1805
2001
  e_preventDefault(e);
1806
2002
  restartBlink(cm);
@@ -1822,18 +2018,18 @@ window.CodeMirror = (function() {
1822
2018
  var lastStoppedKey = null;
1823
2019
  function onKeyDown(e) {
1824
2020
  var cm = this;
1825
- if (!cm.view.focused) onFocus(cm);
2021
+ if (!cm.state.focused) onFocus(cm);
1826
2022
  if (ie && e.keyCode == 27) { e.returnValue = false; }
1827
2023
  if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1828
- var code = e_prop(e, "keyCode");
2024
+ var code = e.keyCode;
1829
2025
  // IE does strange things with escape.
1830
- cm.view.sel.shift = code == 16 || e_prop(e, "shiftKey");
2026
+ cm.doc.sel.shift = code == 16 || e.shiftKey;
1831
2027
  // First give onKeyEvent option a chance to handle this.
1832
2028
  var handled = handleKeyBinding(cm, e);
1833
2029
  if (opera) {
1834
2030
  lastStoppedKey = handled ? code : null;
1835
2031
  // Opera has no cut event... we try to at least catch the key combo
1836
- if (!handled && code == 88 && !hasCopyEvent && e_prop(e, mac ? "metaKey" : "ctrlKey"))
2032
+ if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
1837
2033
  cm.replaceSelection("");
1838
2034
  }
1839
2035
  }
@@ -1841,56 +2037,56 @@ window.CodeMirror = (function() {
1841
2037
  function onKeyPress(e) {
1842
2038
  var cm = this;
1843
2039
  if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1844
- var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
2040
+ var keyCode = e.keyCode, charCode = e.charCode;
1845
2041
  if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
1846
2042
  if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
1847
2043
  var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
1848
- if (this.options.electricChars && this.view.mode.electricChars &&
2044
+ if (this.options.electricChars && this.doc.mode.electricChars &&
1849
2045
  this.options.smartIndent && !isReadOnly(this) &&
1850
- this.view.mode.electricChars.indexOf(ch) > -1)
1851
- setTimeout(operation(cm, function() {indentLine(cm, cm.view.sel.to.line, "smart");}), 75);
2046
+ this.doc.mode.electricChars.indexOf(ch) > -1)
2047
+ setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
1852
2048
  if (handleCharBinding(cm, e, ch)) return;
2049
+ if (ie && !ie_lt9) cm.display.inputHasSelection = null;
1853
2050
  fastPoll(cm);
1854
2051
  }
1855
2052
 
1856
2053
  function onFocus(cm) {
1857
2054
  if (cm.options.readOnly == "nocursor") return;
1858
- if (!cm.view.focused) {
2055
+ if (!cm.state.focused) {
1859
2056
  signal(cm, "focus", cm);
1860
- cm.view.focused = true;
1861
- if (cm.display.scroller.className.search(/\bCodeMirror-focused\b/) == -1)
1862
- cm.display.scroller.className += " CodeMirror-focused";
2057
+ cm.state.focused = true;
2058
+ if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
2059
+ cm.display.wrapper.className += " CodeMirror-focused";
1863
2060
  resetInput(cm, true);
1864
2061
  }
1865
2062
  slowPoll(cm);
1866
2063
  restartBlink(cm);
1867
2064
  }
1868
2065
  function onBlur(cm) {
1869
- if (cm.view.focused) {
2066
+ if (cm.state.focused) {
1870
2067
  signal(cm, "blur", cm);
1871
- cm.view.focused = false;
1872
- cm.display.scroller.className = cm.display.scroller.className.replace(" CodeMirror-focused", "");
2068
+ cm.state.focused = false;
2069
+ cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", "");
1873
2070
  }
1874
2071
  clearInterval(cm.display.blinker);
1875
- setTimeout(function() {if (!cm.view.focused) cm.view.sel.shift = false;}, 150);
2072
+ setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150);
1876
2073
  }
1877
2074
 
1878
2075
  var detectingSelectAll;
1879
2076
  function onContextMenu(cm, e) {
1880
- var display = cm.display;
2077
+ var display = cm.display, sel = cm.doc.sel;
1881
2078
  if (eventInWidget(display, e)) return;
1882
-
1883
- var sel = cm.view.sel;
2079
+
1884
2080
  var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
1885
2081
  if (!pos || opera) return; // Opera is difficult.
1886
2082
  if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1887
- operation(cm, setSelection)(cm, pos, pos);
2083
+ operation(cm, setSelection)(cm.doc, pos, pos);
1888
2084
 
1889
2085
  var oldCSS = display.input.style.cssText;
1890
2086
  display.inputDiv.style.position = "absolute";
1891
2087
  display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1892
2088
  "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" +
1893
- "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
2089
+ "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
1894
2090
  focusInput(cm);
1895
2091
  resetInput(cm, true);
1896
2092
  // Adds "Select all" to context menu in FF
@@ -1902,27 +2098,29 @@ window.CodeMirror = (function() {
1902
2098
  if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
1903
2099
  slowPoll(cm);
1904
2100
 
1905
- // Try to detect the user choosing select-all
1906
- if (display.input.selectionStart != null) {
2101
+ // Try to detect the user choosing select-all
2102
+ if (display.input.selectionStart != null && (!ie || ie_lt9)) {
1907
2103
  clearTimeout(detectingSelectAll);
1908
2104
  var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0;
1909
2105
  display.prevInput = " ";
1910
2106
  display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
1911
- detectingSelectAll = setTimeout(function poll(){
2107
+ var poll = function(){
1912
2108
  if (display.prevInput == " " && display.input.selectionStart == 0)
1913
2109
  operation(cm, commands.selectAll)(cm);
1914
2110
  else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
1915
2111
  else resetInput(cm);
1916
- }, 200);
2112
+ };
2113
+ detectingSelectAll = setTimeout(poll, 200);
1917
2114
  }
1918
2115
  }
1919
2116
 
1920
- if (gecko) {
2117
+ if (captureMiddleClick) {
1921
2118
  e_stop(e);
1922
- on(window, "mouseup", function mouseup() {
2119
+ var mouseup = function() {
1923
2120
  off(window, "mouseup", mouseup);
1924
2121
  setTimeout(rehide, 20);
1925
- });
2122
+ };
2123
+ on(window, "mouseup", mouseup);
1926
2124
  } else {
1927
2125
  setTimeout(rehide, 50);
1928
2126
  }
@@ -1930,124 +2128,212 @@ window.CodeMirror = (function() {
1930
2128
 
1931
2129
  // UPDATING
1932
2130
 
1933
- // Replace the range from from to to by the strings in newText.
1934
- // Afterwards, set the selection to selFrom, selTo.
1935
- function updateDoc(cm, from, to, newText, selUpdate, origin) {
2131
+ function changeEnd(change) {
2132
+ if (!change.text) return change.to;
2133
+ return Pos(change.from.line + change.text.length - 1,
2134
+ lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
2135
+ }
2136
+
2137
+ // Make sure a position will be valid after the given change.
2138
+ function clipPostChange(doc, change, pos) {
2139
+ if (!posLess(change.from, pos)) return clipPos(doc, pos);
2140
+ var diff = (change.text.length - 1) - (change.to.line - change.from.line);
2141
+ if (pos.line > change.to.line + diff) {
2142
+ var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1;
2143
+ if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length);
2144
+ return clipToLen(pos, getLine(doc, preLine).text.length);
2145
+ }
2146
+ if (pos.line == change.to.line + diff)
2147
+ return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) +
2148
+ getLine(doc, change.to.line).text.length - change.to.ch);
2149
+ var inside = pos.line - change.from.line;
2150
+ return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch));
2151
+ }
2152
+
2153
+ // Hint can be null|"end"|"start"|"around"|{anchor,head}
2154
+ function computeSelAfterChange(doc, change, hint) {
2155
+ if (hint && typeof hint == "object") // Assumed to be {anchor, head} object
2156
+ return {anchor: clipPostChange(doc, change, hint.anchor),
2157
+ head: clipPostChange(doc, change, hint.head)};
2158
+
2159
+ if (hint == "start") return {anchor: change.from, head: change.from};
2160
+
2161
+ var end = changeEnd(change);
2162
+ if (hint == "around") return {anchor: change.from, head: end};
2163
+ if (hint == "end") return {anchor: end, head: end};
2164
+
2165
+ // hint is null, leave the selection alone as much as possible
2166
+ var adjustPos = function(pos) {
2167
+ if (posLess(pos, change.from)) return pos;
2168
+ if (!posLess(change.to, pos)) return end;
2169
+
2170
+ var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
2171
+ if (pos.line == change.to.line) ch += end.ch - change.to.ch;
2172
+ return Pos(line, ch);
2173
+ };
2174
+ return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};
2175
+ }
2176
+
2177
+ function filterChange(doc, change) {
2178
+ var obj = {
2179
+ canceled: false,
2180
+ from: change.from,
2181
+ to: change.to,
2182
+ text: change.text,
2183
+ origin: change.origin,
2184
+ update: function(from, to, text, origin) {
2185
+ if (from) this.from = clipPos(doc, from);
2186
+ if (to) this.to = clipPos(doc, to);
2187
+ if (text) this.text = text;
2188
+ if (origin !== undefined) this.origin = origin;
2189
+ },
2190
+ cancel: function() { this.canceled = true; }
2191
+ };
2192
+ signal(doc, "beforeChange", doc, obj);
2193
+ if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
2194
+
2195
+ if (obj.canceled) return null;
2196
+ return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
2197
+ }
2198
+
2199
+ // Replace the range from from to to by the strings in replacement.
2200
+ // change is a {from, to, text [, origin]} object
2201
+ function makeChange(doc, change, selUpdate, ignoreReadOnly) {
2202
+ if (doc.cm) {
2203
+ if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly);
2204
+ if (doc.cm.state.suppressEdits) return;
2205
+ }
2206
+
2207
+ if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
2208
+ change = filterChange(doc, change);
2209
+ if (!change) return;
2210
+ }
2211
+
1936
2212
  // Possibly split or suppress the update based on the presence
1937
2213
  // of read-only spans in its range.
1938
- var split = sawReadOnlySpans &&
1939
- removeReadOnlyRanges(cm.view.doc, from, to);
2214
+ var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
1940
2215
  if (split) {
1941
2216
  for (var i = split.length - 1; i >= 1; --i)
1942
- updateDocInner(cm, split[i].from, split[i].to, [""], origin);
2217
+ makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]});
1943
2218
  if (split.length)
1944
- return updateDocInner(cm, split[0].from, split[0].to, newText, selUpdate, origin);
2219
+ makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate);
1945
2220
  } else {
1946
- return updateDocInner(cm, from, to, newText, selUpdate, origin);
2221
+ makeChangeNoReadonly(doc, change, selUpdate);
1947
2222
  }
1948
2223
  }
1949
2224
 
1950
- function updateDocInner(cm, from, to, newText, selUpdate, origin) {
1951
- if (cm.view.suppressEdits) return;
2225
+ function makeChangeNoReadonly(doc, change, selUpdate) {
2226
+ var selAfter = computeSelAfterChange(doc, change, selUpdate);
2227
+ addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
1952
2228
 
1953
- var view = cm.view, doc = view.doc, old = [];
1954
- doc.iter(from.line, to.line + 1, function(line) {
1955
- old.push(newHL(line.text, line.markedSpans));
2229
+ makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
2230
+ var rebased = [];
2231
+
2232
+ linkedDocs(doc, function(doc, sharedHist) {
2233
+ if (!sharedHist && indexOf(rebased, doc.history) == -1) {
2234
+ rebaseHist(doc.history, change);
2235
+ rebased.push(doc.history);
2236
+ }
2237
+ makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
1956
2238
  });
1957
- var startSelFrom = view.sel.from, startSelTo = view.sel.to;
1958
- var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText);
1959
- var retval = updateDocNoUndo(cm, from, to, lines, selUpdate, origin);
1960
- if (view.history) addChange(cm, from.line, newText.length, old, origin,
1961
- startSelFrom, startSelTo, view.sel.from, view.sel.to);
1962
- return retval;
1963
- }
1964
-
1965
- function unredoHelper(cm, type) {
1966
- var doc = cm.view.doc, hist = cm.view.history;
1967
- var set = (type == "undo" ? hist.done : hist.undone).pop();
1968
- if (!set) return;
1969
- var anti = {events: [], fromBefore: set.fromAfter, toBefore: set.toAfter,
1970
- fromAfter: set.fromBefore, toAfter: set.toBefore};
1971
- for (var i = set.events.length - 1; i >= 0; i -= 1) {
1972
- hist.dirtyCounter += type == "undo" ? -1 : 1;
1973
- var change = set.events[i];
1974
- var replaced = [], end = change.start + change.added;
1975
- doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); });
1976
- anti.events.push({start: change.start, added: change.old.length, old: replaced});
1977
- var selPos = i ? null : {from: set.fromBefore, to: set.toBefore};
1978
- updateDocNoUndo(cm, {line: change.start, ch: 0}, {line: end - 1, ch: getLine(doc, end-1).text.length},
1979
- change.old, selPos, type);
1980
- }
2239
+ }
2240
+
2241
+ function makeChangeFromHistory(doc, type) {
2242
+ if (doc.cm && doc.cm.state.suppressEdits) return;
2243
+
2244
+ var hist = doc.history;
2245
+ var event = (type == "undo" ? hist.done : hist.undone).pop();
2246
+ if (!event) return;
2247
+ hist.dirtyCounter += type == "undo" ? -1 : 1;
2248
+
2249
+ var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
2250
+ anchorAfter: event.anchorBefore, headAfter: event.headBefore};
1981
2251
  (type == "undo" ? hist.undone : hist.done).push(anti);
2252
+
2253
+ for (var i = event.changes.length - 1; i >= 0; --i) {
2254
+ var change = event.changes[i];
2255
+ change.origin = type;
2256
+ anti.changes.push(historyChangeFromChange(doc, change));
2257
+
2258
+ var after = i ? computeSelAfterChange(doc, change, null)
2259
+ : {anchor: event.anchorBefore, head: event.headBefore};
2260
+ makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
2261
+ var rebased = [];
2262
+
2263
+ linkedDocs(doc, function(doc, sharedHist) {
2264
+ if (!sharedHist && indexOf(rebased, doc.history) == -1) {
2265
+ rebaseHist(doc.history, change);
2266
+ rebased.push(doc.history);
2267
+ }
2268
+ makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
2269
+ });
2270
+ }
2271
+ }
2272
+
2273
+ function shiftDoc(doc, distance) {
2274
+ function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);}
2275
+ doc.first += distance;
2276
+ if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance);
2277
+ doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor);
2278
+ doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to);
2279
+ }
2280
+
2281
+ function makeChangeSingleDoc(doc, change, selAfter, spans) {
2282
+ if (doc.cm && !doc.cm.curOp)
2283
+ return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);
2284
+
2285
+ if (change.to.line < doc.first) {
2286
+ shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
2287
+ return;
2288
+ }
2289
+ if (change.from.line > doc.lastLine()) return;
2290
+
2291
+ // Clip the change to the size of this doc
2292
+ if (change.from.line < doc.first) {
2293
+ var shift = change.text.length - 1 - (doc.first - change.from.line);
2294
+ shiftDoc(doc, shift);
2295
+ change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
2296
+ text: [lst(change.text)], origin: change.origin};
2297
+ }
2298
+ var last = doc.lastLine();
2299
+ if (change.to.line > last) {
2300
+ change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
2301
+ text: [change.text[0]], origin: change.origin};
2302
+ }
2303
+
2304
+ change.removed = getBetween(doc, change.from, change.to);
2305
+
2306
+ if (!selAfter) selAfter = computeSelAfterChange(doc, change, null);
2307
+ if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter);
2308
+ else updateDoc(doc, change, spans, selAfter);
1982
2309
  }
1983
2310
 
1984
- function updateDocNoUndo(cm, from, to, lines, selUpdate, origin) {
1985
- var view = cm.view, doc = view.doc, display = cm.display;
1986
- if (view.suppressEdits) return;
2311
+ function makeChangeSingleDocInEditor(cm, change, spans, selAfter) {
2312
+ var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
1987
2313
 
1988
- var nlines = to.line - from.line, firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
1989
2314
  var recomputeMaxLength = false, checkWidthStart = from.line;
1990
2315
  if (!cm.options.lineWrapping) {
1991
- checkWidthStart = lineNo(visualLine(doc, firstLine));
2316
+ checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line)));
1992
2317
  doc.iter(checkWidthStart, to.line + 1, function(line) {
1993
- if (line == view.maxLine) {
2318
+ if (line == display.maxLine) {
1994
2319
  recomputeMaxLength = true;
1995
2320
  return true;
1996
2321
  }
1997
2322
  });
1998
2323
  }
1999
2324
 
2000
- var lastHL = lst(lines), th = textHeight(display);
2325
+ if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head))
2326
+ cm.curOp.cursorActivity = true;
2001
2327
 
2002
- // First adjust the line structure
2003
- if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") {
2004
- // This is a whole-line replace. Treated specially to make
2005
- // sure line objects move the way they are supposed to.
2006
- var added = [];
2007
- for (var i = 0, e = lines.length - 1; i < e; ++i)
2008
- added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th));
2009
- updateLine(cm, lastLine, lastLine.text, hlSpans(lastHL));
2010
- if (nlines) doc.remove(from.line, nlines, cm);
2011
- if (added.length) doc.insert(from.line, added);
2012
- } else if (firstLine == lastLine) {
2013
- if (lines.length == 1) {
2014
- updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) +
2015
- firstLine.text.slice(to.ch), hlSpans(lines[0]));
2016
- } else {
2017
- for (var added = [], i = 1, e = lines.length - 1; i < e; ++i)
2018
- added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th));
2019
- added.push(makeLine(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL), th));
2020
- updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
2021
- doc.insert(from.line + 1, added);
2022
- }
2023
- } else if (lines.length == 1) {
2024
- updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) +
2025
- lastLine.text.slice(to.ch), hlSpans(lines[0]));
2026
- doc.remove(from.line + 1, nlines, cm);
2027
- } else {
2028
- var added = [];
2029
- updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
2030
- updateLine(cm, lastLine, hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL));
2031
- for (var i = 1, e = lines.length - 1; i < e; ++i)
2032
- added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th));
2033
- if (nlines > 1) doc.remove(from.line + 1, nlines - 1, cm);
2034
- doc.insert(from.line + 1, added);
2035
- }
2328
+ updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
2036
2329
 
2037
- if (cm.options.lineWrapping) {
2038
- var perLine = Math.max(5, display.scroller.clientWidth / charWidth(display) - 3);
2039
- doc.iter(from.line, from.line + lines.length, function(line) {
2040
- if (line.height == 0) return;
2041
- var guess = (Math.ceil(line.text.length / perLine) || 1) * th;
2042
- if (guess != line.height) updateLineHeight(line, guess);
2043
- });
2044
- } else {
2045
- doc.iter(checkWidthStart, from.line + lines.length, function(line) {
2330
+ if (!cm.options.lineWrapping) {
2331
+ doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
2046
2332
  var len = lineLength(doc, line);
2047
- if (len > view.maxLineLength) {
2048
- view.maxLine = line;
2049
- view.maxLineLength = len;
2050
- view.maxLineChanged = true;
2333
+ if (len > display.maxLineLength) {
2334
+ display.maxLine = line;
2335
+ display.maxLineLength = len;
2336
+ display.maxLineChanged = true;
2051
2337
  recomputeMaxLength = false;
2052
2338
  }
2053
2339
  });
@@ -2055,82 +2341,66 @@ window.CodeMirror = (function() {
2055
2341
  }
2056
2342
 
2057
2343
  // Adjust frontier, schedule worker
2058
- view.frontier = Math.min(view.frontier, from.line);
2344
+ doc.frontier = Math.min(doc.frontier, from.line);
2059
2345
  startWorker(cm, 400);
2060
2346
 
2061
- var lendiff = lines.length - nlines - 1;
2347
+ var lendiff = change.text.length - (to.line - from.line) - 1;
2062
2348
  // Remember that these lines changed, for updating the display
2063
2349
  regChange(cm, from.line, to.line + 1, lendiff);
2350
+
2064
2351
  if (hasHandler(cm, "change")) {
2065
- // Normalize lines to contain only strings, since that's what
2066
- // the change event handler expects
2067
- for (var i = 0; i < lines.length; ++i)
2068
- if (typeof lines[i] != "string") lines[i] = lines[i].text;
2069
- var changeObj = {from: from, to: to, text: lines, origin: origin};
2352
+ var changeObj = {from: from, to: to,
2353
+ text: change.text,
2354
+ removed: change.removed,
2355
+ origin: change.origin};
2070
2356
  if (cm.curOp.textChanged) {
2071
2357
  for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
2072
2358
  cur.next = changeObj;
2073
2359
  } else cm.curOp.textChanged = changeObj;
2074
2360
  }
2075
-
2076
- // Update the selection
2077
- var newSelFrom, newSelTo, end = {line: from.line + lines.length - 1,
2078
- ch: hlText(lastHL).length + (lines.length == 1 ? from.ch : 0)};
2079
- if (selUpdate && typeof selUpdate != "string") {
2080
- if (selUpdate.from) { newSelFrom = selUpdate.from; newSelTo = selUpdate.to; }
2081
- else newSelFrom = newSelTo = selUpdate;
2082
- } else if (selUpdate == "end") {
2083
- newSelFrom = newSelTo = end;
2084
- } else if (selUpdate == "start") {
2085
- newSelFrom = newSelTo = from;
2086
- } else if (selUpdate == "around") {
2087
- newSelFrom = from; newSelTo = end;
2088
- } else {
2089
- var adjustPos = function(pos) {
2090
- if (posLess(pos, from)) return pos;
2091
- if (!posLess(to, pos)) return end;
2092
- var line = pos.line + lendiff;
2093
- var ch = pos.ch;
2094
- if (pos.line == to.line)
2095
- ch += hlText(lastHL).length - (to.ch - (to.line == from.line ? from.ch : 0));
2096
- return {line: line, ch: ch};
2097
- };
2098
- newSelFrom = adjustPos(view.sel.from);
2099
- newSelTo = adjustPos(view.sel.to);
2100
- }
2101
- setSelection(cm, newSelFrom, newSelTo, null, true);
2102
- return end;
2103
2361
  }
2104
2362
 
2105
- function replaceRange(cm, code, from, to, origin) {
2363
+ function replaceRange(doc, code, from, to, origin) {
2106
2364
  if (!to) to = from;
2107
2365
  if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
2108
- return updateDoc(cm, from, to, splitLines(code), null, origin);
2366
+ if (typeof code == "string") code = splitLines(code);
2367
+ makeChange(doc, {from: from, to: to, text: code, origin: origin}, null);
2109
2368
  }
2110
2369
 
2111
- // SELECTION
2370
+ // POSITION OBJECT
2371
+
2372
+ function Pos(line, ch) {
2373
+ if (!(this instanceof Pos)) return new Pos(line, ch);
2374
+ this.line = line; this.ch = ch;
2375
+ }
2376
+ CodeMirror.Pos = Pos;
2112
2377
 
2113
2378
  function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
2114
2379
  function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2115
- function copyPos(x) {return {line: x.line, ch: x.ch};}
2380
+ function copyPos(x) {return Pos(x.line, x.ch);}
2381
+
2382
+ // SELECTION
2116
2383
 
2117
- function clipLine(doc, n) {return Math.max(0, Math.min(n, doc.size-1));}
2384
+ function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
2118
2385
  function clipPos(doc, pos) {
2119
- if (pos.line < 0) return {line: 0, ch: 0};
2120
- if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc, doc.size-1).text.length};
2121
- var ch = pos.ch, linelen = getLine(doc, pos.line).text.length;
2122
- if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
2123
- else if (ch < 0) return {line: pos.line, ch: 0};
2386
+ if (pos.line < doc.first) return Pos(doc.first, 0);
2387
+ var last = doc.first + doc.size - 1;
2388
+ if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
2389
+ return clipToLen(pos, getLine(doc, pos.line).text.length);
2390
+ }
2391
+ function clipToLen(pos, linelen) {
2392
+ var ch = pos.ch;
2393
+ if (ch == null || ch > linelen) return Pos(pos.line, linelen);
2394
+ else if (ch < 0) return Pos(pos.line, 0);
2124
2395
  else return pos;
2125
2396
  }
2126
- function isLine(doc, l) {return l >= 0 && l < doc.size;}
2397
+ function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
2127
2398
 
2128
2399
  // If shift is held, this will move the selection anchor. Otherwise,
2129
2400
  // it'll set the whole selection.
2130
- function extendSelection(cm, pos, other, bias) {
2131
- var sel = cm.view.sel;
2132
- if (sel.shift || sel.extend) {
2133
- var anchor = sel.anchor;
2401
+ function extendSelection(doc, pos, other, bias) {
2402
+ if (doc.sel.shift || doc.sel.extend) {
2403
+ var anchor = doc.sel.anchor;
2134
2404
  if (other) {
2135
2405
  var posBefore = posLess(pos, anchor);
2136
2406
  if (posBefore != posLess(other, anchor)) {
@@ -2140,24 +2410,38 @@ window.CodeMirror = (function() {
2140
2410
  pos = other;
2141
2411
  }
2142
2412
  }
2143
- setSelection(cm, anchor, pos, bias);
2413
+ setSelection(doc, anchor, pos, bias);
2144
2414
  } else {
2145
- setSelection(cm, pos, other || pos, bias);
2415
+ setSelection(doc, pos, other || pos, bias);
2146
2416
  }
2147
- cm.curOp.userSelChange = true;
2417
+ if (doc.cm) doc.cm.curOp.userSelChange = true;
2418
+ }
2419
+
2420
+ function filterSelectionChange(doc, anchor, head) {
2421
+ var obj = {anchor: anchor, head: head};
2422
+ signal(doc, "beforeSelectionChange", doc, obj);
2423
+ if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
2424
+ obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head);
2425
+ return obj;
2148
2426
  }
2149
2427
 
2150
2428
  // Update the selection. Last two args are only used by
2151
2429
  // updateDoc, since they have to be expressed in the line
2152
2430
  // numbers before the update.
2153
- function setSelection(cm, anchor, head, bias, checkAtomic) {
2154
- cm.view.goalColumn = null;
2155
- var sel = cm.view.sel;
2431
+ function setSelection(doc, anchor, head, bias, checkAtomic) {
2432
+ if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) {
2433
+ var filtered = filterSelectionChange(doc, anchor, head);
2434
+ head = filtered.head;
2435
+ anchor = filtered.anchor;
2436
+ }
2437
+
2438
+ var sel = doc.sel;
2439
+ sel.goalColumn = null;
2156
2440
  // Skip over atomic spans.
2157
2441
  if (checkAtomic || !posEq(anchor, sel.anchor))
2158
- anchor = skipAtomic(cm, anchor, bias, checkAtomic != "push");
2442
+ anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push");
2159
2443
  if (checkAtomic || !posEq(head, sel.head))
2160
- head = skipAtomic(cm, head, bias, checkAtomic != "push");
2444
+ head = skipAtomic(doc, head, bias, checkAtomic != "push");
2161
2445
 
2162
2446
  if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;
2163
2447
 
@@ -2166,47 +2450,54 @@ window.CodeMirror = (function() {
2166
2450
  sel.from = inv ? head : anchor;
2167
2451
  sel.to = inv ? anchor : head;
2168
2452
 
2169
- cm.curOp.updateInput = true;
2170
- cm.curOp.selectionChanged = true;
2453
+ if (doc.cm)
2454
+ doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
2455
+ doc.cm.curOp.cursorActivity = true;
2456
+
2457
+ signalLater(doc, "cursorActivity", doc);
2171
2458
  }
2172
2459
 
2173
2460
  function reCheckSelection(cm) {
2174
- setSelection(cm, cm.view.sel.from, cm.view.sel.to, null, "push");
2461
+ setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push");
2175
2462
  }
2176
2463
 
2177
- function skipAtomic(cm, pos, bias, mayClear) {
2178
- var doc = cm.view.doc, flipped = false, curPos = pos;
2464
+ function skipAtomic(doc, pos, bias, mayClear) {
2465
+ var flipped = false, curPos = pos;
2179
2466
  var dir = bias || 1;
2180
- cm.view.cantEdit = false;
2467
+ doc.cantEdit = false;
2181
2468
  search: for (;;) {
2182
- var line = getLine(doc, curPos.line), toClear;
2469
+ var line = getLine(doc, curPos.line);
2183
2470
  if (line.markedSpans) {
2184
2471
  for (var i = 0; i < line.markedSpans.length; ++i) {
2185
2472
  var sp = line.markedSpans[i], m = sp.marker;
2186
2473
  if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
2187
2474
  (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
2188
- if (mayClear && m.clearOnEnter) {
2189
- (toClear || (toClear = [])).push(m);
2190
- continue;
2191
- } else if (!m.atomic) continue;
2475
+ if (mayClear) {
2476
+ signal(m, "beforeCursorEnter");
2477
+ if (m.explicitlyCleared) {
2478
+ if (!line.markedSpans) break;
2479
+ else {--i; continue;}
2480
+ }
2481
+ }
2482
+ if (!m.atomic) continue;
2192
2483
  var newPos = m.find()[dir < 0 ? "from" : "to"];
2193
2484
  if (posEq(newPos, curPos)) {
2194
2485
  newPos.ch += dir;
2195
2486
  if (newPos.ch < 0) {
2196
- if (newPos.line) newPos = clipPos(doc, {line: newPos.line - 1});
2487
+ if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
2197
2488
  else newPos = null;
2198
2489
  } else if (newPos.ch > line.text.length) {
2199
- if (newPos.line < doc.size - 1) newPos = {line: newPos.line + 1, ch: 0};
2490
+ if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
2200
2491
  else newPos = null;
2201
2492
  }
2202
2493
  if (!newPos) {
2203
2494
  if (flipped) {
2204
2495
  // Driven in a corner -- no valid cursor position found at all
2205
2496
  // -- try again *with* clearing, if we didn't already
2206
- if (!mayClear) return skipAtomic(cm, pos, bias, true);
2497
+ if (!mayClear) return skipAtomic(doc, pos, bias, true);
2207
2498
  // Otherwise, turn off editing until further notice, and return the start of the doc
2208
- cm.view.cantEdit = true;
2209
- return {line: 0, ch: 0};
2499
+ doc.cantEdit = true;
2500
+ return Pos(doc.first, 0);
2210
2501
  }
2211
2502
  flipped = true; newPos = pos; dir = -dir;
2212
2503
  }
@@ -2215,7 +2506,6 @@ window.CodeMirror = (function() {
2215
2506
  continue search;
2216
2507
  }
2217
2508
  }
2218
- if (toClear) for (var i = 0; i < toClear.length; ++i) toClear[i].clear();
2219
2509
  }
2220
2510
  return curPos;
2221
2511
  }
@@ -2224,12 +2514,11 @@ window.CodeMirror = (function() {
2224
2514
  // SCROLLING
2225
2515
 
2226
2516
  function scrollCursorIntoView(cm) {
2227
- var view = cm.view;
2228
- var coords = scrollPosIntoView(cm, view.sel.head);
2229
- if (!view.focused) return;
2230
- var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
2231
- if (coords.top + box.top < 0) doScroll = true;
2232
- else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
2517
+ var coords = scrollPosIntoView(cm, cm.doc.sel.head);
2518
+ if (!cm.state.focused) return;
2519
+ var display = cm.display, box = getRect(display.sizer), doScroll = null, pTop = paddingTop(cm.display);
2520
+ if (coords.top + pTop + box.top < 0) doScroll = true;
2521
+ else if (coords.bottom + pTop + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
2233
2522
  if (doScroll != null && !phantom) {
2234
2523
  var hidden = display.cursor.style.display == "none";
2235
2524
  if (hidden) {
@@ -2242,18 +2531,19 @@ window.CodeMirror = (function() {
2242
2531
  }
2243
2532
  }
2244
2533
 
2245
- function scrollPosIntoView(cm, pos) {
2534
+ function scrollPosIntoView(cm, pos, margin) {
2535
+ if (margin == null) margin = 0;
2246
2536
  for (;;) {
2247
2537
  var changed = false, coords = cursorCoords(cm, pos);
2248
- var scrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
2249
- var startTop = cm.view.scrollTop, startLeft = cm.view.scrollLeft;
2538
+ var scrollPos = calculateScrollPos(cm, coords.left, coords.top - margin, coords.left, coords.bottom + margin);
2539
+ var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
2250
2540
  if (scrollPos.scrollTop != null) {
2251
2541
  setScrollTop(cm, scrollPos.scrollTop);
2252
- if (Math.abs(cm.view.scrollTop - startTop) > 1) changed = true;
2542
+ if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
2253
2543
  }
2254
2544
  if (scrollPos.scrollLeft != null) {
2255
2545
  setScrollLeft(cm, scrollPos.scrollLeft);
2256
- if (Math.abs(cm.view.scrollLeft - startLeft) > 1) changed = true;
2546
+ if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
2257
2547
  }
2258
2548
  if (!changed) return coords;
2259
2549
  }
@@ -2268,11 +2558,16 @@ window.CodeMirror = (function() {
2268
2558
  function calculateScrollPos(cm, x1, y1, x2, y2) {
2269
2559
  var display = cm.display, pt = paddingTop(display);
2270
2560
  y1 += pt; y2 += pt;
2561
+ if (y1 < 0) y1 = 0;
2271
2562
  var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
2272
- var docBottom = cm.view.doc.height + 2 * pt;
2563
+ var docBottom = cm.doc.height + paddingVert(display);
2273
2564
  var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;
2274
- if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
2275
- else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
2565
+ if (y1 < screentop) {
2566
+ result.scrollTop = atTop ? 0 : y1;
2567
+ } else if (y2 > screentop + screen) {
2568
+ var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
2569
+ if (newTop != screentop) result.scrollTop = newTop;
2570
+ }
2276
2571
 
2277
2572
  var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
2278
2573
  x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
@@ -2287,13 +2582,25 @@ window.CodeMirror = (function() {
2287
2582
  return result;
2288
2583
  }
2289
2584
 
2585
+ function updateScrollPos(cm, left, top) {
2586
+ cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left,
2587
+ scrollTop: top == null ? cm.doc.scrollTop : top};
2588
+ }
2589
+
2590
+ function addToScrollPos(cm, left, top) {
2591
+ var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop});
2592
+ var scroll = cm.display.scroller;
2593
+ pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top));
2594
+ pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left));
2595
+ }
2596
+
2290
2597
  // API UTILITIES
2291
2598
 
2292
2599
  function indentLine(cm, n, how, aggressive) {
2293
- var doc = cm.view.doc;
2600
+ var doc = cm.doc;
2294
2601
  if (!how) how = "add";
2295
2602
  if (how == "smart") {
2296
- if (!cm.view.mode.indent) how = "prev";
2603
+ if (!cm.doc.mode.indent) how = "prev";
2297
2604
  else var state = getStateBefore(cm, n);
2298
2605
  }
2299
2606
 
@@ -2301,18 +2608,20 @@ window.CodeMirror = (function() {
2301
2608
  var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
2302
2609
  var curSpaceString = line.text.match(/^\s*/)[0], indentation;
2303
2610
  if (how == "smart") {
2304
- indentation = cm.view.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
2611
+ indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
2305
2612
  if (indentation == Pass) {
2306
2613
  if (!aggressive) return;
2307
2614
  how = "prev";
2308
2615
  }
2309
2616
  }
2310
2617
  if (how == "prev") {
2311
- if (n) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
2618
+ if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
2312
2619
  else indentation = 0;
2620
+ } else if (how == "add") {
2621
+ indentation = curSpace + cm.options.indentUnit;
2622
+ } else if (how == "subtract") {
2623
+ indentation = curSpace - cm.options.indentUnit;
2313
2624
  }
2314
- else if (how == "add") indentation = curSpace + cm.options.indentUnit;
2315
- else if (how == "subtract") indentation = curSpace - cm.options.indentUnit;
2316
2625
  indentation = Math.max(0, indentation);
2317
2626
 
2318
2627
  var indentString = "", pos = 0;
@@ -2321,12 +2630,12 @@ window.CodeMirror = (function() {
2321
2630
  if (pos < indentation) indentString += spaceStr(indentation - pos);
2322
2631
 
2323
2632
  if (indentString != curSpaceString)
2324
- replaceRange(cm, indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}, "input");
2633
+ replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
2325
2634
  line.stateAfter = null;
2326
2635
  }
2327
2636
 
2328
2637
  function changeLine(cm, handle, op) {
2329
- var no = handle, line = handle, doc = cm.view.doc;
2638
+ var no = handle, line = handle, doc = cm.doc;
2330
2639
  if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
2331
2640
  else no = lineNo(handle);
2332
2641
  if (no == null) return null;
@@ -2335,12 +2644,13 @@ window.CodeMirror = (function() {
2335
2644
  return line;
2336
2645
  }
2337
2646
 
2338
- function findPosH(cm, dir, unit, visually) {
2339
- var doc = cm.view.doc, end = cm.view.sel.head, line = end.line, ch = end.ch;
2647
+ function findPosH(doc, pos, dir, unit, visually) {
2648
+ var line = pos.line, ch = pos.ch;
2340
2649
  var lineObj = getLine(doc, line);
2650
+ var possible = true;
2341
2651
  function findNextLine() {
2342
2652
  var l = line + dir;
2343
- if (l < 0 || l == doc.size) return false;
2653
+ if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
2344
2654
  line = l;
2345
2655
  return lineObj = getLine(doc, l);
2346
2656
  }
@@ -2350,22 +2660,50 @@ window.CodeMirror = (function() {
2350
2660
  if (!boundToLine && findNextLine()) {
2351
2661
  if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
2352
2662
  else ch = dir < 0 ? lineObj.text.length : 0;
2353
- } else return false;
2663
+ } else return (possible = false);
2354
2664
  } else ch = next;
2355
2665
  return true;
2356
2666
  }
2667
+
2357
2668
  if (unit == "char") moveOnce();
2358
2669
  else if (unit == "column") moveOnce(true);
2359
- else if (unit == "word") {
2360
- var sawWord = false;
2361
- for (;;) {
2362
- if (dir < 0) if (!moveOnce()) break;
2363
- if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
2364
- else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
2365
- if (dir > 0) if (!moveOnce()) break;
2670
+ else if (unit == "word" || unit == "group") {
2671
+ var sawType = null, group = unit == "group";
2672
+ for (var first = true;; first = false) {
2673
+ if (dir < 0 && !moveOnce(!first)) break;
2674
+ var cur = lineObj.text.charAt(ch) || "\n";
2675
+ var type = isWordChar(cur) ? "w"
2676
+ : !group ? null
2677
+ : /\s/.test(cur) ? null
2678
+ : "p";
2679
+ if (sawType && sawType != type) {
2680
+ if (dir < 0) {dir = 1; moveOnce();}
2681
+ break;
2682
+ }
2683
+ if (type) sawType = type;
2684
+ if (dir > 0 && !moveOnce(!first)) break;
2366
2685
  }
2367
2686
  }
2368
- return skipAtomic(cm, {line: line, ch: ch}, dir, true);
2687
+ var result = skipAtomic(doc, Pos(line, ch), dir, true);
2688
+ if (!possible) result.hitSide = true;
2689
+ return result;
2690
+ }
2691
+
2692
+ function findPosV(cm, pos, dir, unit) {
2693
+ var doc = cm.doc, x = pos.left, y;
2694
+ if (unit == "page") {
2695
+ var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
2696
+ y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
2697
+ } else if (unit == "line") {
2698
+ y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
2699
+ }
2700
+ for (;;) {
2701
+ var target = coordsChar(cm, x, y);
2702
+ if (!target.outside) break;
2703
+ if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
2704
+ y += dir * 5;
2705
+ }
2706
+ return target;
2369
2707
  }
2370
2708
 
2371
2709
  function findWordAt(line, pos) {
@@ -2373,17 +2711,17 @@ window.CodeMirror = (function() {
2373
2711
  if (line) {
2374
2712
  if (pos.after === false || end == line.length) --start; else ++end;
2375
2713
  var startChar = line.charAt(start);
2376
- var check = isWordChar(startChar) ? isWordChar :
2377
- /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} :
2378
- function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
2714
+ var check = isWordChar(startChar) ? isWordChar
2715
+ : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
2716
+ : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
2379
2717
  while (start > 0 && check(line.charAt(start - 1))) --start;
2380
2718
  while (end < line.length && check(line.charAt(end))) ++end;
2381
2719
  }
2382
- return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}};
2720
+ return {from: Pos(pos.line, start), to: Pos(pos.line, end)};
2383
2721
  }
2384
2722
 
2385
2723
  function selectLine(cm, line) {
2386
- extendSelection(cm, {line: line, ch: 0}, clipPos(cm.view.doc, {line: line + 1, ch: 0}));
2724
+ extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0)));
2387
2725
  }
2388
2726
 
2389
2727
  // PROTOTYPE
@@ -2392,24 +2730,6 @@ window.CodeMirror = (function() {
2392
2730
  // 'wrap f in an operation, performed on its `this` parameter'
2393
2731
 
2394
2732
  CodeMirror.prototype = {
2395
- getValue: function(lineSep) {
2396
- var text = [], doc = this.view.doc;
2397
- doc.iter(0, doc.size, function(line) { text.push(line.text); });
2398
- return text.join(lineSep || "\n");
2399
- },
2400
-
2401
- setValue: operation(null, function(code) {
2402
- var doc = this.view.doc, top = {line: 0, ch: 0}, lastLen = getLine(doc, doc.size-1).text.length;
2403
- updateDocInner(this, top, {line: doc.size - 1, ch: lastLen}, splitLines(code), top, top, "setValue");
2404
- }),
2405
-
2406
- getSelection: function(lineSep) { return this.getRange(this.view.sel.from, this.view.sel.to, lineSep); },
2407
-
2408
- replaceSelection: operation(null, function(code, collapse, origin) {
2409
- var sel = this.view.sel;
2410
- updateDoc(this, sel.from, sel.to, splitLines(code), collapse || "around", origin);
2411
- }),
2412
-
2413
2733
  focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
2414
2734
 
2415
2735
  setOption: function(option, value) {
@@ -2421,15 +2741,13 @@ window.CodeMirror = (function() {
2421
2741
  },
2422
2742
 
2423
2743
  getOption: function(option) {return this.options[option];},
2744
+ getDoc: function() {return this.doc;},
2424
2745
 
2425
- getMode: function() {return this.view.mode;},
2426
-
2427
- addKeyMap: function(map) {
2428
- this.view.keyMaps.push(map);
2746
+ addKeyMap: function(map, bottom) {
2747
+ this.state.keyMaps[bottom ? "push" : "unshift"](map);
2429
2748
  },
2430
-
2431
2749
  removeKeyMap: function(map) {
2432
- var maps = this.view.keyMaps;
2750
+ var maps = this.state.keyMaps;
2433
2751
  for (var i = 0; i < maps.length; ++i)
2434
2752
  if ((typeof map == "string" ? maps[i].name : maps[i]) == map) {
2435
2753
  maps.splice(i, 1);
@@ -2440,84 +2758,42 @@ window.CodeMirror = (function() {
2440
2758
  addOverlay: operation(null, function(spec, options) {
2441
2759
  var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
2442
2760
  if (mode.startState) throw new Error("Overlays may not be stateful.");
2443
- this.view.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
2444
- this.view.modeGen++;
2445
- regChange(this, 0, this.view.doc.size);
2761
+ this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
2762
+ this.state.modeGen++;
2763
+ regChange(this);
2446
2764
  }),
2447
2765
  removeOverlay: operation(null, function(spec) {
2448
- var overlays = this.view.overlays;
2766
+ var overlays = this.state.overlays;
2449
2767
  for (var i = 0; i < overlays.length; ++i) {
2450
2768
  if (overlays[i].modeSpec == spec) {
2451
2769
  overlays.splice(i, 1);
2452
- this.view.modeGen++;
2453
- regChange(this, 0, this.view.doc.size);
2770
+ this.state.modeGen++;
2771
+ regChange(this);
2454
2772
  return;
2455
2773
  }
2456
2774
  }
2457
2775
  }),
2458
2776
 
2459
- undo: operation(null, function() {unredoHelper(this, "undo");}),
2460
- redo: operation(null, function() {unredoHelper(this, "redo");}),
2461
-
2462
2777
  indentLine: operation(null, function(n, dir, aggressive) {
2463
2778
  if (typeof dir != "string") {
2464
2779
  if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
2465
2780
  else dir = dir ? "add" : "subtract";
2466
2781
  }
2467
- if (isLine(this.view.doc, n)) indentLine(this, n, dir, aggressive);
2782
+ if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
2468
2783
  }),
2469
-
2470
2784
  indentSelection: operation(null, function(how) {
2471
- var sel = this.view.sel;
2785
+ var sel = this.doc.sel;
2472
2786
  if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
2473
2787
  var e = sel.to.line - (sel.to.ch ? 0 : 1);
2474
2788
  for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
2475
2789
  }),
2476
2790
 
2477
- historySize: function() {
2478
- var hist = this.view.history;
2479
- return {undo: hist.done.length, redo: hist.undone.length};
2480
- },
2481
-
2482
- clearHistory: function() {this.view.history = makeHistory();},
2483
-
2484
- markClean: function() {
2485
- this.view.history.dirtyCounter = 0;
2486
- this.view.history.lastOp = this.view.history.lastOrigin = null;
2487
- },
2488
-
2489
- isClean: function () {return this.view.history.dirtyCounter == 0;},
2490
-
2491
- getHistory: function() {
2492
- var hist = this.view.history;
2493
- function cp(arr) {
2494
- for (var i = 0, nw = [], nwelt; i < arr.length; ++i) {
2495
- var set = arr[i];
2496
- nw.push({events: nwelt = [], fromBefore: set.fromBefore, toBefore: set.toBefore,
2497
- fromAfter: set.fromAfter, toAfter: set.toAfter});
2498
- for (var j = 0, elt = set.events; j < elt.length; ++j) {
2499
- var old = [], cur = elt[j];
2500
- nwelt.push({start: cur.start, added: cur.added, old: old});
2501
- for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k]));
2502
- }
2503
- }
2504
- return nw;
2505
- }
2506
- return {done: cp(hist.done), undone: cp(hist.undone)};
2507
- },
2508
-
2509
- setHistory: function(histData) {
2510
- var hist = this.view.history = makeHistory();
2511
- hist.done = histData.done;
2512
- hist.undone = histData.undone;
2513
- },
2514
-
2515
2791
  // Fetch the parser token for a given character. Useful for hacks
2516
2792
  // that want to inspect the mode state (say, for completion).
2517
2793
  getTokenAt: function(pos) {
2518
- var doc = this.view.doc;
2794
+ var doc = this.doc;
2519
2795
  pos = clipPos(doc, pos);
2520
- var state = getStateBefore(this, pos.line), mode = this.view.mode;
2796
+ var state = getStateBefore(this, pos.line), mode = this.doc.mode;
2521
2797
  var line = getLine(doc, pos.line);
2522
2798
  var stream = new StringStream(line.text, this.options.tabSize);
2523
2799
  while (stream.pos < pos.ch && !stream.eol()) {
@@ -2533,52 +2809,30 @@ window.CodeMirror = (function() {
2533
2809
  },
2534
2810
 
2535
2811
  getStateAfter: function(line) {
2536
- var doc = this.view.doc;
2537
- line = clipLine(doc, line == null ? doc.size - 1: line);
2812
+ var doc = this.doc;
2813
+ line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
2538
2814
  return getStateBefore(this, line + 1);
2539
2815
  },
2540
2816
 
2541
2817
  cursorCoords: function(start, mode) {
2542
- var pos, sel = this.view.sel;
2818
+ var pos, sel = this.doc.sel;
2543
2819
  if (start == null) pos = sel.head;
2544
- else if (typeof start == "object") pos = clipPos(this.view.doc, start);
2820
+ else if (typeof start == "object") pos = clipPos(this.doc, start);
2545
2821
  else pos = start ? sel.from : sel.to;
2546
2822
  return cursorCoords(this, pos, mode || "page");
2547
2823
  },
2548
2824
 
2549
2825
  charCoords: function(pos, mode) {
2550
- return charCoords(this, clipPos(this.view.doc, pos), mode || "page");
2826
+ return charCoords(this, clipPos(this.doc, pos), mode || "page");
2551
2827
  },
2552
2828
 
2553
- coordsChar: function(coords) {
2554
- var off = this.display.lineSpace.getBoundingClientRect();
2555
- return coordsChar(this, coords.left - off.left, coords.top - off.top);
2829
+ coordsChar: function(coords, mode) {
2830
+ coords = fromCoordSystem(this, coords, mode || "page");
2831
+ return coordsChar(this, coords.left, coords.top);
2556
2832
  },
2557
2833
 
2558
2834
  defaultTextHeight: function() { return textHeight(this.display); },
2559
-
2560
- markText: operation(null, function(from, to, options) {
2561
- return markText(this, clipPos(this.view.doc, from), clipPos(this.view.doc, to),
2562
- options, "range");
2563
- }),
2564
-
2565
- setBookmark: operation(null, function(pos, widget) {
2566
- pos = clipPos(this.view.doc, pos);
2567
- return markText(this, pos, pos, widget ? {replacedWith: widget} : {}, "bookmark");
2568
- }),
2569
-
2570
- findMarksAt: function(pos) {
2571
- var doc = this.view.doc;
2572
- pos = clipPos(doc, pos);
2573
- var markers = [], spans = getLine(doc, pos.line).markedSpans;
2574
- if (spans) for (var i = 0; i < spans.length; ++i) {
2575
- var span = spans[i];
2576
- if ((span.from == null || span.from <= pos.ch) &&
2577
- (span.to == null || span.to >= pos.ch))
2578
- markers.push(span.marker);
2579
- }
2580
- return markers;
2581
- },
2835
+ defaultCharWidth: function() { return charWidth(this.display); },
2582
2836
 
2583
2837
  setGutterMarker: operation(null, function(line, gutterID, value) {
2584
2838
  return changeLine(this, line, function(line) {
@@ -2590,8 +2844,8 @@ window.CodeMirror = (function() {
2590
2844
  }),
2591
2845
 
2592
2846
  clearGutter: operation(null, function(gutterID) {
2593
- var i = 0, cm = this, doc = cm.view.doc;
2594
- doc.iter(0, doc.size, function(line) {
2847
+ var cm = this, doc = cm.doc, i = doc.first;
2848
+ doc.iter(function(line) {
2595
2849
  if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
2596
2850
  line.gutterMarkers[gutterID] = null;
2597
2851
  regChange(cm, i, i + 1);
@@ -2634,9 +2888,9 @@ window.CodeMirror = (function() {
2634
2888
 
2635
2889
  lineInfo: function(line) {
2636
2890
  if (typeof line == "number") {
2637
- if (!isLine(this.view.doc, line)) return null;
2891
+ if (!isLine(this.doc, line)) return null;
2638
2892
  var n = line;
2639
- line = getLine(this.view.doc, line);
2893
+ line = getLine(this.doc, line);
2640
2894
  if (!line) return null;
2641
2895
  } else {
2642
2896
  var n = lineNo(line);
@@ -2651,16 +2905,20 @@ window.CodeMirror = (function() {
2651
2905
 
2652
2906
  addWidget: function(pos, node, scroll, vert, horiz) {
2653
2907
  var display = this.display;
2654
- pos = cursorCoords(this, clipPos(this.view.doc, pos));
2655
- var top = pos.top, left = pos.left;
2908
+ pos = cursorCoords(this, clipPos(this.doc, pos));
2909
+ var top = pos.bottom, left = pos.left;
2656
2910
  node.style.position = "absolute";
2657
2911
  display.sizer.appendChild(node);
2658
- if (vert == "over") top = pos.top;
2659
- else if (vert == "near") {
2660
- var vspace = Math.max(display.wrapper.clientHeight, this.view.doc.height),
2912
+ if (vert == "over") {
2913
+ top = pos.top;
2914
+ } else if (vert == "above" || vert == "near") {
2915
+ var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
2661
2916
  hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
2662
- if (pos.bottom + node.offsetHeight > vspace && pos.top > node.offsetHeight)
2917
+ // Default to positioning above (if specified and possible); otherwise default to positioning below
2918
+ if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
2663
2919
  top = pos.top - node.offsetHeight;
2920
+ else if (pos.bottom + node.offsetHeight <= vspace)
2921
+ top = pos.bottom;
2664
2922
  if (left + node.offsetWidth > hspace)
2665
2923
  left = hspace - node.offsetWidth;
2666
2924
  }
@@ -2678,147 +2936,71 @@ window.CodeMirror = (function() {
2678
2936
  scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
2679
2937
  },
2680
2938
 
2681
- lineCount: function() {return this.view.doc.size;},
2939
+ triggerOnKeyDown: operation(null, onKeyDown),
2682
2940
 
2683
- clipPos: function(pos) {return clipPos(this.view.doc, pos);},
2941
+ execCommand: function(cmd) {return commands[cmd](this);},
2684
2942
 
2685
- getCursor: function(start) {
2686
- var sel = this.view.sel, pos;
2687
- if (start == null || start == "head") pos = sel.head;
2688
- else if (start == "anchor") pos = sel.anchor;
2689
- else if (start == "end" || start === false) pos = sel.to;
2690
- else pos = sel.from;
2691
- return copyPos(pos);
2692
- },
2693
-
2694
- somethingSelected: function() {return !posEq(this.view.sel.from, this.view.sel.to);},
2695
-
2696
- setCursor: operation(null, function(line, ch, extend) {
2697
- var pos = clipPos(this.view.doc, typeof line == "number" ? {line: line, ch: ch || 0} : line);
2698
- if (extend) extendSelection(this, pos);
2699
- else setSelection(this, pos, pos);
2700
- }),
2701
-
2702
- setSelection: operation(null, function(anchor, head) {
2703
- var doc = this.view.doc;
2704
- setSelection(this, clipPos(doc, anchor), clipPos(doc, head || anchor));
2705
- }),
2706
-
2707
- extendSelection: operation(null, function(from, to) {
2708
- var doc = this.view.doc;
2709
- extendSelection(this, clipPos(doc, from), to && clipPos(doc, to));
2710
- }),
2711
-
2712
- setExtending: function(val) {this.view.sel.extend = val;},
2713
-
2714
- getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
2715
-
2716
- getLineHandle: function(line) {
2717
- var doc = this.view.doc;
2718
- if (isLine(doc, line)) return getLine(doc, line);
2719
- },
2720
-
2721
- getLineNumber: function(line) {return lineNo(line);},
2722
-
2723
- setLine: operation(null, function(line, text) {
2724
- if (isLine(this.view.doc, line))
2725
- replaceRange(this, text, {line: line, ch: 0}, {line: line, ch: getLine(this.view.doc, line).text.length});
2726
- }),
2727
-
2728
- removeLine: operation(null, function(line) {
2729
- if (isLine(this.view.doc, line))
2730
- replaceRange(this, "", {line: line, ch: 0}, clipPos(this.view.doc, {line: line+1, ch: 0}));
2731
- }),
2732
-
2733
- replaceRange: operation(null, function(code, from, to) {
2734
- var doc = this.view.doc;
2735
- from = clipPos(doc, from);
2736
- to = to ? clipPos(doc, to) : from;
2737
- return replaceRange(this, code, from, to);
2738
- }),
2739
-
2740
- getRange: function(from, to, lineSep) {
2741
- var doc = this.view.doc;
2742
- from = clipPos(doc, from); to = clipPos(doc, to);
2743
- var l1 = from.line, l2 = to.line;
2744
- if (l1 == l2) return getLine(doc, l1).text.slice(from.ch, to.ch);
2745
- var code = [getLine(doc, l1).text.slice(from.ch)];
2746
- doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
2747
- code.push(getLine(doc, l2).text.slice(0, to.ch));
2748
- return code.join(lineSep || "\n");
2943
+ findPosH: function(from, amount, unit, visually) {
2944
+ var dir = 1;
2945
+ if (amount < 0) { dir = -1; amount = -amount; }
2946
+ for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
2947
+ cur = findPosH(this.doc, cur, dir, unit, visually);
2948
+ if (cur.hitSide) break;
2949
+ }
2950
+ return cur;
2749
2951
  },
2750
2952
 
2751
- triggerOnKeyDown: operation(null, onKeyDown),
2752
-
2753
- execCommand: function(cmd) {return commands[cmd](this);},
2754
-
2755
- // Stuff used by commands, probably not much use to outside code.
2756
2953
  moveH: operation(null, function(dir, unit) {
2757
- var sel = this.view.sel, pos = dir < 0 ? sel.from : sel.to;
2954
+ var sel = this.doc.sel, pos;
2758
2955
  if (sel.shift || sel.extend || posEq(sel.from, sel.to))
2759
- pos = findPosH(this, dir, unit, this.options.rtlMoveVisually);
2760
- extendSelection(this, pos, pos, dir);
2956
+ pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually);
2957
+ else
2958
+ pos = dir < 0 ? sel.from : sel.to;
2959
+ extendSelection(this.doc, pos, pos, dir);
2761
2960
  }),
2762
2961
 
2763
2962
  deleteH: operation(null, function(dir, unit) {
2764
- var sel = this.view.sel;
2765
- if (!posEq(sel.from, sel.to)) replaceRange(this, "", sel.from, sel.to, "delete");
2766
- else replaceRange(this, "", sel.from, findPosH(this, dir, unit, false), "delete");
2963
+ var sel = this.doc.sel;
2964
+ if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete");
2965
+ else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete");
2767
2966
  this.curOp.userSelChange = true;
2768
2967
  }),
2769
2968
 
2770
- moveV: operation(null, function(dir, unit) {
2771
- var view = this.view, doc = view.doc, display = this.display;
2772
- var cur = view.sel.head, pos = cursorCoords(this, cur, "div");
2773
- var x = pos.left, y;
2774
- if (view.goalColumn != null) x = view.goalColumn;
2775
- if (unit == "page") {
2776
- var pageSize = Math.min(display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
2777
- y = pos.top + dir * pageSize;
2778
- } else if (unit == "line") {
2779
- y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
2969
+ findPosV: function(from, amount, unit, goalColumn) {
2970
+ var dir = 1, x = goalColumn;
2971
+ if (amount < 0) { dir = -1; amount = -amount; }
2972
+ for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
2973
+ var coords = cursorCoords(this, cur, "div");
2974
+ if (x == null) x = coords.left;
2975
+ else coords.left = x;
2976
+ cur = findPosV(this, coords, dir, unit);
2977
+ if (cur.hitSide) break;
2780
2978
  }
2781
- do {
2782
- var target = coordsChar(this, x, y);
2783
- y += dir * 5;
2784
- } while (target.outside && (dir < 0 ? y > 0 : y < doc.height));
2979
+ return cur;
2980
+ },
2785
2981
 
2786
- if (unit == "page") display.scrollbarV.scrollTop += charCoords(this, target, "div").top - pos.top;
2787
- extendSelection(this, target, target, dir);
2788
- view.goalColumn = x;
2982
+ moveV: operation(null, function(dir, unit) {
2983
+ var sel = this.doc.sel;
2984
+ var pos = cursorCoords(this, sel.head, "div");
2985
+ if (sel.goalColumn != null) pos.left = sel.goalColumn;
2986
+ var target = findPosV(this, pos, dir, unit);
2987
+
2988
+ if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
2989
+ extendSelection(this.doc, target, target, dir);
2990
+ sel.goalColumn = pos.left;
2789
2991
  }),
2790
2992
 
2791
2993
  toggleOverwrite: function() {
2792
- if (this.view.overwrite = !this.view.overwrite)
2994
+ if (this.state.overwrite = !this.state.overwrite)
2793
2995
  this.display.cursor.className += " CodeMirror-overwrite";
2794
2996
  else
2795
2997
  this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
2796
2998
  },
2999
+ hasFocus: function() { return this.state.focused; },
2797
3000
 
2798
- posFromIndex: function(off) {
2799
- var lineNo = 0, ch, doc = this.view.doc;
2800
- doc.iter(0, doc.size, function(line) {
2801
- var sz = line.text.length + 1;
2802
- if (sz > off) { ch = off; return true; }
2803
- off -= sz;
2804
- ++lineNo;
2805
- });
2806
- return clipPos(doc, {line: lineNo, ch: ch});
2807
- },
2808
- indexFromPos: function (coords) {
2809
- coords = clipPos(this.view.doc, coords);
2810
- var index = coords.ch;
2811
- this.view.doc.iter(0, coords.line, function (line) {
2812
- index += line.text.length + 1;
2813
- });
2814
- return index;
2815
- },
2816
-
2817
- scrollTo: function(x, y) {
2818
- if (x != null) this.display.scrollbarH.scrollLeft = this.display.scroller.scrollLeft = x;
2819
- if (y != null) this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = y;
2820
- updateDisplay(this, []);
2821
- },
3001
+ scrollTo: operation(null, function(x, y) {
3002
+ updateScrollPos(this, x, y);
3003
+ }),
2822
3004
  getScrollInfo: function() {
2823
3005
  var scroller = this.display.scroller, co = scrollerCutOff;
2824
3006
  return {left: scroller.scrollLeft, top: scroller.scrollTop,
@@ -2826,15 +3008,19 @@ window.CodeMirror = (function() {
2826
3008
  clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
2827
3009
  },
2828
3010
 
2829
- scrollIntoView: function(pos) {
2830
- if (typeof pos == "number") pos = {line: pos, ch: 0};
3011
+ scrollIntoView: operation(null, function(pos, margin) {
3012
+ if (typeof pos == "number") pos = Pos(pos, 0);
3013
+ if (!margin) margin = 0;
3014
+ var coords = pos;
3015
+
2831
3016
  if (!pos || pos.line != null) {
2832
- pos = pos ? clipPos(this.view.doc, pos) : this.view.sel.head;
2833
- scrollPosIntoView(this, pos);
2834
- } else {
2835
- scrollIntoView(this, pos.left, pos.top, pos.right, pos.bottom);
3017
+ this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head;
3018
+ this.curOp.scrollToPosMargin = margin;
3019
+ coords = cursorCoords(this, this.curOp.scrollToPos);
2836
3020
  }
2837
- },
3021
+ var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin);
3022
+ updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop);
3023
+ }),
2838
3024
 
2839
3025
  setSize: function(width, height) {
2840
3026
  function interpret(val) {
@@ -2848,17 +3034,23 @@ window.CodeMirror = (function() {
2848
3034
  on: function(type, f) {on(this, type, f);},
2849
3035
  off: function(type, f) {off(this, type, f);},
2850
3036
 
2851
- operation: function(f){return operation(this, f)();},
3037
+ operation: function(f){return runInOp(this, f);},
2852
3038
 
2853
- refresh: function() {
3039
+ refresh: operation(null, function() {
2854
3040
  clearCaches(this);
2855
- var sTop = this.view.scrollTop, sLeft = this.view.scrollLeft;
2856
- if (this.display.scroller.scrollHeight > sTop)
2857
- this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = sTop;
2858
- if (this.display.scroller.scrollWidth > sLeft)
2859
- this.display.scrollbarH.scrollLeft = this.display.scroller.scrollLeft = sLeft;
2860
- updateDisplay(this, true);
2861
- },
3041
+ updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop);
3042
+ regChange(this);
3043
+ }),
3044
+
3045
+ swapDoc: operation(null, function(doc) {
3046
+ var old = this.doc;
3047
+ old.cm = null;
3048
+ attachDoc(this, doc);
3049
+ clearCaches(this);
3050
+ resetInput(this, true);
3051
+ updateScrollPos(this, doc.scrollLeft, doc.scrollTop);
3052
+ return old;
3053
+ }),
2862
3054
 
2863
3055
  getInputField: function(){return this.display.input;},
2864
3056
  getWrapperElement: function(){return this.display.wrapper;},
@@ -2883,8 +3075,13 @@ window.CodeMirror = (function() {
2883
3075
 
2884
3076
  // These two are, on init, called from the constructor because they
2885
3077
  // have to be initialized before the editor can start at all.
2886
- option("value", "", function(cm, val) {cm.setValue(val);}, true);
2887
- option("mode", null, loadMode, true);
3078
+ option("value", "", function(cm, val) {
3079
+ cm.setValue(val);
3080
+ }, true);
3081
+ option("mode", null, function(cm, val) {
3082
+ cm.doc.modeOption = val;
3083
+ loadMode(cm);
3084
+ }, true);
2888
3085
 
2889
3086
  option("indentUnit", 2, loadMode, true);
2890
3087
  option("indentWithTabs", false);
@@ -2892,7 +3089,7 @@ window.CodeMirror = (function() {
2892
3089
  option("tabSize", 4, function(cm) {
2893
3090
  loadMode(cm);
2894
3091
  clearCaches(cm);
2895
- updateDisplay(cm, true);
3092
+ regChange(cm);
2896
3093
  }, true);
2897
3094
  option("electricChars", true);
2898
3095
  option("rtlMoveVisually", !windows);
@@ -2923,7 +3120,7 @@ window.CodeMirror = (function() {
2923
3120
  option("firstLineNumber", 1, guttersChanged, true);
2924
3121
  option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
2925
3122
  option("showCursorWhenSelecting", false, updateSelection, true);
2926
-
3123
+
2927
3124
  option("readOnly", false, function(cm, val) {
2928
3125
  if (val == "nocursor") {onBlur(cm); cm.display.input.blur();}
2929
3126
  else if (!val) resetInput(cm, true);
@@ -2936,8 +3133,13 @@ window.CodeMirror = (function() {
2936
3133
  option("workDelay", 100);
2937
3134
  option("flattenSpans", true);
2938
3135
  option("pollInterval", 100);
2939
- option("undoDepth", 40);
3136
+ option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
3137
+ option("historyEventDelay", 500);
2940
3138
  option("viewportMargin", 10, function(cm){cm.refresh();}, true);
3139
+ option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true);
3140
+ option("moveInputWithCursor", true, function(cm, val) {
3141
+ if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
3142
+ });
2941
3143
 
2942
3144
  option("tabindex", null, function(cm, val) {
2943
3145
  cm.display.input.tabIndex = val || "";
@@ -2996,8 +3198,7 @@ window.CodeMirror = (function() {
2996
3198
  var modeExtensions = CodeMirror.modeExtensions = {};
2997
3199
  CodeMirror.extendMode = function(mode, properties) {
2998
3200
  var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
2999
- for (var prop in properties) if (properties.hasOwnProperty(prop))
3000
- exts[prop] = properties[prop];
3201
+ copyObj(properties, exts);
3001
3202
  };
3002
3203
 
3003
3204
  // EXTENSIONS
@@ -3005,7 +3206,9 @@ window.CodeMirror = (function() {
3005
3206
  CodeMirror.defineExtension = function(name, func) {
3006
3207
  CodeMirror.prototype[name] = func;
3007
3208
  };
3008
-
3209
+ CodeMirror.defineDocExtension = function(name, func) {
3210
+ Doc.prototype[name] = func;
3211
+ };
3009
3212
  CodeMirror.defineOption = option;
3010
3213
 
3011
3214
  var initHooks = [];
@@ -3045,21 +3248,21 @@ window.CodeMirror = (function() {
3045
3248
  // STANDARD COMMANDS
3046
3249
 
3047
3250
  var commands = CodeMirror.commands = {
3048
- selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
3251
+ selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));},
3049
3252
  killLine: function(cm) {
3050
3253
  var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
3051
3254
  if (!sel && cm.getLine(from.line).length == from.ch)
3052
- cm.replaceRange("", from, {line: from.line + 1, ch: 0}, "delete");
3053
- else cm.replaceRange("", from, sel ? to : {line: from.line}, "delete");
3255
+ cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete");
3256
+ else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete");
3054
3257
  },
3055
3258
  deleteLine: function(cm) {
3056
3259
  var l = cm.getCursor().line;
3057
- cm.replaceRange("", {line: l, ch: 0}, {line: l}, "delete");
3260
+ cm.replaceRange("", Pos(l, 0), Pos(l), "+delete");
3058
3261
  },
3059
3262
  undo: function(cm) {cm.undo();},
3060
3263
  redo: function(cm) {cm.redo();},
3061
- goDocStart: function(cm) {cm.extendSelection({line: 0, ch: 0});},
3062
- goDocEnd: function(cm) {cm.extendSelection({line: cm.lineCount() - 1});},
3264
+ goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
3265
+ goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
3063
3266
  goLineStart: function(cm) {
3064
3267
  cm.extendSelection(lineStart(cm, cm.getCursor().line));
3065
3268
  },
@@ -3070,12 +3273,20 @@ window.CodeMirror = (function() {
3070
3273
  if (!order || order[0].level == 0) {
3071
3274
  var firstNonWS = Math.max(0, line.text.search(/\S/));
3072
3275
  var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch;
3073
- cm.extendSelection({line: start.line, ch: inWS ? 0 : firstNonWS});
3276
+ cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS));
3074
3277
  } else cm.extendSelection(start);
3075
3278
  },
3076
3279
  goLineEnd: function(cm) {
3077
3280
  cm.extendSelection(lineEnd(cm, cm.getCursor().line));
3078
3281
  },
3282
+ goLineRight: function(cm) {
3283
+ var top = cm.charCoords(cm.getCursor(), "div").top + 5;
3284
+ cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"));
3285
+ },
3286
+ goLineLeft: function(cm) {
3287
+ var top = cm.charCoords(cm.getCursor(), "div").top + 5;
3288
+ cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div"));
3289
+ },
3079
3290
  goLineUp: function(cm) {cm.moveV(-1, "line");},
3080
3291
  goLineDown: function(cm) {cm.moveV(1, "line");},
3081
3292
  goPageUp: function(cm) {cm.moveV(-1, "page");},
@@ -3085,28 +3296,32 @@ window.CodeMirror = (function() {
3085
3296
  goColumnLeft: function(cm) {cm.moveH(-1, "column");},
3086
3297
  goColumnRight: function(cm) {cm.moveH(1, "column");},
3087
3298
  goWordLeft: function(cm) {cm.moveH(-1, "word");},
3299
+ goGroupRight: function(cm) {cm.moveH(1, "group");},
3300
+ goGroupLeft: function(cm) {cm.moveH(-1, "group");},
3088
3301
  goWordRight: function(cm) {cm.moveH(1, "word");},
3089
3302
  delCharBefore: function(cm) {cm.deleteH(-1, "char");},
3090
3303
  delCharAfter: function(cm) {cm.deleteH(1, "char");},
3091
3304
  delWordBefore: function(cm) {cm.deleteH(-1, "word");},
3092
3305
  delWordAfter: function(cm) {cm.deleteH(1, "word");},
3306
+ delGroupBefore: function(cm) {cm.deleteH(-1, "group");},
3307
+ delGroupAfter: function(cm) {cm.deleteH(1, "group");},
3093
3308
  indentAuto: function(cm) {cm.indentSelection("smart");},
3094
3309
  indentMore: function(cm) {cm.indentSelection("add");},
3095
3310
  indentLess: function(cm) {cm.indentSelection("subtract");},
3096
- insertTab: function(cm) {cm.replaceSelection("\t", "end", "input");},
3311
+ insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");},
3097
3312
  defaultTab: function(cm) {
3098
3313
  if (cm.somethingSelected()) cm.indentSelection("add");
3099
- else cm.replaceSelection("\t", "end", "input");
3314
+ else cm.replaceSelection("\t", "end", "+input");
3100
3315
  },
3101
3316
  transposeChars: function(cm) {
3102
3317
  var cur = cm.getCursor(), line = cm.getLine(cur.line);
3103
3318
  if (cur.ch > 0 && cur.ch < line.length - 1)
3104
3319
  cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
3105
- {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
3320
+ Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
3106
3321
  },
3107
3322
  newlineAndIndent: function(cm) {
3108
3323
  operation(cm, function() {
3109
- cm.replaceSelection("\n", "end", "input");
3324
+ cm.replaceSelection("\n", "end", "+input");
3110
3325
  cm.indentLine(cm.getCursor().line, null, true);
3111
3326
  })();
3112
3327
  },
@@ -3127,17 +3342,17 @@ window.CodeMirror = (function() {
3127
3342
  keyMap.pcDefault = {
3128
3343
  "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
3129
3344
  "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
3130
- "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
3131
- "Ctrl-Backspace": "delWordBefore", "Ctrl-Delete": "delWordAfter", "Ctrl-S": "save", "Ctrl-F": "find",
3345
+ "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
3346
+ "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
3132
3347
  "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
3133
3348
  "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
3134
3349
  fallthrough: "basic"
3135
3350
  };
3136
3351
  keyMap.macDefault = {
3137
3352
  "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
3138
- "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
3139
- "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordBefore",
3140
- "Ctrl-Alt-Backspace": "delWordAfter", "Alt-Delete": "delWordAfter", "Cmd-S": "save", "Cmd-F": "find",
3353
+ "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
3354
+ "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore",
3355
+ "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
3141
3356
  "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
3142
3357
  "Cmd-[": "indentLess", "Cmd-]": "indentMore",
3143
3358
  fallthrough: ["basic", "emacsy"]
@@ -3157,37 +3372,47 @@ window.CodeMirror = (function() {
3157
3372
  else return val;
3158
3373
  }
3159
3374
 
3160
- function lookupKey(name, maps, handle, stop) {
3375
+ function lookupKey(name, maps, handle) {
3161
3376
  function lookup(map) {
3162
3377
  map = getKeyMap(map);
3163
3378
  var found = map[name];
3164
- if (found === false) {
3165
- if (stop) stop();
3166
- return true;
3167
- }
3379
+ if (found === false) return "stop";
3168
3380
  if (found != null && handle(found)) return true;
3169
- if (map.nofallthrough) {
3170
- if (stop) stop();
3171
- return true;
3172
- }
3381
+ if (map.nofallthrough) return "stop";
3382
+
3173
3383
  var fallthrough = map.fallthrough;
3174
3384
  if (fallthrough == null) return false;
3175
3385
  if (Object.prototype.toString.call(fallthrough) != "[object Array]")
3176
3386
  return lookup(fallthrough);
3177
3387
  for (var i = 0, e = fallthrough.length; i < e; ++i) {
3178
- if (lookup(fallthrough[i])) return true;
3388
+ var done = lookup(fallthrough[i]);
3389
+ if (done) return done;
3179
3390
  }
3180
3391
  return false;
3181
3392
  }
3182
3393
 
3183
- for (var i = 0; i < maps.length; ++i)
3184
- if (lookup(maps[i])) return true;
3394
+ for (var i = 0; i < maps.length; ++i) {
3395
+ var done = lookup(maps[i]);
3396
+ if (done) return done;
3397
+ }
3185
3398
  }
3186
3399
  function isModifierKey(event) {
3187
- var name = keyNames[e_prop(event, "keyCode")];
3400
+ var name = keyNames[event.keyCode];
3188
3401
  return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
3189
3402
  }
3403
+ function keyName(event, noShift) {
3404
+ if (opera && event.keyCode == 34 && event["char"]) return false;
3405
+ var name = keyNames[event.keyCode];
3406
+ if (name == null || event.altGraphKey) return false;
3407
+ if (event.altKey) name = "Alt-" + name;
3408
+ if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name;
3409
+ if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name;
3410
+ if (!noShift && event.shiftKey) name = "Shift-" + name;
3411
+ return name;
3412
+ }
3413
+ CodeMirror.lookupKey = lookupKey;
3190
3414
  CodeMirror.isModifierKey = isModifierKey;
3415
+ CodeMirror.keyName = keyName;
3191
3416
 
3192
3417
  // FROMTEXTAREA
3193
3418
 
@@ -3196,6 +3421,8 @@ window.CodeMirror = (function() {
3196
3421
  options.value = textarea.value;
3197
3422
  if (!options.tabindex && textarea.tabindex)
3198
3423
  options.tabindex = textarea.tabindex;
3424
+ if (!options.placeholder && textarea.placeholder)
3425
+ options.placeholder = textarea.placeholder;
3199
3426
  // Set autofocus to true if this textarea is focused, or if it has
3200
3427
  // autofocus and no other element is focused.
3201
3428
  if (options.autofocus == null) {
@@ -3208,17 +3435,19 @@ window.CodeMirror = (function() {
3208
3435
 
3209
3436
  function save() {textarea.value = cm.getValue();}
3210
3437
  if (textarea.form) {
3211
- // Deplorable hack to make the submit method do the right thing.
3212
3438
  on(textarea.form, "submit", save);
3213
- var form = textarea.form, realSubmit = form.submit;
3214
- try {
3215
- form.submit = function wrappedSubmit() {
3216
- save();
3217
- form.submit = realSubmit;
3218
- form.submit();
3219
- form.submit = wrappedSubmit;
3220
- };
3221
- } catch(e) {}
3439
+ // Deplorable hack to make the submit method do the right thing.
3440
+ if (!options.leaveSubmitMethodAlone) {
3441
+ var form = textarea.form, realSubmit = form.submit;
3442
+ try {
3443
+ var wrappedSubmit = form.submit = function() {
3444
+ save();
3445
+ form.submit = realSubmit;
3446
+ form.submit();
3447
+ form.submit = wrappedSubmit;
3448
+ };
3449
+ } catch(e) {}
3450
+ }
3222
3451
  }
3223
3452
 
3224
3453
  textarea.style.display = "none";
@@ -3250,6 +3479,7 @@ window.CodeMirror = (function() {
3250
3479
  this.pos = this.start = 0;
3251
3480
  this.string = string;
3252
3481
  this.tabSize = tabSize || 8;
3482
+ this.lastColumnPos = this.lastColumnValue = 0;
3253
3483
  }
3254
3484
 
3255
3485
  StringStream.prototype = {
@@ -3282,12 +3512,19 @@ window.CodeMirror = (function() {
3282
3512
  if (found > -1) {this.pos = found; return true;}
3283
3513
  },
3284
3514
  backUp: function(n) {this.pos -= n;},
3285
- column: function() {return countColumn(this.string, this.start, this.tabSize);},
3515
+ column: function() {
3516
+ if (this.lastColumnPos < this.start) {
3517
+ this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
3518
+ this.lastColumnPos = this.start;
3519
+ }
3520
+ return this.lastColumnValue;
3521
+ },
3286
3522
  indentation: function() {return countColumn(this.string, null, this.tabSize);},
3287
3523
  match: function(pattern, consume, caseInsensitive) {
3288
3524
  if (typeof pattern == "string") {
3289
3525
  var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
3290
- if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
3526
+ var substr = this.string.substr(this.pos, pattern.length);
3527
+ if (cased(substr) == cased(pattern)) {
3291
3528
  if (consume !== false) this.pos += pattern.length;
3292
3529
  return true;
3293
3530
  }
@@ -3304,17 +3541,18 @@ window.CodeMirror = (function() {
3304
3541
 
3305
3542
  // TEXTMARKERS
3306
3543
 
3307
- function TextMarker(cm, type) {
3544
+ function TextMarker(doc, type) {
3308
3545
  this.lines = [];
3309
3546
  this.type = type;
3310
- this.cm = cm;
3547
+ this.doc = doc;
3311
3548
  }
3312
3549
  CodeMirror.TextMarker = TextMarker;
3313
3550
 
3314
3551
  TextMarker.prototype.clear = function() {
3315
3552
  if (this.explicitlyCleared) return;
3316
- startOperation(this.cm);
3317
- var view = this.cm.view, min = null, max = null;
3553
+ var cm = this.doc.cm, withOp = cm && !cm.curOp;
3554
+ if (withOp) startOperation(cm);
3555
+ var min = null, max = null;
3318
3556
  for (var i = 0; i < this.lines.length; ++i) {
3319
3557
  var line = this.lines[i];
3320
3558
  var span = getMarkedSpanFor(line.markedSpans, this);
@@ -3322,27 +3560,27 @@ window.CodeMirror = (function() {
3322
3560
  line.markedSpans = removeMarkedSpan(line.markedSpans, span);
3323
3561
  if (span.from != null)
3324
3562
  min = lineNo(line);
3325
- else if (this.collapsed && !lineIsHidden(line))
3326
- updateLineHeight(line, textHeight(this.cm.display));
3327
- }
3328
- if (this.collapsed && !this.cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
3329
- var visual = visualLine(view.doc, this.lines[i]), len = lineLength(view.doc, visual);
3330
- if (len > view.maxLineLength) {
3331
- view.maxLine = visual;
3332
- view.maxLineLength = len;
3333
- view.maxLineChanged = true;
3563
+ else if (this.collapsed && !lineIsHidden(this.doc, line) && cm)
3564
+ updateLineHeight(line, textHeight(cm.display));
3565
+ }
3566
+ if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
3567
+ var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual);
3568
+ if (len > cm.display.maxLineLength) {
3569
+ cm.display.maxLine = visual;
3570
+ cm.display.maxLineLength = len;
3571
+ cm.display.maxLineChanged = true;
3334
3572
  }
3335
3573
  }
3336
3574
 
3337
- if (min != null) regChange(this.cm, min, max + 1);
3575
+ if (min != null && cm) regChange(cm, min, max + 1);
3338
3576
  this.lines.length = 0;
3339
3577
  this.explicitlyCleared = true;
3340
- if (this.collapsed && this.cm.view.cantEdit) {
3341
- this.cm.view.cantEdit = false;
3342
- reCheckSelection(this.cm);
3578
+ if (this.collapsed && this.doc.cantEdit) {
3579
+ this.doc.cantEdit = false;
3580
+ if (cm) reCheckSelection(cm);
3343
3581
  }
3344
- endOperation(this.cm);
3345
- signalLater(this.cm, this, "clear");
3582
+ if (withOp) endOperation(cm);
3583
+ signalLater(this, "clear");
3346
3584
  };
3347
3585
 
3348
3586
  TextMarker.prototype.find = function() {
@@ -3352,8 +3590,8 @@ window.CodeMirror = (function() {
3352
3590
  var span = getMarkedSpanFor(line.markedSpans, this);
3353
3591
  if (span.from != null || span.to != null) {
3354
3592
  var found = lineNo(line);
3355
- if (span.from != null) from = {line: found, ch: span.from};
3356
- if (span.to != null) to = {line: found, ch: span.to};
3593
+ if (span.from != null) from = Pos(found, span.from);
3594
+ if (span.to != null) to = Pos(found, span.to);
3357
3595
  }
3358
3596
  }
3359
3597
  if (this.type == "bookmark") return from;
@@ -3366,28 +3604,48 @@ window.CodeMirror = (function() {
3366
3604
  inclusiveLeft: this.inclusiveLeft, inclusiveRight: this.inclusiveRight,
3367
3605
  atomic: this.atomic,
3368
3606
  collapsed: this.collapsed,
3369
- clearOnEnter: this.clearOnEnter,
3370
3607
  replacedWith: copyWidget ? repl && repl.cloneNode(true) : repl,
3371
3608
  readOnly: this.readOnly,
3372
3609
  startStyle: this.startStyle, endStyle: this.endStyle};
3373
3610
  };
3374
3611
 
3375
- function markText(cm, from, to, options, type) {
3376
- var doc = cm.view.doc;
3377
- var marker = new TextMarker(cm, type);
3612
+ TextMarker.prototype.attachLine = function(line) {
3613
+ if (!this.lines.length && this.doc.cm) {
3614
+ var op = this.doc.cm.curOp;
3615
+ if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
3616
+ (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this);
3617
+ }
3618
+ this.lines.push(line);
3619
+ };
3620
+ TextMarker.prototype.detachLine = function(line) {
3621
+ this.lines.splice(indexOf(this.lines, line), 1);
3622
+ if (!this.lines.length && this.doc.cm) {
3623
+ var op = this.doc.cm.curOp;
3624
+ (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);
3625
+ }
3626
+ };
3627
+
3628
+ function markText(doc, from, to, options, type) {
3629
+ if (options && options.shared) return markTextShared(doc, from, to, options, type);
3630
+ if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
3631
+
3632
+ var marker = new TextMarker(doc, type);
3378
3633
  if (type == "range" && !posLess(from, to)) return marker;
3379
- if (options) for (var opt in options) if (options.hasOwnProperty(opt))
3380
- marker[opt] = options[opt];
3634
+ if (options) copyObj(options, marker);
3381
3635
  if (marker.replacedWith) {
3382
3636
  marker.collapsed = true;
3383
3637
  marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
3384
3638
  }
3385
3639
  if (marker.collapsed) sawCollapsedSpans = true;
3386
3640
 
3387
- var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd;
3641
+ if (marker.addToHistory)
3642
+ addToHistory(doc, {from: from, to: to, origin: "markText"},
3643
+ {head: doc.sel.head, anchor: doc.sel.anchor}, NaN);
3644
+
3645
+ var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;
3388
3646
  doc.iter(curLine, to.line + 1, function(line) {
3389
- if (marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.view.maxLine)
3390
- cm.curOp.updateMaxLine = true;
3647
+ if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
3648
+ updateMaxLine = true;
3391
3649
  var span = {from: null, to: null, marker: marker};
3392
3650
  size += line.text.length;
3393
3651
  if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
@@ -3401,13 +3659,15 @@ window.CodeMirror = (function() {
3401
3659
  ++curLine;
3402
3660
  });
3403
3661
  if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
3404
- if (lineIsHidden(line)) updateLineHeight(line, 0);
3662
+ if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
3405
3663
  });
3406
3664
 
3665
+ if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); });
3666
+
3407
3667
  if (marker.readOnly) {
3408
3668
  sawReadOnlySpans = true;
3409
- if (cm.view.history.done.length || cm.view.history.undone.length)
3410
- cm.clearHistory();
3669
+ if (doc.history.done.length || doc.history.undone.length)
3670
+ doc.clearHistory();
3411
3671
  }
3412
3672
  if (marker.collapsed) {
3413
3673
  if (collapsedAtStart != collapsedAtEnd)
@@ -3415,12 +3675,58 @@ window.CodeMirror = (function() {
3415
3675
  marker.size = size;
3416
3676
  marker.atomic = true;
3417
3677
  }
3418
- if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed)
3419
- regChange(cm, from.line, to.line + 1);
3420
- if (marker.atomic) reCheckSelection(cm);
3678
+ if (cm) {
3679
+ if (updateMaxLine) cm.curOp.updateMaxLine = true;
3680
+ if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed)
3681
+ regChange(cm, from.line, to.line + 1);
3682
+ if (marker.atomic) reCheckSelection(cm);
3683
+ }
3421
3684
  return marker;
3422
3685
  }
3423
3686
 
3687
+ // SHARED TEXTMARKERS
3688
+
3689
+ function SharedTextMarker(markers, primary) {
3690
+ this.markers = markers;
3691
+ this.primary = primary;
3692
+ for (var i = 0, me = this; i < markers.length; ++i) {
3693
+ markers[i].parent = this;
3694
+ on(markers[i], "clear", function(){me.clear();});
3695
+ }
3696
+ }
3697
+ CodeMirror.SharedTextMarker = SharedTextMarker;
3698
+
3699
+ SharedTextMarker.prototype.clear = function() {
3700
+ if (this.explicitlyCleared) return;
3701
+ this.explicitlyCleared = true;
3702
+ for (var i = 0; i < this.markers.length; ++i)
3703
+ this.markers[i].clear();
3704
+ signalLater(this, "clear");
3705
+ };
3706
+ SharedTextMarker.prototype.find = function() {
3707
+ return this.primary.find();
3708
+ };
3709
+ SharedTextMarker.prototype.getOptions = function(copyWidget) {
3710
+ var inner = this.primary.getOptions(copyWidget);
3711
+ inner.shared = true;
3712
+ return inner;
3713
+ };
3714
+
3715
+ function markTextShared(doc, from, to, options, type) {
3716
+ options = copyObj(options);
3717
+ options.shared = false;
3718
+ var markers = [markText(doc, from, to, options, type)], primary = markers[0];
3719
+ var widget = options.replacedWith;
3720
+ linkedDocs(doc, function(doc) {
3721
+ if (widget) options.replacedWith = widget.cloneNode(true);
3722
+ markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
3723
+ for (var i = 0; i < doc.linked.length; ++i)
3724
+ if (doc.linked[i].isParent) return;
3725
+ primary = lst(markers);
3726
+ });
3727
+ return new SharedTextMarker(markers, primary);
3728
+ }
3729
+
3424
3730
  // TEXTMARKER SPANS
3425
3731
 
3426
3732
  function getMarkedSpanFor(spans, marker) {
@@ -3436,14 +3742,14 @@ window.CodeMirror = (function() {
3436
3742
  }
3437
3743
  function addMarkedSpan(line, span) {
3438
3744
  line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
3439
- span.marker.lines.push(line);
3745
+ span.marker.attachLine(line);
3440
3746
  }
3441
3747
 
3442
- function markedSpansBefore(old, startCh) {
3748
+ function markedSpansBefore(old, startCh, isInsert) {
3443
3749
  if (old) for (var i = 0, nw; i < old.length; ++i) {
3444
3750
  var span = old[i], marker = span.marker;
3445
3751
  var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
3446
- if (startsBefore || marker.type == "bookmark" && span.from == startCh) {
3752
+ if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) {
3447
3753
  var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
3448
3754
  (nw || (nw = [])).push({from: span.from,
3449
3755
  to: endsAfter ? null : span.to,
@@ -3453,11 +3759,11 @@ window.CodeMirror = (function() {
3453
3759
  return nw;
3454
3760
  }
3455
3761
 
3456
- function markedSpansAfter(old, startCh, endCh) {
3762
+ function markedSpansAfter(old, endCh, isInsert) {
3457
3763
  if (old) for (var i = 0, nw; i < old.length; ++i) {
3458
3764
  var span = old[i], marker = span.marker;
3459
3765
  var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
3460
- if (endsAfter || marker.type == "bookmark" && span.from == endCh && span.from != startCh) {
3766
+ if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) {
3461
3767
  var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
3462
3768
  (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
3463
3769
  to: span.to == null ? null : span.to - endCh,
@@ -3467,14 +3773,18 @@ window.CodeMirror = (function() {
3467
3773
  return nw;
3468
3774
  }
3469
3775
 
3470
- function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) {
3471
- if (!oldFirst && !oldLast) return newText;
3776
+ function stretchSpansOverChange(doc, change) {
3777
+ var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
3778
+ var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
3779
+ if (!oldFirst && !oldLast) return null;
3780
+
3781
+ var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to);
3472
3782
  // Get the spans that 'stick out' on both sides
3473
- var first = markedSpansBefore(oldFirst, startCh);
3474
- var last = markedSpansAfter(oldLast, startCh, endCh);
3783
+ var first = markedSpansBefore(oldFirst, startCh, isInsert);
3784
+ var last = markedSpansAfter(oldLast, endCh, isInsert);
3475
3785
 
3476
3786
  // Next, merge those two ends
3477
- var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0);
3787
+ var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);
3478
3788
  if (first) {
3479
3789
  // Fix up .to properties of first
3480
3790
  for (var i = 0; i < first.length; ++i) {
@@ -3504,21 +3814,43 @@ window.CodeMirror = (function() {
3504
3814
  }
3505
3815
  }
3506
3816
 
3507
- var newMarkers = [newHL(newText[0], first)];
3817
+ var newMarkers = [first];
3508
3818
  if (!sameLine) {
3509
3819
  // Fill gap with whole-line-spans
3510
- var gap = newText.length - 2, gapMarkers;
3820
+ var gap = change.text.length - 2, gapMarkers;
3511
3821
  if (gap > 0 && first)
3512
3822
  for (var i = 0; i < first.length; ++i)
3513
3823
  if (first[i].to == null)
3514
3824
  (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker});
3515
3825
  for (var i = 0; i < gap; ++i)
3516
- newMarkers.push(newHL(newText[i+1], gapMarkers));
3517
- newMarkers.push(newHL(lst(newText), last));
3826
+ newMarkers.push(gapMarkers);
3827
+ newMarkers.push(last);
3518
3828
  }
3519
3829
  return newMarkers;
3520
3830
  }
3521
3831
 
3832
+ function mergeOldSpans(doc, change) {
3833
+ var old = getOldSpans(doc, change);
3834
+ var stretched = stretchSpansOverChange(doc, change);
3835
+ if (!old) return stretched;
3836
+ if (!stretched) return old;
3837
+
3838
+ for (var i = 0; i < old.length; ++i) {
3839
+ var oldCur = old[i], stretchCur = stretched[i];
3840
+ if (oldCur && stretchCur) {
3841
+ spans: for (var j = 0; j < stretchCur.length; ++j) {
3842
+ var span = stretchCur[j];
3843
+ for (var k = 0; k < oldCur.length; ++k)
3844
+ if (oldCur[k].marker == span.marker) continue spans;
3845
+ oldCur.push(span);
3846
+ }
3847
+ } else if (stretchCur) {
3848
+ old[i] = stretchCur;
3849
+ }
3850
+ }
3851
+ return old;
3852
+ }
3853
+
3522
3854
  function removeReadOnlyRanges(doc, from, to) {
3523
3855
  var markers = null;
3524
3856
  doc.iter(from.line, to.line + 1, function(line) {
@@ -3531,13 +3863,15 @@ window.CodeMirror = (function() {
3531
3863
  if (!markers) return null;
3532
3864
  var parts = [{from: from, to: to}];
3533
3865
  for (var i = 0; i < markers.length; ++i) {
3534
- var m = markers[i].find();
3866
+ var mk = markers[i], m = mk.find();
3535
3867
  for (var j = 0; j < parts.length; ++j) {
3536
3868
  var p = parts[j];
3537
- if (!posLess(m.from, p.to) || posLess(m.to, p.from)) continue;
3869
+ if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue;
3538
3870
  var newParts = [j, 1];
3539
- if (posLess(p.from, m.from)) newParts.push({from: p.from, to: m.from});
3540
- if (posLess(m.to, p.to)) newParts.push({from: m.to, to: p.to});
3871
+ if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from))
3872
+ newParts.push({from: p.from, to: m.from});
3873
+ if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to))
3874
+ newParts.push({from: m.to, to: p.to});
3541
3875
  parts.splice.apply(parts, newParts);
3542
3876
  j += newParts.length - 1;
3543
3877
  }
@@ -3567,20 +3901,20 @@ window.CodeMirror = (function() {
3567
3901
  return line;
3568
3902
  }
3569
3903
 
3570
- function lineIsHidden(line) {
3904
+ function lineIsHidden(doc, line) {
3571
3905
  var sps = sawCollapsedSpans && line.markedSpans;
3572
3906
  if (sps) for (var sp, i = 0; i < sps.length; ++i) {
3573
3907
  sp = sps[i];
3574
3908
  if (!sp.marker.collapsed) continue;
3575
3909
  if (sp.from == null) return true;
3576
- if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(line, sp))
3910
+ if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
3577
3911
  return true;
3578
3912
  }
3579
3913
  }
3580
- function lineIsHiddenInner(line, span) {
3914
+ function lineIsHiddenInner(doc, line, span) {
3581
3915
  if (span.to == null) {
3582
- var end = span.marker.find().to, endLine = getLine(lineDoc(line), end.line);
3583
- return lineIsHiddenInner(endLine, getMarkedSpanFor(endLine.markedSpans, span.marker));
3916
+ var end = span.marker.find().to, endLine = getLine(doc, end.line);
3917
+ return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker));
3584
3918
  }
3585
3919
  if (span.marker.inclusiveRight && span.to == line.text.length)
3586
3920
  return true;
@@ -3588,39 +3922,22 @@ window.CodeMirror = (function() {
3588
3922
  sp = line.markedSpans[i];
3589
3923
  if (sp.marker.collapsed && sp.from == span.to &&
3590
3924
  (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
3591
- lineIsHiddenInner(line, sp)) return true;
3925
+ lineIsHiddenInner(doc, line, sp)) return true;
3592
3926
  }
3593
3927
  }
3594
3928
 
3595
- // hl stands for history-line, a data structure that can be either a
3596
- // string (line without markers) or a {text, markedSpans} object.
3597
- function hlText(val) { return typeof val == "string" ? val : val.text; }
3598
- function hlSpans(val) {
3599
- if (typeof val == "string") return null;
3600
- var spans = val.markedSpans, out = null;
3601
- for (var i = 0; i < spans.length; ++i) {
3602
- if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
3603
- else if (out) out.push(spans[i]);
3604
- }
3605
- return !out ? spans : out.length ? out : null;
3606
- }
3607
- function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; }
3608
-
3609
3929
  function detachMarkedSpans(line) {
3610
3930
  var spans = line.markedSpans;
3611
3931
  if (!spans) return;
3612
- for (var i = 0; i < spans.length; ++i) {
3613
- var lines = spans[i].marker.lines;
3614
- var ix = indexOf(lines, line);
3615
- lines.splice(ix, 1);
3616
- }
3932
+ for (var i = 0; i < spans.length; ++i)
3933
+ spans[i].marker.detachLine(line);
3617
3934
  line.markedSpans = null;
3618
3935
  }
3619
3936
 
3620
3937
  function attachMarkedSpans(line, spans) {
3621
3938
  if (!spans) return;
3622
3939
  for (var i = 0; i < spans.length; ++i)
3623
- spans[i].marker.lines.push(line);
3940
+ spans[i].marker.attachLine(line);
3624
3941
  line.markedSpans = spans;
3625
3942
  }
3626
3943
 
@@ -3634,9 +3951,10 @@ window.CodeMirror = (function() {
3634
3951
  };
3635
3952
  function widgetOperation(f) {
3636
3953
  return function() {
3637
- startOperation(this.cm);
3954
+ var withOp = !this.cm.curOp;
3955
+ if (withOp) startOperation(this.cm);
3638
3956
  try {var result = f.apply(this, arguments);}
3639
- finally {endOperation(this.cm);}
3957
+ finally {if (withOp) endOperation(this.cm);}
3640
3958
  return result;
3641
3959
  };
3642
3960
  }
@@ -3644,6 +3962,7 @@ window.CodeMirror = (function() {
3644
3962
  var ws = this.line.widgets, no = lineNo(this.line);
3645
3963
  if (no == null || !ws) return;
3646
3964
  for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
3965
+ if (!ws.length) this.line.widgets = null;
3647
3966
  updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this)));
3648
3967
  regChange(this.cm, no, no + 1);
3649
3968
  });
@@ -3670,11 +3989,10 @@ window.CodeMirror = (function() {
3670
3989
  changeLine(cm, handle, function(line) {
3671
3990
  (line.widgets || (line.widgets = [])).push(widget);
3672
3991
  widget.line = line;
3673
- if (!lineIsHidden(line) || widget.showIfHidden) {
3992
+ if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {
3674
3993
  var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop;
3675
3994
  updateLineHeight(line, line.height + widgetHeight(widget));
3676
- if (aboveVisible)
3677
- setTimeout(function() {cm.display.scroller.scrollTop += widget.height;});
3995
+ if (aboveVisible) addToScrollPos(cm, 0, widget.height);
3678
3996
  }
3679
3997
  return true;
3680
3998
  });
@@ -3685,23 +4003,22 @@ window.CodeMirror = (function() {
3685
4003
 
3686
4004
  // Line objects. These hold state related to a line, including
3687
4005
  // highlighting info (the styles array).
3688
- function makeLine(text, markedSpans, height) {
3689
- var line = {text: text, height: height};
4006
+ function makeLine(text, markedSpans, estimateHeight) {
4007
+ var line = {text: text};
3690
4008
  attachMarkedSpans(line, markedSpans);
3691
- if (lineIsHidden(line)) line.height = 0;
4009
+ line.height = estimateHeight ? estimateHeight(line) : 1;
3692
4010
  return line;
3693
4011
  }
3694
4012
 
3695
- function updateLine(cm, line, text, markedSpans) {
4013
+ function updateLine(line, text, markedSpans, estimateHeight) {
3696
4014
  line.text = text;
3697
4015
  if (line.stateAfter) line.stateAfter = null;
3698
4016
  if (line.styles) line.styles = null;
3699
4017
  if (line.order != null) line.order = null;
3700
4018
  detachMarkedSpans(line);
3701
4019
  attachMarkedSpans(line, markedSpans);
3702
- if (lineIsHidden(line)) line.height = 0;
3703
- else if (!line.height) line.height = textHeight(cm.display);
3704
- signalLater(cm, line, "change");
4020
+ var estHeight = estimateHeight ? estimateHeight(line) : 1;
4021
+ if (estHeight != line.height) updateLineHeight(line, estHeight);
3705
4022
  }
3706
4023
 
3707
4024
  function cleanUpLine(line) {
@@ -3713,17 +4030,19 @@ window.CodeMirror = (function() {
3713
4030
  // array, which contains alternating fragments of text and CSS
3714
4031
  // classes.
3715
4032
  function runMode(cm, text, mode, state, f) {
3716
- var flattenSpans = cm.options.flattenSpans;
4033
+ var flattenSpans = mode.flattenSpans;
4034
+ if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
3717
4035
  var curText = "", curStyle = null;
3718
- var stream = new StringStream(text, cm.options.tabSize);
4036
+ var stream = new StringStream(text, cm.options.tabSize), style;
3719
4037
  if (text == "" && mode.blankLine) mode.blankLine(state);
3720
4038
  while (!stream.eol()) {
3721
- var style = mode.token(stream, state);
3722
- if (stream.pos > 5000) {
4039
+ if (stream.pos > cm.options.maxHighlightLength) {
3723
4040
  flattenSpans = false;
3724
4041
  // Webkit seems to refuse to render text nodes longer than 57444 characters
3725
4042
  stream.pos = Math.min(text.length, stream.start + 50000);
3726
4043
  style = null;
4044
+ } else {
4045
+ style = mode.token(stream, state);
3727
4046
  }
3728
4047
  var substr = stream.current();
3729
4048
  stream.start = stream.pos;
@@ -3738,13 +4057,13 @@ window.CodeMirror = (function() {
3738
4057
  function highlightLine(cm, line, state) {
3739
4058
  // A styles array always starts with a number identifying the
3740
4059
  // mode/overlays that it is based on (for easy invalidation).
3741
- var st = [cm.view.modeGen];
4060
+ var st = [cm.state.modeGen];
3742
4061
  // Compute the base array of styles
3743
- runMode(cm, line.text, cm.view.mode, state, function(txt, style) {st.push(txt, style);});
4062
+ runMode(cm, line.text, cm.doc.mode, state, function(txt, style) {st.push(txt, style);});
3744
4063
 
3745
4064
  // Run overlays, adjust style array.
3746
- for (var o = 0; o < cm.view.overlays.length; ++o) {
3747
- var overlay = cm.view.overlays[o], i = 1;
4065
+ for (var o = 0; o < cm.state.overlays.length; ++o) {
4066
+ var overlay = cm.state.overlays[o], i = 1;
3748
4067
  runMode(cm, line.text, overlay.mode, true, function(txt, style) {
3749
4068
  var start = i, len = txt.length;
3750
4069
  // Ensure there's a token end at the current position, and that i points at it
@@ -3775,7 +4094,7 @@ window.CodeMirror = (function() {
3775
4094
  }
3776
4095
 
3777
4096
  function getLineStyles(cm, line) {
3778
- if (!line.styles || line.styles[0] != cm.view.modeGen)
4097
+ if (!line.styles || line.styles[0] != cm.state.modeGen)
3779
4098
  line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
3780
4099
  return line.styles;
3781
4100
  }
@@ -3783,10 +4102,10 @@ window.CodeMirror = (function() {
3783
4102
  // Lightweight form of highlight -- proceed over this line and
3784
4103
  // update state, but don't save a style array.
3785
4104
  function processLine(cm, line, state) {
3786
- var mode = cm.view.mode;
4105
+ var mode = cm.doc.mode;
3787
4106
  var stream = new StringStream(line.text, cm.options.tabSize);
3788
4107
  if (line.text == "" && mode.blankLine) mode.blankLine(state);
3789
- while (!stream.eol() && stream.pos <= 5000) {
4108
+ while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
3790
4109
  mode.token(stream, state);
3791
4110
  stream.start = stream.pos;
3792
4111
  }
@@ -3803,7 +4122,7 @@ window.CodeMirror = (function() {
3803
4122
  var merged, line = realLine, lineBefore, sawBefore, simple = true;
3804
4123
  while (merged = collapsedSpanAtStart(line)) {
3805
4124
  simple = false;
3806
- line = getLine(cm.view.doc, merged.find().from.line);
4125
+ line = getLine(cm.doc, merged.find().from.line);
3807
4126
  if (!lineBefore) lineBefore = line;
3808
4127
  }
3809
4128
 
@@ -3815,6 +4134,8 @@ window.CodeMirror = (function() {
3815
4134
  builder.measure = line == realLine && measure;
3816
4135
  builder.pos = 0;
3817
4136
  builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
4137
+ if ((ie || webkit) && cm.getOption("lineWrapping"))
4138
+ builder.addToken = buildTokenSplitSpaces(builder.addToken);
3818
4139
  if (measure && sawBefore && line != realLine && !builder.addedOne) {
3819
4140
  measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
3820
4141
  builder.addedOne = true;
@@ -3822,20 +4143,36 @@ window.CodeMirror = (function() {
3822
4143
  var next = insertLineContent(line, builder, getLineStyles(cm, line));
3823
4144
  sawBefore = line == lineBefore;
3824
4145
  if (next) {
3825
- line = getLine(cm.view.doc, next.to.line);
4146
+ line = getLine(cm.doc, next.to.line);
3826
4147
  simple = false;
3827
4148
  }
3828
4149
  } while (next);
3829
4150
 
3830
4151
  if (measure && !builder.addedOne)
3831
4152
  measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
3832
- if (!builder.pre.firstChild && !lineIsHidden(realLine))
4153
+ if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine))
3833
4154
  builder.pre.appendChild(document.createTextNode("\u00a0"));
3834
4155
 
4156
+ var order;
4157
+ // Work around problem with the reported dimensions of single-char
4158
+ // direction spans on IE (issue #1129). See also the comment in
4159
+ // cursorCoords.
4160
+ if (measure && ie && (order = getOrder(line))) {
4161
+ var l = order.length - 1;
4162
+ if (order[l].from == order[l].to) --l;
4163
+ var last = order[l], prev = order[l - 1];
4164
+ if (last.from + 1 == last.to && prev && last.level < prev.level) {
4165
+ var span = measure[builder.pos - 1];
4166
+ if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure),
4167
+ span.nextSibling);
4168
+ }
4169
+ }
4170
+
4171
+ signal(cm, "renderLine", cm, realLine, builder.pre);
3835
4172
  return builder.pre;
3836
4173
  }
3837
4174
 
3838
- var tokenSpecialChars = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g;
4175
+ var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g;
3839
4176
  function buildToken(builder, text, style, startStyle, endStyle) {
3840
4177
  if (!text) return;
3841
4178
  if (!tokenSpecialChars.test(text)) {
@@ -3875,18 +4212,42 @@ window.CodeMirror = (function() {
3875
4212
  }
3876
4213
 
3877
4214
  function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
4215
+ var wrapping = builder.cm.options.lineWrapping;
3878
4216
  for (var i = 0; i < text.length; ++i) {
3879
- if (i && i < text.length &&
3880
- builder.cm.options.lineWrapping &&
3881
- spanAffectsWrapping.test(text.slice(i - 1, i + 1)))
4217
+ var ch = text.charAt(i), start = i == 0;
4218
+ if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) {
4219
+ ch = text.slice(i, i + 2);
4220
+ ++i;
4221
+ } else if (i && wrapping &&
4222
+ spanAffectsWrapping.test(text.slice(i - 1, i + 1))) {
3882
4223
  builder.pre.appendChild(elt("wbr"));
3883
- builder.measure[builder.pos++] =
3884
- buildToken(builder, text.charAt(i), style,
3885
- i == 0 && startStyle, i == text.length - 1 && endStyle);
4224
+ }
4225
+ var span = builder.measure[builder.pos] =
4226
+ buildToken(builder, ch, style,
4227
+ start && startStyle, i == text.length - 1 && endStyle);
4228
+ // In IE single-space nodes wrap differently than spaces
4229
+ // embedded in larger text nodes, except when set to
4230
+ // white-space: normal (issue #1268).
4231
+ if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) &&
4232
+ i < text.length - 1 && !/\s/.test(text.charAt(i + 1)))
4233
+ span.style.whiteSpace = "normal";
4234
+ builder.pos += ch.length;
3886
4235
  }
3887
4236
  if (text.length) builder.addedOne = true;
3888
4237
  }
3889
4238
 
4239
+ function buildTokenSplitSpaces(inner) {
4240
+ function split(old) {
4241
+ var out = " ";
4242
+ for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
4243
+ out += " ";
4244
+ return out;
4245
+ }
4246
+ return function(builder, text, style, startStyle, endStyle) {
4247
+ return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle);
4248
+ };
4249
+ }
4250
+
3890
4251
  function buildCollapsedSpan(builder, size, widget) {
3891
4252
  if (widget) {
3892
4253
  if (!builder.display) widget = widget.cloneNode(true);
@@ -3947,7 +4308,7 @@ window.CodeMirror = (function() {
3947
4308
  var end = pos + text.length;
3948
4309
  if (!collapsed) {
3949
4310
  var tokenText = end > upto ? text.slice(0, upto - pos) : text;
3950
- builder.addToken(builder, tokenText, style + spanStyle,
4311
+ builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
3951
4312
  spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "");
3952
4313
  }
3953
4314
  if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
@@ -3961,6 +4322,52 @@ window.CodeMirror = (function() {
3961
4322
 
3962
4323
  // DOCUMENT DATA STRUCTURE
3963
4324
 
4325
+ function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) {
4326
+ function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
4327
+ function update(line, text, spans) {
4328
+ updateLine(line, text, spans, estimateHeight);
4329
+ signalLater(line, "change", line, change);
4330
+ }
4331
+
4332
+ var from = change.from, to = change.to, text = change.text;
4333
+ var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
4334
+ var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
4335
+
4336
+ // First adjust the line structure
4337
+ if (from.ch == 0 && to.ch == 0 && lastText == "") {
4338
+ // This is a whole-line replace. Treated specially to make
4339
+ // sure line objects move the way they are supposed to.
4340
+ for (var i = 0, e = text.length - 1, added = []; i < e; ++i)
4341
+ added.push(makeLine(text[i], spansFor(i), estimateHeight));
4342
+ update(lastLine, lastLine.text, lastSpans);
4343
+ if (nlines) doc.remove(from.line, nlines);
4344
+ if (added.length) doc.insert(from.line, added);
4345
+ } else if (firstLine == lastLine) {
4346
+ if (text.length == 1) {
4347
+ update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
4348
+ } else {
4349
+ for (var added = [], i = 1, e = text.length - 1; i < e; ++i)
4350
+ added.push(makeLine(text[i], spansFor(i), estimateHeight));
4351
+ added.push(makeLine(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
4352
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
4353
+ doc.insert(from.line + 1, added);
4354
+ }
4355
+ } else if (text.length == 1) {
4356
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));
4357
+ doc.remove(from.line + 1, nlines);
4358
+ } else {
4359
+ update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
4360
+ update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
4361
+ for (var i = 1, e = text.length - 1, added = []; i < e; ++i)
4362
+ added.push(makeLine(text[i], spansFor(i), estimateHeight));
4363
+ if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
4364
+ doc.insert(from.line + 1, added);
4365
+ }
4366
+
4367
+ signalLater(doc, "change", doc, change);
4368
+ setSelection(doc, selAfter.anchor, selAfter.head, null, true);
4369
+ }
4370
+
3964
4371
  function LeafChunk(lines) {
3965
4372
  this.lines = lines;
3966
4373
  this.parent = null;
@@ -3973,19 +4380,19 @@ window.CodeMirror = (function() {
3973
4380
 
3974
4381
  LeafChunk.prototype = {
3975
4382
  chunkSize: function() { return this.lines.length; },
3976
- remove: function(at, n, cm) {
4383
+ removeInner: function(at, n) {
3977
4384
  for (var i = at, e = at + n; i < e; ++i) {
3978
4385
  var line = this.lines[i];
3979
4386
  this.height -= line.height;
3980
4387
  cleanUpLine(line);
3981
- signalLater(cm, line, "delete");
4388
+ signalLater(line, "delete");
3982
4389
  }
3983
4390
  this.lines.splice(at, n);
3984
4391
  },
3985
4392
  collapse: function(lines) {
3986
4393
  lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
3987
4394
  },
3988
- insertHeight: function(at, lines, height) {
4395
+ insertInner: function(at, lines, height) {
3989
4396
  this.height += height;
3990
4397
  this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
3991
4398
  for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
@@ -4011,13 +4418,13 @@ window.CodeMirror = (function() {
4011
4418
 
4012
4419
  BranchChunk.prototype = {
4013
4420
  chunkSize: function() { return this.size; },
4014
- remove: function(at, n, callbacks) {
4421
+ removeInner: function(at, n) {
4015
4422
  this.size -= n;
4016
4423
  for (var i = 0; i < this.children.length; ++i) {
4017
4424
  var child = this.children[i], sz = child.chunkSize();
4018
4425
  if (at < sz) {
4019
4426
  var rm = Math.min(n, sz - at), oldHeight = child.height;
4020
- child.remove(at, rm, callbacks);
4427
+ child.removeInner(at, rm);
4021
4428
  this.height -= oldHeight - child.height;
4022
4429
  if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
4023
4430
  if ((n -= rm) == 0) break;
@@ -4034,18 +4441,13 @@ window.CodeMirror = (function() {
4034
4441
  collapse: function(lines) {
4035
4442
  for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
4036
4443
  },
4037
- insert: function(at, lines) {
4038
- var height = 0;
4039
- for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
4040
- this.insertHeight(at, lines, height);
4041
- },
4042
- insertHeight: function(at, lines, height) {
4444
+ insertInner: function(at, lines, height) {
4043
4445
  this.size += lines.length;
4044
4446
  this.height += height;
4045
4447
  for (var i = 0, e = this.children.length; i < e; ++i) {
4046
4448
  var child = this.children[i], sz = child.chunkSize();
4047
4449
  if (at <= sz) {
4048
- child.insertHeight(at, lines, height);
4450
+ child.insertInner(at, lines, height);
4049
4451
  if (child.lines && child.lines.length > 50) {
4050
4452
  while (child.lines.length > 50) {
4051
4453
  var spilled = child.lines.splice(child.lines.length - 25, 25);
@@ -4082,7 +4484,6 @@ window.CodeMirror = (function() {
4082
4484
  } while (me.children.length > 10);
4083
4485
  me.parent.maybeSpill();
4084
4486
  },
4085
- iter: function(from, to, op) { this.iterN(from, to - from, op); },
4086
4487
  iterN: function(at, n, op) {
4087
4488
  for (var i = 0, e = this.children.length; i < e; ++i) {
4088
4489
  var child = this.children[i], sz = child.chunkSize();
@@ -4096,9 +4497,268 @@ window.CodeMirror = (function() {
4096
4497
  }
4097
4498
  };
4098
4499
 
4500
+ var nextDocId = 0;
4501
+ var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
4502
+ if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
4503
+ if (firstLine == null) firstLine = 0;
4504
+
4505
+ BranchChunk.call(this, [new LeafChunk([makeLine("", null)])]);
4506
+ this.first = firstLine;
4507
+ this.scrollTop = this.scrollLeft = 0;
4508
+ this.cantEdit = false;
4509
+ this.history = makeHistory();
4510
+ this.frontier = firstLine;
4511
+ var start = Pos(firstLine, 0);
4512
+ this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null};
4513
+ this.id = ++nextDocId;
4514
+ this.modeOption = mode;
4515
+
4516
+ if (typeof text == "string") text = splitLines(text);
4517
+ updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start});
4518
+ };
4519
+
4520
+ Doc.prototype = createObj(BranchChunk.prototype, {
4521
+ iter: function(from, to, op) {
4522
+ if (op) this.iterN(from - this.first, to - from, op);
4523
+ else this.iterN(this.first, this.first + this.size, from);
4524
+ },
4525
+
4526
+ insert: function(at, lines) {
4527
+ var height = 0;
4528
+ for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
4529
+ this.insertInner(at - this.first, lines, height);
4530
+ },
4531
+ remove: function(at, n) { this.removeInner(at - this.first, n); },
4532
+
4533
+ getValue: function(lineSep) {
4534
+ var lines = getLines(this, this.first, this.first + this.size);
4535
+ if (lineSep === false) return lines;
4536
+ return lines.join(lineSep || "\n");
4537
+ },
4538
+ setValue: function(code) {
4539
+ var top = Pos(this.first, 0), last = this.first + this.size - 1;
4540
+ makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
4541
+ text: splitLines(code), origin: "setValue"},
4542
+ {head: top, anchor: top}, true);
4543
+ },
4544
+ replaceRange: function(code, from, to, origin) {
4545
+ from = clipPos(this, from);
4546
+ to = to ? clipPos(this, to) : from;
4547
+ replaceRange(this, code, from, to, origin);
4548
+ },
4549
+ getRange: function(from, to, lineSep) {
4550
+ var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
4551
+ if (lineSep === false) return lines;
4552
+ return lines.join(lineSep || "\n");
4553
+ },
4554
+
4555
+ getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
4556
+ setLine: function(line, text) {
4557
+ if (isLine(this, line))
4558
+ replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line)));
4559
+ },
4560
+ removeLine: function(line) {
4561
+ if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line)));
4562
+ else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0)));
4563
+ },
4564
+
4565
+ getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
4566
+ getLineNumber: function(line) {return lineNo(line);},
4567
+
4568
+ lineCount: function() {return this.size;},
4569
+ firstLine: function() {return this.first;},
4570
+ lastLine: function() {return this.first + this.size - 1;},
4571
+
4572
+ clipPos: function(pos) {return clipPos(this, pos);},
4573
+
4574
+ getCursor: function(start) {
4575
+ var sel = this.sel, pos;
4576
+ if (start == null || start == "head") pos = sel.head;
4577
+ else if (start == "anchor") pos = sel.anchor;
4578
+ else if (start == "end" || start === false) pos = sel.to;
4579
+ else pos = sel.from;
4580
+ return copyPos(pos);
4581
+ },
4582
+ somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);},
4583
+
4584
+ setCursor: docOperation(function(line, ch, extend) {
4585
+ var pos = clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line);
4586
+ if (extend) extendSelection(this, pos);
4587
+ else setSelection(this, pos, pos);
4588
+ }),
4589
+ setSelection: docOperation(function(anchor, head) {
4590
+ setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor));
4591
+ }),
4592
+ extendSelection: docOperation(function(from, to) {
4593
+ extendSelection(this, clipPos(this, from), to && clipPos(this, to));
4594
+ }),
4595
+
4596
+ getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);},
4597
+ replaceSelection: function(code, collapse, origin) {
4598
+ makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || "around");
4599
+ },
4600
+ undo: docOperation(function() {makeChangeFromHistory(this, "undo");}),
4601
+ redo: docOperation(function() {makeChangeFromHistory(this, "redo");}),
4602
+
4603
+ setExtending: function(val) {this.sel.extend = val;},
4604
+
4605
+ historySize: function() {
4606
+ var hist = this.history;
4607
+ return {undo: hist.done.length, redo: hist.undone.length};
4608
+ },
4609
+ clearHistory: function() {this.history = makeHistory();},
4610
+
4611
+ markClean: function() {
4612
+ this.history.dirtyCounter = 0;
4613
+ this.history.lastOp = this.history.lastOrigin = null;
4614
+ },
4615
+ isClean: function () {return this.history.dirtyCounter == 0;},
4616
+
4617
+ getHistory: function() {
4618
+ return {done: copyHistoryArray(this.history.done),
4619
+ undone: copyHistoryArray(this.history.undone)};
4620
+ },
4621
+ setHistory: function(histData) {
4622
+ var hist = this.history = makeHistory();
4623
+ hist.done = histData.done.slice(0);
4624
+ hist.undone = histData.undone.slice(0);
4625
+ },
4626
+
4627
+ markText: function(from, to, options) {
4628
+ return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
4629
+ },
4630
+ setBookmark: function(pos, options) {
4631
+ var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
4632
+ insertLeft: options && options.insertLeft};
4633
+ pos = clipPos(this, pos);
4634
+ return markText(this, pos, pos, realOpts, "bookmark");
4635
+ },
4636
+ findMarksAt: function(pos) {
4637
+ pos = clipPos(this, pos);
4638
+ var markers = [], spans = getLine(this, pos.line).markedSpans;
4639
+ if (spans) for (var i = 0; i < spans.length; ++i) {
4640
+ var span = spans[i];
4641
+ if ((span.from == null || span.from <= pos.ch) &&
4642
+ (span.to == null || span.to >= pos.ch))
4643
+ markers.push(span.marker.parent || span.marker);
4644
+ }
4645
+ return markers;
4646
+ },
4647
+ getAllMarks: function() {
4648
+ var markers = [];
4649
+ this.iter(function(line) {
4650
+ var sps = line.markedSpans;
4651
+ if (sps) for (var i = 0; i < sps.length; ++i)
4652
+ if (sps[i].from != null) markers.push(sps[i].marker);
4653
+ });
4654
+ return markers;
4655
+ },
4656
+
4657
+ posFromIndex: function(off) {
4658
+ var ch, lineNo = this.first;
4659
+ this.iter(function(line) {
4660
+ var sz = line.text.length + 1;
4661
+ if (sz > off) { ch = off; return true; }
4662
+ off -= sz;
4663
+ ++lineNo;
4664
+ });
4665
+ return clipPos(this, Pos(lineNo, ch));
4666
+ },
4667
+ indexFromPos: function (coords) {
4668
+ coords = clipPos(this, coords);
4669
+ var index = coords.ch;
4670
+ if (coords.line < this.first || coords.ch < 0) return 0;
4671
+ this.iter(this.first, coords.line, function (line) {
4672
+ index += line.text.length + 1;
4673
+ });
4674
+ return index;
4675
+ },
4676
+
4677
+ copy: function(copyHistory) {
4678
+ var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
4679
+ doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
4680
+ doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor,
4681
+ shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn};
4682
+ if (copyHistory) {
4683
+ doc.history.undoDepth = this.history.undoDepth;
4684
+ doc.setHistory(this.getHistory());
4685
+ }
4686
+ return doc;
4687
+ },
4688
+
4689
+ linkedDoc: function(options) {
4690
+ if (!options) options = {};
4691
+ var from = this.first, to = this.first + this.size;
4692
+ if (options.from != null && options.from > from) from = options.from;
4693
+ if (options.to != null && options.to < to) to = options.to;
4694
+ var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);
4695
+ if (options.sharedHist) copy.history = this.history;
4696
+ (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
4697
+ copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
4698
+ return copy;
4699
+ },
4700
+ unlinkDoc: function(other) {
4701
+ if (other instanceof CodeMirror) other = other.doc;
4702
+ if (this.linked) for (var i = 0; i < this.linked.length; ++i) {
4703
+ var link = this.linked[i];
4704
+ if (link.doc != other) continue;
4705
+ this.linked.splice(i, 1);
4706
+ other.unlinkDoc(this);
4707
+ break;
4708
+ }
4709
+ // If the histories were shared, split them again
4710
+ if (other.history == this.history) {
4711
+ var splitIds = [other.id];
4712
+ linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);
4713
+ other.history = makeHistory();
4714
+ other.history.done = copyHistoryArray(this.history.done, splitIds);
4715
+ other.history.undone = copyHistoryArray(this.history.undone, splitIds);
4716
+ }
4717
+ },
4718
+ iterLinkedDocs: function(f) {linkedDocs(this, f);},
4719
+
4720
+ getMode: function() {return this.mode;},
4721
+ getEditor: function() {return this.cm;}
4722
+ });
4723
+
4724
+ Doc.prototype.eachLine = Doc.prototype.iter;
4725
+
4726
+ // The Doc methods that should be available on CodeMirror instances
4727
+ var dontDelegate = "iter insert remove copy getEditor".split(" ");
4728
+ for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
4729
+ CodeMirror.prototype[prop] = (function(method) {
4730
+ return function() {return method.apply(this.doc, arguments);};
4731
+ })(Doc.prototype[prop]);
4732
+
4733
+ function linkedDocs(doc, f, sharedHistOnly) {
4734
+ function propagate(doc, skip, sharedHist) {
4735
+ if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {
4736
+ var rel = doc.linked[i];
4737
+ if (rel.doc == skip) continue;
4738
+ var shared = sharedHist && rel.sharedHist;
4739
+ if (sharedHistOnly && !shared) continue;
4740
+ f(rel.doc, shared);
4741
+ propagate(rel.doc, doc, shared);
4742
+ }
4743
+ }
4744
+ propagate(doc, null, true);
4745
+ }
4746
+
4747
+ function attachDoc(cm, doc) {
4748
+ if (doc.cm) throw new Error("This document is already in use.");
4749
+ cm.doc = doc;
4750
+ doc.cm = cm;
4751
+ estimateLineHeights(cm);
4752
+ loadMode(cm);
4753
+ if (!cm.options.lineWrapping) computeMaxLength(cm);
4754
+ cm.options.mode = doc.modeOption;
4755
+ regChange(cm);
4756
+ }
4757
+
4099
4758
  // LINE UTILITIES
4100
4759
 
4101
4760
  function getLine(chunk, n) {
4761
+ n -= chunk.first;
4102
4762
  while (!chunk.lines) {
4103
4763
  for (var i = 0;; ++i) {
4104
4764
  var child = chunk.children[i], sz = child.chunkSize();
@@ -4109,6 +4769,23 @@ window.CodeMirror = (function() {
4109
4769
  return chunk.lines[n];
4110
4770
  }
4111
4771
 
4772
+ function getBetween(doc, start, end) {
4773
+ var out = [], n = start.line;
4774
+ doc.iter(start.line, end.line + 1, function(line) {
4775
+ var text = line.text;
4776
+ if (n == end.line) text = text.slice(0, end.ch);
4777
+ if (n == start.line) text = text.slice(start.ch);
4778
+ out.push(text);
4779
+ ++n;
4780
+ });
4781
+ return out;
4782
+ }
4783
+ function getLines(doc, from, to) {
4784
+ var out = [];
4785
+ doc.iter(from, to, function(line) { out.push(line.text); });
4786
+ return out;
4787
+ }
4788
+
4112
4789
  function updateLineHeight(line, height) {
4113
4790
  var diff = height - line.height;
4114
4791
  for (var n = line; n; n = n.parent) n.height += diff;
@@ -4123,16 +4800,11 @@ window.CodeMirror = (function() {
4123
4800
  no += chunk.children[i].chunkSize();
4124
4801
  }
4125
4802
  }
4126
- return no;
4127
- }
4128
-
4129
- function lineDoc(line) {
4130
- for (var d = line.parent; d.parent; d = d.parent) {}
4131
- return d;
4803
+ return no + cur.first;
4132
4804
  }
4133
4805
 
4134
4806
  function lineAtHeight(chunk, h) {
4135
- var n = 0;
4807
+ var n = chunk.first;
4136
4808
  outer: do {
4137
4809
  for (var i = 0, e = chunk.children.length; i < e; ++i) {
4138
4810
  var child = chunk.children[i], ch = child.height;
@@ -4151,7 +4823,7 @@ window.CodeMirror = (function() {
4151
4823
  }
4152
4824
 
4153
4825
  function heightAtLine(cm, lineObj) {
4154
- lineObj = visualLine(cm.view.doc, lineObj);
4826
+ lineObj = visualLine(cm.doc, lineObj);
4155
4827
 
4156
4828
  var h = 0, chunk = lineObj.parent;
4157
4829
  for (var i = 0; i < chunk.lines.length; ++i) {
@@ -4182,7 +4854,7 @@ window.CodeMirror = (function() {
4182
4854
  // Arrays of history events. Doing something adds an event to
4183
4855
  // done and clears undo. Undoing moves events from done to
4184
4856
  // undone, redoing moves them in the other direction.
4185
- done: [], undone: [],
4857
+ done: [], undone: [], undoDepth: Infinity,
4186
4858
  // Used to track when changes can be merged into a single undo
4187
4859
  // event
4188
4860
  lastTime: 0, lastOp: null, lastOrigin: null,
@@ -4191,47 +4863,152 @@ window.CodeMirror = (function() {
4191
4863
  };
4192
4864
  }
4193
4865
 
4194
- function addChange(cm, start, added, old, origin, fromBefore, toBefore, fromAfter, toAfter) {
4195
- var history = cm.view.history;
4196
- history.undone.length = 0;
4197
- var time = +new Date, cur = lst(history.done);
4198
-
4866
+ function attachLocalSpans(doc, change, from, to) {
4867
+ var existing = change["spans_" + doc.id], n = 0;
4868
+ doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) {
4869
+ if (line.markedSpans)
4870
+ (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans;
4871
+ ++n;
4872
+ });
4873
+ }
4874
+
4875
+ function historyChangeFromChange(doc, change) {
4876
+ var histChange = {from: change.from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
4877
+ attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
4878
+ linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
4879
+ return histChange;
4880
+ }
4881
+
4882
+ function addToHistory(doc, change, selAfter, opId) {
4883
+ var hist = doc.history;
4884
+ hist.undone.length = 0;
4885
+ var time = +new Date, cur = lst(hist.done);
4886
+
4199
4887
  if (cur &&
4200
- (history.lastOp == cm.curOp.id ||
4201
- history.lastOrigin == origin && (origin == "input" || origin == "delete") &&
4202
- history.lastTime > time - 600)) {
4888
+ (hist.lastOp == opId ||
4889
+ hist.lastOrigin == change.origin && change.origin &&
4890
+ ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) ||
4891
+ change.origin.charAt(0) == "*"))) {
4203
4892
  // Merge this change into the last event
4204
- var last = lst(cur.events);
4205
- if (last.start > start + old.length || last.start + last.added < start) {
4206
- // Doesn't intersect with last sub-event, add new sub-event
4207
- cur.events.push({start: start, added: added, old: old});
4893
+ var last = lst(cur.changes);
4894
+ if (posEq(change.from, change.to) && posEq(change.from, last.to)) {
4895
+ // Optimized case for simple insertion -- don't want to add
4896
+ // new changesets for every character typed
4897
+ last.to = changeEnd(change);
4208
4898
  } else {
4209
- // Patch up the last sub-event
4210
- var startBefore = Math.max(0, last.start - start),
4211
- endAfter = Math.max(0, (start + old.length) - (last.start + last.added));
4212
- for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]);
4213
- for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]);
4214
- if (startBefore) last.start = start;
4215
- last.added += added - (old.length - startBefore - endAfter);
4899
+ // Add new sub-event
4900
+ cur.changes.push(historyChangeFromChange(doc, change));
4216
4901
  }
4217
- cur.fromAfter = fromAfter; cur.toAfter = toAfter;
4902
+ cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head;
4218
4903
  } else {
4219
4904
  // Can not be merged, start a new event.
4220
- cur = {events: [{start: start, added: added, old: old}],
4221
- fromBefore: fromBefore, toBefore: toBefore, fromAfter: fromAfter, toAfter: toAfter};
4222
- history.done.push(cur);
4223
- while (history.done.length > cm.options.undoDepth)
4224
- history.done.shift();
4225
- if (history.dirtyCounter < 0)
4226
- // The user has made a change after undoing past the last clean state.
4227
- // We can never get back to a clean state now until markClean() is called.
4228
- history.dirtyCounter = NaN;
4905
+ cur = {changes: [historyChangeFromChange(doc, change)],
4906
+ anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,
4907
+ anchorAfter: selAfter.anchor, headAfter: selAfter.head};
4908
+ hist.done.push(cur);
4909
+ while (hist.done.length > hist.undoDepth)
4910
+ hist.done.shift();
4911
+ if (hist.dirtyCounter < 0)
4912
+ // The user has made a change after undoing past the last clean state.
4913
+ // We can never get back to a clean state now until markClean() is called.
4914
+ hist.dirtyCounter = NaN;
4229
4915
  else
4230
- history.dirtyCounter++;
4916
+ hist.dirtyCounter++;
4231
4917
  }
4232
- history.lastTime = time;
4233
- history.lastOp = cm.curOp.id;
4234
- history.lastOrigin = origin;
4918
+ hist.lastTime = time;
4919
+ hist.lastOp = opId;
4920
+ hist.lastOrigin = change.origin;
4921
+ }
4922
+
4923
+ function removeClearedSpans(spans) {
4924
+ if (!spans) return null;
4925
+ for (var i = 0, out; i < spans.length; ++i) {
4926
+ if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
4927
+ else if (out) out.push(spans[i]);
4928
+ }
4929
+ return !out ? spans : out.length ? out : null;
4930
+ }
4931
+
4932
+ function getOldSpans(doc, change) {
4933
+ var found = change["spans_" + doc.id];
4934
+ if (!found) return null;
4935
+ for (var i = 0, nw = []; i < change.text.length; ++i)
4936
+ nw.push(removeClearedSpans(found[i]));
4937
+ return nw;
4938
+ }
4939
+
4940
+ // Used both to provide a JSON-safe object in .getHistory, and, when
4941
+ // detaching a document, to split the history in two
4942
+ function copyHistoryArray(events, newGroup) {
4943
+ for (var i = 0, copy = []; i < events.length; ++i) {
4944
+ var event = events[i], changes = event.changes, newChanges = [];
4945
+ copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore,
4946
+ anchorAfter: event.anchorAfter, headAfter: event.headAfter});
4947
+ for (var j = 0; j < changes.length; ++j) {
4948
+ var change = changes[j], m;
4949
+ newChanges.push({from: change.from, to: change.to, text: change.text});
4950
+ if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) {
4951
+ if (indexOf(newGroup, Number(m[1])) > -1) {
4952
+ lst(newChanges)[prop] = change[prop];
4953
+ delete change[prop];
4954
+ }
4955
+ }
4956
+ }
4957
+ }
4958
+ return copy;
4959
+ }
4960
+
4961
+ // Rebasing/resetting history to deal with externally-sourced changes
4962
+
4963
+ function rebaseHistSel(pos, from, to, diff) {
4964
+ if (to < pos.line) {
4965
+ pos.line += diff;
4966
+ } else if (from < pos.line) {
4967
+ pos.line = from;
4968
+ pos.ch = 0;
4969
+ }
4970
+ }
4971
+
4972
+ // Tries to rebase an array of history events given a change in the
4973
+ // document. If the change touches the same lines as the event, the
4974
+ // event, and everything 'behind' it, is discarded. If the change is
4975
+ // before the event, the event's positions are updated. Uses a
4976
+ // copy-on-write scheme for the positions, to avoid having to
4977
+ // reallocate them all on every rebase, but also avoid problems with
4978
+ // shared position objects being unsafely updated.
4979
+ function rebaseHistArray(array, from, to, diff) {
4980
+ for (var i = 0; i < array.length; ++i) {
4981
+ var sub = array[i], ok = true;
4982
+ for (var j = 0; j < sub.changes.length; ++j) {
4983
+ var cur = sub.changes[j];
4984
+ if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); }
4985
+ if (to < cur.from.line) {
4986
+ cur.from.line += diff;
4987
+ cur.to.line += diff;
4988
+ } else if (from <= cur.to.line) {
4989
+ ok = false;
4990
+ break;
4991
+ }
4992
+ }
4993
+ if (!sub.copied) {
4994
+ sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore);
4995
+ sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter);
4996
+ sub.copied = true;
4997
+ }
4998
+ if (!ok) {
4999
+ array.splice(0, i + 1);
5000
+ i = 0;
5001
+ } else {
5002
+ rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore);
5003
+ rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter);
5004
+ }
5005
+ }
5006
+ }
5007
+
5008
+ function rebaseHist(hist, change) {
5009
+ var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;
5010
+ rebaseHistArray(hist.done, from, to, diff);
5011
+ rebaseHistArray(hist.undone, from, to, diff);
4235
5012
  }
4236
5013
 
4237
5014
  // EVENT OPERATORS
@@ -4268,13 +5045,6 @@ window.CodeMirror = (function() {
4268
5045
  return b;
4269
5046
  }
4270
5047
 
4271
- // Allow 3rd-party code to override event properties by adding an override
4272
- // object to an event object.
4273
- function e_prop(e, prop) {
4274
- var overridden = e.override && e.override.hasOwnProperty(prop);
4275
- return overridden ? e.override[prop] : e[prop];
4276
- }
4277
-
4278
5048
  // EVENT HANDLING
4279
5049
 
4280
5050
  function on(emitter, type, f) {
@@ -4309,14 +5079,26 @@ window.CodeMirror = (function() {
4309
5079
  for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
4310
5080
  }
4311
5081
 
4312
- function signalLater(cm, emitter, type /*, values...*/) {
5082
+ var delayedCallbacks, delayedCallbackDepth = 0;
5083
+ function signalLater(emitter, type /*, values...*/) {
4313
5084
  var arr = emitter._handlers && emitter._handlers[type];
4314
5085
  if (!arr) return;
4315
- var args = Array.prototype.slice.call(arguments, 3), flist = cm.curOp && cm.curOp.delayedCallbacks;
5086
+ var args = Array.prototype.slice.call(arguments, 2);
5087
+ if (!delayedCallbacks) {
5088
+ ++delayedCallbackDepth;
5089
+ delayedCallbacks = [];
5090
+ setTimeout(fireDelayed, 0);
5091
+ }
4316
5092
  function bnd(f) {return function(){f.apply(null, args);};};
4317
5093
  for (var i = 0; i < arr.length; ++i)
4318
- if (flist) flist.push(bnd(arr[i]));
4319
- else arr[i].apply(null, args);
5094
+ delayedCallbacks.push(bnd(arr[i]));
5095
+ }
5096
+
5097
+ function fireDelayed() {
5098
+ --delayedCallbackDepth;
5099
+ var delayed = delayedCallbacks;
5100
+ delayedCallbacks = null;
5101
+ for (var i = 0; i < delayed.length; ++i) delayed[i]();
4320
5102
  }
4321
5103
 
4322
5104
  function hasHandler(emitter, type) {
@@ -4340,12 +5122,12 @@ window.CodeMirror = (function() {
4340
5122
 
4341
5123
  // Counts the column offset in a string, taking tabs into account.
4342
5124
  // Used mostly to find indentation.
4343
- function countColumn(string, end, tabSize) {
5125
+ function countColumn(string, end, tabSize, startIndex, startValue) {
4344
5126
  if (end == null) {
4345
5127
  end = string.search(/[^\s\u00a0]/);
4346
5128
  if (end == -1) end = string.length;
4347
5129
  }
4348
- for (var i = 0, n = 0; i < end; ++i) {
5130
+ for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) {
4349
5131
  if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
4350
5132
  else ++n;
4351
5133
  }
@@ -4376,6 +5158,20 @@ window.CodeMirror = (function() {
4376
5158
  return -1;
4377
5159
  }
4378
5160
 
5161
+ function createObj(base, props) {
5162
+ function Obj() {}
5163
+ Obj.prototype = base;
5164
+ var inst = new Obj();
5165
+ if (props) copyObj(props, inst);
5166
+ return inst;
5167
+ }
5168
+
5169
+ function copyObj(obj, target) {
5170
+ if (!target) target = {};
5171
+ for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
5172
+ return target;
5173
+ }
5174
+
4379
5175
  function emptyArray(size) {
4380
5176
  for (var a = [], i = 0; i < size; ++i) a.push(undefined);
4381
5177
  return a;
@@ -4393,12 +5189,11 @@ window.CodeMirror = (function() {
4393
5189
  }
4394
5190
 
4395
5191
  function isEmpty(obj) {
4396
- var c = 0;
4397
- for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) ++c;
4398
- return !c;
5192
+ for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
5193
+ return true;
4399
5194
  }
4400
5195
 
4401
- var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F]/;
5196
+ var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/;
4402
5197
 
4403
5198
  // DOM UTILITIES
4404
5199
 
@@ -4412,9 +5207,8 @@ window.CodeMirror = (function() {
4412
5207
  }
4413
5208
 
4414
5209
  function removeChildren(e) {
4415
- // IE will break all parent-child relations in subnodes when setting innerHTML
4416
- if (!ie) e.innerHTML = "";
4417
- else while (e.firstChild) e.removeChild(e.firstChild);
5210
+ for (var count = e.childNodes.length; count > 0; --count)
5211
+ e.removeChild(e.firstChild);
4418
5212
  return e;
4419
5213
  }
4420
5214
 
@@ -4429,6 +5223,11 @@ window.CodeMirror = (function() {
4429
5223
  } else e.textContent = str;
4430
5224
  }
4431
5225
 
5226
+ function getRect(node) {
5227
+ return node.getBoundingClientRect();
5228
+ }
5229
+ CodeMirror.replaceGetRect = function(f) { getRect = f; };
5230
+
4432
5231
  // FEATURE DETECTION
4433
5232
 
4434
5233
  // Detect drag-and-drop
@@ -4449,8 +5248,8 @@ window.CodeMirror = (function() {
4449
5248
  // various browsers.
4450
5249
  var spanAffectsWrapping = /^$/; // Won't match any two-character string
4451
5250
  if (gecko) spanAffectsWrapping = /$'/;
4452
- else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
4453
- else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
5251
+ else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent)) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
5252
+ else if (webkit) spanAffectsWrapping = /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.]|\?[\w~`@#$%\^&*(_=+{[|><]/;
4454
5253
 
4455
5254
  var knownScrollbarWidth;
4456
5255
  function scrollbarWidth(measure) {
@@ -4553,20 +5352,20 @@ window.CodeMirror = (function() {
4553
5352
  }
4554
5353
 
4555
5354
  function lineStart(cm, lineN) {
4556
- var line = getLine(cm.view.doc, lineN);
4557
- var visual = visualLine(cm.view.doc, line);
5355
+ var line = getLine(cm.doc, lineN);
5356
+ var visual = visualLine(cm.doc, line);
4558
5357
  if (visual != line) lineN = lineNo(visual);
4559
5358
  var order = getOrder(visual);
4560
5359
  var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
4561
- return {line: lineN, ch: ch};
5360
+ return Pos(lineN, ch);
4562
5361
  }
4563
- function lineEnd(cm, lineNo) {
5362
+ function lineEnd(cm, lineN) {
4564
5363
  var merged, line;
4565
- while (merged = collapsedSpanAtEnd(line = getLine(cm.view.doc, lineNo)))
4566
- lineNo = merged.find().to.line;
5364
+ while (merged = collapsedSpanAtEnd(line = getLine(cm.doc, lineN)))
5365
+ lineN = merged.find().to.line;
4567
5366
  var order = getOrder(line);
4568
5367
  var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
4569
- return {line: lineNo, ch: ch};
5368
+ return Pos(lineN, ch);
4570
5369
  }
4571
5370
 
4572
5371
  // This is somewhat involved. It is needed in order to move
@@ -4658,7 +5457,7 @@ window.CodeMirror = (function() {
4658
5457
  // Browsers seem to always treat the boundaries of block elements as being L.
4659
5458
  var outerType = "L";
4660
5459
 
4661
- return function charOrdering(str) {
5460
+ return function(str) {
4662
5461
  if (!bidiRE.test(str)) return false;
4663
5462
  var len = str.length, types = [];
4664
5463
  for (var i = 0, type; i < len; ++i)
@@ -4780,7 +5579,7 @@ window.CodeMirror = (function() {
4780
5579
 
4781
5580
  // THE END
4782
5581
 
4783
- CodeMirror.version = "3.02";
5582
+ CodeMirror.version = "3.12";
4784
5583
 
4785
5584
  return CodeMirror;
4786
5585
  })();