Show More
This diff has been collapsed as it changes many lines, (1093 lines changed) Show them Hide them | |||||
@@ -13,32 +13,45 b' var CodeMirror = (function() {' | |||||
13 | if (defaults.hasOwnProperty(opt)) |
|
13 | if (defaults.hasOwnProperty(opt)) | |
14 | options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; |
|
14 | options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; | |
15 |
|
15 | |||
16 | // The element in which the editor lives. Takes care of scrolling |
|
16 | var targetDocument = options["document"]; | |
17 | // (if enabled). |
|
17 | // The element in which the editor lives. | |
18 |
var wrapper = |
|
18 | var wrapper = targetDocument.createElement("div"); | |
19 | wrapper.className = "CodeMirror"; |
|
19 | wrapper.className = "CodeMirror"; | |
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="position: relative">' + // Set to the height of the text, causes scrolling |
|
22 | '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea | |
23 | '<pre style="position: relative; height: 0; visibility: hidden; overflow: hidden;">' + // To measure line/char size |
|
23 | '<textarea style="position: absolute; width: 10000px;" wrap="off" ' + | |
24 | '<span>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</span></pre>' + |
|
24 | 'autocorrect="off" autocapitalize="off"></textarea></div>' + | |
25 | '<div style="position: relative">' + // Moved around its parent to cover visible view |
|
25 | '<div class="CodeMirror-scroll cm-s-' + options.theme + '">' + | |
26 | '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' + |
|
26 | '<div style="position: relative">' + // Set to the height of the text, causes scrolling | |
27 | '<div style="overflow: hidden; position: absolute; width: 0; left: 0">' + // Wraps and hides input textarea |
|
27 | '<div style="position: absolute; height: 0; width: 0; overflow: hidden;"></div>' + | |
28 | '<textarea style="height: 1px; position: absolute; width: 1px;" wrap="off"></textarea></div>' + |
|
28 | '<div style="position: relative">' + // Moved around its parent to cover visible view | |
29 | // Provides positioning relative to (visible) text origin |
|
29 | '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' + | |
30 | '<div class="CodeMirror-lines"><div style="position: relative">' + |
|
30 | // Provides positioning relative to (visible) text origin | |
31 | '<pre class="CodeMirror-cursor"> </pre>' + // Absolutely positioned blinky cursor |
|
31 | '<div class="CodeMirror-lines"><div style="position: relative" draggable="true">' + | |
32 | '<div></div></div></div></div></div>'; // This DIV contains the actual code |
|
32 | '<pre class="CodeMirror-cursor"> </pre>' + // Absolutely positioned blinky cursor | |
|
33 | '<div></div>' + // This DIV contains the actual code | |||
|
34 | '</div></div></div></div></div>'; | |||
33 | if (place.appendChild) place.appendChild(wrapper); else place(wrapper); |
|
35 | if (place.appendChild) place.appendChild(wrapper); else place(wrapper); | |
34 | // I've never seen more elegant code in my life. |
|
36 | // I've never seen more elegant code in my life. | |
35 | var code = wrapper.firstChild, measure = code.firstChild, mover = measure.nextSibling, |
|
37 | var inputDiv = wrapper.firstChild, input = inputDiv.firstChild, | |
|
38 | scroller = wrapper.lastChild, code = scroller.firstChild, | |||
|
39 | measure = code.firstChild, mover = measure.nextSibling, | |||
36 | gutter = mover.firstChild, gutterText = gutter.firstChild, |
|
40 | gutter = mover.firstChild, gutterText = gutter.firstChild, | |
37 |
|
|
41 | lineSpace = gutter.nextSibling.firstChild, | |
38 |
|
|
42 | cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling; | |
39 | if (options.tabindex != null) input.tabindex = options.tabindex; |
|
43 | if (options.tabindex != null) input.tabindex = options.tabindex; | |
40 | if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; |
|
44 | if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; | |
41 |
|
45 | |||
|
46 | // Check for problem with IE innerHTML not working when we have a | |||
|
47 | // P (or similar) parent node. | |||
|
48 | try { stringWidth("x"); } | |||
|
49 | catch (e) { | |||
|
50 | if (e.message.match(/unknown runtime/i)) | |||
|
51 | e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)"); | |||
|
52 | throw e; | |||
|
53 | } | |||
|
54 | ||||
42 | // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. |
|
55 | // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. | |
43 | var poll = new Delayed(), highlight = new Delayed(), blinker; |
|
56 | var poll = new Delayed(), highlight = new Delayed(), blinker; | |
44 |
|
57 | |||
@@ -46,7 +59,7 b' var CodeMirror = (function() {' | |||||
46 | // (see Line constructor), work an array of lines that should be |
|
59 | // (see Line constructor), work an array of lines that should be | |
47 | // parsed, and history the undo history (instance of History |
|
60 | // parsed, and history the undo history (instance of History | |
48 | // constructor). |
|
61 | // constructor). | |
49 |
var mode, lines = [new Line("")], work, |
|
62 | var mode, lines = [new Line("")], work, focused; | |
50 | loadMode(); |
|
63 | loadMode(); | |
51 | // The selection. These are always maintained to point at valid |
|
64 | // The selection. These are always maintained to point at valid | |
52 | // positions. Inverted is used to remember that the user is |
|
65 | // positions. Inverted is used to remember that the user is | |
@@ -56,10 +69,10 b' var CodeMirror = (function() {' | |||||
56 | // whether the user is holding shift. reducedSelection is a hack |
|
69 | // whether the user is holding shift. reducedSelection is a hack | |
57 | // to get around the fact that we can't create inverted |
|
70 | // to get around the fact that we can't create inverted | |
58 | // selections. See below. |
|
71 | // selections. See below. | |
59 | var shiftSelecting, reducedSelection; |
|
72 | var shiftSelecting, reducedSelection, lastClick, lastDoubleClick, draggingText; | |
60 | // Variables used by startOperation/endOperation to track what |
|
73 | // Variables used by startOperation/endOperation to track what | |
61 | // happened during the operation. |
|
74 | // happened during the operation. | |
62 | var updateInput, changes, textChanged, selectionChanged, leaveInputAlone; |
|
75 | var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty; | |
63 | // Current visible range (may be bigger than the view window). |
|
76 | // Current visible range (may be bigger than the view window). | |
64 | var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null; |
|
77 | var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null; | |
65 | // editing will hold an object describing the things we put in the |
|
78 | // editing will hold an object describing the things we put in the | |
@@ -67,35 +80,46 b' var CodeMirror = (function() {' | |||||
67 | // bracketHighlighted is used to remember that a backet has been |
|
80 | // bracketHighlighted is used to remember that a backet has been | |
68 | // marked. |
|
81 | // marked. | |
69 | var editing, bracketHighlighted; |
|
82 | var editing, bracketHighlighted; | |
|
83 | // Tracks the maximum line length so that the horizontal scrollbar | |||
|
84 | // can be kept static when scrolling. | |||
|
85 | var maxLine = "", maxWidth; | |||
70 |
|
86 | |||
71 |
// Initialize the content. |
|
87 | // Initialize the content. | |
72 | // to work around browser issues. |
|
|||
73 | operation(function(){setValue(options.value || ""); updateInput = false;})(); |
|
88 | operation(function(){setValue(options.value || ""); updateInput = false;})(); | |
74 | setTimeout(prepareInput, 20); |
|
89 | var history = new History(); | |
75 |
|
90 | |||
76 | // Register our event handlers. |
|
91 | // Register our event handlers. | |
77 |
connect( |
|
92 | connect(scroller, "mousedown", operation(onMouseDown)); | |
|
93 | connect(scroller, "dblclick", operation(onDoubleClick)); | |||
|
94 | connect(lineSpace, "dragstart", onDragStart); | |||
78 | // Gecko browsers fire contextmenu *after* opening the menu, at |
|
95 | // Gecko browsers fire contextmenu *after* opening the menu, at | |
79 | // which point we can't mess with it anymore. Context menu is |
|
96 | // which point we can't mess with it anymore. Context menu is | |
80 | // handled in onMouseDown for Gecko. |
|
97 | // handled in onMouseDown for Gecko. | |
81 |
if (!gecko) connect( |
|
98 | if (!gecko) connect(scroller, "contextmenu", onContextMenu); | |
82 | connect(code, "dblclick", operation(onDblClick)); |
|
99 | connect(scroller, "scroll", function() { | |
83 | connect(wrapper, "scroll", function() {updateDisplay([]); if (options.onScroll) options.onScroll(instance);}); |
|
100 | updateDisplay([]); | |
|
101 | if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; | |||
|
102 | if (options.onScroll) options.onScroll(instance); | |||
|
103 | }); | |||
84 | connect(window, "resize", function() {updateDisplay(true);}); |
|
104 | connect(window, "resize", function() {updateDisplay(true);}); | |
85 | connect(input, "keyup", operation(onKeyUp)); |
|
105 | connect(input, "keyup", operation(onKeyUp)); | |
|
106 | connect(input, "input", function() {fastPoll(curKeyId);}); | |||
86 | connect(input, "keydown", operation(onKeyDown)); |
|
107 | connect(input, "keydown", operation(onKeyDown)); | |
87 | connect(input, "keypress", operation(onKeyPress)); |
|
108 | connect(input, "keypress", operation(onKeyPress)); | |
88 | connect(input, "focus", onFocus); |
|
109 | connect(input, "focus", onFocus); | |
89 | connect(input, "blur", onBlur); |
|
110 | connect(input, "blur", onBlur); | |
90 |
|
111 | |||
91 |
connect( |
|
112 | connect(scroller, "dragenter", e_stop); | |
92 |
connect( |
|
113 | connect(scroller, "dragover", e_stop); | |
93 |
connect( |
|
114 | connect(scroller, "drop", operation(onDrop)); | |
94 |
connect( |
|
115 | connect(scroller, "paste", function(){focusInput(); fastPoll();}); | |
95 | connect(input, "paste", function(){fastPoll();}); |
|
116 | connect(input, "paste", function(){fastPoll();}); | |
96 | connect(input, "cut", function(){fastPoll();}); |
|
117 | connect(input, "cut", function(){fastPoll();}); | |
97 |
|
118 | |||
98 | if (document.activeElement == input) onFocus(); |
|
119 | // IE throws unspecified error in certain cases, when | |
|
120 | // trying to access activeElement before onload | |||
|
121 | var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { } | |||
|
122 | if (hasFocus) setTimeout(onFocus, 20); | |||
99 | else onBlur(); |
|
123 | else onBlur(); | |
100 |
|
124 | |||
101 | function isLine(l) {return l >= 0 && l < lines.length;} |
|
125 | function isLine(l) {return l >= 0 && l < lines.length;} | |
@@ -104,27 +128,37 b' var CodeMirror = (function() {' | |||||
104 | // range checking and/or clipping. operation is used to wrap the |
|
128 | // range checking and/or clipping. operation is used to wrap the | |
105 | // call so that changes it makes are tracked, and the display is |
|
129 | // call so that changes it makes are tracked, and the display is | |
106 | // updated afterwards. |
|
130 | // updated afterwards. | |
107 | var instance = { |
|
131 | var instance = wrapper.CodeMirror = { | |
108 | getValue: getValue, |
|
132 | getValue: getValue, | |
109 | setValue: operation(setValue), |
|
133 | setValue: operation(setValue), | |
110 | getSelection: getSelection, |
|
134 | getSelection: getSelection, | |
111 | replaceSelection: operation(replaceSelection), |
|
135 | replaceSelection: operation(replaceSelection), | |
112 |
focus: function(){ |
|
136 | focus: function(){focusInput(); onFocus(); fastPoll();}, | |
113 | setOption: function(option, value) { |
|
137 | setOption: function(option, value) { | |
114 | options[option] = value; |
|
138 | options[option] = value; | |
115 |
if (option == "lineNumbers" || option == "gutter") |
|
139 | if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber") | |
|
140 | operation(gutterChanged)(); | |||
116 | else if (option == "mode" || option == "indentUnit") loadMode(); |
|
141 | else if (option == "mode" || option == "indentUnit") loadMode(); | |
|
142 | else if (option == "readOnly" && value == "nocursor") input.blur(); | |||
|
143 | else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value); | |||
117 | }, |
|
144 | }, | |
118 | getOption: function(option) {return options[option];}, |
|
145 | getOption: function(option) {return options[option];}, | |
119 | undo: operation(undo), |
|
146 | undo: operation(undo), | |
120 | redo: operation(redo), |
|
147 | redo: operation(redo), | |
121 |
indentLine: operation(function(n) { |
|
148 | indentLine: operation(function(n, dir) { | |
|
149 | if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract"); | |||
|
150 | }), | |||
122 | historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, |
|
151 | historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, | |
|
152 | clearHistory: function() {history = new History();}, | |||
123 | matchBrackets: operation(function(){matchBrackets(true);}), |
|
153 | matchBrackets: operation(function(){matchBrackets(true);}), | |
124 | getTokenAt: function(pos) { |
|
154 | getTokenAt: function(pos) { | |
125 | pos = clipPos(pos); |
|
155 | pos = clipPos(pos); | |
126 | return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch); |
|
156 | return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch); | |
127 | }, |
|
157 | }, | |
|
158 | getStateAfter: function(line) { | |||
|
159 | line = clipLine(line == null ? lines.length - 1: line); | |||
|
160 | return getStateBefore(line + 1); | |||
|
161 | }, | |||
128 | cursorCoords: function(start){ |
|
162 | cursorCoords: function(start){ | |
129 | if (start == null) start = sel.inverted; |
|
163 | if (start == null) start = sel.inverted; | |
130 | return pageCoords(start ? sel.from : sel.to); |
|
164 | return pageCoords(start ? sel.from : sel.to); | |
@@ -132,22 +166,41 b' var CodeMirror = (function() {' | |||||
132 | charCoords: function(pos){return pageCoords(clipPos(pos));}, |
|
166 | charCoords: function(pos){return pageCoords(clipPos(pos));}, | |
133 | coordsChar: function(coords) { |
|
167 | coordsChar: function(coords) { | |
134 | var off = eltOffset(lineSpace); |
|
168 | var off = eltOffset(lineSpace); | |
135 |
var line = Math.min( |
|
169 | var line = clipLine(Math.min(lines.length - 1, showingFrom + Math.floor((coords.y - off.top) / lineHeight()))); | |
136 | return clipPos({line: line, ch: charFromX(clipLine(line), coords.x)}); |
|
170 | return clipPos({line: line, ch: charFromX(clipLine(line), coords.x - off.left)}); | |
137 | }, |
|
171 | }, | |
138 | getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);}, |
|
172 | getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);}, | |
139 |
markText: operation( |
|
173 | markText: operation(markText), | |
140 | setMarker: addGutterMarker, |
|
174 | setMarker: operation(addGutterMarker), | |
141 | clearMarker: removeGutterMarker, |
|
175 | clearMarker: operation(removeGutterMarker), | |
142 | setLineClass: operation(setLineClass), |
|
176 | setLineClass: operation(setLineClass), | |
143 | lineInfo: lineInfo, |
|
177 | lineInfo: lineInfo, | |
144 | addWidget: function(pos, node, scroll) { |
|
178 | addWidget: function(pos, node, scroll, vert, horiz) { | |
145 |
|
|
179 | pos = localCoords(clipPos(pos)); | |
146 | node.style.top = (showingFrom * lineHeight() + pos.yBot + paddingTop()) + "px"; |
|
180 | var top = pos.yBot, left = pos.x; | |
147 | node.style.left = (pos.x + paddingLeft()) + "px"; |
|
181 | node.style.position = "absolute"; | |
148 | code.appendChild(node); |
|
182 | code.appendChild(node); | |
|
183 | if (vert == "over") top = pos.y; | |||
|
184 | else if (vert == "near") { | |||
|
185 | var vspace = Math.max(scroller.offsetHeight, lines.length * lineHeight()), | |||
|
186 | hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft(); | |||
|
187 | if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) | |||
|
188 | top = pos.y - node.offsetHeight; | |||
|
189 | if (left + node.offsetWidth > hspace) | |||
|
190 | left = hspace - node.offsetWidth; | |||
|
191 | } | |||
|
192 | node.style.top = (top + paddingTop()) + "px"; | |||
|
193 | node.style.left = node.style.right = ""; | |||
|
194 | if (horiz == "right") { | |||
|
195 | left = code.clientWidth - node.offsetWidth; | |||
|
196 | node.style.right = "0px"; | |||
|
197 | } else { | |||
|
198 | if (horiz == "left") left = 0; | |||
|
199 | else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2; | |||
|
200 | node.style.left = (left + paddingLeft()) + "px"; | |||
|
201 | } | |||
149 | if (scroll) |
|
202 | if (scroll) | |
150 |
scrollIntoView( |
|
203 | scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); | |
151 | }, |
|
204 | }, | |
152 |
|
205 | |||
153 | lineCount: function() {return lines.length;}, |
|
206 | lineCount: function() {return lines.length;}, | |
@@ -171,18 +224,30 b' var CodeMirror = (function() {' | |||||
171 | replaceRange: operation(replaceRange), |
|
224 | replaceRange: operation(replaceRange), | |
172 | getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));}, |
|
225 | getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));}, | |
173 |
|
226 | |||
|
227 | coordsFromIndex: function(index) { | |||
|
228 | var total = lines.length, pos = 0, line, ch, len; | |||
|
229 | ||||
|
230 | for (line = 0; line < total; line++) { | |||
|
231 | len = lines[line].text.length + 1; | |||
|
232 | if (pos + len > index) { ch = index - pos; break; } | |||
|
233 | pos += len; | |||
|
234 | } | |||
|
235 | return clipPos({line: line, ch: ch}); | |||
|
236 | }, | |||
|
237 | ||||
174 | operation: function(f){return operation(f)();}, |
|
238 | operation: function(f){return operation(f)();}, | |
175 | refresh: function(){updateDisplay(true);}, |
|
239 | refresh: function(){updateDisplay(true);}, | |
176 | getInputField: function(){return input;}, |
|
240 | getInputField: function(){return input;}, | |
177 | getWrapperElement: function(){return wrapper;} |
|
241 | getWrapperElement: function(){return wrapper;}, | |
|
242 | getScrollerElement: function(){return scroller;}, | |||
|
243 | getGutterElement: function(){return gutter;} | |||
178 | }; |
|
244 | }; | |
179 |
|
245 | |||
180 | function setValue(code) { |
|
246 | function setValue(code) { | |
181 | history = null; |
|
|||
182 | var top = {line: 0, ch: 0}; |
|
247 | var top = {line: 0, ch: 0}; | |
183 | updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length}, |
|
248 | updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length}, | |
184 | splitLines(code), top, top); |
|
249 | splitLines(code), top, top); | |
185 | history = new History(); |
|
250 | updateInput = true; | |
186 | } |
|
251 | } | |
187 | function getValue(code) { |
|
252 | function getValue(code) { | |
188 | var text = []; |
|
253 | var text = []; | |
@@ -192,38 +257,70 b' var CodeMirror = (function() {' | |||||
192 | } |
|
257 | } | |
193 |
|
258 | |||
194 | function onMouseDown(e) { |
|
259 | function onMouseDown(e) { | |
|
260 | // Check whether this is a click in a widget | |||
|
261 | for (var n = e_target(e); n != wrapper; n = n.parentNode) | |||
|
262 | if (n.parentNode == code && n != mover) return; | |||
|
263 | ||||
195 | // First, see if this is a click in the gutter |
|
264 | // First, see if this is a click in the gutter | |
196 |
for (var n = e |
|
265 | for (var n = e_target(e); n != wrapper; n = n.parentNode) | |
197 | if (n.parentNode == gutterText) { |
|
266 | if (n.parentNode == gutterText) { | |
198 | if (options.onGutterClick) |
|
267 | if (options.onGutterClick) | |
199 | options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom); |
|
268 | options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e); | |
200 |
return e |
|
269 | return e_preventDefault(e); | |
201 | } |
|
270 | } | |
202 |
|
271 | |||
203 | if (gecko && e.button() == 3) onContextMenu(e); |
|
272 | var start = posFromMouse(e); | |
204 | if (e.button() != 1) return; |
|
273 | ||
|
274 | switch (e_button(e)) { | |||
|
275 | case 3: | |||
|
276 | if (gecko && !mac) onContextMenu(e); | |||
|
277 | return; | |||
|
278 | case 2: | |||
|
279 | if (start) setCursor(start.line, start.ch, true); | |||
|
280 | return; | |||
|
281 | } | |||
205 | // For button 1, if it was clicked inside the editor |
|
282 | // For button 1, if it was clicked inside the editor | |
206 | // (posFromMouse returning non-null), we have to adjust the |
|
283 | // (posFromMouse returning non-null), we have to adjust the | |
207 | // selection. |
|
284 | // selection. | |
208 | var start = posFromMouse(e), last = start, going; |
|
285 | if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;} | |
209 | if (!start) {if (e.target() == wrapper) e.stop(); return;} |
|
|||
210 | setCursor(start.line, start.ch, false); |
|
|||
211 |
|
286 | |||
212 | if (!focused) onFocus(); |
|
287 | if (!focused) onFocus(); | |
213 | e.stop(); |
|
288 | ||
214 | // And then we have to see if it's a drag event, in which case |
|
289 | var now = +new Date; | |
215 | // the dragged-over text must be selected. |
|
290 | if (lastDoubleClick > now - 400) { | |
216 | function end() { |
|
291 | e_preventDefault(e); | |
217 | input.focus(); |
|
292 | return selectLine(start.line); | |
218 | updateInput = true; |
|
293 | } else if (lastClick > now - 400) { | |
219 | move(); up(); |
|
294 | lastDoubleClick = now; | |
|
295 | e_preventDefault(e); | |||
|
296 | return selectWordAt(start); | |||
|
297 | } else { lastClick = now; } | |||
|
298 | ||||
|
299 | var last = start, going; | |||
|
300 | if (dragAndDrop && !posEq(sel.from, sel.to) && | |||
|
301 | !posLess(start, sel.from) && !posLess(sel.to, start)) { | |||
|
302 | // Let the drag handler handle this. | |||
|
303 | var up = connect(targetDocument, "mouseup", operation(function(e2) { | |||
|
304 | draggingText = false; | |||
|
305 | up(); | |||
|
306 | if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { | |||
|
307 | e_preventDefault(e2); | |||
|
308 | setCursor(start.line, start.ch, true); | |||
|
309 | focusInput(); | |||
|
310 | } | |||
|
311 | }), true); | |||
|
312 | draggingText = true; | |||
|
313 | return; | |||
220 | } |
|
314 | } | |
|
315 | e_preventDefault(e); | |||
|
316 | setCursor(start.line, start.ch, true); | |||
|
317 | ||||
221 | function extend(e) { |
|
318 | function extend(e) { | |
222 | var cur = posFromMouse(e, true); |
|
319 | var cur = posFromMouse(e, true); | |
223 | if (cur && !posEq(cur, last)) { |
|
320 | if (cur && !posEq(cur, last)) { | |
224 | if (!focused) onFocus(); |
|
321 | if (!focused) onFocus(); | |
225 | last = cur; |
|
322 | last = cur; | |
226 | setSelection(start, cur); |
|
323 | setSelectionUser(start, cur); | |
227 | updateInput = false; |
|
324 | updateInput = false; | |
228 | var visible = visibleLines(); |
|
325 | var visible = visibleLines(); | |
229 | if (cur.line >= visible.to || cur.line < visible.from) |
|
326 | if (cur.line >= visible.to || cur.line < visible.from) | |
@@ -231,68 +328,95 b' var CodeMirror = (function() {' | |||||
231 | } |
|
328 | } | |
232 | } |
|
329 | } | |
233 |
|
330 | |||
234 |
var move = connect( |
|
331 | var move = connect(targetDocument, "mousemove", operation(function(e) { | |
235 | clearTimeout(going); |
|
332 | clearTimeout(going); | |
236 | e.stop(); |
|
333 | e_preventDefault(e); | |
237 | extend(e); |
|
334 | extend(e); | |
238 | }), true); |
|
335 | }), true); | |
239 |
var up = connect( |
|
336 | var up = connect(targetDocument, "mouseup", operation(function(e) { | |
240 | clearTimeout(going); |
|
337 | clearTimeout(going); | |
241 | var cur = posFromMouse(e); |
|
338 | var cur = posFromMouse(e); | |
242 | if (cur) setSelection(start, cur); |
|
339 | if (cur) setSelectionUser(start, cur); | |
243 | e.stop(); |
|
340 | e_preventDefault(e); | |
244 |
|
|
341 | focusInput(); | |
|
342 | updateInput = true; | |||
|
343 | move(); up(); | |||
245 | }), true); |
|
344 | }), true); | |
246 | } |
|
345 | } | |
247 | function onDblClick(e) { |
|
346 | function onDoubleClick(e) { | |
248 |
var |
|
347 | var start = posFromMouse(e); | |
249 |
if (! |
|
348 | if (!start) return; | |
250 | selectWordAt(pos); |
|
349 | lastDoubleClick = +new Date; | |
251 | e.stop(); |
|
350 | e_preventDefault(e); | |
|
351 | selectWordAt(start); | |||
252 | } |
|
352 | } | |
253 | function onDrop(e) { |
|
353 | function onDrop(e) { | |
254 | var pos = posFromMouse(e, true), files = e.e.dataTransfer.files; |
|
354 | e.preventDefault(); | |
|
355 | var pos = posFromMouse(e, true), files = e.dataTransfer.files; | |||
255 | if (!pos || options.readOnly) return; |
|
356 | if (!pos || options.readOnly) return; | |
256 | if (files && files.length && window.FileReader && window.File) { |
|
357 | if (files && files.length && window.FileReader && window.File) { | |
257 | var n = files.length, text = Array(n), read = 0; |
|
|||
258 | for (var i = 0; i < n; ++i) loadFile(files[i], i); |
|
|||
259 | function loadFile(file, i) { |
|
358 | function loadFile(file, i) { | |
260 | var reader = new FileReader; |
|
359 | var reader = new FileReader; | |
261 | reader.onload = function() { |
|
360 | reader.onload = function() { | |
262 | text[i] = reader.result; |
|
361 | text[i] = reader.result; | |
263 | if (++read == n) replaceRange(text.join(""), clipPos(pos), clipPos(pos)); |
|
362 | if (++read == n) { | |
|
363 | pos = clipPos(pos); | |||
|
364 | var end = replaceRange(text.join(""), pos, pos); | |||
|
365 | setSelectionUser(pos, end); | |||
|
366 | } | |||
264 | }; |
|
367 | }; | |
265 | reader.readAsText(file); |
|
368 | reader.readAsText(file); | |
266 | } |
|
369 | } | |
|
370 | var n = files.length, text = Array(n), read = 0; | |||
|
371 | for (var i = 0; i < n; ++i) loadFile(files[i], i); | |||
267 | } |
|
372 | } | |
268 | else { |
|
373 | else { | |
269 | try { |
|
374 | try { | |
270 |
var text = |
|
375 | var text = e.dataTransfer.getData("Text"); | |
271 | if (text) replaceRange(text, pos, pos); |
|
376 | if (text) { | |
|
377 | var end = replaceRange(text, pos, pos); | |||
|
378 | var curFrom = sel.from, curTo = sel.to; | |||
|
379 | setSelectionUser(pos, end); | |||
|
380 | if (draggingText) replaceRange("", curFrom, curTo); | |||
|
381 | focusInput(); | |||
|
382 | } | |||
272 | } |
|
383 | } | |
273 | catch(e){} |
|
384 | catch(e){} | |
274 | } |
|
385 | } | |
275 | } |
|
386 | } | |
|
387 | function onDragStart(e) { | |||
|
388 | var txt = getSelection(); | |||
|
389 | // This will reset escapeElement | |||
|
390 | htmlEscape(txt); | |||
|
391 | e.dataTransfer.setDragImage(escapeElement, 0, 0); | |||
|
392 | e.dataTransfer.setData("Text", txt); | |||
|
393 | } | |||
276 | function onKeyDown(e) { |
|
394 | function onKeyDown(e) { | |
277 | if (!focused) onFocus(); |
|
395 | if (!focused) onFocus(); | |
278 |
|
396 | |||
279 |
var code = e. |
|
397 | var code = e.keyCode; | |
|
398 | // IE does strange things with escape. | |||
|
399 | if (ie && code == 27) { e.returnValue = false; } | |||
280 | // Tries to detect ctrl on non-mac, cmd on mac. |
|
400 | // Tries to detect ctrl on non-mac, cmd on mac. | |
281 |
var mod = (mac ? e. |
|
401 | var mod = (mac ? e.metaKey : e.ctrlKey) && !e.altKey, anyMod = e.ctrlKey || e.altKey || e.metaKey; | |
282 |
if (code == 16 || |
|
402 | if (code == 16 || e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); | |
283 | else shiftSelecting = null; |
|
403 | else shiftSelecting = null; | |
284 | // First give onKeyEvent option a chance to handle this. |
|
404 | // First give onKeyEvent option a chance to handle this. | |
285 |
if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e |
|
405 | if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; | |
286 |
|
406 | |||
287 |
if (code == 33 || code == 34) {scrollPage(code == 34); return e |
|
407 | if (code == 33 || code == 34) {scrollPage(code == 34); return e_preventDefault(e);} // page up/down | |
288 |
if (mod && (code == 36 || code == 35) |
|
408 | if (mod && ((code == 36 || code == 35) || // ctrl-home/end | |
289 | if (mod && code == 65) {selectAll(); return e.stop();} // ctrl-a |
|
409 | mac && (code == 38 || code == 40))) { // cmd-up/down | |
|
410 | scrollEnd(code == 36 || code == 38); return e_preventDefault(e); | |||
|
411 | } | |||
|
412 | if (mod && code == 65) {selectAll(); return e_preventDefault(e);} // ctrl-a | |||
290 | if (!options.readOnly) { |
|
413 | if (!options.readOnly) { | |
291 | if (!anyMod && code == 13) {return;} // enter |
|
414 | if (!anyMod && code == 13) {return;} // enter | |
292 |
if (!anyMod && code == 9 && handleTab(e. |
|
415 | if (!anyMod && code == 9 && handleTab(e.shiftKey)) return e_preventDefault(e); // tab | |
293 |
if (mod && code == 90) {undo(); return e |
|
416 | if (mod && code == 90) {undo(); return e_preventDefault(e);} // ctrl-z | |
294 |
if (mod && (( |
|
417 | if (mod && ((e.shiftKey && code == 90) || code == 89)) {redo(); return e_preventDefault(e);} // ctrl-shift-z, ctrl-y | |
295 | } |
|
418 | } | |
|
419 | if (code == 36) { if (options.smartHome) { smartHome(); return e_preventDefault(e); } } | |||
296 |
|
420 | |||
297 | // Key id to use in the movementKeys map. We also pass it to |
|
421 | // Key id to use in the movementKeys map. We also pass it to | |
298 | // fastPoll in order to 'self learn'. We need this because |
|
422 | // fastPoll in order to 'self learn'. We need this because | |
@@ -300,51 +424,60 b' var CodeMirror = (function() {' | |||||
300 | // its start when it is inverted and a movement key is pressed |
|
424 | // its start when it is inverted and a movement key is pressed | |
301 | // (and later restore it again), shouldn't be used for |
|
425 | // (and later restore it again), shouldn't be used for | |
302 | // non-movement keys. |
|
426 | // non-movement keys. | |
303 | curKeyId = (mod ? "c" : "") + code; |
|
427 | curKeyId = (mod ? "c" : "") + (e.altKey ? "a" : "") + code; | |
304 |
if (sel.inverted && movementKeys |
|
428 | if (sel.inverted && movementKeys[curKeyId] === true) { | |
305 | var range = selRange(input); |
|
429 | var range = selRange(input); | |
306 | if (range) { |
|
430 | if (range) { | |
307 | reducedSelection = {anchor: range.start}; |
|
431 | reducedSelection = {anchor: range.start}; | |
308 | setSelRange(input, range.start, range.start); |
|
432 | setSelRange(input, range.start, range.start); | |
309 | } |
|
433 | } | |
310 | } |
|
434 | } | |
|
435 | // Don't save the key as a movementkey unless it had a modifier | |||
|
436 | if (!mod && !e.altKey) curKeyId = null; | |||
311 | fastPoll(curKeyId); |
|
437 | fastPoll(curKeyId); | |
312 | } |
|
438 | } | |
313 | function onKeyUp(e) { |
|
439 | function onKeyUp(e) { | |
|
440 | if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; | |||
314 | if (reducedSelection) { |
|
441 | if (reducedSelection) { | |
315 | reducedSelection = null; |
|
442 | reducedSelection = null; | |
316 | updateInput = true; |
|
443 | updateInput = true; | |
317 | } |
|
444 | } | |
318 |
if ( |
|
445 | if (e.keyCode == 16) shiftSelecting = null; | |
319 | } |
|
446 | } | |
320 | function onKeyPress(e) { |
|
447 | function onKeyPress(e) { | |
321 |
if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e |
|
448 | if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; | |
322 | if (options.electricChars && mode.electricChars) { |
|
449 | if (options.electricChars && mode.electricChars) { | |
323 |
var ch = String.fromCharCode(e. |
|
450 | var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode); | |
324 | if (mode.electricChars.indexOf(ch) > -1) |
|
451 | if (mode.electricChars.indexOf(ch) > -1) | |
325 | setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50); |
|
452 | setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50); | |
326 | } |
|
453 | } | |
327 |
var code = e. |
|
454 | var code = e.keyCode; | |
328 | // Re-stop tab and enter. Necessary on some browsers. |
|
455 | // Re-stop tab and enter. Necessary on some browsers. | |
329 |
if (code == 13) {handleEnter(); e |
|
456 | if (code == 13) {if (!options.readOnly) handleEnter(); e_preventDefault(e);} | |
330 |
else if (code == 9 && options.tabMode != "default") e |
|
457 | else if (!e.ctrlKey && !e.altKey && !e.metaKey && code == 9 && options.tabMode != "default") e_preventDefault(e); | |
331 | else fastPoll(curKeyId); |
|
458 | else fastPoll(curKeyId); | |
332 | } |
|
459 | } | |
333 |
|
460 | |||
334 | function onFocus() { |
|
461 | function onFocus() { | |
335 | if (!focused && options.onFocus) options.onFocus(instance); |
|
462 | if (options.readOnly == "nocursor") return; | |
336 |
focused |
|
463 | if (!focused) { | |
|
464 | if (options.onFocus) options.onFocus(instance); | |||
|
465 | focused = true; | |||
|
466 | if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1) | |||
|
467 | wrapper.className += " CodeMirror-focused"; | |||
|
468 | if (!leaveInputAlone) prepareInput(); | |||
|
469 | } | |||
337 | slowPoll(); |
|
470 | slowPoll(); | |
338 | if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1) |
|
|||
339 | wrapper.className += " CodeMirror-focused"; |
|
|||
340 | restartBlink(); |
|
471 | restartBlink(); | |
341 | } |
|
472 | } | |
342 | function onBlur() { |
|
473 | function onBlur() { | |
343 | if (focused && options.onBlur) options.onBlur(instance); |
|
474 | if (focused) { | |
|
475 | if (options.onBlur) options.onBlur(instance); | |||
|
476 | focused = false; | |||
|
477 | wrapper.className = wrapper.className.replace(" CodeMirror-focused", ""); | |||
|
478 | } | |||
344 | clearInterval(blinker); |
|
479 | clearInterval(blinker); | |
345 | shiftSelecting = null; |
|
480 | setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); | |
346 | focused = false; |
|
|||
347 | wrapper.className = wrapper.className.replace(" CodeMirror-focused", ""); |
|
|||
348 | } |
|
481 | } | |
349 |
|
482 | |||
350 | // Replace the range from from to to by the strings in newText. |
|
483 | // Replace the range from from to to by the strings in newText. | |
@@ -367,12 +500,18 b' var CodeMirror = (function() {' | |||||
367 | var pos = clipPos({line: change.start + change.old.length - 1, |
|
500 | var pos = clipPos({line: change.start + change.old.length - 1, | |
368 | ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}); |
|
501 | ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}); | |
369 | updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos); |
|
502 | updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos); | |
|
503 | updateInput = true; | |||
370 | } |
|
504 | } | |
371 | } |
|
505 | } | |
372 | function undo() {unredoHelper(history.done, history.undone);} |
|
506 | function undo() {unredoHelper(history.done, history.undone);} | |
373 | function redo() {unredoHelper(history.undone, history.done);} |
|
507 | function redo() {unredoHelper(history.undone, history.done);} | |
374 |
|
508 | |||
375 | function updateLinesNoUndo(from, to, newText, selFrom, selTo) { |
|
509 | function updateLinesNoUndo(from, to, newText, selFrom, selTo) { | |
|
510 | var recomputeMaxLength = false, maxLineLength = maxLine.length; | |||
|
511 | for (var i = from.line; i <= to.line; ++i) { | |||
|
512 | if (lines[i].text.length == maxLineLength) {recomputeMaxLength = true; break;} | |||
|
513 | } | |||
|
514 | ||||
376 | var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line]; |
|
515 | var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line]; | |
377 | // First adjust the line structure, taking some care to leave highlighting intact. |
|
516 | // First adjust the line structure, taking some care to leave highlighting intact. | |
378 | if (firstLine == lastLine) { |
|
517 | if (firstLine == lastLine) { | |
@@ -381,24 +520,46 b' var CodeMirror = (function() {' | |||||
381 | else { |
|
520 | else { | |
382 | lastLine = firstLine.split(to.ch, newText[newText.length-1]); |
|
521 | lastLine = firstLine.split(to.ch, newText[newText.length-1]); | |
383 | var spliceargs = [from.line + 1, nlines]; |
|
522 | var spliceargs = [from.line + 1, nlines]; | |
384 |
firstLine.replace(from.ch, |
|
523 | firstLine.replace(from.ch, null, newText[0]); | |
385 |
for (var i = 1, e = newText.length - 1; i < e; ++i) |
|
524 | for (var i = 1, e = newText.length - 1; i < e; ++i) | |
|
525 | spliceargs.push(Line.inheritMarks(newText[i], firstLine)); | |||
386 | spliceargs.push(lastLine); |
|
526 | spliceargs.push(lastLine); | |
387 | lines.splice.apply(lines, spliceargs); |
|
527 | lines.splice.apply(lines, spliceargs); | |
388 | } |
|
528 | } | |
389 | } |
|
529 | } | |
390 | else if (newText.length == 1) { |
|
530 | else if (newText.length == 1) { | |
391 |
firstLine.replace(from.ch, |
|
531 | firstLine.replace(from.ch, null, newText[0]); | |
|
532 | lastLine.replace(null, to.ch, ""); | |||
|
533 | firstLine.append(lastLine); | |||
392 | lines.splice(from.line + 1, nlines); |
|
534 | lines.splice(from.line + 1, nlines); | |
393 | } |
|
535 | } | |
394 | else { |
|
536 | else { | |
395 | var spliceargs = [from.line + 1, nlines - 1]; |
|
537 | var spliceargs = [from.line + 1, nlines - 1]; | |
396 |
firstLine.replace(from.ch, |
|
538 | firstLine.replace(from.ch, null, newText[0]); | |
397 |
lastLine.replace( |
|
539 | lastLine.replace(null, to.ch, newText[newText.length-1]); | |
398 |
for (var i = 1, e = newText.length - 1; i < e; ++i) |
|
540 | for (var i = 1, e = newText.length - 1; i < e; ++i) | |
|
541 | spliceargs.push(Line.inheritMarks(newText[i], firstLine)); | |||
399 | lines.splice.apply(lines, spliceargs); |
|
542 | lines.splice.apply(lines, spliceargs); | |
400 | } |
|
543 | } | |
401 |
|
544 | |||
|
545 | ||||
|
546 | for (var i = from.line, e = i + newText.length; i < e; ++i) { | |||
|
547 | var l = lines[i].text; | |||
|
548 | if (l.length > maxLineLength) { | |||
|
549 | maxLine = l; maxLineLength = l.length; maxWidth = null; | |||
|
550 | recomputeMaxLength = false; | |||
|
551 | } | |||
|
552 | } | |||
|
553 | if (recomputeMaxLength) { | |||
|
554 | maxLineLength = 0; maxLine = ""; maxWidth = null; | |||
|
555 | for (var i = 0, e = lines.length; i < e; ++i) { | |||
|
556 | var l = lines[i].text; | |||
|
557 | if (l.length > maxLineLength) { | |||
|
558 | maxLineLength = l.length; maxLine = l; | |||
|
559 | } | |||
|
560 | } | |||
|
561 | } | |||
|
562 | ||||
402 | // Add these lines to the work array, so that they will be |
|
563 | // Add these lines to the work array, so that they will be | |
403 | // highlighted. Adjust work lines if lines were added/removed. |
|
564 | // highlighted. Adjust work lines if lines were added/removed. | |
404 | var newWork = [], lendiff = newText.length - nlines - 1; |
|
565 | var newWork = [], lendiff = newText.length - nlines - 1; | |
@@ -407,12 +568,17 b' var CodeMirror = (function() {' | |||||
407 | if (task < from.line) newWork.push(task); |
|
568 | if (task < from.line) newWork.push(task); | |
408 | else if (task > to.line) newWork.push(task + lendiff); |
|
569 | else if (task > to.line) newWork.push(task + lendiff); | |
409 | } |
|
570 | } | |
410 |
if (newText.length) |
|
571 | if (newText.length < 5) { | |
|
572 | highlightLines(from.line, from.line + newText.length); | |||
|
573 | newWork.push(from.line + newText.length); | |||
|
574 | } else { | |||
|
575 | newWork.push(from.line); | |||
|
576 | } | |||
411 | work = newWork; |
|
577 | work = newWork; | |
412 | startWorker(100); |
|
578 | startWorker(100); | |
413 | // Remember that these lines changed, for updating the display |
|
579 | // Remember that these lines changed, for updating the display | |
414 | changes.push({from: from.line, to: to.line + 1, diff: lendiff}); |
|
580 | changes.push({from: from.line, to: to.line + 1, diff: lendiff}); | |
415 | textChanged = true; |
|
581 | textChanged = {from: from, to: to, text: newText}; | |
416 |
|
582 | |||
417 | // Update the selection |
|
583 | // Update the selection | |
418 | function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} |
|
584 | function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} | |
@@ -483,7 +649,10 b' var CodeMirror = (function() {' | |||||
483 | function p() { |
|
649 | function p() { | |
484 | startOperation(); |
|
650 | startOperation(); | |
485 | var changed = readInput(); |
|
651 | var changed = readInput(); | |
486 |
if (changed |
|
652 | if (changed && keyId) { | |
|
653 | if (changed == "moved" && movementKeys[keyId] == null) movementKeys[keyId] = true; | |||
|
654 | if (changed == "changed") movementKeys[keyId] = false; | |||
|
655 | } | |||
487 | if (!changed && !missed) {missed = true; poll.set(80, p);} |
|
656 | if (!changed && !missed) {missed = true; poll.set(80, p);} | |
488 | else {pollingFast = false; slowPoll();} |
|
657 | else {pollingFast = false; slowPoll();} | |
489 | endOperation(); |
|
658 | endOperation(); | |
@@ -495,13 +664,12 b' var CodeMirror = (function() {' | |||||
495 | // to the data in the editing variable, and updates the editor |
|
664 | // to the data in the editing variable, and updates the editor | |
496 | // content or cursor if something changed. |
|
665 | // content or cursor if something changed. | |
497 | function readInput() { |
|
666 | function readInput() { | |
|
667 | if (leaveInputAlone || !focused) return; | |||
498 | var changed = false, text = input.value, sr = selRange(input); |
|
668 | var changed = false, text = input.value, sr = selRange(input); | |
499 | if (!sr) return false; |
|
669 | if (!sr) return false; | |
500 | var changed = editing.text != text, rs = reducedSelection; |
|
670 | var changed = editing.text != text, rs = reducedSelection; | |
501 | var moved = changed || sr.start != editing.start || sr.end != (rs ? editing.start : editing.end); |
|
671 | var moved = changed || sr.start != editing.start || sr.end != (rs ? editing.start : editing.end); | |
502 | if (reducedSelection && !moved && sel.from.line == 0 && sel.from.ch == 0) |
|
672 | if (!moved && !rs) return false; | |
503 | reducedSelection = null; |
|
|||
504 | else if (!moved) return false; |
|
|||
505 | if (changed) { |
|
673 | if (changed) { | |
506 | shiftSelecting = reducedSelection = null; |
|
674 | shiftSelecting = reducedSelection = null; | |
507 | if (options.readOnly) {updateInput = true; return "changed";} |
|
675 | if (options.readOnly) {updateInput = true; return "changed";} | |
@@ -524,13 +692,10 b' var CodeMirror = (function() {' | |||||
524 | // so that you can, for example, press shift-up at the start of |
|
692 | // so that you can, for example, press shift-up at the start of | |
525 | // your selection and have the right thing happen. |
|
693 | // your selection and have the right thing happen. | |
526 | if (rs) { |
|
694 | if (rs) { | |
527 |
|
|
695 | var head = sr.start == rs.anchor ? to : from; | |
528 |
|
|
696 | var tail = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to; | |
529 | if (!posLess(from, to)) { |
|
697 | if (sel.inverted = posLess(head, tail)) { from = head; to = tail; } | |
530 | reducedSelection = null; |
|
698 | else { reducedSelection = null; from = tail; to = head; } | |
531 | sel.inverted = false; |
|
|||
532 | var tmp = from; from = to; to = tmp; |
|
|||
533 | } |
|
|||
534 | } |
|
699 | } | |
535 |
|
700 | |||
536 | // In some cases (cursor on same line as before), we don't have |
|
701 | // In some cases (cursor on same line as before), we don't have | |
@@ -550,8 +715,8 b' var CodeMirror = (function() {' | |||||
550 | var ch = nl > -1 ? start - nl : start, endline = editing.to - 1, edend = editing.text.length; |
|
715 | var ch = nl > -1 ? start - nl : start, endline = editing.to - 1, edend = editing.text.length; | |
551 | for (;;) { |
|
716 | for (;;) { | |
552 | c = editing.text.charAt(edend); |
|
717 | c = editing.text.charAt(edend); | |
|
718 | if (text.charAt(end) != c) {++end; ++edend; break;} | |||
553 | if (c == "\n") endline--; |
|
719 | if (c == "\n") endline--; | |
554 | if (text.charAt(end) != c) {++end; ++edend; break;} |
|
|||
555 | if (edend <= start || end <= start) break; |
|
720 | if (edend <= start || end <= start) break; | |
556 | --end; --edend; |
|
721 | --end; --edend; | |
557 | } |
|
722 | } | |
@@ -580,22 +745,36 b' var CodeMirror = (function() {' | |||||
580 | editing = {text: text, from: from, to: to, start: startch, end: endch}; |
|
745 | editing = {text: text, from: from, to: to, start: startch, end: endch}; | |
581 | setSelRange(input, startch, reducedSelection ? startch : endch); |
|
746 | setSelRange(input, startch, reducedSelection ? startch : endch); | |
582 | } |
|
747 | } | |
|
748 | function focusInput() { | |||
|
749 | if (options.readOnly != "nocursor") input.focus(); | |||
|
750 | } | |||
583 |
|
751 | |||
|
752 | function scrollEditorIntoView() { | |||
|
753 | if (!cursor.getBoundingClientRect) return; | |||
|
754 | var rect = cursor.getBoundingClientRect(); | |||
|
755 | var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); | |||
|
756 | if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView(); | |||
|
757 | } | |||
584 | function scrollCursorIntoView() { |
|
758 | function scrollCursorIntoView() { | |
585 | var cursor = localCoords(sel.inverted ? sel.from : sel.to); |
|
759 | var cursor = localCoords(sel.inverted ? sel.from : sel.to); | |
586 | return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot); |
|
760 | return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot); | |
587 | } |
|
761 | } | |
588 | function scrollIntoView(x1, y1, x2, y2) { |
|
762 | function scrollIntoView(x1, y1, x2, y2) { | |
589 | var pl = paddingLeft(), pt = paddingTop(); |
|
763 | var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight(); | |
590 | y1 += pt; y2 += pt; x1 += pl; x2 += pl; |
|
764 | y1 += pt; y2 += pt; x1 += pl; x2 += pl; | |
591 |
var screen = |
|
765 | var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true; | |
592 |
if (y1 < screentop) { |
|
766 | if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;} | |
593 |
else if (y2 > screentop + screen) { |
|
767 | else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;} | |
594 |
|
768 | |||
595 |
var screenw = |
|
769 | var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; | |
596 | if (x1 < screenleft) {wrapper.scrollLeft = Math.max(0, x1 - 10); scrolled = true;} |
|
770 | var gutterw = options.fixedGutter ? gutter.clientWidth : 0; | |
|
771 | if (x1 < screenleft + gutterw) { | |||
|
772 | if (x1 < 50) x1 = 0; | |||
|
773 | scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw); | |||
|
774 | scrolled = true; | |||
|
775 | } | |||
597 | else if (x2 > screenw + screenleft) { |
|
776 | else if (x2 > screenw + screenleft) { | |
598 |
|
|
777 | scroller.scrollLeft = x2 + 10 - screenw; | |
599 | scrolled = true; |
|
778 | scrolled = true; | |
600 | if (x2 > code.clientWidth) result = false; |
|
779 | if (x2 > code.clientWidth) result = false; | |
601 | } |
|
780 | } | |
@@ -604,15 +783,15 b' var CodeMirror = (function() {' | |||||
604 | } |
|
783 | } | |
605 |
|
784 | |||
606 | function visibleLines() { |
|
785 | function visibleLines() { | |
607 |
var lh = lineHeight(), top = |
|
786 | var lh = lineHeight(), top = scroller.scrollTop - paddingTop(); | |
608 | return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))), |
|
787 | return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))), | |
609 |
to: Math.min(lines.length, Math.ceil((top + |
|
788 | to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))}; | |
610 | } |
|
789 | } | |
611 | // Uses a set of changes plus the current scroll position to |
|
790 | // Uses a set of changes plus the current scroll position to | |
612 | // determine which DOM updates have to be made, and makes the |
|
791 | // determine which DOM updates have to be made, and makes the | |
613 | // updates. |
|
792 | // updates. | |
614 | function updateDisplay(changes) { |
|
793 | function updateDisplay(changes) { | |
615 |
if (! |
|
794 | if (!scroller.clientWidth) { | |
616 | showingFrom = showingTo = 0; |
|
795 | showingFrom = showingTo = 0; | |
617 | return; |
|
796 | return; | |
618 | } |
|
797 | } | |
@@ -629,7 +808,7 b' var CodeMirror = (function() {' | |||||
629 | intact2.push(range); |
|
808 | intact2.push(range); | |
630 | else { |
|
809 | else { | |
631 | if (change.from > range.from) |
|
810 | if (change.from > range.from) | |
632 | intact2.push({from: range.from, to: change.from, domStart: range.domStart}) |
|
811 | intact2.push({from: range.from, to: change.from, domStart: range.domStart}); | |
633 | if (change.to < range.to) |
|
812 | if (change.to < range.to) | |
634 | intact2.push({from: change.to + diff, to: range.to + diff, |
|
813 | intact2.push({from: change.to + diff, to: range.to + diff, | |
635 | domStart: range.domStart + (change.to - range.from)}); |
|
814 | domStart: range.domStart + (change.to - range.from)}); | |
@@ -659,6 +838,7 b' var CodeMirror = (function() {' | |||||
659 | if (domPos != domEnd || pos != to) { |
|
838 | if (domPos != domEnd || pos != to) { | |
660 | changedLines += Math.abs(to - pos); |
|
839 | changedLines += Math.abs(to - pos); | |
661 | updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos}); |
|
840 | updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos}); | |
|
841 | if (to - pos != domEnd - domPos) gutterDirty = true; | |||
662 | } |
|
842 | } | |
663 |
|
843 | |||
664 | if (!updates.length) return; |
|
844 | if (!updates.length) return; | |
@@ -674,13 +854,23 b' var CodeMirror = (function() {' | |||||
674 |
|
854 | |||
675 | // Position the mover div to align with the lines it's supposed |
|
855 | // Position the mover div to align with the lines it's supposed | |
676 | // to be showing (which will cover the visible display) |
|
856 | // to be showing (which will cover the visible display) | |
677 |
var different = from != showingFrom || to != showingTo || lastHeight != |
|
857 | var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight; | |
678 | showingFrom = from; showingTo = to; |
|
858 | showingFrom = from; showingTo = to; | |
679 | mover.style.top = (from * lineHeight()) + "px"; |
|
859 | mover.style.top = (from * lineHeight()) + "px"; | |
680 | if (different) { |
|
860 | if (different) { | |
681 |
lastHeight = |
|
861 | lastHeight = scroller.clientHeight; | |
682 | code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px"; |
|
862 | code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px"; | |
683 | updateGutter(); |
|
863 | } | |
|
864 | if (different || gutterDirty) updateGutter(); | |||
|
865 | ||||
|
866 | if (maxWidth == null) maxWidth = stringWidth(maxLine); | |||
|
867 | if (maxWidth > scroller.clientWidth) { | |||
|
868 | lineSpace.style.width = maxWidth + "px"; | |||
|
869 | // Needed to prevent odd wrapping/hiding of widgets placed in here. | |||
|
870 | code.style.width = ""; | |||
|
871 | code.style.width = scroller.scrollWidth + "px"; | |||
|
872 | } else { | |||
|
873 | lineSpace.style.width = code.style.width = ""; | |||
684 | } |
|
874 | } | |
685 |
|
875 | |||
686 | // Since this is all rather error prone, it is honoured with the |
|
876 | // Since this is all rather error prone, it is honoured with the | |
@@ -712,7 +902,7 b' var CodeMirror = (function() {' | |||||
712 | // there .innerHTML on PRE nodes is dumb, and discards |
|
902 | // there .innerHTML on PRE nodes is dumb, and discards | |
713 | // whitespace. |
|
903 | // whitespace. | |
714 | var sfrom = sel.from.line, sto = sel.to.line, off = 0, |
|
904 | var sfrom = sel.from.line, sto = sel.to.line, off = 0, | |
715 |
scratch = badInnerHTML && |
|
905 | scratch = badInnerHTML && targetDocument.createElement("div"); | |
716 | for (var i = 0, e = updates.length; i < e; ++i) { |
|
906 | for (var i = 0, e = updates.length; i < e; ++i) { | |
717 | var rec = updates[i]; |
|
907 | var rec = updates[i]; | |
718 | var extra = (rec.to - rec.from) - rec.domSize; |
|
908 | var extra = (rec.to - rec.from) - rec.domSize; | |
@@ -722,7 +912,7 b' var CodeMirror = (function() {' | |||||
722 | lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild); |
|
912 | lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild); | |
723 | else if (extra) { |
|
913 | else if (extra) { | |
724 | for (var j = Math.max(0, extra); j > 0; --j) |
|
914 | for (var j = Math.max(0, extra); j > 0; --j) | |
725 |
lineDiv.insertBefore( |
|
915 | lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter); | |
726 | for (var j = Math.max(0, -extra); j > 0; --j) |
|
916 | for (var j = Math.max(0, -extra); j > 0; --j) | |
727 | lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild); |
|
917 | lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild); | |
728 | } |
|
918 | } | |
@@ -753,10 +943,10 b' var CodeMirror = (function() {' | |||||
753 |
|
943 | |||
754 | function updateGutter() { |
|
944 | function updateGutter() { | |
755 | if (!options.gutter && !options.lineNumbers) return; |
|
945 | if (!options.gutter && !options.lineNumbers) return; | |
756 |
var hText = mover.offsetHeight, hEditor = |
|
946 | var hText = mover.offsetHeight, hEditor = scroller.clientHeight; | |
757 | gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; |
|
947 | gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; | |
758 | var html = []; |
|
948 | var html = []; | |
759 | for (var i = showingFrom; i < showingTo; ++i) { |
|
949 | for (var i = showingFrom; i < Math.max(showingTo, showingFrom + 1); ++i) { | |
760 | var marker = lines[i].gutterMarker; |
|
950 | var marker = lines[i].gutterMarker; | |
761 | var text = options.lineNumbers ? i + options.firstLineNumber : null; |
|
951 | var text = options.lineNumbers ? i + options.firstLineNumber : null; | |
762 | if (marker && marker.text) |
|
952 | if (marker && marker.text) | |
@@ -769,37 +959,43 b' var CodeMirror = (function() {' | |||||
769 | gutterText.innerHTML = html.join(""); |
|
959 | gutterText.innerHTML = html.join(""); | |
770 | var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = ""; |
|
960 | var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = ""; | |
771 | while (val.length + pad.length < minwidth) pad += "\u00a0"; |
|
961 | while (val.length + pad.length < minwidth) pad += "\u00a0"; | |
772 |
if (pad) firstNode.insertBefore( |
|
962 | if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild); | |
773 | gutter.style.display = ""; |
|
963 | gutter.style.display = ""; | |
774 | lineSpace.style.marginLeft = gutter.offsetWidth + "px"; |
|
964 | lineSpace.style.marginLeft = gutter.offsetWidth + "px"; | |
|
965 | gutterDirty = false; | |||
775 | } |
|
966 | } | |
776 | function updateCursor() { |
|
967 | function updateCursor() { | |
777 | var head = sel.inverted ? sel.from : sel.to; |
|
968 | var head = sel.inverted ? sel.from : sel.to, lh = lineHeight(); | |
778 | var x = charX(head.line, head.ch) + "px", y = (head.line - showingFrom) * lineHeight() + "px"; |
|
969 | var x = charX(head.line, head.ch); | |
779 | inputDiv.style.top = y; inputDiv.style.left = x; |
|
970 | var top = head.line * lh - scroller.scrollTop; | |
|
971 | inputDiv.style.top = Math.max(Math.min(top, scroller.offsetHeight), 0) + "px"; | |||
|
972 | inputDiv.style.left = (x - scroller.scrollLeft) + "px"; | |||
780 | if (posEq(sel.from, sel.to)) { |
|
973 | if (posEq(sel.from, sel.to)) { | |
781 | cursor.style.top = y; cursor.style.left = x; |
|
974 | cursor.style.top = (head.line - showingFrom) * lh + "px"; | |
|
975 | cursor.style.left = x + "px"; | |||
782 | cursor.style.display = ""; |
|
976 | cursor.style.display = ""; | |
783 | } |
|
977 | } | |
784 | else cursor.style.display = "none"; |
|
978 | else cursor.style.display = "none"; | |
785 | } |
|
979 | } | |
786 |
|
980 | |||
|
981 | function setSelectionUser(from, to) { | |||
|
982 | var sh = shiftSelecting && clipPos(shiftSelecting); | |||
|
983 | if (sh) { | |||
|
984 | if (posLess(sh, from)) from = sh; | |||
|
985 | else if (posLess(to, sh)) to = sh; | |||
|
986 | } | |||
|
987 | setSelection(from, to); | |||
|
988 | } | |||
787 | // Update the selection. Last two args are only used by |
|
989 | // Update the selection. Last two args are only used by | |
788 | // updateLines, since they have to be expressed in the line |
|
990 | // updateLines, since they have to be expressed in the line | |
789 | // numbers before the update. |
|
991 | // numbers before the update. | |
790 | function setSelection(from, to, oldFrom, oldTo) { |
|
992 | function setSelection(from, to, oldFrom, oldTo) { | |
791 | if (posEq(sel.from, from) && posEq(sel.to, to)) return; |
|
993 | if (posEq(sel.from, from) && posEq(sel.to, to)) return; | |
792 | var sh = shiftSelecting && clipPos(shiftSelecting); |
|
|||
793 | if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} |
|
994 | if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} | |
794 | if (sh) { |
|
|||
795 | if (posLess(sh, from)) from = sh; |
|
|||
796 | else if (posLess(to, sh)) to = sh; |
|
|||
797 | } |
|
|||
798 |
|
995 | |||
799 | var startEq = posEq(sel.to, to), endEq = posEq(sel.from, from); |
|
|||
800 | if (posEq(from, to)) sel.inverted = false; |
|
996 | if (posEq(from, to)) sel.inverted = false; | |
801 |
else if (s |
|
997 | else if (posEq(from, sel.to)) sel.inverted = false; | |
802 |
else if ( |
|
998 | else if (posEq(to, sel.from)) sel.inverted = true; | |
803 |
|
999 | |||
804 | // Some ugly logic used to only mark the lines that actually did |
|
1000 | // Some ugly logic used to only mark the lines that actually did | |
805 | // see a change in selection as changed, rather than the whole |
|
1001 | // see a change in selection as changed, rather than the whole | |
@@ -829,9 +1025,9 b' var CodeMirror = (function() {' | |||||
829 | sel.from = from; sel.to = to; |
|
1025 | sel.from = from; sel.to = to; | |
830 | selectionChanged = true; |
|
1026 | selectionChanged = true; | |
831 | } |
|
1027 | } | |
832 | function setCursor(line, ch) { |
|
1028 | function setCursor(line, ch, user) { | |
833 | var pos = clipPos({line: line, ch: ch || 0}); |
|
1029 | var pos = clipPos({line: line, ch: ch || 0}); | |
834 | setSelection(pos, pos); |
|
1030 | (user ? setSelectionUser : setSelection)(pos, pos); | |
835 | } |
|
1031 | } | |
836 |
|
1032 | |||
837 | function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));} |
|
1033 | function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));} | |
@@ -845,11 +1041,12 b' var CodeMirror = (function() {' | |||||
845 | } |
|
1041 | } | |
846 |
|
1042 | |||
847 | function scrollPage(down) { |
|
1043 | function scrollPage(down) { | |
848 |
var linesPerPage = Math.floor( |
|
1044 | var linesPerPage = Math.floor(scroller.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to; | |
849 | setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch); |
|
1045 | setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch, true); | |
850 | } |
|
1046 | } | |
851 | function scrollEnd(top) { |
|
1047 | function scrollEnd(top) { | |
852 | setCursor(top ? 0 : lines.length - 1); |
|
1048 | var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length}; | |
|
1049 | setSelectionUser(pos, pos); | |||
853 | } |
|
1050 | } | |
854 | function selectAll() { |
|
1051 | function selectAll() { | |
855 | var endLine = lines.length - 1; |
|
1052 | var endLine = lines.length - 1; | |
@@ -859,8 +1056,11 b' var CodeMirror = (function() {' | |||||
859 | var line = lines[pos.line].text; |
|
1056 | var line = lines[pos.line].text; | |
860 | var start = pos.ch, end = pos.ch; |
|
1057 | var start = pos.ch, end = pos.ch; | |
861 | while (start > 0 && /\w/.test(line.charAt(start - 1))) --start; |
|
1058 | while (start > 0 && /\w/.test(line.charAt(start - 1))) --start; | |
862 |
while (end < line.length |
|
1059 | while (end < line.length && /\w/.test(line.charAt(end))) ++end; | |
863 | setSelection({line: pos.line, ch: start}, {line: pos.line, ch: end}); |
|
1060 | setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end}); | |
|
1061 | } | |||
|
1062 | function selectLine(line) { | |||
|
1063 | setSelectionUser({line: line, ch: 0}, {line: line, ch: lines[line].text.length}); | |||
864 | } |
|
1064 | } | |
865 | function handleEnter() { |
|
1065 | function handleEnter() { | |
866 | replaceSelection("\n", "end"); |
|
1066 | replaceSelection("\n", "end"); | |
@@ -868,12 +1068,17 b' var CodeMirror = (function() {' | |||||
868 | indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart"); |
|
1068 | indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart"); | |
869 | } |
|
1069 | } | |
870 | function handleTab(shift) { |
|
1070 | function handleTab(shift) { | |
|
1071 | function indentSelected(mode) { | |||
|
1072 | if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); | |||
|
1073 | var e = sel.to.line - (sel.to.ch ? 0 : 1); | |||
|
1074 | for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); | |||
|
1075 | } | |||
871 | shiftSelecting = null; |
|
1076 | shiftSelecting = null; | |
872 | switch (options.tabMode) { |
|
1077 | switch (options.tabMode) { | |
873 | case "default": |
|
1078 | case "default": | |
874 | return false; |
|
1079 | return false; | |
875 | case "indent": |
|
1080 | case "indent": | |
876 | for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, "smart"); |
|
1081 | indentSelected("smart"); | |
877 | break; |
|
1082 | break; | |
878 | case "classic": |
|
1083 | case "classic": | |
879 | if (posEq(sel.from, sel.to)) { |
|
1084 | if (posEq(sel.from, sel.to)) { | |
@@ -882,11 +1087,15 b' var CodeMirror = (function() {' | |||||
882 | break; |
|
1087 | break; | |
883 | } |
|
1088 | } | |
884 | case "shift": |
|
1089 | case "shift": | |
885 | for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, shift ? "subtract" : "add"); |
|
1090 | indentSelected(shift ? "subtract" : "add"); | |
886 | break; |
|
1091 | break; | |
887 | } |
|
1092 | } | |
888 | return true; |
|
1093 | return true; | |
889 | } |
|
1094 | } | |
|
1095 | function smartHome() { | |||
|
1096 | var firstNonWS = Math.max(0, lines[sel.from.line].text.search(/\S/)); | |||
|
1097 | setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true); | |||
|
1098 | } | |||
890 |
|
1099 | |||
891 | function indentLine(n, how) { |
|
1100 | function indentLine(n, how) { | |
892 | if (how == "smart") { |
|
1101 | if (how == "smart") { | |
@@ -924,21 +1133,20 b' var CodeMirror = (function() {' | |||||
924 | for (var i = 0, l = lines.length; i < l; ++i) |
|
1133 | for (var i = 0, l = lines.length; i < l; ++i) | |
925 | lines[i].stateAfter = null; |
|
1134 | lines[i].stateAfter = null; | |
926 | work = [0]; |
|
1135 | work = [0]; | |
|
1136 | startWorker(); | |||
927 | } |
|
1137 | } | |
928 | function gutterChanged() { |
|
1138 | function gutterChanged() { | |
929 | var visible = options.gutter || options.lineNumbers; |
|
1139 | var visible = options.gutter || options.lineNumbers; | |
930 | gutter.style.display = visible ? "" : "none"; |
|
1140 | gutter.style.display = visible ? "" : "none"; | |
931 |
if (visible) |
|
1141 | if (visible) gutterDirty = true; | |
932 | else lineDiv.parentNode.style.marginLeft = 0; |
|
1142 | else lineDiv.parentNode.style.marginLeft = 0; | |
933 | } |
|
1143 | } | |
934 |
|
1144 | |||
935 | function markText(from, to, className) { |
|
1145 | function markText(from, to, className) { | |
936 | from = clipPos(from); to = clipPos(to); |
|
1146 | from = clipPos(from); to = clipPos(to); | |
937 |
var |
|
1147 | var set = []; | |
938 | function add(line, from, to, className) { |
|
1148 | function add(line, from, to, className) { | |
939 |
|
|
1149 | mark = lines[line].addMark(from, to, className, set); | |
940 | mark.line = line; |
|
|||
941 | accum.push(mark); |
|
|||
942 | } |
|
1150 | } | |
943 | if (from.line == to.line) add(from.line, from.ch, to.ch, className); |
|
1151 | if (from.line == to.line) add(from.line, from.ch, to.ch, className); | |
944 | else { |
|
1152 | else { | |
@@ -948,30 +1156,51 b' var CodeMirror = (function() {' | |||||
948 | add(to.line, 0, to.ch, className); |
|
1156 | add(to.line, 0, to.ch, className); | |
949 | } |
|
1157 | } | |
950 | changes.push({from: from.line, to: to.line + 1}); |
|
1158 | changes.push({from: from.line, to: to.line + 1}); | |
951 | return function() { |
|
1159 | return new TextMarker(set); | |
952 | var start, end; |
|
1160 | } | |
953 | for (var i = 0; i < accum.length; ++i) { |
|
1161 | ||
954 | var mark = accum[i], found = indexOf(lines, mark.line); |
|
1162 | function TextMarker(set) { this.set = set; } | |
955 | mark.line.removeMark(mark); |
|
1163 | TextMarker.prototype.clear = operation(function() { | |
956 | if (found > -1) { |
|
1164 | for (var i = 0, e = this.set.length; i < e; ++i) { | |
957 | if (start == null) start = found; |
|
1165 | var mk = this.set[i].marked; | |
958 | end = found; |
|
1166 | for (var j = 0; j < mk.length; ++j) { | |
|
1167 | if (mk[j].set == this.set) mk.splice(j--, 1); | |||
|
1168 | } | |||
|
1169 | } | |||
|
1170 | // We don't know the exact lines that changed. Refreshing is | |||
|
1171 | // cheaper than finding them. | |||
|
1172 | changes.push({from: 0, to: lines.length}); | |||
|
1173 | }); | |||
|
1174 | TextMarker.prototype.find = function() { | |||
|
1175 | var from, to; | |||
|
1176 | for (var i = 0, e = this.set.length; i < e; ++i) { | |||
|
1177 | var line = this.set[i], mk = line.marked; | |||
|
1178 | for (var j = 0; j < mk.length; ++j) { | |||
|
1179 | var mark = mk[j]; | |||
|
1180 | if (mark.set == this.set) { | |||
|
1181 | if (mark.from != null || mark.to != null) { | |||
|
1182 | var found = indexOf(lines, line); | |||
|
1183 | if (found > -1) { | |||
|
1184 | if (mark.from != null) from = {line: found, ch: mark.from}; | |||
|
1185 | if (mark.to != null) to = {line: found, ch: mark.to}; | |||
|
1186 | } | |||
|
1187 | } | |||
959 | } |
|
1188 | } | |
960 | } |
|
1189 | } | |
961 | if (start != null) changes.push({from: start, to: end + 1}); |
|
1190 | } | |
962 | }; |
|
1191 | return {from: from, to: to}; | |
963 | } |
|
1192 | }; | |
964 |
|
1193 | |||
965 | function addGutterMarker(line, text, className) { |
|
1194 | function addGutterMarker(line, text, className) { | |
966 | if (typeof line == "number") line = lines[clipLine(line)]; |
|
1195 | if (typeof line == "number") line = lines[clipLine(line)]; | |
967 | line.gutterMarker = {text: text, style: className}; |
|
1196 | line.gutterMarker = {text: text, style: className}; | |
968 | updateGutter(); |
|
1197 | gutterDirty = true; | |
969 | return line; |
|
1198 | return line; | |
970 | } |
|
1199 | } | |
971 | function removeGutterMarker(line) { |
|
1200 | function removeGutterMarker(line) { | |
972 | if (typeof line == "number") line = lines[clipLine(line)]; |
|
1201 | if (typeof line == "number") line = lines[clipLine(line)]; | |
973 | line.gutterMarker = null; |
|
1202 | line.gutterMarker = null; | |
974 | updateGutter(); |
|
1203 | gutterDirty = true; | |
975 | } |
|
1204 | } | |
976 | function setLineClass(line, className) { |
|
1205 | function setLineClass(line, className) { | |
977 | if (typeof line == "number") { |
|
1206 | if (typeof line == "number") { | |
@@ -982,8 +1211,10 b' var CodeMirror = (function() {' | |||||
982 | var no = indexOf(lines, line); |
|
1211 | var no = indexOf(lines, line); | |
983 | if (no == -1) return null; |
|
1212 | if (no == -1) return null; | |
984 | } |
|
1213 | } | |
985 |
line.className = className |
|
1214 | if (line.className != className) { | |
986 | changes.push({from: no, to: no + 1}); |
|
1215 | line.className = className; | |
|
1216 | changes.push({from: no, to: no + 1}); | |||
|
1217 | } | |||
987 | return line; |
|
1218 | return line; | |
988 | } |
|
1219 | } | |
989 |
|
1220 | |||
@@ -1001,35 +1232,44 b' var CodeMirror = (function() {' | |||||
1001 | return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style}; |
|
1232 | return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style}; | |
1002 | } |
|
1233 | } | |
1003 |
|
1234 | |||
|
1235 | function stringWidth(str) { | |||
|
1236 | measure.innerHTML = "<pre><span>x</span></pre>"; | |||
|
1237 | measure.firstChild.firstChild.firstChild.nodeValue = str; | |||
|
1238 | return measure.firstChild.firstChild.offsetWidth || 10; | |||
|
1239 | } | |||
1004 | // These are used to go from pixel positions to character |
|
1240 | // These are used to go from pixel positions to character | |
1005 |
// positions, taking |
|
1241 | // positions, taking varying character widths into account. | |
1006 | function charX(line, pos) { |
|
1242 | function charX(line, pos) { | |
1007 | var text = lines[line].text, span = measure.firstChild; |
|
1243 | if (pos == 0) return 0; | |
1008 | if (text.lastIndexOf("\t", pos) == -1) return pos * charWidth(); |
|
1244 | measure.innerHTML = "<pre><span>" + lines[line].getHTML(null, null, false, pos) + "</span></pre>"; | |
1009 | var old = span.firstChild.nodeValue; |
|
1245 | return measure.firstChild.firstChild.offsetWidth; | |
1010 | try { |
|
|||
1011 | span.firstChild.nodeValue = text.slice(0, pos); |
|
|||
1012 | return span.offsetWidth; |
|
|||
1013 | } finally {span.firstChild.nodeValue = old;} |
|
|||
1014 | } |
|
1246 | } | |
1015 | function charFromX(line, x) { |
|
1247 | function charFromX(line, x) { | |
1016 | var text = lines[line].text, cw = charWidth(); |
|
|||
1017 | if (x <= 0) return 0; |
|
1248 | if (x <= 0) return 0; | |
1018 | if (text.indexOf("\t") == -1) return Math.min(text.length, Math.round(x / cw)); |
|
1249 | var lineObj = lines[line], text = lineObj.text; | |
1019 | var mspan = measure.firstChild, mtext = mspan.firstChild, old = mtext.nodeValue; |
|
1250 | function getX(len) { | |
1020 | try { |
|
1251 | measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>"; | |
1021 | mtext.nodeValue = text; |
|
1252 | return measure.firstChild.firstChild.offsetWidth; | |
1022 | var from = 0, fromX = 0, to = text.length, toX = mspan.offsetWidth; |
|
1253 | } | |
1023 | if (x > toX) return to; |
|
1254 | var from = 0, fromX = 0, to = text.length, toX; | |
1024 | for (;;) { |
|
1255 | // Guess a suitable upper bound for our search. | |
1025 | if (to - from <= 1) return (toX - x > x - fromX) ? from : to; |
|
1256 | var estimated = Math.min(to, Math.ceil(x / stringWidth("x"))); | |
1026 | var middle = Math.ceil((from + to) / 2); |
|
1257 | for (;;) { | |
1027 | mtext.nodeValue = text.slice(0, middle); |
|
1258 | var estX = getX(estimated); | |
1028 | var curX = mspan.offsetWidth; |
|
1259 | if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); | |
1029 | if (curX > x) {to = middle; toX = curX;} |
|
1260 | else {toX = estX; to = estimated; break;} | |
1030 | else {from = middle; fromX = curX;} |
|
1261 | } | |
1031 | } |
|
1262 | if (x > toX) return to; | |
1032 | } finally {mtext.nodeValue = old;} |
|
1263 | // Try to guess a suitable lower bound as well. | |
|
1264 | estimated = Math.floor(to * 0.8); estX = getX(estimated); | |||
|
1265 | if (estX < x) {from = estimated; fromX = estX;} | |||
|
1266 | // Do a binary search between these bounds. | |||
|
1267 | for (;;) { | |||
|
1268 | if (to - from <= 1) return (toX - x > x - fromX) ? from : to; | |||
|
1269 | var middle = Math.ceil((from + to) / 2), middleX = getX(middle); | |||
|
1270 | if (middleX > x) {to = middle; toX = middleX;} | |||
|
1271 | else {from = middle; fromX = middleX;} | |||
|
1272 | } | |||
1033 | } |
|
1273 | } | |
1034 |
|
1274 | |||
1035 | function localCoords(pos, inLineWrap) { |
|
1275 | function localCoords(pos, inLineWrap) { | |
@@ -1043,45 +1283,61 b' var CodeMirror = (function() {' | |||||
1043 |
|
1283 | |||
1044 | function lineHeight() { |
|
1284 | function lineHeight() { | |
1045 | var nlines = lineDiv.childNodes.length; |
|
1285 | var nlines = lineDiv.childNodes.length; | |
1046 | if (nlines) return lineDiv.offsetHeight / nlines; |
|
1286 | if (nlines) return (lineDiv.offsetHeight / nlines) || 1; | |
1047 | else return measure.firstChild.offsetHeight || 1; |
|
1287 | measure.innerHTML = "<pre>x</pre>"; | |
|
1288 | return measure.firstChild.offsetHeight || 1; | |||
1048 | } |
|
1289 | } | |
1049 | function charWidth() {return (measure.firstChild.offsetWidth || 320) / 40;} |
|
|||
1050 | function paddingTop() {return lineSpace.offsetTop;} |
|
1290 | function paddingTop() {return lineSpace.offsetTop;} | |
1051 | function paddingLeft() {return lineSpace.offsetLeft;} |
|
1291 | function paddingLeft() {return lineSpace.offsetLeft;} | |
1052 |
|
1292 | |||
1053 | function posFromMouse(e, liberal) { |
|
1293 | function posFromMouse(e, liberal) { | |
1054 |
var off = eltOffset( |
|
1294 | var offW = eltOffset(scroller, true), x, y; | |
1055 | x = e.pageX() - off.left, |
|
1295 | // Fails unpredictably on IE[67] when mouse is dragged around quickly. | |
1056 | y = e.pageY() - off.top; |
|
1296 | try { x = e.clientX; y = e.clientY; } catch (e) { return null; } | |
1057 | if (!liberal && e.target() != lineSpace.parentNode && !(e.target() == wrapper && y > (lines.length * lineHeight()))) |
|
1297 | // This is a mess of a heuristic to try and determine whether a | |
1058 | for (var n = e.target(); n != lineDiv && n != cursor; n = n.parentNode) |
|
1298 | // scroll-bar was clicked or not, and to return null if one was | |
1059 | if (!n || n == wrapper) return null; |
|
1299 | // (and !liberal). | |
1060 | var line = showingFrom + Math.floor(y / lineHeight()); |
|
1300 | if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight)) | |
1061 | return clipPos({line: line, ch: charFromX(clipLine(line), x)}); |
|
1301 | return null; | |
|
1302 | var offL = eltOffset(lineSpace, true); | |||
|
1303 | var line = showingFrom + Math.floor((y - offL.top) / lineHeight()); | |||
|
1304 | return clipPos({line: line, ch: charFromX(clipLine(line), x - offL.left)}); | |||
1062 | } |
|
1305 | } | |
1063 | function onContextMenu(e) { |
|
1306 | function onContextMenu(e) { | |
1064 | var pos = posFromMouse(e); |
|
1307 | var pos = posFromMouse(e); | |
1065 | if (!pos || window.opera) return; // Opera is difficult. |
|
1308 | if (!pos || window.opera) return; // Opera is difficult. | |
1066 | if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) |
|
1309 | if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) | |
1067 | setCursor(pos.line, pos.ch); |
|
1310 | operation(setCursor)(pos.line, pos.ch); | |
1068 |
|
1311 | |||
1069 | var oldCSS = input.style.cssText; |
|
1312 | var oldCSS = input.style.cssText; | |
1070 | input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.pageY() - 1) + |
|
1313 | inputDiv.style.position = "absolute"; | |
1071 | "px; left: " + (e.pageX() - 1) + "px; z-index: 1000; background: white; " + |
|
1314 | input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + | |
1072 | "border-width: 0; outline: none; overflow: hidden;"; |
|
1315 | "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + | |
|
1316 | "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; | |||
|
1317 | leaveInputAlone = true; | |||
1073 | var val = input.value = getSelection(); |
|
1318 | var val = input.value = getSelection(); | |
1074 |
|
|
1319 | focusInput(); | |
1075 | setSelRange(input, 0, val.length); |
|
1320 | setSelRange(input, 0, input.value.length); | |
1076 | if (gecko) e.stop(); |
|
1321 | function rehide() { | |
1077 | leaveInputAlone = true; |
|
1322 | var newVal = splitLines(input.value).join("\n"); | |
1078 | setTimeout(function() { |
|
1323 | if (newVal != val) operation(replaceSelection)(newVal, "end"); | |
1079 | if (input.value != val) operation(replaceSelection)(input.value, "end"); |
|
1324 | inputDiv.style.position = "relative"; | |
1080 | input.style.cssText = oldCSS; |
|
1325 | input.style.cssText = oldCSS; | |
1081 | leaveInputAlone = false; |
|
1326 | leaveInputAlone = false; | |
1082 | prepareInput(); |
|
1327 | prepareInput(); | |
1083 | slowPoll(); |
|
1328 | slowPoll(); | |
1084 |
} |
|
1329 | } | |
|
1330 | ||||
|
1331 | if (gecko) { | |||
|
1332 | e_stop(e); | |||
|
1333 | var mouseup = connect(window, "mouseup", function() { | |||
|
1334 | mouseup(); | |||
|
1335 | setTimeout(rehide, 20); | |||
|
1336 | }, true); | |||
|
1337 | } | |||
|
1338 | else { | |||
|
1339 | setTimeout(rehide, 50); | |||
|
1340 | } | |||
1085 | } |
|
1341 | } | |
1086 |
|
1342 | |||
1087 | // Cursor-blinking |
|
1343 | // Cursor-blinking | |
@@ -1120,19 +1376,18 b' var CodeMirror = (function() {' | |||||
1120 | } |
|
1376 | } | |
1121 | } |
|
1377 | } | |
1122 | } |
|
1378 | } | |
1123 |
for (var i = head.line, e = forward ? Math.min(i + |
|
1379 | for (var i = head.line, e = forward ? Math.min(i + 100, lines.length) : Math.max(-1, i - 100); i != e; i+=d) { | |
1124 | var line = lines[i], first = i == head.line; |
|
1380 | var line = lines[i], first = i == head.line; | |
1125 | var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); |
|
1381 | var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); | |
1126 |
if (found) |
|
1382 | if (found) break; | |
1127 | var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; |
|
|||
1128 | var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style), |
|
|||
1129 | two = markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style); |
|
|||
1130 | var clear = operation(function(){one(); two();}); |
|
|||
1131 | if (autoclear) setTimeout(clear, 800); |
|
|||
1132 | else bracketHighlighted = clear; |
|
|||
1133 | break; |
|
|||
1134 | } |
|
|||
1135 | } |
|
1383 | } | |
|
1384 | if (!found) found = {pos: null, match: false}; | |||
|
1385 | var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; | |||
|
1386 | var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style), | |||
|
1387 | two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style); | |||
|
1388 | var clear = operation(function(){one.clear(); two && two.clear();}); | |||
|
1389 | if (autoclear) setTimeout(clear, 800); | |||
|
1390 | else bracketHighlighted = clear; | |||
1136 | } |
|
1391 | } | |
1137 |
|
1392 | |||
1138 | // Finds the line to start with when starting a parse. Tries to |
|
1393 | // Finds the line to start with when starting a parse. Tries to | |
@@ -1148,7 +1403,7 b' var CodeMirror = (function() {' | |||||
1148 | if (line.stateAfter) return search; |
|
1403 | if (line.stateAfter) return search; | |
1149 | var indented = line.indentation(); |
|
1404 | var indented = line.indentation(); | |
1150 | if (minline == null || minindent > indented) { |
|
1405 | if (minline == null || minindent > indented) { | |
1151 | minline = search; |
|
1406 | minline = search - 1; | |
1152 | minindent = indented; |
|
1407 | minindent = indented; | |
1153 | } |
|
1408 | } | |
1154 | } |
|
1409 | } | |
@@ -1163,11 +1418,21 b' var CodeMirror = (function() {' | |||||
1163 | line.highlight(mode, state); |
|
1418 | line.highlight(mode, state); | |
1164 | line.stateAfter = copyState(mode, state); |
|
1419 | line.stateAfter = copyState(mode, state); | |
1165 | } |
|
1420 | } | |
1166 | if (!lines[n].stateAfter) work.push(n); |
|
1421 | changes.push({from: start, to: n}); | |
|
1422 | if (n < lines.length && !lines[n].stateAfter) work.push(n); | |||
1167 | return state; |
|
1423 | return state; | |
1168 | } |
|
1424 | } | |
|
1425 | function highlightLines(start, end) { | |||
|
1426 | var state = getStateBefore(start); | |||
|
1427 | for (var i = start; i < end; ++i) { | |||
|
1428 | var line = lines[i]; | |||
|
1429 | line.highlight(mode, state); | |||
|
1430 | line.stateAfter = copyState(mode, state); | |||
|
1431 | } | |||
|
1432 | } | |||
1169 | function highlightWorker() { |
|
1433 | function highlightWorker() { | |
1170 | var end = +new Date + options.workTime; |
|
1434 | var end = +new Date + options.workTime; | |
|
1435 | var foundWork = work.length; | |||
1171 | while (work.length) { |
|
1436 | while (work.length) { | |
1172 | if (!lines[showingFrom].stateAfter) var task = showingFrom; |
|
1437 | if (!lines[showingFrom].stateAfter) var task = showingFrom; | |
1173 | else var task = work.pop(); |
|
1438 | else var task = work.pop(); | |
@@ -1176,20 +1441,29 b' var CodeMirror = (function() {' | |||||
1176 | if (state) state = copyState(mode, state); |
|
1441 | if (state) state = copyState(mode, state); | |
1177 | else state = startState(mode); |
|
1442 | else state = startState(mode); | |
1178 |
|
1443 | |||
|
1444 | var unchanged = 0, compare = mode.compareStates, realChange = false; | |||
1179 | for (var i = start, l = lines.length; i < l; ++i) { |
|
1445 | for (var i = start, l = lines.length; i < l; ++i) { | |
1180 | var line = lines[i], hadState = line.stateAfter; |
|
1446 | var line = lines[i], hadState = line.stateAfter; | |
1181 | if (+new Date > end) { |
|
1447 | if (+new Date > end) { | |
1182 | work.push(i); |
|
1448 | work.push(i); | |
1183 | startWorker(options.workDelay); |
|
1449 | startWorker(options.workDelay); | |
1184 | changes.push({from: task, to: i}); |
|
1450 | if (realChange) changes.push({from: task, to: i + 1}); | |
1185 | return; |
|
1451 | return; | |
1186 | } |
|
1452 | } | |
1187 | var changed = line.highlight(mode, state); |
|
1453 | var changed = line.highlight(mode, state); | |
|
1454 | if (changed) realChange = true; | |||
1188 | line.stateAfter = copyState(mode, state); |
|
1455 | line.stateAfter = copyState(mode, state); | |
1189 | if (hadState && !changed && line.text) break; |
|
1456 | if (compare) { | |
|
1457 | if (hadState && compare(hadState, state)) break; | |||
|
1458 | } else { | |||
|
1459 | if (changed !== false || !hadState) unchanged = 0; | |||
|
1460 | else if (++unchanged > 3) break; | |||
|
1461 | } | |||
1190 | } |
|
1462 | } | |
1191 | changes.push({from: task, to: i}); |
|
1463 | if (realChange) changes.push({from: task, to: i + 1}); | |
1192 | } |
|
1464 | } | |
|
1465 | if (foundWork && options.onHighlightComplete) | |||
|
1466 | options.onHighlightComplete(instance); | |||
1193 | } |
|
1467 | } | |
1194 | function startWorker(time) { |
|
1468 | function startWorker(time) { | |
1195 | if (!work.length) return; |
|
1469 | if (!work.length) return; | |
@@ -1207,24 +1481,29 b' var CodeMirror = (function() {' | |||||
1207 | var reScroll = false; |
|
1481 | var reScroll = false; | |
1208 | if (selectionChanged) reScroll = !scrollCursorIntoView(); |
|
1482 | if (selectionChanged) reScroll = !scrollCursorIntoView(); | |
1209 | if (changes.length) updateDisplay(changes); |
|
1483 | if (changes.length) updateDisplay(changes); | |
1210 | else if (selectionChanged) updateCursor(); |
|
1484 | else { | |
|
1485 | if (selectionChanged) updateCursor(); | |||
|
1486 | if (gutterDirty) updateGutter(); | |||
|
1487 | } | |||
1211 | if (reScroll) scrollCursorIntoView(); |
|
1488 | if (reScroll) scrollCursorIntoView(); | |
1212 | if (selectionChanged) restartBlink(); |
|
1489 | if (selectionChanged) {scrollEditorIntoView(); restartBlink();} | |
1213 |
|
1490 | |||
1214 | // updateInput can be set to a boolean value to force/prevent an |
|
1491 | // updateInput can be set to a boolean value to force/prevent an | |
1215 | // update. |
|
1492 | // update. | |
1216 | if (!leaveInputAlone && (updateInput === true || (updateInput !== false && selectionChanged))) |
|
1493 | if (focused && !leaveInputAlone && | |
|
1494 | (updateInput === true || (updateInput !== false && selectionChanged))) | |||
1217 | prepareInput(); |
|
1495 | prepareInput(); | |
1218 |
|
1496 | |||
1219 | if (selectionChanged && options.onCursorActivity) |
|
|||
1220 | options.onCursorActivity(instance); |
|
|||
1221 | if (textChanged && options.onChange) |
|
|||
1222 | options.onChange(instance); |
|
|||
1223 | if (selectionChanged && options.matchBrackets) |
|
1497 | if (selectionChanged && options.matchBrackets) | |
1224 | setTimeout(operation(function() { |
|
1498 | setTimeout(operation(function() { | |
1225 | if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} |
|
1499 | if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} | |
1226 | matchBrackets(false); |
|
1500 | matchBrackets(false); | |
1227 | }), 20); |
|
1501 | }), 20); | |
|
1502 | var tc = textChanged; // textChanged can be reset by cursoractivity callback | |||
|
1503 | if (selectionChanged && options.onCursorActivity) | |||
|
1504 | options.onCursorActivity(instance); | |||
|
1505 | if (tc && options.onChange && instance) | |||
|
1506 | options.onChange(instance, tc); | |||
1228 | } |
|
1507 | } | |
1229 | var nestedOperation = 0; |
|
1508 | var nestedOperation = 0; | |
1230 | function operation(f) { |
|
1509 | function operation(f) { | |
@@ -1259,6 +1538,7 b' var CodeMirror = (function() {' | |||||
1259 | var newmatch = line.match(query); |
|
1538 | var newmatch = line.match(query); | |
1260 | if (newmatch) match = newmatch; |
|
1539 | if (newmatch) match = newmatch; | |
1261 | else break; |
|
1540 | else break; | |
|
1541 | start++; | |||
1262 | } |
|
1542 | } | |
1263 | } |
|
1543 | } | |
1264 | else { |
|
1544 | else { | |
@@ -1338,9 +1618,21 b' var CodeMirror = (function() {' | |||||
1338 | }, |
|
1618 | }, | |
1339 |
|
1619 | |||
1340 | from: function() {if (this.atOccurrence) return copyPos(this.pos.from);}, |
|
1620 | from: function() {if (this.atOccurrence) return copyPos(this.pos.from);}, | |
1341 | to: function() {if (this.atOccurrence) return copyPos(this.pos.to);} |
|
1621 | to: function() {if (this.atOccurrence) return copyPos(this.pos.to);}, | |
|
1622 | ||||
|
1623 | replace: function(newText) { | |||
|
1624 | var self = this; | |||
|
1625 | if (this.atOccurrence) | |||
|
1626 | operation(function() { | |||
|
1627 | self.pos.to = replaceRange(newText, self.pos.from, self.pos.to); | |||
|
1628 | })(); | |||
|
1629 | } | |||
1342 | }; |
|
1630 | }; | |
1343 |
|
1631 | |||
|
1632 | for (var ext in extensions) | |||
|
1633 | if (extensions.propertyIsEnumerable(ext) && | |||
|
1634 | !instance.propertyIsEnumerable(ext)) | |||
|
1635 | instance[ext] = extensions[ext]; | |||
1344 | return instance; |
|
1636 | return instance; | |
1345 | } // (end of function CodeMirror) |
|
1637 | } // (end of function CodeMirror) | |
1346 |
|
1638 | |||
@@ -1348,6 +1640,7 b' var CodeMirror = (function() {' | |||||
1348 | CodeMirror.defaults = { |
|
1640 | CodeMirror.defaults = { | |
1349 | value: "", |
|
1641 | value: "", | |
1350 | mode: null, |
|
1642 | mode: null, | |
|
1643 | theme: "default", | |||
1351 | indentUnit: 2, |
|
1644 | indentUnit: 2, | |
1352 | indentWithTabs: false, |
|
1645 | indentWithTabs: false, | |
1353 | tabMode: "classic", |
|
1646 | tabMode: "classic", | |
@@ -1356,17 +1649,21 b' var CodeMirror = (function() {' | |||||
1356 | onKeyEvent: null, |
|
1649 | onKeyEvent: null, | |
1357 | lineNumbers: false, |
|
1650 | lineNumbers: false, | |
1358 | gutter: false, |
|
1651 | gutter: false, | |
|
1652 | fixedGutter: false, | |||
1359 | firstLineNumber: 1, |
|
1653 | firstLineNumber: 1, | |
1360 | readOnly: false, |
|
1654 | readOnly: false, | |
|
1655 | smartHome: true, | |||
1361 | onChange: null, |
|
1656 | onChange: null, | |
1362 | onCursorActivity: null, |
|
1657 | onCursorActivity: null, | |
1363 | onGutterClick: null, |
|
1658 | onGutterClick: null, | |
|
1659 | onHighlightComplete: null, | |||
1364 | onFocus: null, onBlur: null, onScroll: null, |
|
1660 | onFocus: null, onBlur: null, onScroll: null, | |
1365 | matchBrackets: false, |
|
1661 | matchBrackets: false, | |
1366 | workTime: 100, |
|
1662 | workTime: 100, | |
1367 | workDelay: 200, |
|
1663 | workDelay: 200, | |
1368 | undoDepth: 40, |
|
1664 | undoDepth: 40, | |
1369 | tabindex: null |
|
1665 | tabindex: null, | |
|
1666 | document: window.document | |||
1370 | }; |
|
1667 | }; | |
1371 |
|
1668 | |||
1372 | // Known modes, by name and by MIME |
|
1669 | // Known modes, by name and by MIME | |
@@ -1383,15 +1680,15 b' var CodeMirror = (function() {' | |||||
1383 | spec = mimeModes[spec]; |
|
1680 | spec = mimeModes[spec]; | |
1384 | if (typeof spec == "string") |
|
1681 | if (typeof spec == "string") | |
1385 | var mname = spec, config = {}; |
|
1682 | var mname = spec, config = {}; | |
1386 | else |
|
1683 | else if (spec != null) | |
1387 | var mname = spec.name, config = spec; |
|
1684 | var mname = spec.name, config = spec; | |
1388 | var mfactory = modes[mname]; |
|
1685 | var mfactory = modes[mname]; | |
1389 | if (!mfactory) { |
|
1686 | if (!mfactory) { | |
1390 | if (window.console) console.warn("No mode " + mname + " found, falling back to plain text."); |
|
1687 | if (window.console) console.warn("No mode " + mname + " found, falling back to plain text."); | |
1391 | return CodeMirror.getMode(options, "text/plain"); |
|
1688 | return CodeMirror.getMode(options, "text/plain"); | |
1392 | } |
|
1689 | } | |
1393 | return mfactory(options, config); |
|
1690 | return mfactory(options, config || {}); | |
1394 | } |
|
1691 | }; | |
1395 | CodeMirror.listModes = function() { |
|
1692 | CodeMirror.listModes = function() { | |
1396 | var list = []; |
|
1693 | var list = []; | |
1397 | for (var m in modes) |
|
1694 | for (var m in modes) | |
@@ -1401,10 +1698,15 b' var CodeMirror = (function() {' | |||||
1401 | CodeMirror.listMIMEs = function() { |
|
1698 | CodeMirror.listMIMEs = function() { | |
1402 | var list = []; |
|
1699 | var list = []; | |
1403 | for (var m in mimeModes) |
|
1700 | for (var m in mimeModes) | |
1404 | if (mimeModes.propertyIsEnumerable(m)) list.push(m); |
|
1701 | if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]}); | |
1405 | return list; |
|
1702 | return list; | |
1406 | }; |
|
1703 | }; | |
1407 |
|
1704 | |||
|
1705 | var extensions = {}; | |||
|
1706 | CodeMirror.defineExtension = function(name, func) { | |||
|
1707 | extensions[name] = func; | |||
|
1708 | }; | |||
|
1709 | ||||
1408 | CodeMirror.fromTextArea = function(textarea, options) { |
|
1710 | CodeMirror.fromTextArea = function(textarea, options) { | |
1409 | if (!options) options = {}; |
|
1711 | if (!options) options = {}; | |
1410 | options.value = textarea.value; |
|
1712 | options.value = textarea.value; | |
@@ -1484,7 +1786,7 b' var CodeMirror = (function() {' | |||||
1484 | if (ok) {++this.pos; return ch;} |
|
1786 | if (ok) {++this.pos; return ch;} | |
1485 | }, |
|
1787 | }, | |
1486 | eatWhile: function(match) { |
|
1788 | eatWhile: function(match) { | |
1487 |
var start = this.s |
|
1789 | var start = this.pos; | |
1488 | while (this.eat(match)){} |
|
1790 | while (this.eat(match)){} | |
1489 | return this.pos > start; |
|
1791 | return this.pos > start; | |
1490 | }, |
|
1792 | }, | |
@@ -1517,6 +1819,7 b' var CodeMirror = (function() {' | |||||
1517 | }, |
|
1819 | }, | |
1518 | current: function(){return this.string.slice(this.start, this.pos);} |
|
1820 | current: function(){return this.string.slice(this.start, this.pos);} | |
1519 | }; |
|
1821 | }; | |
|
1822 | CodeMirror.StringStream = StringStream; | |||
1520 |
|
1823 | |||
1521 | // Line objects. These hold state related to a line, including |
|
1824 | // Line objects. These hold state related to a line, including | |
1522 | // highlighting info (the styles array). |
|
1825 | // highlighting info (the styles array). | |
@@ -1526,10 +1829,23 b' var CodeMirror = (function() {' | |||||
1526 | this.text = text; |
|
1829 | this.text = text; | |
1527 | this.marked = this.gutterMarker = this.className = null; |
|
1830 | this.marked = this.gutterMarker = this.className = null; | |
1528 | } |
|
1831 | } | |
|
1832 | Line.inheritMarks = function(text, orig) { | |||
|
1833 | var ln = new Line(text), mk = orig.marked; | |||
|
1834 | if (mk) { | |||
|
1835 | for (var i = 0; i < mk.length; ++i) { | |||
|
1836 | if (mk[i].to == null) { | |||
|
1837 | var newmk = ln.marked || (ln.marked = []), mark = mk[i]; | |||
|
1838 | newmk.push({from: null, to: null, style: mark.style, set: mark.set}); | |||
|
1839 | mark.set.push(ln); | |||
|
1840 | } | |||
|
1841 | } | |||
|
1842 | } | |||
|
1843 | return ln; | |||
|
1844 | } | |||
1529 | Line.prototype = { |
|
1845 | Line.prototype = { | |
1530 | // Replace a piece of a line, keeping the styles around it intact. |
|
1846 | // Replace a piece of a line, keeping the styles around it intact. | |
1531 | replace: function(from, to, text) { |
|
1847 | replace: function(from, to_, text) { | |
1532 | var st = [], mk = this.marked; |
|
1848 | var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_; | |
1533 | copyStyles(0, from, this.styles, st); |
|
1849 | copyStyles(0, from, this.styles, st); | |
1534 | if (text) st.push(text, null); |
|
1850 | if (text) st.push(text, null); | |
1535 | copyStyles(to, this.text.length, this.styles, st); |
|
1851 | copyStyles(to, this.text.length, this.styles, st); | |
@@ -1538,39 +1854,86 b' var CodeMirror = (function() {' | |||||
1538 | this.stateAfter = null; |
|
1854 | this.stateAfter = null; | |
1539 | if (mk) { |
|
1855 | if (mk) { | |
1540 | var diff = text.length - (to - from), end = this.text.length; |
|
1856 | var diff = text.length - (to - from), end = this.text.length; | |
1541 | function fix(n) {return n <= Math.min(to, to + diff) ? n : n + diff;} |
|
1857 | var changeStart = Math.min(from, from + diff); | |
1542 | for (var i = 0; i < mk.length; ++i) { |
|
1858 | for (var i = 0; i < mk.length; ++i) { | |
1543 | var mark = mk[i], del = false; |
|
1859 | var mark = mk[i], del = false; | |
1544 | if (mark.from >= end) del = true; |
|
1860 | if (mark.from != null && mark.from >= end) del = true; | |
1545 | else {mark.from = fix(mark.from); if (mark.to != null) mark.to = fix(mark.to);} |
|
1861 | else { | |
1546 | if (del || mark.from >= mark.to) {mk.splice(i, 1); i--;} |
|
1862 | if (mark.from != null && mark.from >= from) { | |
|
1863 | mark.from += diff; | |||
|
1864 | if (mark.from <= 0) mark.from = from == null ? null : 0; | |||
|
1865 | } | |||
|
1866 | else if (to_ == null) mark.to = null; | |||
|
1867 | if (mark.to != null && mark.to > from) { | |||
|
1868 | mark.to += diff; | |||
|
1869 | if (mark.to < 0) del = true; | |||
|
1870 | } | |||
|
1871 | } | |||
|
1872 | if (del || (mark.from != null && mark.to != null && mark.from >= mark.to)) mk.splice(i--, 1); | |||
1547 | } |
|
1873 | } | |
1548 | } |
|
1874 | } | |
1549 | }, |
|
1875 | }, | |
1550 |
// Split a |
|
1876 | // Split a part off a line, keeping styles and markers intact. | |
1551 | split: function(pos, textBefore) { |
|
1877 | split: function(pos, textBefore) { | |
1552 | var st = [textBefore, null]; |
|
1878 | var st = [textBefore, null], mk = this.marked; | |
1553 | copyStyles(pos, this.text.length, this.styles, st); |
|
1879 | copyStyles(pos, this.text.length, this.styles, st); | |
1554 |
re |
|
1880 | var taken = new Line(textBefore + this.text.slice(pos), st); | |
|
1881 | if (mk) { | |||
|
1882 | for (var i = 0; i < mk.length; ++i) { | |||
|
1883 | var mark = mk[i]; | |||
|
1884 | if (mark.to > pos || mark.to == null) { | |||
|
1885 | if (!taken.marked) taken.marked = []; | |||
|
1886 | taken.marked.push({ | |||
|
1887 | from: mark.from < pos || mark.from == null ? null : mark.from - pos + textBefore.length, | |||
|
1888 | to: mark.to == null ? null : mark.to - pos + textBefore.length, | |||
|
1889 | style: mark.style, set: mark.set | |||
|
1890 | }); | |||
|
1891 | mark.set.push(taken); | |||
|
1892 | } | |||
|
1893 | } | |||
|
1894 | } | |||
|
1895 | return taken; | |||
1555 | }, |
|
1896 | }, | |
1556 |
a |
|
1897 | append: function(line) { | |
1557 | var mk = this.marked, mark = {from: from, to: to, style: style}; |
|
1898 | if (!line.text.length) return; | |
|
1899 | var mylen = this.text.length, mk = line.marked; | |||
|
1900 | this.text += line.text; | |||
|
1901 | copyStyles(0, line.text.length, line.styles, this.styles); | |||
|
1902 | if (mk && mk.length) { | |||
|
1903 | var mymk = this.marked || (this.marked = []); | |||
|
1904 | for (var i = 0; i < mymk.length; ++i) | |||
|
1905 | if (mymk[i].to == null) mymk[i].to = mylen; | |||
|
1906 | outer: for (var i = 0; i < mk.length; ++i) { | |||
|
1907 | var mark = mk[i]; | |||
|
1908 | if (!mark.from) { | |||
|
1909 | for (var j = 0; j < mymk.length; ++j) { | |||
|
1910 | var mymark = mymk[j]; | |||
|
1911 | if (mymark.to == mylen && mymark.set == mark.set) { | |||
|
1912 | mymark.to = mark.to == null ? null : mark.to + mylen; | |||
|
1913 | continue outer; | |||
|
1914 | } | |||
|
1915 | } | |||
|
1916 | } | |||
|
1917 | mymk.push(mark); | |||
|
1918 | mark.set.push(this); | |||
|
1919 | mark.from += mylen; | |||
|
1920 | if (mark.to != null) mark.to += mylen; | |||
|
1921 | } | |||
|
1922 | } | |||
|
1923 | }, | |||
|
1924 | addMark: function(from, to, style, set) { | |||
|
1925 | set.push(this); | |||
1558 | if (this.marked == null) this.marked = []; |
|
1926 | if (this.marked == null) this.marked = []; | |
1559 | this.marked.push(mark); |
|
1927 | this.marked.push({from: from, to: to, style: style, set: set}); | |
1560 | this.marked.sort(function(a, b){return a.from - b.from;}); |
|
1928 | this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);}); | |
1561 | return mark; |
|
|||
1562 | }, |
|
|||
1563 | removeMark: function(mark) { |
|
|||
1564 | var mk = this.marked; |
|
|||
1565 | if (!mk) return; |
|
|||
1566 | for (var i = 0; i < mk.length; ++i) |
|
|||
1567 | if (mk[i] == mark) {mk.splice(i, 1); break;} |
|
|||
1568 | }, |
|
1929 | }, | |
1569 | // Run the given mode's parser over a line, update the styles |
|
1930 | // Run the given mode's parser over a line, update the styles | |
1570 | // array, which contains alternating fragments of text and CSS |
|
1931 | // array, which contains alternating fragments of text and CSS | |
1571 | // classes. |
|
1932 | // classes. | |
1572 | highlight: function(mode, state) { |
|
1933 | highlight: function(mode, state) { | |
1573 |
var stream = new StringStream(this.text), st = this.styles, pos = 0 |
|
1934 | var stream = new StringStream(this.text), st = this.styles, pos = 0; | |
|
1935 | var changed = false, curWord = st[0], prevWord; | |||
|
1936 | if (this.text == "" && mode.blankLine) mode.blankLine(state); | |||
1574 | while (!stream.eol()) { |
|
1937 | while (!stream.eol()) { | |
1575 | var style = mode.token(stream, state); |
|
1938 | var style = mode.token(stream, state); | |
1576 | var substr = this.text.slice(stream.start, stream.pos); |
|
1939 | var substr = this.text.slice(stream.start, stream.pos); | |
@@ -1578,8 +1941,9 b' var CodeMirror = (function() {' | |||||
1578 | if (pos && st[pos-1] == style) |
|
1941 | if (pos && st[pos-1] == style) | |
1579 | st[pos-2] += substr; |
|
1942 | st[pos-2] += substr; | |
1580 | else if (substr) { |
|
1943 | else if (substr) { | |
1581 |
if (!changed && |
|
1944 | if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true; | |
1582 | st[pos++] = substr; st[pos++] = style; |
|
1945 | st[pos++] = substr; st[pos++] = style; | |
|
1946 | prevWord = curWord; curWord = st[pos]; | |||
1583 | } |
|
1947 | } | |
1584 | // Give up when line is ridiculously long |
|
1948 | // Give up when line is ridiculously long | |
1585 | if (stream.pos > 5000) { |
|
1949 | if (stream.pos > 5000) { | |
@@ -1588,7 +1952,11 b' var CodeMirror = (function() {' | |||||
1588 | } |
|
1952 | } | |
1589 | } |
|
1953 | } | |
1590 | if (st.length != pos) {st.length = pos; changed = true;} |
|
1954 | if (st.length != pos) {st.length = pos; changed = true;} | |
1591 | return changed; |
|
1955 | if (pos && st[pos-2] != prevWord) changed = true; | |
|
1956 | // Short lines with simple highlights return null, and are | |||
|
1957 | // counted as changed by the driver because they are likely to | |||
|
1958 | // highlight the same way in various contexts. | |||
|
1959 | return changed || (st.length < 5 && this.text.length < 10 ? null : false); | |||
1592 | }, |
|
1960 | }, | |
1593 | // Fetch the parser token for a given character. Useful for hacks |
|
1961 | // Fetch the parser token for a given character. Useful for hacks | |
1594 | // that want to inspect the mode state (say, for completion). |
|
1962 | // that want to inspect the mode state (say, for completion). | |
@@ -1607,7 +1975,7 b' var CodeMirror = (function() {' | |||||
1607 | indentation: function() {return countColumn(this.text);}, |
|
1975 | indentation: function() {return countColumn(this.text);}, | |
1608 | // Produces an HTML fragment for the line, taking selection, |
|
1976 | // Produces an HTML fragment for the line, taking selection, | |
1609 | // marking, and highlighting into account. |
|
1977 | // marking, and highlighting into account. | |
1610 | getHTML: function(sfrom, sto, includePre) { |
|
1978 | getHTML: function(sfrom, sto, includePre, endAt) { | |
1611 | var html = []; |
|
1979 | var html = []; | |
1612 | if (includePre) |
|
1980 | if (includePre) | |
1613 | html.push(this.className ? '<pre class="' + this.className + '">': "<pre>"); |
|
1981 | html.push(this.className ? '<pre class="' + this.className + '">': "<pre>"); | |
@@ -1618,11 +1986,18 b' var CodeMirror = (function() {' | |||||
1618 | } |
|
1986 | } | |
1619 | var st = this.styles, allText = this.text, marked = this.marked; |
|
1987 | var st = this.styles, allText = this.text, marked = this.marked; | |
1620 | if (sfrom == sto) sfrom = null; |
|
1988 | if (sfrom == sto) sfrom = null; | |
|
1989 | var len = allText.length; | |||
|
1990 | if (endAt != null) len = Math.min(endAt, len); | |||
1621 |
|
1991 | |||
1622 | if (!allText) |
|
1992 | if (!allText && endAt == null) | |
1623 | span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null); |
|
1993 | span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null); | |
1624 | else if (!marked && sfrom == null) |
|
1994 | else if (!marked && sfrom == null) | |
1625 |
for (var i = 0, |
|
1995 | for (var i = 0, ch = 0; ch < len; i+=2) { | |
|
1996 | var str = st[i], style = st[i+1], l = str.length; | |||
|
1997 | if (ch + l > len) str = str.slice(0, len - ch); | |||
|
1998 | ch += l; | |||
|
1999 | span(str, style && "cm-" + style); | |||
|
2000 | } | |||
1626 | else { |
|
2001 | else { | |
1627 | var pos = 0, i = 0, text = "", style, sg = 0; |
|
2002 | var pos = 0, i = 0, text = "", style, sg = 0; | |
1628 | var markpos = -1, mark = null; |
|
2003 | var markpos = -1, mark = null; | |
@@ -1632,9 +2007,9 b' var CodeMirror = (function() {' | |||||
1632 | mark = (markpos < marked.length) ? marked[markpos] : null; |
|
2007 | mark = (markpos < marked.length) ? marked[markpos] : null; | |
1633 | } |
|
2008 | } | |
1634 | } |
|
2009 | } | |
1635 |
nextMark(); |
|
2010 | nextMark(); | |
1636 |
while (pos < |
|
2011 | while (pos < len) { | |
1637 |
var upto = |
|
2012 | var upto = len; | |
1638 | var extraStyle = ""; |
|
2013 | var extraStyle = ""; | |
1639 | if (sfrom != null) { |
|
2014 | if (sfrom != null) { | |
1640 | if (sfrom > pos) upto = sfrom; |
|
2015 | if (sfrom > pos) upto = sfrom; | |
@@ -1653,12 +2028,12 b' var CodeMirror = (function() {' | |||||
1653 | } |
|
2028 | } | |
1654 | for (;;) { |
|
2029 | for (;;) { | |
1655 | var end = pos + text.length; |
|
2030 | var end = pos + text.length; | |
1656 | var apliedStyle = style; |
|
2031 | var appliedStyle = style; | |
1657 | if (extraStyle) apliedStyle = style ? style + extraStyle : extraStyle; |
|
2032 | if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle; | |
1658 | span(end > upto ? text.slice(0, upto - pos) : text, apliedStyle); |
|
2033 | span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle); | |
1659 | if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} |
|
2034 | if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} | |
1660 | pos = end; |
|
2035 | pos = end; | |
1661 | text = st[i++]; style = st[i++]; |
|
2036 | text = st[i++]; style = "cm-" + st[i++]; | |
1662 | } |
|
2037 | } | |
1663 | } |
|
2038 | } | |
1664 | if (sfrom != null && sto == null) span(" ", "CodeMirror-selected"); |
|
2039 | if (sfrom != null && sto == null) span(" ", "CodeMirror-selected"); | |
@@ -1716,42 +2091,34 b' var CodeMirror = (function() {' | |||||
1716 | } |
|
2091 | } | |
1717 | }; |
|
2092 | }; | |
1718 |
|
2093 | |||
1719 | // Event stopping compatibility wrapper. |
|
2094 | function stopMethod() {e_stop(this);} | |
1720 | function stopEvent() { |
|
|||
1721 | if (this.preventDefault) {this.preventDefault(); this.stopPropagation();} |
|
|||
1722 | else {this.returnValue = false; this.cancelBubble = true;} |
|
|||
1723 | } |
|
|||
1724 | // Ensure an event has a stop method. |
|
2095 | // Ensure an event has a stop method. | |
1725 | function addStop(event) { |
|
2096 | function addStop(event) { | |
1726 |
if (!event.stop) event.stop = stop |
|
2097 | if (!event.stop) event.stop = stopMethod; | |
1727 | return event; |
|
2098 | return event; | |
1728 | } |
|
2099 | } | |
1729 |
|
2100 | |||
1730 | // Event wrapper, exposing the few operations we need. |
|
2101 | function e_preventDefault(e) { | |
1731 | function Event(orig) {this.e = orig;} |
|
2102 | if (e.preventDefault) e.preventDefault(); | |
1732 | Event.prototype = { |
|
2103 | else e.returnValue = false; | |
1733 | stop: function() {stopEvent.call(this.e);}, |
|
2104 | } | |
1734 | target: function() {return this.e.target || this.e.srcElement;}, |
|
2105 | function e_stopPropagation(e) { | |
1735 | button: function() { |
|
2106 | if (e.stopPropagation) e.stopPropagation(); | |
1736 | if (this.e.which) return this.e.which; |
|
2107 | else e.cancelBubble = true; | |
1737 | else if (this.e.button & 1) return 1; |
|
2108 | } | |
1738 | else if (this.e.button & 2) return 3; |
|
2109 | function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} | |
1739 | else if (this.e.button & 4) return 2; |
|
2110 | function e_target(e) {return e.target || e.srcElement;} | |
1740 | }, |
|
2111 | function e_button(e) { | |
1741 | pageX: function() { |
|
2112 | if (e.which) return e.which; | |
1742 | if (this.e.pageX != null) return this.e.pageX; |
|
2113 | else if (e.button & 1) return 1; | |
1743 | else return this.e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; |
|
2114 | else if (e.button & 2) return 3; | |
1744 | }, |
|
2115 | else if (e.button & 4) return 2; | |
1745 | pageY: function() { |
|
2116 | } | |
1746 | if (this.e.pageY != null) return this.e.pageY; |
|
|||
1747 | else return this.e.clientY + document.body.scrollTop + document.documentElement.scrollTop; |
|
|||
1748 | } |
|
|||
1749 | }; |
|
|||
1750 |
|
2117 | |||
1751 | // Event handler registration. If disconnect is true, it'll return a |
|
2118 | // Event handler registration. If disconnect is true, it'll return a | |
1752 | // function that unregisters the handler. |
|
2119 | // function that unregisters the handler. | |
1753 | function connect(node, type, handler, disconnect) { |
|
2120 | function connect(node, type, handler, disconnect) { | |
1754 |
function wrapHandler(event) {handler( |
|
2121 | function wrapHandler(event) {handler(event || window.event);} | |
1755 | if (typeof node.addEventListener == "function") { |
|
2122 | if (typeof node.addEventListener == "function") { | |
1756 | node.addEventListener(type, wrapHandler, false); |
|
2123 | node.addEventListener(type, wrapHandler, false); | |
1757 | if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);}; |
|
2124 | if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);}; | |
@@ -1772,7 +2139,18 b' var CodeMirror = (function() {' | |||||
1772 | pre.innerHTML = " "; return !pre.innerHTML; |
|
2139 | pre.innerHTML = " "; return !pre.innerHTML; | |
1773 | })(); |
|
2140 | })(); | |
1774 |
|
2141 | |||
|
2142 | // Detect drag-and-drop | |||
|
2143 | var dragAndDrop = (function() { | |||
|
2144 | // IE8 has ondragstart and ondrop properties, but doesn't seem to | |||
|
2145 | // actually support ondragstart the way it's supposed to work. | |||
|
2146 | if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false; | |||
|
2147 | var div = document.createElement('div'); | |||
|
2148 | return "ondragstart" in div && "ondrop" in div; | |||
|
2149 | })(); | |||
|
2150 | ||||
1775 | var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); |
|
2151 | var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); | |
|
2152 | var ie = /MSIE \d/.test(navigator.userAgent); | |||
|
2153 | var safari = /Apple Computer/.test(navigator.vendor); | |||
1776 |
|
2154 | |||
1777 | var lineSep = "\n"; |
|
2155 | var lineSep = "\n"; | |
1778 | // Feature-detect whether newlines in textareas are converted to \r\n |
|
2156 | // Feature-detect whether newlines in textareas are converted to \r\n | |
@@ -1802,11 +2180,23 b' var CodeMirror = (function() {' | |||||
1802 | return n; |
|
2180 | return n; | |
1803 | } |
|
2181 | } | |
1804 |
|
2182 | |||
|
2183 | function computedStyle(elt) { | |||
|
2184 | if (elt.currentStyle) return elt.currentStyle; | |||
|
2185 | return window.getComputedStyle(elt, null); | |||
|
2186 | } | |||
1805 | // Find the position of an element by following the offsetParent chain. |
|
2187 | // Find the position of an element by following the offsetParent chain. | |
1806 | function eltOffset(node) { |
|
2188 | // If screen==true, it returns screen (rather than page) coordinates. | |
1807 | var x = 0, y = 0, n2 = node; |
|
2189 | function eltOffset(node, screen) { | |
1808 | for (var n = node; n; n = n.offsetParent) {x += n.offsetLeft; y += n.offsetTop;} |
|
2190 | var doc = node.ownerDocument.body; | |
1809 | for (var n = node; n != document.body; n = n.parentNode) {x -= n.scrollLeft; y -= n.scrollTop;} |
|
2191 | var x = 0, y = 0, skipDoc = false; | |
|
2192 | for (var n = node; n; n = n.offsetParent) { | |||
|
2193 | x += n.offsetLeft; y += n.offsetTop; | |||
|
2194 | if (screen && computedStyle(n).position == "fixed") | |||
|
2195 | skipDoc = true; | |||
|
2196 | } | |||
|
2197 | var e = screen && !skipDoc ? null : doc; | |||
|
2198 | for (var n = node.parentNode; n != e; n = n.parentNode) | |||
|
2199 | if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;} | |||
1810 | return {left: x, top: y}; |
|
2200 | return {left: x, top: y}; | |
1811 | } |
|
2201 | } | |
1812 | // Get a node's text content. |
|
2202 | // Get a node's text content. | |
@@ -1819,9 +2209,18 b' var CodeMirror = (function() {' | |||||
1819 | function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} |
|
2209 | function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} | |
1820 | function copyPos(x) {return {line: x.line, ch: x.ch};} |
|
2210 | function copyPos(x) {return {line: x.line, ch: x.ch};} | |
1821 |
|
2211 | |||
|
2212 | var escapeElement = document.createElement("pre"); | |||
1822 | function htmlEscape(str) { |
|
2213 | function htmlEscape(str) { | |
1823 | return str.replace(/[<&]/g, function(str) {return str == "&" ? "&" : "<";}); |
|
2214 | if (badTextContent) { | |
|
2215 | escapeElement.innerHTML = ""; | |||
|
2216 | escapeElement.appendChild(document.createTextNode(str)); | |||
|
2217 | } else { | |||
|
2218 | escapeElement.textContent = str; | |||
|
2219 | } | |||
|
2220 | return escapeElement.innerHTML; | |||
1824 | } |
|
2221 | } | |
|
2222 | var badTextContent = htmlEscape("\t") != "\t"; | |||
|
2223 | CodeMirror.htmlEscape = htmlEscape; | |||
1825 |
|
2224 | |||
1826 | // Used to position the cursor after an undo/redo by finding the |
|
2225 | // Used to position the cursor after an undo/redo by finding the | |
1827 | // last edited character. |
|
2226 | // last edited character. | |
@@ -1842,8 +2241,9 b' var CodeMirror = (function() {' | |||||
1842 |
|
2241 | |||
1843 | // See if "".split is the broken IE version, if so, provide an |
|
2242 | // See if "".split is the broken IE version, if so, provide an | |
1844 | // alternative way to split lines. |
|
2243 | // alternative way to split lines. | |
|
2244 | var splitLines, selRange, setSelRange; | |||
1845 | if ("\n\nb".split(/\n/).length != 3) |
|
2245 | if ("\n\nb".split(/\n/).length != 3) | |
1846 |
|
|
2246 | splitLines = function(string) { | |
1847 | var pos = 0, nl, result = []; |
|
2247 | var pos = 0, nl, result = []; | |
1848 | while ((nl = string.indexOf("\n", pos)) > -1) { |
|
2248 | while ((nl = string.indexOf("\n", pos)) > -1) { | |
1849 | result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl)); |
|
2249 | result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl)); | |
@@ -1853,23 +2253,40 b' var CodeMirror = (function() {' | |||||
1853 | return result; |
|
2253 | return result; | |
1854 | }; |
|
2254 | }; | |
1855 | else |
|
2255 | else | |
1856 |
|
|
2256 | splitLines = function(string){return string.split(/\r?\n/);}; | |
|
2257 | CodeMirror.splitLines = splitLines; | |||
1857 |
|
2258 | |||
1858 | // Sane model of finding and setting the selection in a textarea |
|
2259 | // Sane model of finding and setting the selection in a textarea | |
1859 | if (window.getSelection) { |
|
2260 | if (window.getSelection) { | |
1860 |
|
|
2261 | selRange = function(te) { | |
1861 | try {return {start: te.selectionStart, end: te.selectionEnd};} |
|
2262 | try {return {start: te.selectionStart, end: te.selectionEnd};} | |
1862 | catch(e) {return null;} |
|
2263 | catch(e) {return null;} | |
1863 | }; |
|
2264 | }; | |
1864 | var setSelRange = function(te, start, end) { |
|
2265 | if (safari) | |
1865 | try {te.setSelectionRange(start, end);} |
|
2266 | // On Safari, selection set with setSelectionRange are in a sort | |
1866 | catch(e) {} // Fails on Firefox when textarea isn't part of the document |
|
2267 | // of limbo wrt their anchor. If you press shift-left in them, | |
1867 | }; |
|
2268 | // the anchor is put at the end, and the selection expanded to | |
|
2269 | // the left. If you press shift-right, the anchor ends up at the | |||
|
2270 | // front. This is not what CodeMirror wants, so it does a | |||
|
2271 | // spurious modify() call to get out of limbo. | |||
|
2272 | setSelRange = function(te, start, end) { | |||
|
2273 | if (start == end) | |||
|
2274 | te.setSelectionRange(start, end); | |||
|
2275 | else { | |||
|
2276 | te.setSelectionRange(start, end - 1); | |||
|
2277 | window.getSelection().modify("extend", "forward", "character"); | |||
|
2278 | } | |||
|
2279 | }; | |||
|
2280 | else | |||
|
2281 | setSelRange = function(te, start, end) { | |||
|
2282 | try {te.setSelectionRange(start, end);} | |||
|
2283 | catch(e) {} // Fails on Firefox when textarea isn't part of the document | |||
|
2284 | }; | |||
1868 | } |
|
2285 | } | |
1869 | // IE model. Don't ask. |
|
2286 | // IE model. Don't ask. | |
1870 | else { |
|
2287 | else { | |
1871 |
|
|
2288 | selRange = function(te) { | |
1872 |
try {var range = |
|
2289 | try {var range = te.ownerDocument.selection.createRange();} | |
1873 | catch(e) {return null;} |
|
2290 | catch(e) {return null;} | |
1874 | if (!range || range.parentElement() != te) return null; |
|
2291 | if (!range || range.parentElement() != te) return null; | |
1875 | var val = te.value, len = val.length, localRange = te.createTextRange(); |
|
2292 | var val = te.value, len = val.length, localRange = te.createTextRange(); | |
@@ -1890,7 +2307,7 b' var CodeMirror = (function() {' | |||||
1890 | for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {} |
|
2307 | for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {} | |
1891 | return {start: start, end: end}; |
|
2308 | return {start: start, end: end}; | |
1892 | }; |
|
2309 | }; | |
1893 |
|
|
2310 | setSelRange = function(te, start, end) { | |
1894 | var range = te.createTextRange(); |
|
2311 | var range = te.createTextRange(); | |
1895 | range.collapse(true); |
|
2312 | range.collapse(true); | |
1896 | var endrange = range.duplicate(); |
|
2313 | var endrange = range.duplicate(); |
General Comments 0
You need to be logged in to leave comments.
Login now