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