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