resin 0.2.2 → 0.2.3

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