codemirror-rails 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  *.gem
2
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/README.md CHANGED
@@ -7,12 +7,15 @@ applications.
7
7
 
8
8
  If you're using Bundler, you can add codemirror-rails to your Gemfile:
9
9
 
10
- gem 'codemirror-rails'
10
+ ```ruby
11
+ gem 'codemirror-rails'
12
+ ```
11
13
 
12
14
  Or manually install the codemirror-rails gem:
13
15
 
14
- gem install codemirror-rails
15
-
16
+ ```shell
17
+ gem install codemirror-rails
18
+ ```
16
19
 
17
20
  ## CodeMirror for Rails 3.1
18
21
 
@@ -20,23 +23,31 @@ All of the assets from the most latest stable CodeMirror release are vendored
20
23
  so that you can use them with the asset pipeline. At a minimum, you will
21
24
  probably want the following in your application.js and application.css:
22
25
 
23
- //= require codemirror
26
+ ```js
27
+ //= require codemirror
28
+ ```
24
29
 
25
30
  ### Adding a mode
26
31
 
27
32
  Additional syntax modes can be added to your application.js:
28
33
 
29
- //= require codemirror/modes/ruby
34
+ ```js
35
+ //= require codemirror/modes/ruby
36
+ ```
30
37
 
31
38
  ### Adding a theme
32
39
 
33
40
  Additional CSS themes can be added to your application.css
34
41
 
35
- //= require codemirror/themes/night
42
+ ```js
43
+ //= require codemirror/themes/night
44
+ ```
36
45
 
37
46
  ## CodeMirror for Rails 3
38
47
 
39
48
  You can use the generator included with this gem to copy the CodeMirror 2
40
49
  assets into your Rails 3 public directory.
41
50
 
42
- rails generate codemirror:install
51
+ ```shell
52
+ rails generate codemirror:install
53
+ ```
@@ -10,4 +10,6 @@ Gem::Specification.new do |s|
10
10
  s.homepage = 'https://rubygems.org/gems/codemirror-rails'
11
11
 
12
12
  s.files = `git ls-files`.split("\n")
13
+
14
+ s.add_runtime_dependency 'railties', '~> 3.0'
13
15
  end
@@ -1,6 +1,6 @@
1
1
  module Codemirror
2
2
  module Rails
3
- VERSION = '0.3.1'
4
- CODEMIRROR_VERSION = '2.16'
3
+ VERSION = '0.3.2'
4
+ CODEMIRROR_VERSION = '2.18'
5
5
  end
6
6
  end
@@ -1,3 +1,5 @@
1
+ // CodeMirror v2.18
2
+
1
3
  // All functions that need access to the editor's state live inside
2
4
  // the CodeMirror function. Below that, at the bottom of the file,
3
5
  // some utilities are defined.
@@ -16,7 +18,7 @@ var CodeMirror = (function() {
16
18
  var targetDocument = options["document"];
17
19
  // The element in which the editor lives.
18
20
  var wrapper = targetDocument.createElement("div");
19
- wrapper.className = "CodeMirror";
21
+ wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
20
22
  // This mess creates the base DOM structure for the editor.
21
23
  wrapper.innerHTML =
22
24
  '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea
@@ -24,11 +26,11 @@ var CodeMirror = (function() {
24
26
  'autocorrect="off" autocapitalize="off"></textarea></div>' +
25
27
  '<div class="CodeMirror-scroll cm-s-' + options.theme + '">' +
26
28
  '<div style="position: relative">' + // Set to the height of the text, causes scrolling
27
- '<div style="position: absolute; height: 0; width: 0; overflow: hidden;"></div>' +
28
29
  '<div style="position: relative">' + // Moved around its parent to cover visible view
29
30
  '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
30
31
  // Provides positioning relative to (visible) text origin
31
- '<div class="CodeMirror-lines"><div style="position: relative" draggable="true">' +
32
+ '<div class="CodeMirror-lines"><div style="position: relative">' +
33
+ '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden"></div>' +
32
34
  '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
33
35
  '<div></div>' + // This DIV contains the actual code
34
36
  '</div></div></div></div></div>';
@@ -36,10 +38,10 @@ var CodeMirror = (function() {
36
38
  // I've never seen more elegant code in my life.
37
39
  var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
38
40
  scroller = wrapper.lastChild, code = scroller.firstChild,
39
- measure = code.firstChild, mover = measure.nextSibling,
40
- gutter = mover.firstChild, gutterText = gutter.firstChild,
41
- lineSpace = gutter.nextSibling.firstChild,
42
- cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
41
+ mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
42
+ lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
43
+ cursor = measure.nextSibling, lineDiv = cursor.nextSibling;
44
+ if (!webkit) lineSpace.draggable = true;
43
45
  if (options.tabindex != null) input.tabindex = options.tabindex;
44
46
  if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
45
47
 
@@ -55,11 +57,10 @@ var CodeMirror = (function() {
55
57
  // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
56
58
  var poll = new Delayed(), highlight = new Delayed(), blinker;
57
59
 
58
- // mode holds a mode API object. lines an array of Line objects
59
- // (see Line constructor), work an array of lines that should be
60
- // parsed, and history the undo history (instance of History
61
- // constructor).
62
- var mode, lines = [new Line("")], work, focused;
60
+ // mode holds a mode API object. doc is the tree of Line objects,
61
+ // work an array of lines that should be parsed, and history the
62
+ // undo history (instance of History constructor).
63
+ var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
63
64
  loadMode();
64
65
  // The selection. These are always maintained to point at valid
65
66
  // positions. Inverted is used to remember that the user is
@@ -74,7 +75,7 @@ var CodeMirror = (function() {
74
75
  // happened during the operation.
75
76
  var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty;
76
77
  // Current visible range (may be bigger than the view window).
77
- var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
78
+ var displayOffset = 0, showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
78
79
  // editing will hold an object describing the things we put in the
79
80
  // textarea, to help figure out whether something changed.
80
81
  // bracketHighlighted is used to remember that a backet has been
@@ -88,10 +89,19 @@ var CodeMirror = (function() {
88
89
  operation(function(){setValue(options.value || ""); updateInput = false;})();
89
90
  var history = new History();
90
91
 
92
+ var slowPollInterval = 2000;
93
+ // Gecko and Opera Linux do not reliably fire any event when starting an IME compose
94
+ var alwaysPollForIME = (!win && !mac) && (gecko || window.opera);
95
+ if (options.pollForIME && alwaysPollForIME) slowPollInterval = 50;
96
+ function keyMightStartIME(keyCode) {
97
+ return (win && ((gecko && keyCode == 229) || (window.opera && keyCode == 197))) || (mac && gecko);
98
+ }
99
+
91
100
  // Register our event handlers.
92
101
  connect(scroller, "mousedown", operation(onMouseDown));
93
102
  connect(scroller, "dblclick", operation(onDoubleClick));
94
103
  connect(lineSpace, "dragstart", onDragStart);
104
+ connect(lineSpace, "selectstart", e_preventDefault);
95
105
  // Gecko browsers fire contextmenu *after* opening the menu, at
96
106
  // which point we can't mess with it anymore. Context menu is
97
107
  // handled in onMouseDown for Gecko.
@@ -122,7 +132,7 @@ var CodeMirror = (function() {
122
132
  if (hasFocus) setTimeout(onFocus, 20);
123
133
  else onBlur();
124
134
 
125
- function isLine(l) {return l >= 0 && l < lines.length;}
135
+ function isLine(l) {return l >= 0 && l < doc.size;}
126
136
  // The instance object that we'll return. Mostly calls out to
127
137
  // local functions in the CodeMirror function. Some do some extra
128
138
  // range checking and/or clipping. operation is used to wrap the
@@ -135,12 +145,15 @@ var CodeMirror = (function() {
135
145
  replaceSelection: operation(replaceSelection),
136
146
  focus: function(){focusInput(); onFocus(); fastPoll();},
137
147
  setOption: function(option, value) {
148
+ var oldVal = options[option];
138
149
  options[option] = value;
139
- if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber")
140
- operation(gutterChanged)();
141
- else if (option == "mode" || option == "indentUnit") loadMode();
150
+ if (option == "mode" || option == "indentUnit") loadMode();
142
151
  else if (option == "readOnly" && value == "nocursor") input.blur();
143
152
  else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value);
153
+ else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
154
+ else if (option == "pollForIME" && alwaysPollForIME) slowPollInterval = value ? 50 : 2000;
155
+ if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme")
156
+ operation(gutterChanged)();
144
157
  },
145
158
  getOption: function(option) {return options[option];},
146
159
  undo: operation(undo),
@@ -151,12 +164,12 @@ var CodeMirror = (function() {
151
164
  historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
152
165
  clearHistory: function() {history = new History();},
153
166
  matchBrackets: operation(function(){matchBrackets(true);}),
154
- getTokenAt: function(pos) {
167
+ getTokenAt: operation(function(pos) {
155
168
  pos = clipPos(pos);
156
- return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch);
157
- },
169
+ return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);
170
+ }),
158
171
  getStateAfter: function(line) {
159
- line = clipLine(line == null ? lines.length - 1: line);
172
+ line = clipLine(line == null ? doc.size - 1: line);
160
173
  return getStateBefore(line + 1);
161
174
  },
162
175
  cursorCoords: function(start){
@@ -166,14 +179,16 @@ var CodeMirror = (function() {
166
179
  charCoords: function(pos){return pageCoords(clipPos(pos));},
167
180
  coordsChar: function(coords) {
168
181
  var off = eltOffset(lineSpace);
169
- var line = clipLine(Math.min(lines.length - 1, showingFrom + Math.floor((coords.y - off.top) / lineHeight())));
170
- return clipPos({line: line, ch: charFromX(clipLine(line), coords.x - off.left)});
182
+ return coordsChar(coords.x - off.left, coords.y - off.top);
171
183
  },
172
184
  getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);},
173
185
  markText: operation(markText),
186
+ setBookmark: setBookmark,
174
187
  setMarker: operation(addGutterMarker),
175
188
  clearMarker: operation(removeGutterMarker),
176
189
  setLineClass: operation(setLineClass),
190
+ hideLine: operation(function(h) {return setLineHidden(h, true);}),
191
+ showLine: operation(function(h) {return setLineHidden(h, false);}),
177
192
  lineInfo: lineInfo,
178
193
  addWidget: function(pos, node, scroll, vert, horiz) {
179
194
  pos = localCoords(clipPos(pos));
@@ -182,7 +197,7 @@ var CodeMirror = (function() {
182
197
  code.appendChild(node);
183
198
  if (vert == "over") top = pos.y;
184
199
  else if (vert == "near") {
185
- var vspace = Math.max(scroller.offsetHeight, lines.length * lineHeight()),
200
+ var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
186
201
  hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
187
202
  if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
188
203
  top = pos.y - node.offsetHeight;
@@ -203,7 +218,7 @@ var CodeMirror = (function() {
203
218
  scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
204
219
  },
205
220
 
206
- lineCount: function() {return lines.length;},
221
+ lineCount: function() {return doc.size;},
207
222
  getCursor: function(start) {
208
223
  if (start == null) start = sel.inverted;
209
224
  return copyPos(start ? sel.from : sel.to);
@@ -214,9 +229,9 @@ var CodeMirror = (function() {
214
229
  else setCursor(line, ch);
215
230
  }),
216
231
  setSelection: operation(function(from, to) {setSelection(clipPos(from), clipPos(to || from));}),
217
- getLine: function(line) {if (isLine(line)) return lines[line].text;},
232
+ getLine: function(line) {if (isLine(line)) return getLine(line).text;},
218
233
  setLine: operation(function(line, text) {
219
- if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: lines[line].text.length});
234
+ if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
220
235
  }),
221
236
  removeLine: operation(function(line) {
222
237
  if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
@@ -224,15 +239,15 @@ var CodeMirror = (function() {
224
239
  replaceRange: operation(replaceRange),
225
240
  getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
226
241
 
227
- coordsFromIndex: function(index) {
228
- var total = lines.length, pos = 0, line, ch, len;
229
-
230
- for (line = 0; line < total; line++) {
231
- len = lines[line].text.length + 1;
232
- if (pos + len > index) { ch = index - pos; break; }
233
- pos += len;
234
- }
235
- return clipPos({line: line, ch: ch});
242
+ coordsFromIndex: function(off) {
243
+ var lineNo = 0, ch;
244
+ doc.iter(0, doc.size, function(line) {
245
+ var sz = line.text.length + 1;
246
+ if (sz > off) { ch = off; return true; }
247
+ off -= sz;
248
+ ++lineNo;
249
+ });
250
+ return clipPos({line: lineNo, ch: ch});
236
251
  },
237
252
 
238
253
  operation: function(f){return operation(f)();},
@@ -243,16 +258,22 @@ var CodeMirror = (function() {
243
258
  getGutterElement: function(){return gutter;}
244
259
  };
245
260
 
261
+ function getLine(n) { return getLineAt(doc, n); }
262
+ function updateLineHeight(line, height) {
263
+ gutterDirty = true;
264
+ var diff = height - line.height;
265
+ for (var n = line; n; n = n.parent) n.height += diff;
266
+ }
267
+
246
268
  function setValue(code) {
247
269
  var top = {line: 0, ch: 0};
248
- updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length},
270
+ updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
249
271
  splitLines(code), top, top);
250
272
  updateInput = true;
251
273
  }
252
274
  function getValue(code) {
253
275
  var text = [];
254
- for (var i = 0, l = lines.length; i < l; ++i)
255
- text.push(lines[i].text);
276
+ doc.iter(0, doc.size, function(line) { text.push(line.text); });
256
277
  return text.join("\n");
257
278
  }
258
279
 
@@ -261,7 +282,7 @@ var CodeMirror = (function() {
261
282
  for (var n = e_target(e); n != wrapper; n = n.parentNode)
262
283
  if (n.parentNode == code && n != mover) return;
263
284
 
264
- // First, see if this is a click in the gutter
285
+ // See if this is a click in the gutter
265
286
  for (var n = e_target(e); n != wrapper; n = n.parentNode)
266
287
  if (n.parentNode == gutterText) {
267
288
  if (options.onGutterClick)
@@ -287,20 +308,23 @@ var CodeMirror = (function() {
287
308
  if (!focused) onFocus();
288
309
 
289
310
  var now = +new Date;
290
- if (lastDoubleClick > now - 400) {
311
+ if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
291
312
  e_preventDefault(e);
313
+ setTimeout(focusInput, 20);
292
314
  return selectLine(start.line);
293
- } else if (lastClick > now - 400) {
294
- lastDoubleClick = now;
315
+ } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
316
+ lastDoubleClick = {time: now, pos: start};
295
317
  e_preventDefault(e);
296
318
  return selectWordAt(start);
297
- } else { lastClick = now; }
319
+ } else { lastClick = {time: now, pos: start}; }
298
320
 
299
321
  var last = start, going;
300
322
  if (dragAndDrop && !posEq(sel.from, sel.to) &&
301
323
  !posLess(start, sel.from) && !posLess(sel.to, start)) {
302
324
  // Let the drag handler handle this.
325
+ if (webkit) lineSpace.draggable = true;
303
326
  var up = connect(targetDocument, "mouseup", operation(function(e2) {
327
+ if (webkit) lineSpace.draggable = false;
304
328
  draggingText = false;
305
329
  up();
306
330
  if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
@@ -344,9 +368,11 @@ var CodeMirror = (function() {
344
368
  }), true);
345
369
  }
346
370
  function onDoubleClick(e) {
371
+ for (var n = e_target(e); n != wrapper; n = n.parentNode)
372
+ if (n.parentNode == gutterText) return e_preventDefault(e);
347
373
  var start = posFromMouse(e);
348
374
  if (!start) return;
349
- lastDoubleClick = +new Date;
375
+ lastDoubleClick = {time: +new Date, pos: start};
350
376
  e_preventDefault(e);
351
377
  selectWordAt(start);
352
378
  }
@@ -361,8 +387,10 @@ var CodeMirror = (function() {
361
387
  text[i] = reader.result;
362
388
  if (++read == n) {
363
389
  pos = clipPos(pos);
364
- var end = replaceRange(text.join(""), pos, pos);
365
- setSelectionUser(pos, end);
390
+ operation(function() {
391
+ var end = replaceRange(text.join(""), pos, pos);
392
+ setSelectionUser(pos, end);
393
+ })();
366
394
  }
367
395
  };
368
396
  reader.readAsText(file);
@@ -435,6 +463,8 @@ var CodeMirror = (function() {
435
463
  // Don't save the key as a movementkey unless it had a modifier
436
464
  if (!mod && !e.altKey) curKeyId = null;
437
465
  fastPoll(curKeyId);
466
+
467
+ if (options.pollForIME && keyMightStartIME(code)) slowPollInterval = 50;
438
468
  }
439
469
  function onKeyUp(e) {
440
470
  if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
@@ -443,6 +473,8 @@ var CodeMirror = (function() {
443
473
  updateInput = true;
444
474
  }
445
475
  if (e.keyCode == 16) shiftSelecting = null;
476
+
477
+ if (slowPollInterval < 2000 && !alwaysPollForIME) slowPollInterval = 2000;
446
478
  }
447
479
  function onKeyPress(e) {
448
480
  if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
@@ -485,7 +517,7 @@ var CodeMirror = (function() {
485
517
  function updateLines(from, to, newText, selFrom, selTo) {
486
518
  if (history) {
487
519
  var old = [];
488
- for (var i = from.line, e = to.line + 1; i < e; ++i) old.push(lines[i].text);
520
+ doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
489
521
  history.addChange(from.line, newText.length, old);
490
522
  while (history.done.length > options.undoDepth) history.done.shift();
491
523
  }
@@ -495,11 +527,11 @@ var CodeMirror = (function() {
495
527
  var change = from.pop();
496
528
  if (change) {
497
529
  var replaced = [], end = change.start + change.added;
498
- for (var i = change.start; i < end; ++i) replaced.push(lines[i].text);
530
+ doc.iter(change.start, end, function(line) { replaced.push(line.text); });
499
531
  to.push({start: change.start, added: change.old.length, old: replaced});
500
532
  var pos = clipPos({line: change.start + change.old.length - 1,
501
533
  ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
502
- updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos);
534
+ updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
503
535
  updateInput = true;
504
536
  }
505
537
  }
@@ -508,55 +540,66 @@ var CodeMirror = (function() {
508
540
 
509
541
  function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
510
542
  var recomputeMaxLength = false, maxLineLength = maxLine.length;
511
- for (var i = from.line; i <= to.line; ++i) {
512
- if (lines[i].text.length == maxLineLength) {recomputeMaxLength = true; break;}
513
- }
543
+ if (!options.lineWrapping)
544
+ doc.iter(from.line, to.line, function(line) {
545
+ if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
546
+ });
514
547
 
515
- var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line];
548
+ var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
516
549
  // First adjust the line structure, taking some care to leave highlighting intact.
517
550
  if (firstLine == lastLine) {
518
551
  if (newText.length == 1)
519
552
  firstLine.replace(from.ch, to.ch, newText[0]);
520
553
  else {
521
554
  lastLine = firstLine.split(to.ch, newText[newText.length-1]);
522
- var spliceargs = [from.line + 1, nlines];
523
555
  firstLine.replace(from.ch, null, newText[0]);
556
+ firstLine.fixMarkEnds(lastLine);
557
+ var added = [];
524
558
  for (var i = 1, e = newText.length - 1; i < e; ++i)
525
- spliceargs.push(Line.inheritMarks(newText[i], firstLine));
526
- spliceargs.push(lastLine);
527
- lines.splice.apply(lines, spliceargs);
559
+ added.push(Line.inheritMarks(newText[i], firstLine));
560
+ added.push(lastLine);
561
+ doc.insert(from.line + 1, added);
528
562
  }
529
563
  }
530
564
  else if (newText.length == 1) {
531
565
  firstLine.replace(from.ch, null, newText[0]);
532
566
  lastLine.replace(null, to.ch, "");
533
567
  firstLine.append(lastLine);
534
- lines.splice(from.line + 1, nlines);
568
+ doc.remove(from.line + 1, nlines);
535
569
  }
536
570
  else {
537
- var spliceargs = [from.line + 1, nlines - 1];
571
+ var added = [];
538
572
  firstLine.replace(from.ch, null, newText[0]);
539
573
  lastLine.replace(null, to.ch, newText[newText.length-1]);
574
+ firstLine.fixMarkEnds(lastLine);
540
575
  for (var i = 1, e = newText.length - 1; i < e; ++i)
541
- spliceargs.push(Line.inheritMarks(newText[i], firstLine));
542
- lines.splice.apply(lines, spliceargs);
543
- }
544
-
545
-
546
- for (var i = from.line, e = i + newText.length; i < e; ++i) {
547
- var l = lines[i].text;
548
- if (l.length > maxLineLength) {
549
- maxLine = l; maxLineLength = l.length; maxWidth = null;
550
- recomputeMaxLength = false;
551
- }
552
- }
553
- if (recomputeMaxLength) {
554
- maxLineLength = 0; maxLine = ""; maxWidth = null;
555
- for (var i = 0, e = lines.length; i < e; ++i) {
556
- var l = lines[i].text;
576
+ added.push(Line.inheritMarks(newText[i], firstLine));
577
+ if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
578
+ doc.insert(from.line + 1, added);
579
+ }
580
+ if (options.lineWrapping) {
581
+ var perLine = scroller.clientWidth / charWidth() - 3;
582
+ doc.iter(from.line, from.line + newText.length, function(line) {
583
+ if (line.hidden) return;
584
+ var guess = Math.ceil(line.text.length / perLine) || 1;
585
+ if (guess != line.height) updateLineHeight(line, guess);
586
+ });
587
+ } else {
588
+ doc.iter(from.line, i + newText.length, function(line) {
589
+ var l = line.text;
557
590
  if (l.length > maxLineLength) {
558
- maxLineLength = l.length; maxLine = l;
591
+ maxLine = l; maxLineLength = l.length; maxWidth = null;
592
+ recomputeMaxLength = false;
559
593
  }
594
+ });
595
+ if (recomputeMaxLength) {
596
+ maxLineLength = 0; maxLine = ""; maxWidth = null;
597
+ doc.iter(0, doc.size, function(line) {
598
+ var l = line.text;
599
+ if (l.length > maxLineLength) {
600
+ maxLineLength = l.length; maxLine = l;
601
+ }
602
+ });
560
603
  }
561
604
  }
562
605
 
@@ -568,12 +611,9 @@ var CodeMirror = (function() {
568
611
  if (task < from.line) newWork.push(task);
569
612
  else if (task > to.line) newWork.push(task + lendiff);
570
613
  }
571
- if (newText.length < 5) {
572
- highlightLines(from.line, from.line + newText.length);
573
- newWork.push(from.line + newText.length);
574
- } else {
575
- newWork.push(from.line);
576
- }
614
+ var hlEnd = from.line + Math.min(newText.length, 500);
615
+ highlightLines(from.line, hlEnd);
616
+ newWork.push(hlEnd);
577
617
  work = newWork;
578
618
  startWorker(100);
579
619
  // Remember that these lines changed, for updating the display
@@ -585,7 +625,7 @@ var CodeMirror = (function() {
585
625
  setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
586
626
 
587
627
  // Make sure the scroll-size div has the correct height.
588
- code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
628
+ code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
589
629
  }
590
630
 
591
631
  function replaceRange(code, from, to) {
@@ -623,10 +663,10 @@ var CodeMirror = (function() {
623
663
 
624
664
  function getRange(from, to) {
625
665
  var l1 = from.line, l2 = to.line;
626
- if (l1 == l2) return lines[l1].text.slice(from.ch, to.ch);
627
- var code = [lines[l1].text.slice(from.ch)];
628
- for (var i = l1 + 1; i < l2; ++i) code.push(lines[i].text);
629
- code.push(lines[l2].text.slice(0, to.ch));
666
+ if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
667
+ var code = [getLine(l1).text.slice(from.ch)];
668
+ doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
669
+ code.push(getLine(l2).text.slice(0, to.ch));
630
670
  return code.join("\n");
631
671
  }
632
672
  function getSelection() {
@@ -636,7 +676,7 @@ var CodeMirror = (function() {
636
676
  var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
637
677
  function slowPoll() {
638
678
  if (pollingFast) return;
639
- poll.set(2000, function() {
679
+ poll.set(slowPollInterval, function() {
640
680
  startOperation();
641
681
  readInput();
642
682
  if (focused) slowPoll();
@@ -734,14 +774,16 @@ var CodeMirror = (function() {
734
774
  // editor state.
735
775
  function prepareInput() {
736
776
  var text = [];
737
- var from = Math.max(0, sel.from.line - 1), to = Math.min(lines.length, sel.to.line + 2);
738
- for (var i = from; i < to; ++i) text.push(lines[i].text);
777
+ var from = Math.max(0, sel.from.line - 1), to = Math.min(doc.size, sel.to.line + 2);
778
+ doc.iter(from, to, function(line) { text.push(line.text); });
739
779
  text = input.value = text.join(lineSep);
740
780
  var startch = sel.from.ch, endch = sel.to.ch;
741
- for (var i = from; i < sel.from.line; ++i)
742
- startch += lineSep.length + lines[i].text.length;
743
- for (var i = from; i < sel.to.line; ++i)
744
- endch += lineSep.length + lines[i].text.length;
781
+ doc.iter(from, sel.from.line, function(line) {
782
+ startch += lineSep.length + line.text.length;
783
+ });
784
+ doc.iter(from, sel.to.line, function(line) {
785
+ endch += lineSep.length + line.text.length;
786
+ });
745
787
  editing = {text: text, from: from, to: to, start: startch, end: endch};
746
788
  setSelRange(input, startch, reducedSelection ? startch : endch);
747
789
  }
@@ -757,10 +799,11 @@ var CodeMirror = (function() {
757
799
  }
758
800
  function scrollCursorIntoView() {
759
801
  var cursor = localCoords(sel.inverted ? sel.from : sel.to);
760
- return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot);
802
+ var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
803
+ return scrollIntoView(x, cursor.y, x, cursor.yBot);
761
804
  }
762
805
  function scrollIntoView(x1, y1, x2, y2) {
763
- var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight();
806
+ var pl = paddingLeft(), pt = paddingTop(), lh = textHeight();
764
807
  y1 += pt; y2 += pt; x1 += pl; x2 += pl;
765
808
  var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
766
809
  if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}
@@ -783,28 +826,99 @@ var CodeMirror = (function() {
783
826
  }
784
827
 
785
828
  function visibleLines() {
786
- var lh = lineHeight(), top = scroller.scrollTop - paddingTop();
787
- return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))),
788
- to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))};
829
+ var lh = textHeight(), top = scroller.scrollTop - paddingTop();
830
+ var from_height = Math.max(0, Math.floor(top / lh));
831
+ var to_height = Math.ceil((top + scroller.clientHeight) / lh);
832
+ return {from: lineAtHeight(doc, from_height),
833
+ to: lineAtHeight(doc, to_height)};
789
834
  }
790
835
  // Uses a set of changes plus the current scroll position to
791
836
  // determine which DOM updates have to be made, and makes the
792
837
  // updates.
793
838
  function updateDisplay(changes) {
794
839
  if (!scroller.clientWidth) {
795
- showingFrom = showingTo = 0;
840
+ showingFrom = showingTo = displayOffset = 0;
796
841
  return;
797
842
  }
798
- // First create a range of theoretically intact lines, and punch
799
- // holes in that using the change info.
800
- var intact = changes === true ? [] : [{from: showingFrom, to: showingTo, domStart: 0}];
843
+ // Compute the new visible window
844
+ var visible = visibleLines();
845
+ // Bail out if the visible area is already rendered and nothing changed.
846
+ if (changes !== true && changes.length == 0 && visible.from >= showingFrom && visible.to <= showingTo) return;
847
+ var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
848
+ if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
849
+ if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
850
+
851
+ // Create a range of theoretically intact lines, and punch holes
852
+ // in that using the change info.
853
+ var intact = changes === true ? [] :
854
+ computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
855
+ // Clip off the parts that won't be visible
856
+ var intactLines = 0;
857
+ for (var i = 0; i < intact.length; ++i) {
858
+ var range = intact[i];
859
+ if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
860
+ if (range.to > to) range.to = to;
861
+ if (range.from >= range.to) intact.splice(i--, 1);
862
+ else intactLines += range.to - range.from;
863
+ }
864
+ if (intactLines == to - from) return;
865
+ intact.sort(function(a, b) {return a.domStart - b.domStart;});
866
+
867
+ var th = textHeight(), gutterDisplay = gutter.style.display;
868
+ lineDiv.style.display = gutter.style.display = "none";
869
+ patchDisplay(from, to, intact);
870
+ lineDiv.style.display = "";
871
+
872
+ // Position the mover div to align with the lines it's supposed
873
+ // to be showing (which will cover the visible display)
874
+ var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
875
+ if (different) lastHeight = scroller.clientHeight;
876
+ showingFrom = from; showingTo = to;
877
+ displayOffset = heightAtLine(doc, from);
878
+ mover.style.top = (displayOffset * th) + "px";
879
+ code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
880
+
881
+ // Since this is all rather error prone, it is honoured with the
882
+ // only assertion in the whole file.
883
+ if (lineDiv.childNodes.length != showingTo - showingFrom)
884
+ throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
885
+ " nodes=" + lineDiv.childNodes.length);
886
+
887
+ if (options.lineWrapping) {
888
+ maxWidth = scroller.clientWidth;
889
+ var curNode = lineDiv.firstChild;
890
+ doc.iter(showingFrom, showingTo, function(line) {
891
+ if (!line.hidden) {
892
+ var height = Math.round(curNode.offsetHeight / th) || 1;
893
+ if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;}
894
+ }
895
+ curNode = curNode.nextSibling;
896
+ });
897
+ } else {
898
+ if (maxWidth == null) maxWidth = stringWidth(maxLine);
899
+ if (maxWidth > scroller.clientWidth) {
900
+ lineSpace.style.width = maxWidth + "px";
901
+ // Needed to prevent odd wrapping/hiding of widgets placed in here.
902
+ code.style.width = "";
903
+ code.style.width = scroller.scrollWidth + "px";
904
+ } else {
905
+ lineSpace.style.width = code.style.width = "";
906
+ }
907
+ }
908
+ gutter.style.display = gutterDisplay;
909
+ if (different || gutterDirty) updateGutter();
910
+ updateCursor();
911
+ }
912
+
913
+ function computeIntact(intact, changes) {
801
914
  for (var i = 0, l = changes.length || 0; i < l; ++i) {
802
915
  var change = changes[i], intact2 = [], diff = change.diff || 0;
803
916
  for (var j = 0, l2 = intact.length; j < l2; ++j) {
804
917
  var range = intact[j];
805
- if (change.to <= range.from)
806
- intact2.push({from: range.from + diff, to: range.to + diff, domStart: range.domStart});
807
- else if (range.to <= change.from)
918
+ if (change.to <= range.from && change.diff)
919
+ intact2.push({from: range.from + diff, to: range.to + diff,
920
+ domStart: range.domStart});
921
+ else if (change.to <= range.from || change.from >= range.to)
808
922
  intact2.push(range);
809
923
  else {
810
924
  if (change.from > range.from)
@@ -816,148 +930,75 @@ var CodeMirror = (function() {
816
930
  }
817
931
  intact = intact2;
818
932
  }
933
+ return intact;
934
+ }
819
935
 
820
- // Then, determine which lines we'd want to see, and which
821
- // updates have to be made to get there.
822
- var visible = visibleLines();
823
- var from = Math.min(showingFrom, Math.max(visible.from - 3, 0)),
824
- to = Math.min(lines.length, Math.max(showingTo, visible.to + 3)),
825
- updates = [], domPos = 0, domEnd = showingTo - showingFrom, pos = from, changedLines = 0;
826
-
827
- for (var i = 0, l = intact.length; i < l; ++i) {
828
- var range = intact[i];
829
- if (range.to <= from) continue;
830
- if (range.from >= to) break;
831
- if (range.domStart > domPos || range.from > pos) {
832
- updates.push({from: pos, to: range.from, domSize: range.domStart - domPos, domStart: domPos});
833
- changedLines += range.from - pos;
936
+ function patchDisplay(from, to, intact) {
937
+ // The first pass removes the DOM nodes that aren't intact.
938
+ if (!intact.length) lineDiv.innerHTML = "";
939
+ else {
940
+ function killNode(node) {
941
+ var tmp = node.nextSibling;
942
+ node.parentNode.removeChild(node);
943
+ return tmp;
834
944
  }
835
- pos = range.to;
836
- domPos = range.domStart + (range.to - range.from);
837
- }
838
- if (domPos != domEnd || pos != to) {
839
- changedLines += Math.abs(to - pos);
840
- updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos});
841
- if (to - pos != domEnd - domPos) gutterDirty = true;
842
- }
843
-
844
- if (!updates.length) return;
845
- lineDiv.style.display = "none";
846
- // If more than 30% of the screen needs update, just do a full
847
- // redraw (which is quicker than patching)
848
- if (changedLines > (visible.to - visible.from) * .3)
849
- refreshDisplay(from = Math.max(visible.from - 10, 0), to = Math.min(visible.to + 7, lines.length));
850
- // Otherwise, only update the stuff that needs updating.
851
- else
852
- patchDisplay(updates);
853
- lineDiv.style.display = "";
854
-
855
- // Position the mover div to align with the lines it's supposed
856
- // to be showing (which will cover the visible display)
857
- var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
858
- showingFrom = from; showingTo = to;
859
- mover.style.top = (from * lineHeight()) + "px";
860
- if (different) {
861
- lastHeight = scroller.clientHeight;
862
- code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
863
- }
864
- if (different || gutterDirty) updateGutter();
865
-
866
- if (maxWidth == null) maxWidth = stringWidth(maxLine);
867
- if (maxWidth > scroller.clientWidth) {
868
- lineSpace.style.width = maxWidth + "px";
869
- // Needed to prevent odd wrapping/hiding of widgets placed in here.
870
- code.style.width = "";
871
- code.style.width = scroller.scrollWidth + "px";
872
- } else {
873
- lineSpace.style.width = code.style.width = "";
945
+ var domPos = 0, curNode = lineDiv.firstChild, n;
946
+ for (var i = 0; i < intact.length; ++i) {
947
+ var cur = intact[i];
948
+ while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
949
+ for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
950
+ }
951
+ while (curNode) curNode = killNode(curNode);
874
952
  }
875
-
876
- // Since this is all rather error prone, it is honoured with the
877
- // only assertion in the whole file.
878
- if (lineDiv.childNodes.length != showingTo - showingFrom)
879
- throw new Error("BAD PATCH! " + JSON.stringify(updates) + " size=" + (showingTo - showingFrom) +
880
- " nodes=" + lineDiv.childNodes.length);
881
- updateCursor();
882
- }
883
-
884
- function refreshDisplay(from, to) {
885
- var html = [], start = {line: from, ch: 0}, inSel = posLess(sel.from, start) && !posLess(sel.to, start);
886
- for (var i = from; i < to; ++i) {
953
+ // This pass fills in the lines that actually changed.
954
+ var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
955
+ var sfrom = sel.from.line, sto = sel.to.line, inSel = sfrom < from && sto >= from;
956
+ var scratch = targetDocument.createElement("div"), newElt;
957
+ doc.iter(from, to, function(line) {
887
958
  var ch1 = null, ch2 = null;
888
959
  if (inSel) {
889
960
  ch1 = 0;
890
- if (sel.to.line == i) {inSel = false; ch2 = sel.to.ch;}
891
- }
892
- else if (sel.from.line == i) {
893
- if (sel.to.line == i) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
961
+ if (sto == j) {inSel = false; ch2 = sel.to.ch;}
962
+ } else if (sfrom == j) {
963
+ if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
894
964
  else {inSel = true; ch1 = sel.from.ch;}
895
965
  }
896
- html.push(lines[i].getHTML(ch1, ch2, true));
897
- }
898
- lineDiv.innerHTML = html.join("");
899
- }
900
- function patchDisplay(updates) {
901
- // Slightly different algorithm for IE (badInnerHTML), since
902
- // there .innerHTML on PRE nodes is dumb, and discards
903
- // whitespace.
904
- var sfrom = sel.from.line, sto = sel.to.line, off = 0,
905
- scratch = badInnerHTML && targetDocument.createElement("div");
906
- for (var i = 0, e = updates.length; i < e; ++i) {
907
- var rec = updates[i];
908
- var extra = (rec.to - rec.from) - rec.domSize;
909
- var nodeAfter = lineDiv.childNodes[rec.domStart + rec.domSize + off] || null;
910
- if (badInnerHTML)
911
- for (var j = Math.max(-extra, rec.domSize); j > 0; --j)
912
- lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
913
- else if (extra) {
914
- for (var j = Math.max(0, extra); j > 0; --j)
915
- lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter);
916
- for (var j = Math.max(0, -extra); j > 0; --j)
917
- lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
918
- }
919
- var node = lineDiv.childNodes[rec.domStart + off], inSel = sfrom < rec.from && sto >= rec.from;
920
- for (var j = rec.from; j < rec.to; ++j) {
921
- var ch1 = null, ch2 = null;
922
- if (inSel) {
923
- ch1 = 0;
924
- if (sto == j) {inSel = false; ch2 = sel.to.ch;}
925
- }
926
- else if (sfrom == j) {
927
- if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
928
- else {inSel = true; ch1 = sel.from.ch;}
929
- }
930
- if (badInnerHTML) {
931
- scratch.innerHTML = lines[j].getHTML(ch1, ch2, true);
932
- lineDiv.insertBefore(scratch.firstChild, nodeAfter);
933
- }
934
- else {
935
- node.innerHTML = lines[j].getHTML(ch1, ch2, false);
936
- node.className = lines[j].className || "";
937
- node = node.nextSibling;
938
- }
966
+ if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
967
+ if (!nextIntact || nextIntact.from > j) {
968
+ if (line.hidden) scratch.innerHTML = "<pre></pre>";
969
+ else scratch.innerHTML = line.getHTML(ch1, ch2, true);
970
+ lineDiv.insertBefore(scratch.firstChild, curNode);
971
+ } else {
972
+ curNode = curNode.nextSibling;
939
973
  }
940
- off += extra;
941
- }
974
+ ++j;
975
+ });
942
976
  }
943
977
 
944
978
  function updateGutter() {
945
979
  if (!options.gutter && !options.lineNumbers) return;
946
980
  var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
947
981
  gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
948
- var html = [];
949
- for (var i = showingFrom; i < Math.max(showingTo, showingFrom + 1); ++i) {
950
- var marker = lines[i].gutterMarker;
951
- var text = options.lineNumbers ? i + options.firstLineNumber : null;
952
- if (marker && marker.text)
953
- text = marker.text.replace("%N%", text != null ? text : "");
954
- else if (text == null)
955
- text = "\u00a0";
956
- html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text, "</pre>");
957
- }
982
+ var html = [], i = showingFrom;
983
+ doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
984
+ if (line.hidden) {
985
+ html.push("<pre></pre>");
986
+ } else {
987
+ var marker = line.gutterMarker;
988
+ var text = options.lineNumbers ? i + options.firstLineNumber : null;
989
+ if (marker && marker.text)
990
+ text = marker.text.replace("%N%", text != null ? text : "");
991
+ else if (text == null)
992
+ text = "\u00a0";
993
+ html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
994
+ for (var j = 1; j < line.height; ++j) html.push("<br>&nbsp;");
995
+ html.push("</pre>");
996
+ }
997
+ ++i;
998
+ });
958
999
  gutter.style.display = "none";
959
1000
  gutterText.innerHTML = html.join("");
960
- var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
1001
+ var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
961
1002
  while (val.length + pad.length < minwidth) pad += "\u00a0";
962
1003
  if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);
963
1004
  gutter.style.display = "";
@@ -965,14 +1006,14 @@ var CodeMirror = (function() {
965
1006
  gutterDirty = false;
966
1007
  }
967
1008
  function updateCursor() {
968
- var head = sel.inverted ? sel.from : sel.to, lh = lineHeight();
969
- var x = charX(head.line, head.ch);
970
- var top = head.line * lh - scroller.scrollTop;
971
- inputDiv.style.top = Math.max(Math.min(top, scroller.offsetHeight), 0) + "px";
972
- inputDiv.style.left = (x - scroller.scrollLeft) + "px";
1009
+ var head = sel.inverted ? sel.from : sel.to, lh = textHeight();
1010
+ var pos = localCoords(head, true);
1011
+ var globalY = pos.y + displayOffset * textHeight();
1012
+ inputDiv.style.top = Math.max(Math.min(globalY, scroller.offsetHeight), 0) + "px";
1013
+ inputDiv.style.left = (pos.x - scroller.scrollLeft) + "px";
973
1014
  if (posEq(sel.from, sel.to)) {
974
- cursor.style.top = (head.line - showingFrom) * lh + "px";
975
- cursor.style.left = x + "px";
1015
+ cursor.style.top = pos.y + "px";
1016
+ cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + "px";
976
1017
  cursor.style.display = "";
977
1018
  }
978
1019
  else cursor.style.display = "none";
@@ -990,9 +1031,14 @@ var CodeMirror = (function() {
990
1031
  // updateLines, since they have to be expressed in the line
991
1032
  // numbers before the update.
992
1033
  function setSelection(from, to, oldFrom, oldTo) {
1034
+ if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
993
1035
  if (posEq(sel.from, from) && posEq(sel.to, to)) return;
994
1036
  if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
995
1037
 
1038
+ // Skip over hidden lines.
1039
+ if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch);
1040
+ if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
1041
+
996
1042
  if (posEq(from, to)) sel.inverted = false;
997
1043
  else if (posEq(from, sel.to)) sel.inverted = false;
998
1044
  else if (posEq(to, sel.from)) sel.inverted = true;
@@ -1000,7 +1046,6 @@ var CodeMirror = (function() {
1000
1046
  // Some ugly logic used to only mark the lines that actually did
1001
1047
  // see a change in selection as changed, rather than the whole
1002
1048
  // selected range.
1003
- if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
1004
1049
  if (posEq(from, to)) {
1005
1050
  if (!posEq(sel.from, sel.to))
1006
1051
  changes.push({from: oldFrom, to: oldTo + 1});
@@ -1025,42 +1070,61 @@ var CodeMirror = (function() {
1025
1070
  sel.from = from; sel.to = to;
1026
1071
  selectionChanged = true;
1027
1072
  }
1073
+ function skipHidden(pos, oldLine, oldCh) {
1074
+ function getNonHidden(dir) {
1075
+ var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
1076
+ while (lNo != end) {
1077
+ var line = getLine(lNo);
1078
+ if (!line.hidden) {
1079
+ var ch = pos.ch;
1080
+ if (ch > oldCh || ch > line.text.length) ch = line.text.length;
1081
+ return {line: lNo, ch: ch};
1082
+ }
1083
+ lNo += dir;
1084
+ }
1085
+ }
1086
+ var line = getLine(pos.line);
1087
+ if (!line.hidden) return pos;
1088
+ if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
1089
+ else return getNonHidden(-1) || getNonHidden(1);
1090
+ }
1028
1091
  function setCursor(line, ch, user) {
1029
1092
  var pos = clipPos({line: line, ch: ch || 0});
1030
1093
  (user ? setSelectionUser : setSelection)(pos, pos);
1031
1094
  }
1032
1095
 
1033
- function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));}
1096
+ function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
1034
1097
  function clipPos(pos) {
1035
1098
  if (pos.line < 0) return {line: 0, ch: 0};
1036
- if (pos.line >= lines.length) return {line: lines.length-1, ch: lines[lines.length-1].text.length};
1037
- var ch = pos.ch, linelen = lines[pos.line].text.length;
1099
+ if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
1100
+ var ch = pos.ch, linelen = getLine(pos.line).text.length;
1038
1101
  if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
1039
1102
  else if (ch < 0) return {line: pos.line, ch: 0};
1040
1103
  else return pos;
1041
1104
  }
1042
1105
 
1043
1106
  function scrollPage(down) {
1044
- var linesPerPage = Math.floor(scroller.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
1045
- setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch, true);
1107
+ var linesPerPage = Math.floor(scroller.clientHeight / textHeight()), head = sel.inverted ? sel.from : sel.to;
1108
+ var target = heightAtLine(doc, head.line) + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1));
1109
+ setCursor(lineAtHeight(doc, target), head.ch, true);
1046
1110
  }
1047
1111
  function scrollEnd(top) {
1048
- var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length};
1112
+ var pos = top ? {line: 0, ch: 0} : {line: doc.size - 1, ch: getLine(doc.size-1).text.length};
1049
1113
  setSelectionUser(pos, pos);
1050
1114
  }
1051
1115
  function selectAll() {
1052
- var endLine = lines.length - 1;
1053
- setSelection({line: 0, ch: 0}, {line: endLine, ch: lines[endLine].text.length});
1116
+ var endLine = doc.size - 1;
1117
+ setSelection({line: 0, ch: 0}, {line: endLine, ch: getLine(endLine).text.length});
1054
1118
  }
1055
1119
  function selectWordAt(pos) {
1056
- var line = lines[pos.line].text;
1120
+ var line = getLine(pos.line).text;
1057
1121
  var start = pos.ch, end = pos.ch;
1058
1122
  while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
1059
1123
  while (end < line.length && /\w/.test(line.charAt(end))) ++end;
1060
1124
  setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
1061
1125
  }
1062
1126
  function selectLine(line) {
1063
- setSelectionUser({line: line, ch: 0}, {line: line, ch: lines[line].text.length});
1127
+ setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
1064
1128
  }
1065
1129
  function handleEnter() {
1066
1130
  replaceSelection("\n", "end");
@@ -1093,7 +1157,7 @@ var CodeMirror = (function() {
1093
1157
  return true;
1094
1158
  }
1095
1159
  function smartHome() {
1096
- var firstNonWS = Math.max(0, lines[sel.from.line].text.search(/\S/));
1160
+ var firstNonWS = Math.max(0, getLine(sel.from.line).text.search(/\S/));
1097
1161
  setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true);
1098
1162
  }
1099
1163
 
@@ -1103,9 +1167,9 @@ var CodeMirror = (function() {
1103
1167
  else var state = getStateBefore(n);
1104
1168
  }
1105
1169
 
1106
- var line = lines[n], curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
1170
+ var line = getLine(n), curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
1107
1171
  if (how == "prev") {
1108
- if (n) indentation = lines[n-1].indentation();
1172
+ if (n) indentation = getLine(n-1).indentation();
1109
1173
  else indentation = 0;
1110
1174
  }
1111
1175
  else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length));
@@ -1130,8 +1194,7 @@ var CodeMirror = (function() {
1130
1194
 
1131
1195
  function loadMode() {
1132
1196
  mode = CodeMirror.getMode(options, options.mode);
1133
- for (var i = 0, l = lines.length; i < l; ++i)
1134
- lines[i].stateAfter = null;
1197
+ doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
1135
1198
  work = [0];
1136
1199
  startWorker();
1137
1200
  }
@@ -1141,35 +1204,38 @@ var CodeMirror = (function() {
1141
1204
  if (visible) gutterDirty = true;
1142
1205
  else lineDiv.parentNode.style.marginLeft = 0;
1143
1206
  }
1144
-
1145
- function markText(from, to, className) {
1146
- from = clipPos(from); to = clipPos(to);
1147
- var set = [];
1148
- function add(line, from, to, className) {
1149
- mark = lines[line].addMark(from, to, className, set);
1150
- }
1151
- if (from.line == to.line) add(from.line, from.ch, to.ch, className);
1152
- else {
1153
- add(from.line, from.ch, null, className);
1154
- for (var i = from.line + 1, e = to.line; i < e; ++i)
1155
- add(i, 0, null, className);
1156
- add(to.line, 0, to.ch, className);
1207
+ function wrappingChanged(from, to) {
1208
+ if (options.lineWrapping) {
1209
+ wrapper.className += " CodeMirror-wrap";
1210
+ var perLine = scroller.clientWidth / charWidth() - 3;
1211
+ doc.iter(0, doc.size, function(line) {
1212
+ if (line.hidden) return;
1213
+ var guess = Math.ceil(line.text.length / perLine) || 1;
1214
+ if (guess != 1) updateLineHeight(line, guess);
1215
+ });
1216
+ lineSpace.style.width = code.style.width = "";
1217
+ } else {
1218
+ wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
1219
+ maxWidth = null; maxLine = "";
1220
+ doc.iter(0, doc.size, function(line) {
1221
+ if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
1222
+ if (line.text.length > maxLine.length) maxLine = line.text;
1223
+ });
1157
1224
  }
1158
- changes.push({from: from.line, to: to.line + 1});
1159
- return new TextMarker(set);
1225
+ changes.push({from: 0, to: doc.size});
1160
1226
  }
1161
1227
 
1162
- function TextMarker(set) { this.set = set; }
1228
+ function TextMarker() { this.set = []; }
1163
1229
  TextMarker.prototype.clear = operation(function() {
1164
1230
  for (var i = 0, e = this.set.length; i < e; ++i) {
1165
1231
  var mk = this.set[i].marked;
1166
- for (var j = 0; j < mk.length; ++j) {
1232
+ if (!mk) continue;
1233
+ for (var j = 0; j < mk.length; ++j)
1167
1234
  if (mk[j].set == this.set) mk.splice(j--, 1);
1168
- }
1169
1235
  }
1170
1236
  // We don't know the exact lines that changed. Refreshing is
1171
1237
  // cheaper than finding them.
1172
- changes.push({from: 0, to: lines.length});
1238
+ changes.push({from: 0, to: doc.size});
1173
1239
  });
1174
1240
  TextMarker.prototype.find = function() {
1175
1241
  var from, to;
@@ -1179,8 +1245,8 @@ var CodeMirror = (function() {
1179
1245
  var mark = mk[j];
1180
1246
  if (mark.set == this.set) {
1181
1247
  if (mark.from != null || mark.to != null) {
1182
- var found = indexOf(lines, line);
1183
- if (found > -1) {
1248
+ var found = lineNo(line);
1249
+ if (found != null) {
1184
1250
  if (mark.from != null) from = {line: found, ch: mark.from};
1185
1251
  if (mark.to != null) to = {line: found, ch: mark.to};
1186
1252
  }
@@ -1191,45 +1257,85 @@ var CodeMirror = (function() {
1191
1257
  return {from: from, to: to};
1192
1258
  };
1193
1259
 
1260
+ function markText(from, to, className) {
1261
+ from = clipPos(from); to = clipPos(to);
1262
+ var tm = new TextMarker();
1263
+ function add(line, from, to, className) {
1264
+ mark = getLine(line).addMark(new MarkedText(from, to, className, tm.set));
1265
+ }
1266
+ if (from.line == to.line) add(from.line, from.ch, to.ch, className);
1267
+ else {
1268
+ add(from.line, from.ch, null, className);
1269
+ for (var i = from.line + 1, e = to.line; i < e; ++i)
1270
+ add(i, null, null, className);
1271
+ add(to.line, null, to.ch, className);
1272
+ }
1273
+ changes.push({from: from.line, to: to.line + 1});
1274
+ return tm;
1275
+ }
1276
+
1277
+ function setBookmark(pos) {
1278
+ pos = clipPos(pos);
1279
+ var bm = new Bookmark(pos.ch);
1280
+ getLine(pos.line).addMark(bm);
1281
+ return bm;
1282
+ }
1283
+
1194
1284
  function addGutterMarker(line, text, className) {
1195
- if (typeof line == "number") line = lines[clipLine(line)];
1285
+ if (typeof line == "number") line = getLine(clipLine(line));
1196
1286
  line.gutterMarker = {text: text, style: className};
1197
1287
  gutterDirty = true;
1198
1288
  return line;
1199
1289
  }
1200
1290
  function removeGutterMarker(line) {
1201
- if (typeof line == "number") line = lines[clipLine(line)];
1291
+ if (typeof line == "number") line = getLine(clipLine(line));
1202
1292
  line.gutterMarker = null;
1203
1293
  gutterDirty = true;
1204
1294
  }
1205
- function setLineClass(line, className) {
1206
- if (typeof line == "number") {
1207
- var no = line;
1208
- line = lines[clipLine(line)];
1209
- }
1210
- else {
1211
- var no = indexOf(lines, line);
1212
- if (no == -1) return null;
1213
- }
1214
- if (line.className != className) {
1215
- line.className = className;
1216
- changes.push({from: no, to: no + 1});
1217
- }
1295
+
1296
+ function changeLine(handle, op) {
1297
+ var no = handle, line = handle;
1298
+ if (typeof handle == "number") line = getLine(clipLine(handle));
1299
+ else no = lineNo(handle);
1300
+ if (no == null) return null;
1301
+ if (op(line, no)) changes.push({from: no, to: no + 1});
1218
1302
  return line;
1219
1303
  }
1304
+ function setLineClass(handle, className) {
1305
+ return changeLine(handle, function(line) {
1306
+ if (line.className != className) {
1307
+ line.className = className;
1308
+ return true;
1309
+ }
1310
+ });
1311
+ }
1312
+ function setLineHidden(handle, hidden) {
1313
+ return changeLine(handle, function(line, no) {
1314
+ if (line.hidden != hidden) {
1315
+ line.hidden = hidden;
1316
+ updateLineHeight(line, hidden ? 0 : 1);
1317
+ if (hidden && (sel.from.line == no || sel.to.line == no))
1318
+ setSelection(skipHidden(sel.from, sel.from.line, sel.from.ch),
1319
+ skipHidden(sel.to, sel.to.line, sel.to.ch));
1320
+ return (gutterDirty = true);
1321
+ }
1322
+ });
1323
+ }
1220
1324
 
1221
1325
  function lineInfo(line) {
1222
1326
  if (typeof line == "number") {
1327
+ if (!isLine(line)) return null;
1223
1328
  var n = line;
1224
- line = lines[line];
1329
+ line = getLine(line);
1225
1330
  if (!line) return null;
1226
1331
  }
1227
1332
  else {
1228
- var n = indexOf(lines, line);
1229
- if (n == -1) return null;
1333
+ var n = lineNo(line);
1334
+ if (n == null) return null;
1230
1335
  }
1231
1336
  var marker = line.gutterMarker;
1232
- return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style};
1337
+ return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
1338
+ markerClass: marker && marker.style, lineClass: line.className};
1233
1339
  }
1234
1340
 
1235
1341
  function stringWidth(str) {
@@ -1239,21 +1345,16 @@ var CodeMirror = (function() {
1239
1345
  }
1240
1346
  // These are used to go from pixel positions to character
1241
1347
  // positions, taking varying character widths into account.
1242
- function charX(line, pos) {
1243
- if (pos == 0) return 0;
1244
- measure.innerHTML = "<pre><span>" + lines[line].getHTML(null, null, false, pos) + "</span></pre>";
1245
- return measure.firstChild.firstChild.offsetWidth;
1246
- }
1247
1348
  function charFromX(line, x) {
1248
1349
  if (x <= 0) return 0;
1249
- var lineObj = lines[line], text = lineObj.text;
1350
+ var lineObj = getLine(line), text = lineObj.text;
1250
1351
  function getX(len) {
1251
1352
  measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>";
1252
1353
  return measure.firstChild.firstChild.offsetWidth;
1253
1354
  }
1254
1355
  var from = 0, fromX = 0, to = text.length, toX;
1255
1356
  // Guess a suitable upper bound for our search.
1256
- var estimated = Math.min(to, Math.ceil(x / stringWidth("x")));
1357
+ var estimated = Math.min(to, Math.ceil(x / charWidth()));
1257
1358
  for (;;) {
1258
1359
  var estX = getX(estimated);
1259
1360
  if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
@@ -1272,20 +1373,93 @@ var CodeMirror = (function() {
1272
1373
  }
1273
1374
  }
1274
1375
 
1376
+ var tempId = Math.floor(Math.random() * 0xffffff).toString(16);
1377
+ function measureLine(line, ch) {
1378
+ var extra = "";
1379
+ // Include extra text at the end to make sure the measured line is wrapped in the right way.
1380
+ if (options.lineWrapping) {
1381
+ var end = line.text.indexOf(" ", ch + 2);
1382
+ extra = line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0));
1383
+ }
1384
+ measure.innerHTML = "<pre>" + line.getHTML(null, null, false, ch) +
1385
+ '<span id="CodeMirror-temp-' + tempId + '">' + (line.text.charAt(ch) || " ") + "</span>" +
1386
+ extra + "</pre>";
1387
+ var elt = document.getElementById("CodeMirror-temp-" + tempId);
1388
+ var top = elt.offsetTop, left = elt.offsetLeft;
1389
+ // Older IEs report zero offsets for spans directly after a wrap
1390
+ if (ie && ch && top == 0 && left == 0) {
1391
+ var backup = document.createElement("span");
1392
+ backup.innerHTML = "x";
1393
+ elt.parentNode.insertBefore(backup, elt.nextSibling);
1394
+ top = backup.offsetTop;
1395
+ }
1396
+ return {top: top, left: left};
1397
+ }
1275
1398
  function localCoords(pos, inLineWrap) {
1276
- var lh = lineHeight(), line = pos.line - (inLineWrap ? showingFrom : 0);
1277
- return {x: charX(pos.line, pos.ch), y: line * lh, yBot: (line + 1) * lh};
1399
+ var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
1400
+ if (pos.ch == 0) x = 0;
1401
+ else {
1402
+ var sp = measureLine(getLine(pos.line), pos.ch);
1403
+ x = sp.left;
1404
+ if (options.lineWrapping) y += Math.max(0, sp.top);
1405
+ }
1406
+ return {x: x, y: y, yBot: y + lh};
1407
+ }
1408
+ // Coords must be lineSpace-local
1409
+ function coordsChar(x, y) {
1410
+ if (y < 0) y = 0;
1411
+ var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
1412
+ var lineNo = lineAtHeight(doc, heightPos);
1413
+ if (lineNo >= doc.size) return {line: doc.size - 1, ch: 0};
1414
+ var lineObj = getLine(lineNo), text = lineObj.text;
1415
+ var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
1416
+ if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
1417
+ function getX(len) {
1418
+ var sp = measureLine(lineObj, len);
1419
+ if (tw) {
1420
+ var off = Math.round(sp.top / th);
1421
+ return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
1422
+ }
1423
+ return sp.left;
1424
+ }
1425
+ var from = 0, fromX = 0, to = text.length, toX;
1426
+ // Guess a suitable upper bound for our search.
1427
+ var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));
1428
+ for (;;) {
1429
+ var estX = getX(estimated);
1430
+ if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1431
+ else {toX = estX; to = estimated; break;}
1432
+ }
1433
+ if (x > toX) return {line: lineNo, ch: to};
1434
+ // Try to guess a suitable lower bound as well.
1435
+ estimated = Math.floor(to * 0.8); estX = getX(estimated);
1436
+ if (estX < x) {from = estimated; fromX = estX;}
1437
+ // Do a binary search between these bounds.
1438
+ for (;;) {
1439
+ if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to};
1440
+ var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1441
+ if (middleX > x) {to = middle; toX = middleX;}
1442
+ else {from = middle; fromX = middleX;}
1443
+ }
1278
1444
  }
1279
1445
  function pageCoords(pos) {
1280
1446
  var local = localCoords(pos, true), off = eltOffset(lineSpace);
1281
1447
  return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
1282
1448
  }
1283
1449
 
1284
- function lineHeight() {
1285
- var nlines = lineDiv.childNodes.length;
1286
- if (nlines) return (lineDiv.offsetHeight / nlines) || 1;
1287
- measure.innerHTML = "<pre>x</pre>";
1288
- return measure.firstChild.offsetHeight || 1;
1450
+ var cachedHeight, cachedFor;
1451
+ function textHeight() {
1452
+ var offsetHeight = lineDiv.offsetHeight;
1453
+ if (offsetHeight == cachedFor) return cachedHeight;
1454
+ cachedFor = offsetHeight;
1455
+ measure.innerHTML = "<pre>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x</pre>";
1456
+ return (cachedHeight = measure.firstChild.offsetHeight / 10 || 1);
1457
+ }
1458
+ var cachedWidth, cachedFor = 0;
1459
+ function charWidth() {
1460
+ if (scroller.clientWidth == cachedFor) return cachedWidth;
1461
+ cachedFor = scroller.clientWidth;
1462
+ return (cachedWidth = stringWidth("x"));
1289
1463
  }
1290
1464
  function paddingTop() {return lineSpace.offsetTop;}
1291
1465
  function paddingLeft() {return lineSpace.offsetLeft;}
@@ -1300,8 +1474,7 @@ var CodeMirror = (function() {
1300
1474
  if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
1301
1475
  return null;
1302
1476
  var offL = eltOffset(lineSpace, true);
1303
- var line = showingFrom + Math.floor((y - offL.top) / lineHeight());
1304
- return clipPos({line: line, ch: charFromX(clipLine(line), x - offL.left)});
1477
+ return coordsChar(x - offL.left, y - offL.top);
1305
1478
  }
1306
1479
  function onContextMenu(e) {
1307
1480
  var pos = posFromMouse(e);
@@ -1352,7 +1525,7 @@ var CodeMirror = (function() {
1352
1525
 
1353
1526
  var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
1354
1527
  function matchBrackets(autoclear) {
1355
- var head = sel.inverted ? sel.from : sel.to, line = lines[head.line], pos = head.ch - 1;
1528
+ var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
1356
1529
  var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
1357
1530
  if (!match) return;
1358
1531
  var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
@@ -1376,8 +1549,8 @@ var CodeMirror = (function() {
1376
1549
  }
1377
1550
  }
1378
1551
  }
1379
- for (var i = head.line, e = forward ? Math.min(i + 100, lines.length) : Math.max(-1, i - 100); i != e; i+=d) {
1380
- var line = lines[i], first = i == head.line;
1552
+ for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
1553
+ var line = getLine(i), first = i == head.line;
1381
1554
  var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
1382
1555
  if (found) break;
1383
1556
  }
@@ -1399,7 +1572,7 @@ var CodeMirror = (function() {
1399
1572
  var minindent, minline;
1400
1573
  for (var search = n, lim = n - 40; search > lim; --search) {
1401
1574
  if (search == 0) return 0;
1402
- var line = lines[search-1];
1575
+ var line = getLine(search-1);
1403
1576
  if (line.stateAfter) return search;
1404
1577
  var indented = line.indentation();
1405
1578
  if (minline == null || minindent > indented) {
@@ -1410,56 +1583,57 @@ var CodeMirror = (function() {
1410
1583
  return minline;
1411
1584
  }
1412
1585
  function getStateBefore(n) {
1413
- var start = findStartLine(n), state = start && lines[start-1].stateAfter;
1586
+ var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
1414
1587
  if (!state) state = startState(mode);
1415
1588
  else state = copyState(mode, state);
1416
- for (var i = start; i < n; ++i) {
1417
- var line = lines[i];
1589
+ doc.iter(start, n, function(line) {
1418
1590
  line.highlight(mode, state);
1419
1591
  line.stateAfter = copyState(mode, state);
1420
- }
1421
- changes.push({from: start, to: n});
1422
- if (n < lines.length && !lines[n].stateAfter) work.push(n);
1592
+ });
1593
+ if (start < n) changes.push({from: start, to: n});
1594
+ if (n < doc.size && !getLine(n).stateAfter) work.push(n);
1423
1595
  return state;
1424
1596
  }
1425
1597
  function highlightLines(start, end) {
1426
1598
  var state = getStateBefore(start);
1427
- for (var i = start; i < end; ++i) {
1428
- var line = lines[i];
1599
+ doc.iter(start, end, function(line) {
1429
1600
  line.highlight(mode, state);
1430
1601
  line.stateAfter = copyState(mode, state);
1431
- }
1602
+ });
1432
1603
  }
1433
1604
  function highlightWorker() {
1434
1605
  var end = +new Date + options.workTime;
1435
1606
  var foundWork = work.length;
1436
1607
  while (work.length) {
1437
- if (!lines[showingFrom].stateAfter) var task = showingFrom;
1608
+ if (!getLine(showingFrom).stateAfter) var task = showingFrom;
1438
1609
  else var task = work.pop();
1439
- if (task >= lines.length) continue;
1440
- var start = findStartLine(task), state = start && lines[start-1].stateAfter;
1610
+ if (task >= doc.size) continue;
1611
+ var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
1441
1612
  if (state) state = copyState(mode, state);
1442
1613
  else state = startState(mode);
1443
1614
 
1444
- var unchanged = 0, compare = mode.compareStates, realChange = false;
1445
- for (var i = start, l = lines.length; i < l; ++i) {
1446
- var line = lines[i], hadState = line.stateAfter;
1615
+ var unchanged = 0, compare = mode.compareStates, realChange = false,
1616
+ i = start, bail = false;
1617
+ doc.iter(i, doc.size, function(line) {
1618
+ var hadState = line.stateAfter;
1447
1619
  if (+new Date > end) {
1448
1620
  work.push(i);
1449
1621
  startWorker(options.workDelay);
1450
1622
  if (realChange) changes.push({from: task, to: i + 1});
1451
- return;
1623
+ return (bail = true);
1452
1624
  }
1453
1625
  var changed = line.highlight(mode, state);
1454
1626
  if (changed) realChange = true;
1455
1627
  line.stateAfter = copyState(mode, state);
1456
1628
  if (compare) {
1457
- if (hadState && compare(hadState, state)) break;
1629
+ if (hadState && compare(hadState, state)) return true;
1458
1630
  } else {
1459
1631
  if (changed !== false || !hadState) unchanged = 0;
1460
- else if (++unchanged > 3) break;
1632
+ else if (++unchanged > 3) return true;
1461
1633
  }
1462
- }
1634
+ ++i;
1635
+ });
1636
+ if (bail) return;
1463
1637
  if (realChange) changes.push({from: task, to: i + 1});
1464
1638
  }
1465
1639
  if (foundWork && options.onHighlightComplete)
@@ -1530,7 +1704,7 @@ var CodeMirror = (function() {
1530
1704
  if (typeof query != "string") // Regexp match
1531
1705
  this.matches = function(reverse, pos) {
1532
1706
  if (reverse) {
1533
- var line = lines[pos.line].text.slice(0, pos.ch), match = line.match(query), start = 0;
1707
+ var line = getLine(pos.line).text.slice(0, pos.ch), match = line.match(query), start = 0;
1534
1708
  while (match) {
1535
1709
  var ind = line.indexOf(match[0]);
1536
1710
  start += ind;
@@ -1542,7 +1716,7 @@ var CodeMirror = (function() {
1542
1716
  }
1543
1717
  }
1544
1718
  else {
1545
- var line = lines[pos.line].text.slice(pos.ch), match = line.match(query),
1719
+ var line = getLine(pos.line).text.slice(pos.ch), match = line.match(query),
1546
1720
  start = match && pos.ch + line.indexOf(match[0]);
1547
1721
  }
1548
1722
  if (match)
@@ -1557,7 +1731,7 @@ var CodeMirror = (function() {
1557
1731
  // Different methods for single-line and multi-line queries
1558
1732
  if (target.length == 1)
1559
1733
  this.matches = function(reverse, pos) {
1560
- var line = fold(lines[pos.line].text), len = query.length, match;
1734
+ var line = fold(getLine(pos.line).text), len = query.length, match;
1561
1735
  if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
1562
1736
  : (match = line.indexOf(query, pos.ch)) != -1)
1563
1737
  return {from: {line: pos.line, ch: match},
@@ -1565,14 +1739,14 @@ var CodeMirror = (function() {
1565
1739
  };
1566
1740
  else
1567
1741
  this.matches = function(reverse, pos) {
1568
- var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(lines[ln].text);
1742
+ var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(getLine(ln).text);
1569
1743
  var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
1570
1744
  if (reverse ? offsetA >= pos.ch || offsetA != match.length
1571
1745
  : offsetA <= pos.ch || offsetA != line.length - match.length)
1572
1746
  return;
1573
1747
  for (;;) {
1574
- if (reverse ? !ln : ln == lines.length - 1) return;
1575
- line = fold(lines[ln += reverse ? -1 : 1].text);
1748
+ if (reverse ? !ln : ln == doc.size - 1) return;
1749
+ line = fold(getLine(ln += reverse ? -1 : 1).text);
1576
1750
  match = target[reverse ? --idx : ++idx];
1577
1751
  if (idx > 0 && idx < target.length - 1) {
1578
1752
  if (line != match) return;
@@ -1608,10 +1782,10 @@ var CodeMirror = (function() {
1608
1782
  }
1609
1783
  if (reverse) {
1610
1784
  if (!pos.line) return savePosAndFail(0);
1611
- pos = {line: pos.line-1, ch: lines[pos.line-1].text.length};
1785
+ pos = {line: pos.line-1, ch: getLine(pos.line-1).text.length};
1612
1786
  }
1613
1787
  else {
1614
- if (pos.line == lines.length - 1) return savePosAndFail(lines.length);
1788
+ if (pos.line == doc.size - 1) return savePosAndFail(doc.size);
1615
1789
  pos = {line: pos.line+1, ch: 0};
1616
1790
  }
1617
1791
  }
@@ -1647,6 +1821,7 @@ var CodeMirror = (function() {
1647
1821
  enterMode: "indent",
1648
1822
  electricChars: true,
1649
1823
  onKeyEvent: null,
1824
+ lineWrapping: false,
1650
1825
  lineNumbers: false,
1651
1826
  gutter: false,
1652
1827
  fixedGutter: false,
@@ -1663,6 +1838,7 @@ var CodeMirror = (function() {
1663
1838
  workDelay: 200,
1664
1839
  undoDepth: 40,
1665
1840
  tabindex: null,
1841
+ pollForIME: false,
1666
1842
  document: window.document
1667
1843
  };
1668
1844
 
@@ -1760,11 +1936,11 @@ var CodeMirror = (function() {
1760
1936
  }
1761
1937
  return nstate;
1762
1938
  }
1763
- CodeMirror.startState = startState;
1939
+ CodeMirror.copyState = copyState;
1764
1940
  function startState(mode, a1, a2) {
1765
1941
  return mode.startState ? mode.startState(a1, a2) : true;
1766
1942
  }
1767
- CodeMirror.copyState = copyState;
1943
+ CodeMirror.startState = startState;
1768
1944
 
1769
1945
  // The character stream used by a mode's parser.
1770
1946
  function StringStream(string) {
@@ -1821,22 +1997,86 @@ var CodeMirror = (function() {
1821
1997
  };
1822
1998
  CodeMirror.StringStream = StringStream;
1823
1999
 
2000
+ function MarkedText(from, to, className, set) {
2001
+ this.from = from; this.to = to; this.style = className; this.set = set;
2002
+ }
2003
+ MarkedText.prototype = {
2004
+ attach: function(line) { this.set.push(line); },
2005
+ detach: function(line) {
2006
+ var ix = indexOf(this.set, line);
2007
+ if (ix > -1) this.set.splice(ix, 1);
2008
+ },
2009
+ split: function(pos, lenBefore) {
2010
+ if (this.to <= pos && this.to != null) return null;
2011
+ var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
2012
+ var to = this.to == null ? null : this.to - pos + lenBefore;
2013
+ return new MarkedText(from, to, this.style, this.set);
2014
+ },
2015
+ dup: function() { return new MarkedText(null, null, this.style, this.set); },
2016
+ clipTo: function(fromOpen, from, toOpen, to, diff) {
2017
+ if (this.from != null && this.from >= from)
2018
+ this.from = Math.max(to, this.from) + diff;
2019
+ if (this.to != null && this.to > from)
2020
+ this.to = to < this.to ? this.to + diff : from;
2021
+ if (fromOpen && to > this.from && (to < this.to || this.to == null))
2022
+ this.from = null;
2023
+ if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
2024
+ this.to = null;
2025
+ },
2026
+ isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
2027
+ sameSet: function(x) { return this.set == x.set; }
2028
+ };
2029
+
2030
+ function Bookmark(pos) {
2031
+ this.from = pos; this.to = pos; this.line = null;
2032
+ }
2033
+ Bookmark.prototype = {
2034
+ attach: function(line) { this.line = line; },
2035
+ detach: function(line) { if (this.line == line) this.line = null; },
2036
+ split: function(pos, lenBefore) {
2037
+ if (pos < this.from) {
2038
+ this.from = this.to = (this.from - pos) + lenBefore;
2039
+ return this;
2040
+ }
2041
+ },
2042
+ isDead: function() { return this.from > this.to; },
2043
+ clipTo: function(fromOpen, from, toOpen, to, diff) {
2044
+ if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
2045
+ this.from = 0; this.to = -1;
2046
+ } else if (this.from > from) {
2047
+ this.from = this.to = Math.max(to, this.from) + diff;
2048
+ }
2049
+ },
2050
+ sameSet: function(x) { return false; },
2051
+ find: function() {
2052
+ if (!this.line || !this.line.parent) return null;
2053
+ return {line: lineNo(this.line), ch: this.from};
2054
+ },
2055
+ clear: function() {
2056
+ if (this.line) {
2057
+ var found = indexOf(this.line.marked, this);
2058
+ if (found != -1) this.line.marked.splice(found, 1);
2059
+ this.line = null;
2060
+ }
2061
+ }
2062
+ };
2063
+
1824
2064
  // Line objects. These hold state related to a line, including
1825
2065
  // highlighting info (the styles array).
1826
2066
  function Line(text, styles) {
1827
2067
  this.styles = styles || [text, null];
1828
- this.stateAfter = null;
1829
2068
  this.text = text;
2069
+ this.height = 1;
1830
2070
  this.marked = this.gutterMarker = this.className = null;
2071
+ this.stateAfter = this.parent = this.hidden = null;
1831
2072
  }
1832
2073
  Line.inheritMarks = function(text, orig) {
1833
2074
  var ln = new Line(text), mk = orig.marked;
1834
2075
  if (mk) {
1835
2076
  for (var i = 0; i < mk.length; ++i) {
1836
- if (mk[i].to == null) {
2077
+ if (mk[i].to == null && mk[i].style) {
1837
2078
  var newmk = ln.marked || (ln.marked = []), mark = mk[i];
1838
- newmk.push({from: null, to: null, style: mark.style, set: mark.set});
1839
- mark.set.push(ln);
2079
+ var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
1840
2080
  }
1841
2081
  }
1842
2082
  }
@@ -1845,6 +2085,9 @@ var CodeMirror = (function() {
1845
2085
  Line.prototype = {
1846
2086
  // Replace a piece of a line, keeping the styles around it intact.
1847
2087
  replace: function(from, to_, text) {
2088
+ // Reset line class if the whole text was replaced.
2089
+ if (!from && (to_ == null || to_ == this.text.length))
2090
+ this.className = this.gutterMarker = null;
1848
2091
  var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
1849
2092
  copyStyles(0, from, this.styles, st);
1850
2093
  if (text) st.push(text, null);
@@ -1853,23 +2096,10 @@ var CodeMirror = (function() {
1853
2096
  this.text = this.text.slice(0, from) + text + this.text.slice(to);
1854
2097
  this.stateAfter = null;
1855
2098
  if (mk) {
1856
- var diff = text.length - (to - from), end = this.text.length;
1857
- var changeStart = Math.min(from, from + diff);
1858
- for (var i = 0; i < mk.length; ++i) {
1859
- var mark = mk[i], del = false;
1860
- if (mark.from != null && mark.from >= end) del = true;
1861
- else {
1862
- if (mark.from != null && mark.from >= from) {
1863
- mark.from += diff;
1864
- if (mark.from <= 0) mark.from = from == null ? null : 0;
1865
- }
1866
- else if (to_ == null) mark.to = null;
1867
- if (mark.to != null && mark.to > from) {
1868
- mark.to += diff;
1869
- if (mark.to < 0) del = true;
1870
- }
1871
- }
1872
- if (del || (mark.from != null && mark.to != null && mark.from >= mark.to)) mk.splice(i--, 1);
2099
+ var diff = text.length - (to - from);
2100
+ for (var i = 0, mark = mk[i]; i < mk.length; ++i) {
2101
+ mark.clipTo(from == null, from || 0, to_ == null, to, diff);
2102
+ if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
1873
2103
  }
1874
2104
  }
1875
2105
  },
@@ -1881,50 +2111,63 @@ var CodeMirror = (function() {
1881
2111
  if (mk) {
1882
2112
  for (var i = 0; i < mk.length; ++i) {
1883
2113
  var mark = mk[i];
1884
- if (mark.to > pos || mark.to == null) {
2114
+ var newmark = mark.split(pos, textBefore.length);
2115
+ if (newmark) {
1885
2116
  if (!taken.marked) taken.marked = [];
1886
- taken.marked.push({
1887
- from: mark.from < pos || mark.from == null ? null : mark.from - pos + textBefore.length,
1888
- to: mark.to == null ? null : mark.to - pos + textBefore.length,
1889
- style: mark.style, set: mark.set
1890
- });
1891
- mark.set.push(taken);
2117
+ taken.marked.push(newmark); newmark.attach(taken);
1892
2118
  }
1893
2119
  }
1894
2120
  }
1895
2121
  return taken;
1896
2122
  },
1897
2123
  append: function(line) {
1898
- if (!line.text.length) return;
1899
- var mylen = this.text.length, mk = line.marked;
2124
+ var mylen = this.text.length, mk = line.marked, mymk = this.marked;
1900
2125
  this.text += line.text;
1901
2126
  copyStyles(0, line.text.length, line.styles, this.styles);
1902
- if (mk && mk.length) {
1903
- var mymk = this.marked || (this.marked = []);
2127
+ if (mymk) {
1904
2128
  for (var i = 0; i < mymk.length; ++i)
1905
2129
  if (mymk[i].to == null) mymk[i].to = mylen;
2130
+ }
2131
+ if (mk && mk.length) {
2132
+ if (!mymk) this.marked = mymk = [];
1906
2133
  outer: for (var i = 0; i < mk.length; ++i) {
1907
2134
  var mark = mk[i];
1908
2135
  if (!mark.from) {
1909
2136
  for (var j = 0; j < mymk.length; ++j) {
1910
2137
  var mymark = mymk[j];
1911
- if (mymark.to == mylen && mymark.set == mark.set) {
2138
+ if (mymark.to == mylen && mymark.sameSet(mark)) {
1912
2139
  mymark.to = mark.to == null ? null : mark.to + mylen;
2140
+ if (mymark.isDead()) {
2141
+ mymark.detach(this);
2142
+ mk.splice(i--, 1);
2143
+ }
1913
2144
  continue outer;
1914
2145
  }
1915
2146
  }
1916
2147
  }
1917
2148
  mymk.push(mark);
1918
- mark.set.push(this);
2149
+ mark.attach(this);
1919
2150
  mark.from += mylen;
1920
2151
  if (mark.to != null) mark.to += mylen;
1921
2152
  }
1922
2153
  }
1923
2154
  },
1924
- addMark: function(from, to, style, set) {
1925
- set.push(this);
2155
+ fixMarkEnds: function(other) {
2156
+ var mk = this.marked, omk = other.marked;
2157
+ if (!mk) return;
2158
+ for (var i = 0; i < mk.length; ++i) {
2159
+ var mark = mk[i], close = mark.to == null;
2160
+ if (close && omk) {
2161
+ for (var j = 0; j < omk.length; ++j)
2162
+ if (omk[j].sameSet(mark)) {close = false; break;}
2163
+ }
2164
+ if (close) mark.to = this.text.length;
2165
+ }
2166
+ },
2167
+ addMark: function(mark) {
2168
+ mark.attach(this);
1926
2169
  if (this.marked == null) this.marked = [];
1927
- this.marked.push({from: from, to: to, style: style, set: set});
2170
+ this.marked.push(mark);
1928
2171
  this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
1929
2172
  },
1930
2173
  // Run the given mode's parser over a line, update the styles
@@ -1976,11 +2219,14 @@ var CodeMirror = (function() {
1976
2219
  // Produces an HTML fragment for the line, taking selection,
1977
2220
  // marking, and highlighting into account.
1978
2221
  getHTML: function(sfrom, sto, includePre, endAt) {
1979
- var html = [];
2222
+ var html = [], first = true;
1980
2223
  if (includePre)
1981
2224
  html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
1982
2225
  function span(text, style) {
1983
2226
  if (!text) return;
2227
+ // Work around a bug where, in some compat modes, IE ignores leading spaces
2228
+ if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
2229
+ first = false;
1984
2230
  if (style) html.push('<span class="', style, '">', htmlEscape(text), "</span>");
1985
2231
  else html.push(htmlEscape(text));
1986
2232
  }
@@ -2040,6 +2286,11 @@ var CodeMirror = (function() {
2040
2286
  }
2041
2287
  if (includePre) html.push("</pre>");
2042
2288
  return html.join("");
2289
+ },
2290
+ cleanUp: function() {
2291
+ this.parent = null;
2292
+ if (this.marked)
2293
+ for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
2043
2294
  }
2044
2295
  };
2045
2296
  // Utility used by replace and split above
@@ -2058,6 +2309,191 @@ var CodeMirror = (function() {
2058
2309
  }
2059
2310
  }
2060
2311
 
2312
+ // Data structure that holds the sequence of lines.
2313
+ function LeafChunk(lines) {
2314
+ this.lines = lines;
2315
+ this.parent = null;
2316
+ for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
2317
+ lines[i].parent = this;
2318
+ height += lines[i].height;
2319
+ }
2320
+ this.height = height;
2321
+ }
2322
+ LeafChunk.prototype = {
2323
+ chunkSize: function() { return this.lines.length; },
2324
+ remove: function(at, n) {
2325
+ for (var i = at, e = at + n; i < e; ++i) {
2326
+ var line = this.lines[i];
2327
+ line.cleanUp();
2328
+ this.height -= line.height;
2329
+ }
2330
+ this.lines.splice(at, n);
2331
+ },
2332
+ collapse: function(lines) {
2333
+ lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
2334
+ },
2335
+ insertHeight: function(at, lines, height) {
2336
+ this.height += height;
2337
+ this.lines.splice.apply(this.lines, [at, 0].concat(lines));
2338
+ for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
2339
+ },
2340
+ iterN: function(at, n, op) {
2341
+ for (var e = at + n; at < e; ++at)
2342
+ if (op(this.lines[at])) return true;
2343
+ }
2344
+ };
2345
+ function BranchChunk(children) {
2346
+ this.children = children;
2347
+ var size = 0, height = 0;
2348
+ for (var i = 0, e = children.length; i < e; ++i) {
2349
+ var ch = children[i];
2350
+ size += ch.chunkSize(); height += ch.height;
2351
+ ch.parent = this;
2352
+ }
2353
+ this.size = size;
2354
+ this.height = height;
2355
+ this.parent = null;
2356
+ }
2357
+ BranchChunk.prototype = {
2358
+ chunkSize: function() { return this.size; },
2359
+ remove: function(at, n) {
2360
+ this.size -= n;
2361
+ for (var i = 0; i < this.children.length; ++i) {
2362
+ var child = this.children[i], sz = child.chunkSize();
2363
+ if (at < sz) {
2364
+ var rm = Math.min(n, sz - at), oldHeight = child.height;
2365
+ child.remove(at, rm);
2366
+ this.height -= oldHeight - child.height;
2367
+ if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
2368
+ if ((n -= rm) == 0) break;
2369
+ at = 0;
2370
+ } else at -= sz;
2371
+ }
2372
+ if (this.size - n < 25) {
2373
+ var lines = [];
2374
+ this.collapse(lines);
2375
+ this.children = [new LeafChunk(lines)];
2376
+ }
2377
+ },
2378
+ collapse: function(lines) {
2379
+ for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
2380
+ },
2381
+ insert: function(at, lines) {
2382
+ var height = 0;
2383
+ for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
2384
+ this.insertHeight(at, lines, height);
2385
+ },
2386
+ insertHeight: function(at, lines, height) {
2387
+ this.size += lines.length;
2388
+ this.height += height;
2389
+ for (var i = 0, e = this.children.length; i < e; ++i) {
2390
+ var child = this.children[i], sz = child.chunkSize();
2391
+ if (at <= sz) {
2392
+ child.insertHeight(at, lines, height);
2393
+ if (child.lines && child.lines.length > 50) {
2394
+ while (child.lines.length > 50) {
2395
+ var spilled = child.lines.splice(child.lines.length - 25, 25);
2396
+ var newleaf = new LeafChunk(spilled);
2397
+ child.height -= newleaf.height;
2398
+ this.children.splice(i + 1, 0, newleaf);
2399
+ newleaf.parent = this;
2400
+ }
2401
+ this.maybeSpill();
2402
+ }
2403
+ break;
2404
+ }
2405
+ at -= sz;
2406
+ }
2407
+ },
2408
+ maybeSpill: function() {
2409
+ if (this.children.length <= 10) return;
2410
+ var me = this;
2411
+ do {
2412
+ var spilled = me.children.splice(me.children.length - 5, 5);
2413
+ var sibling = new BranchChunk(spilled);
2414
+ if (!me.parent) { // Become the parent node
2415
+ var copy = new BranchChunk(me.children);
2416
+ copy.parent = me;
2417
+ me.children = [copy, sibling];
2418
+ me = copy;
2419
+ } else {
2420
+ me.size -= sibling.size;
2421
+ me.height -= sibling.height;
2422
+ var myIndex = indexOf(me.parent.children, me);
2423
+ me.parent.children.splice(myIndex + 1, 0, sibling);
2424
+ }
2425
+ sibling.parent = me.parent;
2426
+ } while (me.children.length > 10);
2427
+ me.parent.maybeSpill();
2428
+ },
2429
+ iter: function(from, to, op) { this.iterN(from, to - from, op); },
2430
+ iterN: function(at, n, op) {
2431
+ for (var i = 0, e = this.children.length; i < e; ++i) {
2432
+ var child = this.children[i], sz = child.chunkSize();
2433
+ if (at < sz) {
2434
+ var used = Math.min(n, sz - at);
2435
+ if (child.iterN(at, used, op)) return true;
2436
+ if ((n -= used) == 0) break;
2437
+ at = 0;
2438
+ } else at -= sz;
2439
+ }
2440
+ }
2441
+ };
2442
+
2443
+ function getLineAt(chunk, n) {
2444
+ for (;;) {
2445
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
2446
+ var child = chunk.children[i], sz = child.chunkSize();
2447
+ if (n < sz) { chunk = child; break; }
2448
+ n -= sz;
2449
+ }
2450
+ if (chunk.lines) return chunk.lines[n];
2451
+ }
2452
+ }
2453
+ function lineNo(line) {
2454
+ if (line.parent == null) return null;
2455
+ var cur = line.parent, no = indexOf(cur.lines, line);
2456
+ for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
2457
+ for (var i = 0, e = chunk.children.length; ; ++i) {
2458
+ if (chunk.children[i] == cur) break;
2459
+ no += chunk.children[i].chunkSize();
2460
+ }
2461
+ }
2462
+ return no;
2463
+ }
2464
+ function lineAtHeight(chunk, h) {
2465
+ var n = 0;
2466
+ outer: do {
2467
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
2468
+ var child = chunk.children[i], ch = child.height;
2469
+ if (h < ch) { chunk = child; continue outer; }
2470
+ h -= ch;
2471
+ n += child.chunkSize();
2472
+ }
2473
+ return n;
2474
+ } while (!chunk.lines);
2475
+ for (var i = 0, e = chunk.lines.length; i < e; ++i) {
2476
+ var line = chunk.lines[i], lh = line.height;
2477
+ if (h < lh) break;
2478
+ h -= lh;
2479
+ }
2480
+ return n + i;
2481
+ }
2482
+ function heightAtLine(chunk, n) {
2483
+ var h = 0;
2484
+ outer: do {
2485
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
2486
+ var child = chunk.children[i], sz = child.chunkSize();
2487
+ if (n < sz) { chunk = child; continue outer; }
2488
+ n -= sz;
2489
+ h += child.height;
2490
+ }
2491
+ return h;
2492
+ } while (!chunk.lines);
2493
+ for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
2494
+ return h;
2495
+ }
2496
+
2061
2497
  // The history object 'chunks' changes that are made close together
2062
2498
  // and at almost the same time into bigger undoable units.
2063
2499
  function History() {
@@ -2132,25 +2568,18 @@ var CodeMirror = (function() {
2132
2568
  function Delayed() {this.id = null;}
2133
2569
  Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
2134
2570
 
2135
- // Some IE versions don't preserve whitespace when setting the
2136
- // innerHTML of a PRE tag.
2137
- var badInnerHTML = (function() {
2138
- var pre = document.createElement("pre");
2139
- pre.innerHTML = " "; return !pre.innerHTML;
2140
- })();
2141
-
2142
2571
  // Detect drag-and-drop
2143
- var dragAndDrop = (function() {
2572
+ var dragAndDrop = function() {
2144
2573
  // IE8 has ondragstart and ondrop properties, but doesn't seem to
2145
2574
  // actually support ondragstart the way it's supposed to work.
2146
2575
  if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false;
2147
2576
  var div = document.createElement('div');
2148
- return "ondragstart" in div && "ondrop" in div;
2149
- })();
2577
+ return "draggable" in div;
2578
+ }();
2150
2579
 
2151
2580
  var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
2152
2581
  var ie = /MSIE \d/.test(navigator.userAgent);
2153
- var safari = /Apple Computer/.test(navigator.vendor);
2582
+ var webkit = /WebKit\//.test(navigator.userAgent);
2154
2583
 
2155
2584
  var lineSep = "\n";
2156
2585
  // Feature-detect whether newlines in textareas are converted to \r\n
@@ -2162,6 +2591,7 @@ var CodeMirror = (function() {
2162
2591
 
2163
2592
  var tabSize = 8;
2164
2593
  var mac = /Mac/.test(navigator.platform);
2594
+ var win = /Win/.test(navigator.platform);
2165
2595
  var movementKeys = {};
2166
2596
  for (var i = 35; i <= 40; ++i)
2167
2597
  movementKeys[i] = movementKeys["c" + i] = true;
@@ -2184,21 +2614,44 @@ var CodeMirror = (function() {
2184
2614
  if (elt.currentStyle) return elt.currentStyle;
2185
2615
  return window.getComputedStyle(elt, null);
2186
2616
  }
2617
+
2187
2618
  // Find the position of an element by following the offsetParent chain.
2188
2619
  // If screen==true, it returns screen (rather than page) coordinates.
2189
2620
  function eltOffset(node, screen) {
2190
- var doc = node.ownerDocument.body;
2191
- var x = 0, y = 0, skipDoc = false;
2621
+ var bod = node.ownerDocument.body;
2622
+ var x = 0, y = 0, skipBody = false;
2192
2623
  for (var n = node; n; n = n.offsetParent) {
2193
- x += n.offsetLeft; y += n.offsetTop;
2624
+ var ol = n.offsetLeft, ot = n.offsetTop;
2625
+ // Firefox reports weird inverted offsets when the body has a border.
2626
+ if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); }
2627
+ else { x += ol, y += ot; }
2194
2628
  if (screen && computedStyle(n).position == "fixed")
2195
- skipDoc = true;
2629
+ skipBody = true;
2196
2630
  }
2197
- var e = screen && !skipDoc ? null : doc;
2631
+ var e = screen && !skipBody ? null : bod;
2198
2632
  for (var n = node.parentNode; n != e; n = n.parentNode)
2199
2633
  if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}
2200
2634
  return {left: x, top: y};
2201
2635
  }
2636
+ // Use the faster and saner getBoundingClientRect method when possible.
2637
+ if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) {
2638
+ // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
2639
+ // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
2640
+ try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
2641
+ catch(e) { box = {top: 0, left: 0}; }
2642
+ if (!screen) {
2643
+ // Get the toplevel scroll, working around browser differences.
2644
+ if (window.pageYOffset == null) {
2645
+ var t = document.documentElement || document.body.parentNode;
2646
+ if (t.scrollTop == null) t = document.body;
2647
+ box.top += t.scrollTop; box.left += t.scrollLeft;
2648
+ } else {
2649
+ box.top += window.pageYOffset; box.left += window.pageXOffset;
2650
+ }
2651
+ }
2652
+ return box;
2653
+ };
2654
+
2202
2655
  // Get a node's text content.
2203
2656
  function eltText(node) {
2204
2657
  return node.textContent || node.innerText || node.nodeValue || "";
@@ -2262,7 +2715,7 @@ var CodeMirror = (function() {
2262
2715
  try {return {start: te.selectionStart, end: te.selectionEnd};}
2263
2716
  catch(e) {return null;}
2264
2717
  };
2265
- if (safari)
2718
+ if (webkit)
2266
2719
  // On Safari, selection set with setSelectionRange are in a sort
2267
2720
  // of limbo wrt their anchor. If you press shift-left in them,
2268
2721
  // the anchor is put at the end, and the selection expanded to