Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(311)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/cm/codemirror.js

Issue 2862603003: Revert of DevTools: Roll CodeMirror to 5.25.1
Patch Set: Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others 1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: http://codemirror.net/LICENSE 2 // Distributed under an MIT license: http://codemirror.net/LICENSE
3 3
4 // This is CodeMirror (http://codemirror.net), a code editor 4 // This is CodeMirror (http://codemirror.net), a code editor
5 // implemented in JavaScript on top of the browser's DOM. 5 // implemented in JavaScript on top of the browser's DOM.
6 // 6 //
7 // You can find some technical background for some of the code below 7 // You can find some technical background for some of the code below
8 // at http://marijnhaverbeke.nl/blog/#cm-internals . 8 // at http://marijnhaverbeke.nl/blog/#cm-internals .
9 9
10 (function (global, factory) { 10 (function(mod) {
11 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 11 if (typeof exports == "object" && typeof module == "object") // CommonJS
12 typeof define === 'function' && define.amd ? define(factory) : 12 module.exports = mod();
13 (global.CodeMirror = factory()); 13 else if (typeof define == "function" && define.amd) // AMD
14 }(this, (function () { 'use strict'; 14 return define([], mod);
15 15 else // Plain browser env
16 // Kludges for bugs and behavior differences that can't be feature 16 (this || window).CodeMirror = mod();
17 // detected are enabled based on userAgent etc sniffing. 17 })(function() {
18 var userAgent = navigator.userAgent 18 "use strict";
19 var platform = navigator.platform 19
20 20 // BROWSER SNIFFING
21 var gecko = /gecko\/\d/i.test(userAgent) 21
22 var ie_upto10 = /MSIE \d/.test(userAgent) 22 // Kludges for bugs and behavior differences that can't be feature
23 var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent) 23 // detected are enabled based on userAgent etc sniffing.
24 var edge = /Edge\/(\d+)/.exec(userAgent) 24 var userAgent = navigator.userAgent;
25 var ie = ie_upto10 || ie_11up || edge 25 var platform = navigator.platform;
26 var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11 up)[1]) 26
27 var webkit = !edge && /WebKit\//.test(userAgent) 27 var gecko = /gecko\/\d/i.test(userAgent);
28 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent) 28 var ie_upto10 = /MSIE \d/.test(userAgent);
29 var chrome = !edge && /Chrome\//.test(userAgent) 29 var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent);
30 var presto = /Opera\//.test(userAgent) 30 var ie = ie_upto10 || ie_11up;
31 var safari = /Apple Computer/.test(navigator.vendor) 31 var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]);
32 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent) 32 var webkit = /WebKit\//.test(userAgent);
33 var phantom = /PhantomJS/.test(userAgent) 33 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent);
34 34 var chrome = /Chrome\//.test(userAgent);
35 var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent ) 35 var presto = /Opera\//.test(userAgent);
36 var android = /Android/.test(userAgent) 36 var safari = /Apple Computer/.test(navigator.vendor);
37 // This is woefully incomplete. Suggestions for alternative methods welcome. 37 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent);
38 var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/ i.test(userAgent) 38 var phantom = /PhantomJS/.test(userAgent);
39 var mac = ios || /Mac/.test(platform) 39
40 var chromeOS = /\bCrOS\b/.test(userAgent) 40 var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent);
41 var windows = /win/i.test(platform) 41 // This is woefully incomplete. Suggestions for alternative methods welcome.
42 42 var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i .test(userAgent);
43 var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/) 43 var mac = ios || /Mac/.test(platform);
44 if (presto_version) { presto_version = Number(presto_version[1]) } 44 var chromeOS = /\bCrOS\b/.test(userAgent);
45 if (presto_version && presto_version >= 15) { presto = false; webkit = true } 45 var windows = /win/i.test(platform);
46 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X 46
47 var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || prest o_version < 12.11)) 47 var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/);
48 var captureRightClick = gecko || (ie && ie_version >= 9) 48 if (presto_version) presto_version = Number(presto_version[1]);
49 49 if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
50 function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } 50 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
51 51 var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || pre sto_version < 12.11));
52 var rmClass = function(node, cls) { 52 var captureRightClick = gecko || (ie && ie_version >= 9);
53 var current = node.className 53
54 var match = classTest(cls).exec(current) 54 // Optimize some code when these features are not used.
55 if (match) { 55 var sawReadOnlySpans = false, sawCollapsedSpans = false;
56 var after = current.slice(match.index + match[0].length) 56
57 node.className = current.slice(0, match.index) + (after ? match[1] + after : "") 57 // EDITOR CONSTRUCTOR
58 } 58
59 } 59 // A CodeMirror instance represents an editor. This is the object
60 60 // that user code is usually dealing with.
61 function removeChildren(e) { 61
62 for (var count = e.childNodes.length; count > 0; --count) 62 function CodeMirror(place, options) {
63 { e.removeChild(e.firstChild) } 63 if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
64 return e 64
65 } 65 this.options = options = options ? copyObj(options) : {};
66 66 // Determine effective options based on given values and defaults.
67 function removeChildrenAndAdd(parent, e) { 67 copyObj(defaults, options, false);
68 return removeChildren(parent).appendChild(e) 68 setGuttersForLineNumbers(options);
69 } 69
70 70 var doc = options.value;
71 function elt(tag, content, className, style) { 71 if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.l ineSeparator);
72 var e = document.createElement(tag) 72 this.doc = doc;
73 if (className) { e.className = className } 73
74 if (style) { e.style.cssText = style } 74 var input = new CodeMirror.inputStyles[options.inputStyle](this);
75 if (typeof content == "string") { e.appendChild(document.createTextNode(conten t)) } 75 var display = this.display = new Display(place, doc, input);
76 else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(c ontent[i]) } } 76 display.wrapper.CodeMirror = this;
77 return e 77 updateGutters(this);
78 } 78 themeChanged(this);
79 // wrapper for elt, which removes the elt from the accessibility tree 79 if (options.lineWrapping)
80 function eltP(tag, content, className, style) { 80 this.display.wrapper.className += " CodeMirror-wrap";
81 var e = elt(tag, content, className, style) 81 if (options.autofocus && !mobile) display.input.focus();
82 e.setAttribute("role", "presentation") 82 initScrollbars(this);
83 return e 83
84 } 84 this.state = {
85 85 keyMaps: [], // stores maps added by addKeyMap
86 var range 86 overlays: [], // highlighting overlays, as added by addOverlay
87 if (document.createRange) { range = function(node, start, end, endNode) { 87 modeGen: 0, // bumped when mode/overlay changes, used to invalidate high lighting info
88 var r = document.createRange() 88 overwrite: false,
89 r.setEnd(endNode || node, end) 89 delayingBlurEvent: false,
90 r.setStart(node, start) 90 focused: false,
91 return r 91 suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
92 } } 92 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edit s in input.poll
93 else { range = function(node, start, end) { 93 selectingText: false,
94 var r = document.body.createTextRange() 94 draggingText: false,
95 try { r.moveToElementText(node.parentNode) } 95 highlight: new Delayed(), // stores highlight worker timeout
96 catch(e) { return r } 96 keySeq: null, // Unfinished key sequence
97 r.collapse(true) 97 specialChars: null
98 r.moveEnd("character", end) 98 };
99 r.moveStart("character", start) 99
100 return r 100 var cm = this;
101 } } 101
102 102 // Override magic textarea content restore that IE sometimes does
103 function contains(parent, child) { 103 // on our hidden textarea on reload
104 if (child.nodeType == 3) // Android browser always returns false when child is a textnode 104 if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(tr ue); }, 20);
105 { child = child.parentNode } 105
106 if (parent.contains) 106 registerEventHandlers(this);
107 { return parent.contains(child) } 107 ensureGlobalHandlers();
108 do { 108
109 if (child.nodeType == 11) { child = child.host } 109 startOperation(this);
110 if (child == parent) { return true } 110 this.curOp.forceUpdate = true;
111 } while (child = child.parentNode) 111 attachDoc(this, doc);
112 } 112
113 113 if ((options.autofocus && !mobile) || cm.hasFocus())
114 function activeElt() { 114 setTimeout(bind(onFocus, this), 20);
115 // IE and Edge may throw an "Unspecified Error" when accessing document.active Element. 115 else
116 // IE < 10 will throw when accessed while the page is loading or in an iframe. 116 onBlur(this);
117 // IE > 9 and Edge will throw when accessed in an iframe if document.body is u navailable. 117
118 var activeElement 118 for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
119 try { 119 optionHandlers[opt](this, options[opt], Init);
120 activeElement = document.activeElement 120 maybeUpdateLineNumberWidth(this);
121 } catch(e) { 121 if (options.finishInit) options.finishInit(this);
122 activeElement = document.body || null 122 for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
123 } 123 endOperation(this);
124 while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.a ctiveElement) 124 // Suppress optimizelegibility in Webkit, since it breaks text
125 { activeElement = activeElement.shadowRoot.activeElement } 125 // measuring on line wrapping boundaries.
126 return activeElement 126 if (webkit && options.lineWrapping &&
127 } 127 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility")
128 128 display.lineDiv.style.textRendering = "auto";
129 function addClass(node, cls) { 129 }
130 var current = node.className 130
131 if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls } 131 // DISPLAY CONSTRUCTOR
132 } 132
133 function joinClasses(a, b) { 133 // The display handles the DOM integration, both for input reading
134 var as = a.split(" ") 134 // and content drawing. It holds references to DOM nodes and
135 for (var i = 0; i < as.length; i++) 135 // display-related state.
136 { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i] } } 136
137 return b 137 function Display(place, doc, input) {
138 } 138 var d = this;
139 139 this.input = input;
140 var selectInput = function(node) { node.select() } 140
141 if (ios) // Mobile Safari apparently has a bug where select() is broken. 141 // Covers bottom-right square when both scrollbars are present.
142 { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length } } 142 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
143 else if (ie) // Suppress mysterious IE10 errors 143 d.scrollbarFiller.setAttribute("cm-not-content", "true");
144 { selectInput = function(node) { try { node.select() } catch(_e) {} } } 144 // Covers bottom of gutter when coverGutterNextToScrollbar is on
145 145 // and h scrollbar is present.
146 function bind(f) { 146 d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
147 var args = Array.prototype.slice.call(arguments, 1) 147 d.gutterFiller.setAttribute("cm-not-content", "true");
148 return function(){return f.apply(null, args)} 148 // Will contain the actual code, positioned to cover the viewport.
149 } 149 d.lineDiv = elt("div", null, "CodeMirror-code");
150 150 // Elements are added to these to represent selection and cursors.
151 function copyObj(obj, target, overwrite) { 151 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
152 if (!target) { target = {} } 152 d.cursorDiv = elt("div", null, "CodeMirror-cursors");
153 for (var prop in obj) 153 // A visibility: hidden element used to find the size of things.
154 { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProp erty(prop))) 154 d.measure = elt("div", null, "CodeMirror-measure");
155 { target[prop] = obj[prop] } } 155 // When lines outside of the viewport are measured, they are drawn in this.
156 return target 156 d.lineMeasure = elt("div", null, "CodeMirror-measure");
157 } 157 // Wraps everything that needs to exist inside the vertically-padded coordin ate system
158 158 d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursor Div, d.lineDiv],
159 // Counts the column offset in a string, taking tabs into account. 159 null, "position: relative; outline: none");
160 // Used mostly to find indentation. 160 // Moved around its parent to cover visible view.
161 function countColumn(string, end, tabSize, startIndex, startValue) { 161 d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
162 if (end == null) { 162 // Set to the height of the document, allowing scrolling.
163 end = string.search(/[^\s\u00a0]/) 163 d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
164 if (end == -1) { end = string.length } 164 d.sizerWidth = null;
165 } 165 // Behavior of elts with overflow: auto and padding is
166 for (var i = startIndex || 0, n = startValue || 0;;) { 166 // inconsistent across browsers. This is used to ensure the
167 var nextTab = string.indexOf("\t", i) 167 // scrollable area is big enough.
168 if (nextTab < 0 || nextTab >= end) 168 d.heightForcer = elt("div", null, null, "position: absolute; height: " + scr ollerGap + "px; width: 1px;");
169 { return n + (end - i) } 169 // Will contain the gutters, if any.
170 n += nextTab - i 170 d.gutters = elt("div", null, "CodeMirror-gutters");
171 n += tabSize - (n % tabSize) 171 d.lineGutter = null;
172 i = nextTab + 1 172 // Actual scrollable element.
173 } 173 d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-sc roll");
174 } 174 d.scroller.setAttribute("tabIndex", "-1");
175 175 // The element in which the editor lives.
176 var Delayed = function Delayed() {this.id = null}; 176 d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "Cod eMirror");
177 Delayed.prototype.set = function set (ms, f) { 177
178 clearTimeout(this.id) 178 // Work around IE7 z-index bug (not perfect, hence IE7 not really being supp orted)
179 this.id = setTimeout(f, ms) 179 if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.pa ddingRight = 0; }
180 }; 180 if (!webkit && !(gecko && mobile)) d.scroller.draggable = true;
181 181
182 function indexOf(array, elt) { 182 if (place) {
183 for (var i = 0; i < array.length; ++i) 183 if (place.appendChild) place.appendChild(d.wrapper);
184 { if (array[i] == elt) { return i } } 184 else place(d.wrapper);
185 return -1 185 }
186 } 186
187 187 // Current rendered range (may be bigger than the view window).
188 // Number of pixels added to scroller and sizer to hide scrollbar 188 d.viewFrom = d.viewTo = doc.first;
189 var scrollerGap = 30 189 d.reportedViewFrom = d.reportedViewTo = doc.first;
190 190 // Information about the rendered lines.
191 // Returned or thrown by various protocols to signal 'I'm not 191 d.view = [];
192 // handling this'. 192 d.renderedView = null;
193 var Pass = {toString: function(){return "CodeMirror.Pass"}} 193 // Holds info about a single rendered line when it was rendered
194 194 // for measurement, while not in view.
195 // Reused option objects for setSelection & friends 195 d.externalMeasured = null;
196 var sel_dontScroll = {scroll: false}; 196 // Empty space (in pixels) above the view
197 var sel_mouse = {origin: "*mouse"}; 197 d.viewOffset = 0;
198 var sel_move = {origin: "+move"}; 198 d.lastWrapHeight = d.lastWrapWidth = 0;
199 // The inverse of countColumn -- find the offset that corresponds to 199 d.updateLineNumbers = null;
200 // a particular column. 200
201 function findColumn(string, goal, tabSize) { 201 d.nativeBarWidth = d.barHeight = d.barWidth = 0;
202 for (var pos = 0, col = 0;;) { 202 d.scrollbarsClipped = false;
203 var nextTab = string.indexOf("\t", pos) 203
204 if (nextTab == -1) { nextTab = string.length } 204 // Used to only resize the line number gutter when necessary (when
205 var skipped = nextTab - pos 205 // the amount of lines crosses a boundary that makes its width change)
206 if (nextTab == string.length || col + skipped >= goal) 206 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
207 { return pos + Math.min(skipped, goal - col) } 207 // Set to true when a non-horizontal-scrolling line widget is
208 col += nextTab - pos 208 // added. As an optimization, line widget aligning is skipped when
209 col += tabSize - (col % tabSize) 209 // this is false.
210 pos = nextTab + 1 210 d.alignWidgets = false;
211 if (col >= goal) { return pos } 211
212 } 212 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
213 } 213
214 214 // Tracks the maximum line length so that the horizontal scrollbar
215 var spaceStrs = [""] 215 // can be kept static when scrolling.
216 function spaceStr(n) { 216 d.maxLine = null;
217 while (spaceStrs.length <= n) 217 d.maxLineLength = 0;
218 { spaceStrs.push(lst(spaceStrs) + " ") } 218 d.maxLineChanged = false;
219 return spaceStrs[n] 219
220 } 220 // Used for measuring wheel scrolling granularity
221 221 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
222 function lst(arr) { return arr[arr.length-1] } 222
223 223 // True when shift is held down.
224 function map(array, f) { 224 d.shift = false;
225 var out = [] 225
226 for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i) } 226 // Used to track whether anything happened since the context menu
227 return out 227 // was opened.
228 } 228 d.selForContextMenu = null;
229 229
230 function insertSorted(array, value, score) { 230 d.activeTouch = null;
231 var pos = 0, priority = score(value) 231
232 while (pos < array.length && score(array[pos]) <= priority) { pos++ } 232 input.init(d);
233 array.splice(pos, 0, value) 233 }
234 } 234
235 235 // STATE UPDATES
236 function nothing() {} 236
237 237 // Used to get the editor into a consistent state again when options change.
238 function createObj(base, props) { 238
239 var inst 239 function loadMode(cm) {
240 if (Object.create) { 240 cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
241 inst = Object.create(base) 241 resetModeState(cm);
242 } else { 242 }
243 nothing.prototype = base 243
244 inst = new nothing() 244 function resetModeState(cm) {
245 } 245 cm.doc.iter(function(line) {
246 if (props) { copyObj(props, inst) } 246 if (line.stateAfter) line.stateAfter = null;
247 return inst 247 if (line.styles) line.styles = null;
248 } 248 });
249 249 cm.doc.frontier = cm.doc.first;
250 var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040- \u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/ 250 startWorker(cm, 100);
251 function isWordCharBasic(ch) { 251 cm.state.modeGen++;
252 return /\w/.test(ch) || ch > "\x80" && 252 if (cm.curOp) regChange(cm);
253 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch) ) 253 }
254 } 254
255 function isWordChar(ch, helper) { 255 function wrappingChanged(cm) {
256 if (!helper) { return isWordCharBasic(ch) } 256 if (cm.options.lineWrapping) {
257 if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true } 257 addClass(cm.display.wrapper, "CodeMirror-wrap");
258 return helper.test(ch) 258 cm.display.sizer.style.minWidth = "";
259 } 259 cm.display.sizerWidth = null;
260 260 } else {
261 function isEmpty(obj) { 261 rmClass(cm.display.wrapper, "CodeMirror-wrap");
262 for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } } 262 findMaxLine(cm);
263 return true 263 }
264 } 264 estimateLineHeights(cm);
265 265 regChange(cm);
266 // Extending unicode characters. A series of a non-extending char + 266 clearCaches(cm);
267 // any number of extending chars is treated as a single unit as far 267 setTimeout(function(){updateScrollbars(cm);}, 100);
268 // as editing and measuring is concerned. This is not fully correct, 268 }
269 // since some scripts/fonts/browsers also treat other configurations 269
270 // of code points as a group. 270 // Returns a function that estimates the height of a line, to use as
271 var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2 \u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06 e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\ u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u09 51-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a 01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a8 1\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f \u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e- \u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6 \u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\ u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4 -\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f8 0-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u 1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u 108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\ u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939 -\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a 7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u 1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8 \u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2 dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua 80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2- \uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00- \ufe0f\ufe20-\ufe26\uff9e\uff9f]/ 271 // first approximation until the line becomes visible (and is thus
272 function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars. test(ch) } 272 // properly measurable).
273 273 function estimateHeight(cm) {
274 // Returns a number from the range [`0`; `str.length`] unless `pos` is outside t hat range. 274 var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
275 function skipExtendingChars(str, pos, dir) { 275 var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / char Width(cm.display) - 3);
276 while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(po s))) { pos += dir } 276 return function(line) {
277 return pos 277 if (lineIsHidden(cm.doc, line)) return 0;
278 } 278
279 279 var widgetsHeight = 0;
280 // Returns the value from the range [`from`; `to`] that satisfies 280 if (line.widgets) for (var i = 0; i < line.widgets.length; i++) {
281 // `pred` and is closest to `from`. Assumes that at least `to` satisfies `pred`. 281 if (line.widgets[i].height) widgetsHeight += line.widgets[i].height;
282 function findFirst(pred, from, to) { 282 }
283 for (;;) { 283
284 if (Math.abs(from - to) <= 1) { return pred(from) ? from : to } 284 if (wrapping)
285 var mid = Math.floor((from + to) / 2) 285 return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th ;
286 if (pred(mid)) { to = mid } 286 else
287 else { from = mid } 287 return widgetsHeight + th;
288 } 288 };
289 } 289 }
290 290
291 // The display handles the DOM integration, both for input reading 291 function estimateLineHeights(cm) {
292 // and content drawing. It holds references to DOM nodes and 292 var doc = cm.doc, est = estimateHeight(cm);
293 // display-related state. 293 doc.iter(function(line) {
294 294 var estHeight = est(line);
295 function Display(place, doc, input) { 295 if (estHeight != line.height) updateLineHeight(line, estHeight);
296 var d = this 296 });
297 this.input = input 297 }
298 298
299 // Covers bottom-right square when both scrollbars are present. 299 function themeChanged(cm) {
300 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler") 300 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s -\S+/g, "") +
301 d.scrollbarFiller.setAttribute("cm-not-content", "true") 301 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
302 // Covers bottom of gutter when coverGutterNextToScrollbar is on 302 clearCaches(cm);
303 // and h scrollbar is present. 303 }
304 d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler") 304
305 d.gutterFiller.setAttribute("cm-not-content", "true") 305 function guttersChanged(cm) {
306 // Will contain the actual code, positioned to cover the viewport. 306 updateGutters(cm);
307 d.lineDiv = eltP("div", null, "CodeMirror-code") 307 regChange(cm);
308 // Elements are added to these to represent selection and cursors. 308 setTimeout(function(){alignHorizontally(cm);}, 20);
309 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1") 309 }
310 d.cursorDiv = elt("div", null, "CodeMirror-cursors") 310
311 // A visibility: hidden element used to find the size of things. 311 // Rebuild the gutter elements, ensure the margin to the left of the
312 d.measure = elt("div", null, "CodeMirror-measure") 312 // code matches their width.
313 // When lines outside of the viewport are measured, they are drawn in this. 313 function updateGutters(cm) {
314 d.lineMeasure = elt("div", null, "CodeMirror-measure") 314 var gutters = cm.display.gutters, specs = cm.options.gutters;
315 // Wraps everything that needs to exist inside the vertically-padded coordinat e system 315 removeChildren(gutters);
316 d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorD iv, d.lineDiv], 316 for (var i = 0; i < specs.length; ++i) {
317 null, "position: relative; outline: none") 317 var gutterClass = specs[i];
318 var lines = eltP("div", [d.lineSpace], "CodeMirror-lines") 318 var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gut terClass));
319 // Moved around its parent to cover visible view. 319 if (gutterClass == "CodeMirror-linenumbers") {
320 d.mover = elt("div", [lines], null, "position: relative") 320 cm.display.lineGutter = gElt;
321 // Set to the height of the document, allowing scrolling. 321 gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
322 d.sizer = elt("div", [d.mover], "CodeMirror-sizer") 322 }
323 d.sizerWidth = null 323 }
324 // Behavior of elts with overflow: auto and padding is 324 gutters.style.display = i ? "" : "none";
325 // inconsistent across browsers. This is used to ensure the 325 updateGutterSpace(cm);
326 // scrollable area is big enough. 326 }
327 d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrol lerGap + "px; width: 1px;") 327
328 // Will contain the gutters, if any. 328 function updateGutterSpace(cm) {
329 d.gutters = elt("div", null, "CodeMirror-gutters") 329 var width = cm.display.gutters.offsetWidth;
330 d.lineGutter = null 330 cm.display.sizer.style.marginLeft = width + "px";
331 // Actual scrollable element. 331 }
332 d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scro ll") 332
333 d.scroller.setAttribute("tabIndex", "-1") 333 // Compute the character length of a line, taking into account
334 // The element in which the editor lives. 334 // collapsed ranges (see markText) that might hide parts, and join
335 d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeM irror") 335 // other lines onto it.
336 336 function lineLength(line) {
337 // Work around IE7 z-index bug (not perfect, hence IE7 not really being suppor ted) 337 if (line.height == 0) return 0;
338 if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.padd ingRight = 0 } 338 var len = line.text.length, merged, cur = line;
339 if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true } 339 while (merged = collapsedSpanAtStart(cur)) {
340 340 var found = merged.find(0, true);
341 if (place) { 341 cur = found.from.line;
342 if (place.appendChild) { place.appendChild(d.wrapper) } 342 len += found.from.ch - found.to.ch;
343 else { place(d.wrapper) } 343 }
344 } 344 cur = line;
345 345 while (merged = collapsedSpanAtEnd(cur)) {
346 // Current rendered range (may be bigger than the view window). 346 var found = merged.find(0, true);
347 d.viewFrom = d.viewTo = doc.first 347 len -= cur.text.length - found.from.ch;
348 d.reportedViewFrom = d.reportedViewTo = doc.first 348 cur = found.to.line;
349 // Information about the rendered lines. 349 len += cur.text.length - found.to.ch;
350 d.view = [] 350 }
351 d.renderedView = null 351 return len;
352 // Holds info about a single rendered line when it was rendered 352 }
353 // for measurement, while not in view. 353
354 d.externalMeasured = null 354 // Find the longest line in the document.
355 // Empty space (in pixels) above the view 355 function findMaxLine(cm) {
356 d.viewOffset = 0 356 var d = cm.display, doc = cm.doc;
357 d.lastWrapHeight = d.lastWrapWidth = 0 357 d.maxLine = getLine(doc, doc.first);
358 d.updateLineNumbers = null 358 d.maxLineLength = lineLength(d.maxLine);
359 359 d.maxLineChanged = true;
360 d.nativeBarWidth = d.barHeight = d.barWidth = 0 360 doc.iter(function(line) {
361 d.scrollbarsClipped = false 361 var len = lineLength(line);
362 362 if (len > d.maxLineLength) {
363 // Used to only resize the line number gutter when necessary (when 363 d.maxLineLength = len;
364 // the amount of lines crosses a boundary that makes its width change) 364 d.maxLine = line;
365 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null 365 }
366 // Set to true when a non-horizontal-scrolling line widget is 366 });
367 // added. As an optimization, line widget aligning is skipped when 367 }
368 // this is false. 368
369 d.alignWidgets = false 369 // Make sure the gutters options contains the element
370 370 // "CodeMirror-linenumbers" when the lineNumbers option is true.
371 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null 371 function setGuttersForLineNumbers(options) {
372 372 var found = indexOf(options.gutters, "CodeMirror-linenumbers");
373 // Tracks the maximum line length so that the horizontal scrollbar 373 if (found == -1 && options.lineNumbers) {
374 // can be kept static when scrolling. 374 options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]);
375 d.maxLine = null 375 } else if (found > -1 && !options.lineNumbers) {
376 d.maxLineLength = 0 376 options.gutters = options.gutters.slice(0);
377 d.maxLineChanged = false 377 options.gutters.splice(found, 1);
378 378 }
379 // Used for measuring wheel scrolling granularity 379 }
380 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null 380
381 381 // SCROLLBARS
382 // True when shift is held down. 382
383 d.shift = false 383 // Prepare DOM reads needed to update the scrollbars. Done in one
384 384 // shot to minimize update/measure roundtrips.
385 // Used to track whether anything happened since the context menu 385 function measureForScrollbars(cm) {
386 // was opened. 386 var d = cm.display, gutterW = d.gutters.offsetWidth;
387 d.selForContextMenu = null 387 var docH = Math.round(cm.doc.height + paddingVert(cm.display));
388 388 return {
389 d.activeTouch = null 389 clientHeight: d.scroller.clientHeight,
390 390 viewHeight: d.wrapper.clientHeight,
391 input.init(d) 391 scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth,
392 } 392 viewWidth: d.wrapper.clientWidth,
393 393 barLeft: cm.options.fixedGutter ? gutterW : 0,
394 // Find the line object corresponding to the given line number. 394 docHeight: docH,
395 function getLine(doc, n) { 395 scrollHeight: docH + scrollGap(cm) + d.barHeight,
396 n -= doc.first 396 nativeBarWidth: d.nativeBarWidth,
397 if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.f irst) + " in the document.") } 397 gutterWidth: gutterW
398 var chunk = doc 398 };
399 while (!chunk.lines) { 399 }
400 for (var i = 0;; ++i) { 400
401 var child = chunk.children[i], sz = child.chunkSize() 401 function NativeScrollbars(place, scroll, cm) {
402 if (n < sz) { chunk = child; break } 402 this.cm = cm;
403 n -= sz 403 var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")] , "CodeMirror-vscrollbar");
404 } 404 var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; m in-height: 1px")], "CodeMirror-hscrollbar");
405 } 405 place(vert); place(horiz);
406 return chunk.lines[n] 406
407 } 407 on(vert, "scroll", function() {
408 408 if (vert.clientHeight) scroll(vert.scrollTop, "vertical");
409 // Get the part of a document between two positions, as an array of 409 });
410 // strings. 410 on(horiz, "scroll", function() {
411 function getBetween(doc, start, end) { 411 if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal");
412 var out = [], n = start.line 412 });
413 doc.iter(start.line, end.line + 1, function (line) { 413
414 var text = line.text 414 this.checkedZeroWidth = false;
415 if (n == end.line) { text = text.slice(0, end.ch) } 415 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
416 if (n == start.line) { text = text.slice(start.ch) } 416 if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWi dth = "18px";
417 out.push(text) 417 }
418 ++n 418
419 }) 419 NativeScrollbars.prototype = copyObj({
420 return out 420 update: function(measure) {
421 } 421 var needsH = measure.scrollWidth > measure.clientWidth + 1;
422 // Get the lines between from and to, as array of strings. 422 var needsV = measure.scrollHeight > measure.clientHeight + 1;
423 function getLines(doc, from, to) { 423 var sWidth = measure.nativeBarWidth;
424 var out = [] 424
425 doc.iter(from, to, function (line) { out.push(line.text) }) // iter aborts whe n callback returns truthy value 425 if (needsV) {
426 return out 426 this.vert.style.display = "block";
427 } 427 this.vert.style.bottom = needsH ? sWidth + "px" : "0";
428 428 var totalHeight = measure.viewHeight - (needsH ? sWidth : 0);
429 // Update the height of a line, propagating the height change 429 // A bug in IE8 can cause this value to be negative, so guard it.
430 // upwards to parent nodes. 430 this.vert.firstChild.style.height =
431 function updateLineHeight(line, height) { 431 Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px";
432 var diff = height - line.height 432 } else {
433 if (diff) { for (var n = line; n; n = n.parent) { n.height += diff } } 433 this.vert.style.display = "";
434 } 434 this.vert.firstChild.style.height = "0";
435 435 }
436 // Given a line object, find its line number by walking up through 436
437 // its parent links. 437 if (needsH) {
438 function lineNo(line) { 438 this.horiz.style.display = "block";
439 if (line.parent == null) { return null } 439 this.horiz.style.right = needsV ? sWidth + "px" : "0";
440 var cur = line.parent, no = indexOf(cur.lines, line) 440 this.horiz.style.left = measure.barLeft + "px";
441 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { 441 var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0);
442 for (var i = 0;; ++i) { 442 this.horiz.firstChild.style.width =
443 if (chunk.children[i] == cur) { break } 443 (measure.scrollWidth - measure.clientWidth + totalWidth) + "px";
444 no += chunk.children[i].chunkSize() 444 } else {
445 } 445 this.horiz.style.display = "";
446 } 446 this.horiz.firstChild.style.width = "0";
447 return no + cur.first 447 }
448 } 448
449 449 if (!this.checkedZeroWidth && measure.clientHeight > 0) {
450 // Find the line at the given vertical position, using the height 450 if (sWidth == 0) this.zeroWidthHack();
451 // information in the document tree. 451 this.checkedZeroWidth = true;
452 function lineAtHeight(chunk, h) { 452 }
453 var n = chunk.first 453
454 outer: do { 454 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0};
455 for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) { 455 },
456 var child = chunk.children[i$1], ch = child.height 456 setScrollLeft: function(pos) {
457 if (h < ch) { chunk = child; continue outer } 457 if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos;
458 h -= ch 458 if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHor iz);
459 n += child.chunkSize() 459 },
460 } 460 setScrollTop: function(pos) {
461 return n 461 if (this.vert.scrollTop != pos) this.vert.scrollTop = pos;
462 } while (!chunk.lines) 462 if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert) ;
463 var i = 0 463 },
464 for (; i < chunk.lines.length; ++i) { 464 zeroWidthHack: function() {
465 var line = chunk.lines[i], lh = line.height 465 var w = mac && !mac_geMountainLion ? "12px" : "18px";
466 if (h < lh) { break } 466 this.horiz.style.height = this.vert.style.width = w;
467 h -= lh 467 this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none";
468 } 468 this.disableHoriz = new Delayed;
469 return n + i 469 this.disableVert = new Delayed;
470 } 470 },
471 471 enableZeroWidthBar: function(bar, delay) {
472 function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} 472 bar.style.pointerEvents = "auto";
473 473 function maybeDisable() {
474 function lineNumberFor(options, i) { 474 // To find out whether the scrollbar is still visible, we
475 return String(options.lineNumberFormatter(i + options.firstLineNumber)) 475 // check whether the element under the pixel in the bottom
476 } 476 // left corner of the scrollbar box is the scrollbar box
477 477 // itself (when the bar is still visible) or its filler child
478 // A Pos instance represents a position within the text. 478 // (when the bar is hidden). If it is still visible, we keep
479 function Pos(line, ch, sticky) { 479 // it enabled, if it's hidden, we disable pointer events.
480 if ( sticky === void 0 ) sticky = null; 480 var box = bar.getBoundingClientRect();
481 481 var elt = document.elementFromPoint(box.left + 1, box.bottom - 1);
482 if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) } 482 if (elt != bar) bar.style.pointerEvents = "none";
483 this.line = line 483 else delay.set(1000, maybeDisable);
484 this.ch = ch 484 }
485 this.sticky = sticky 485 delay.set(1000, maybeDisable);
486 } 486 },
487 487 clear: function() {
488 // Compare two positions, return 0 if they are the same, a negative 488 var parent = this.horiz.parentNode;
489 // number when a is less, and a positive number otherwise. 489 parent.removeChild(this.horiz);
490 function cmp(a, b) { return a.line - b.line || a.ch - b.ch } 490 parent.removeChild(this.vert);
491 491 }
492 function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } 492 }, NativeScrollbars.prototype);
493 493
494 function copyPos(x) {return Pos(x.line, x.ch)} 494 function NullScrollbars() {}
495 function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } 495
496 function minPos(a, b) { return cmp(a, b) < 0 ? a : b } 496 NullScrollbars.prototype = copyObj({
497 497 update: function() { return {bottom: 0, right: 0}; },
498 // Most of the external API clips given positions to make sure they 498 setScrollLeft: function() {},
499 // actually exist within the document. 499 setScrollTop: function() {},
500 function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + do c.size - 1))} 500 clear: function() {}
501 function clipPos(doc, pos) { 501 }, NullScrollbars.prototype);
502 if (pos.line < doc.first) { return Pos(doc.first, 0) } 502
503 var last = doc.first + doc.size - 1 503 CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbar s};
504 if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) } 504
505 return clipToLen(pos, getLine(doc, pos.line).text.length) 505 function initScrollbars(cm) {
506 } 506 if (cm.display.scrollbars) {
507 function clipToLen(pos, linelen) { 507 cm.display.scrollbars.clear();
508 var ch = pos.ch 508 if (cm.display.scrollbars.addClass)
509 if (ch == null || ch > linelen) { return Pos(pos.line, linelen) } 509 rmClass(cm.display.wrapper, cm.display.scrollbars.addClass);
510 else if (ch < 0) { return Pos(pos.line, 0) } 510 }
511 else { return pos } 511
512 } 512 cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarSt yle](function(node) {
513 function clipPosArray(doc, array) { 513 cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller);
514 var out = [] 514 // Prevent clicks in the scrollbars from killing focus
515 for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]) } 515 on(node, "mousedown", function() {
516 return out 516 if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0);
517 } 517 });
518 518 node.setAttribute("cm-not-content", "true");
519 // Optimize some code when these features are not used. 519 }, function(pos, axis) {
520 var sawReadOnlySpans = false; 520 if (axis == "horizontal") setScrollLeft(cm, pos);
521 var sawCollapsedSpans = false; 521 else setScrollTop(cm, pos);
522 function seeReadOnlySpans() { 522 }, cm);
523 sawReadOnlySpans = true 523 if (cm.display.scrollbars.addClass)
524 } 524 addClass(cm.display.wrapper, cm.display.scrollbars.addClass);
525 525 }
526 function seeCollapsedSpans() { 526
527 sawCollapsedSpans = true 527 function updateScrollbars(cm, measure) {
528 } 528 if (!measure) measure = measureForScrollbars(cm);
529 529 var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight;
530 // TEXTMARKER SPANS 530 updateScrollbarsInner(cm, measure);
531 531 for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) {
532 function MarkedSpan(marker, from, to) { 532 if (startWidth != cm.display.barWidth && cm.options.lineWrapping)
533 this.marker = marker 533 updateHeightsInViewport(cm);
534 this.from = from; this.to = to 534 updateScrollbarsInner(cm, measureForScrollbars(cm));
535 } 535 startWidth = cm.display.barWidth; startHeight = cm.display.barHeight;
536 536 }
537 // Search an array of spans for a span matching the given marker. 537 }
538 function getMarkedSpanFor(spans, marker) { 538
539 if (spans) { for (var i = 0; i < spans.length; ++i) { 539 // Re-synchronize the fake scrollbars with the actual size of the
540 var span = spans[i] 540 // content.
541 if (span.marker == marker) { return span } 541 function updateScrollbarsInner(cm, measure) {
542 } } 542 var d = cm.display;
543 } 543 var sizes = d.scrollbars.update(measure);
544 // Remove a span from an array, returning undefined if no spans are 544
545 // left (we don't store arrays for lines without spans). 545 d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px";
546 function removeMarkedSpan(spans, span) { 546 d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px";
547 var r 547 d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"
548 for (var i = 0; i < spans.length; ++i) 548
549 { if (spans[i] != span) { (r || (r = [])).push(spans[i]) } } 549 if (sizes.right && sizes.bottom) {
550 return r 550 d.scrollbarFiller.style.display = "block";
551 } 551 d.scrollbarFiller.style.height = sizes.bottom + "px";
552 // Add a span to a line. 552 d.scrollbarFiller.style.width = sizes.right + "px";
553 function addMarkedSpan(line, span) { 553 } else d.scrollbarFiller.style.display = "";
554 line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span] 554 if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixe dGutter) {
555 span.marker.attachLine(line) 555 d.gutterFiller.style.display = "block";
556 } 556 d.gutterFiller.style.height = sizes.bottom + "px";
557 557 d.gutterFiller.style.width = measure.gutterWidth + "px";
558 // Used for the algorithm that adjusts markers for a change in the 558 } else d.gutterFiller.style.display = "";
559 // document. These functions cut an array of spans at a given 559 }
560 // character position, returning an array of remaining chunks (or 560
561 // undefined if nothing remains). 561 // Compute the lines that are visible in a given viewport (defaults
562 function markedSpansBefore(old, startCh, isInsert) { 562 // the the current scroll position). viewport may contain top,
563 var nw 563 // height, and ensure (see op.scrollToPos) properties.
564 if (old) { for (var i = 0; i < old.length; ++i) { 564 function visibleLines(display, doc, viewport) {
565 var span = old[i], marker = span.marker 565 var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : dis play.scroller.scrollTop;
566 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh) 566 top = Math.floor(top - paddingTop(display));
567 if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!i sInsert || !span.marker.insertLeft)) { 567 var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + d isplay.wrapper.clientHeight;
568 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= sta rtCh : span.to > startCh) 568
569 ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)) 569 var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);
570 } 570 // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
571 } } 571 // forces those lines into the viewport (if possible).
572 return nw 572 if (viewport && viewport.ensure) {
573 } 573 var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to. line;
574 function markedSpansAfter(old, endCh, isInsert) { 574 if (ensureFrom < from) {
575 var nw 575 from = ensureFrom;
576 if (old) { for (var i = 0; i < old.length; ++i) { 576 to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display. wrapper.clientHeight);
577 var span = old[i], marker = span.marker 577 } else if (Math.min(ensureTo, doc.lastLine()) >= to) {
578 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh) 578 from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display. wrapper.clientHeight);
579 if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInse rt || span.marker.insertLeft)) { 579 to = ensureTo;
580 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) 580 }
581 ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span. from - endCh, 581 }
582 span.to == null ? null : span.to - e ndCh)) 582 return {from: from, to: Math.max(to, from + 1)};
583 } 583 }
584 } } 584
585 return nw 585 // LINE NUMBERS
586 } 586
587 587 // Re-align line numbers and gutter marks to compensate for
588 // Given a change object, compute the new set of marker spans that 588 // horizontal scrolling.
589 // cover the line in which the change took place. Removes spans 589 function alignHorizontally(cm) {
590 // entirely within the change, reconnects spans belonging to the 590 var display = cm.display, view = display.view;
591 // same marker that appear on both sides of the change, and cuts off 591 if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fix edGutter)) return;
592 // spans partially within the change. Returns an array of span 592 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm. doc.scrollLeft;
593 // arrays with one element for each line in (after) the change. 593 var gutterW = display.gutters.offsetWidth, left = comp + "px";
594 function stretchSpansOverChange(doc, change) { 594 for (var i = 0; i < view.length; i++) if (!view[i].hidden) {
595 if (change.full) { return null } 595 if (cm.options.fixedGutter) {
596 var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line) .markedSpans 596 if (view[i].gutter)
597 var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).mark edSpans 597 view[i].gutter.style.left = left;
598 if (!oldFirst && !oldLast) { return null } 598 if (view[i].gutterBackground)
599 599 view[i].gutterBackground.style.left = left;
600 var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from , change.to) == 0 600 }
601 // Get the spans that 'stick out' on both sides 601 var align = view[i].alignable;
602 var first = markedSpansBefore(oldFirst, startCh, isInsert) 602 if (align) for (var j = 0; j < align.length; j++)
603 var last = markedSpansAfter(oldLast, endCh, isInsert) 603 align[j].style.left = left;
604 604 }
605 // Next, merge those two ends 605 if (cm.options.fixedGutter)
606 var sameLine = change.text.length == 1, offset = lst(change.text).length + (sa meLine ? startCh : 0) 606 display.gutters.style.left = (comp + gutterW) + "px";
607 if (first) { 607 }
608 // Fix up .to properties of first 608
609 for (var i = 0; i < first.length; ++i) { 609 // Used to ensure that the line number gutter is still the right
610 var span = first[i] 610 // size for the current document size. Returns true when an update
611 if (span.to == null) { 611 // is needed.
612 var found = getMarkedSpanFor(last, span.marker) 612 function maybeUpdateLineNumberWidth(cm) {
613 if (!found) { span.to = startCh } 613 if (!cm.options.lineNumbers) return false;
614 else if (sameLine) { span.to = found.to == null ? null : found.to + offs et } 614 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1) , display = cm.display;
615 } 615 if (last.length != display.lineNumChars) {
616 } 616 var test = display.measure.appendChild(elt("div", [elt("div", last)],
617 } 617 "CodeMirror-linenumber CodeMirr or-gutter-elt"));
618 if (last) { 618 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - inn erW;
619 // Fix up .from in last (or move them into first in case of sameLine) 619 display.lineGutter.style.width = "";
620 for (var i$1 = 0; i$1 < last.length; ++i$1) { 620 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidt h - padding) + 1;
621 var span$1 = last[i$1] 621 display.lineNumWidth = display.lineNumInnerWidth + padding;
622 if (span$1.to != null) { span$1.to += offset } 622 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
623 if (span$1.from == null) { 623 display.lineGutter.style.width = display.lineNumWidth + "px";
624 var found$1 = getMarkedSpanFor(first, span$1.marker) 624 updateGutterSpace(cm);
625 if (!found$1) { 625 return true;
626 span$1.from = offset 626 }
627 if (sameLine) { (first || (first = [])).push(span$1) } 627 return false;
628 }
629
630 function lineNumberFor(options, i) {
631 return String(options.lineNumberFormatter(i + options.firstLineNumber));
632 }
633
634 // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
635 // but using getBoundingClientRect to get a sub-pixel-accurate
636 // result.
637 function compensateForHScroll(display) {
638 return display.scroller.getBoundingClientRect().left - display.sizer.getBoun dingClientRect().left;
639 }
640
641 // DISPLAY DRAWING
642
643 function DisplayUpdate(cm, viewport, force) {
644 var display = cm.display;
645
646 this.viewport = viewport;
647 // Store some values that we'll need later (but don't want to force a relayo ut for)
648 this.visible = visibleLines(display, cm.doc, viewport);
649 this.editorIsHidden = !display.wrapper.offsetWidth;
650 this.wrapperHeight = display.wrapper.clientHeight;
651 this.wrapperWidth = display.wrapper.clientWidth;
652 this.oldDisplayWidth = displayWidth(cm);
653 this.force = force;
654 this.dims = getDimensions(cm);
655 this.events = [];
656 }
657
658 DisplayUpdate.prototype.signal = function(emitter, type) {
659 if (hasHandler(emitter, type))
660 this.events.push(arguments);
661 };
662 DisplayUpdate.prototype.finish = function() {
663 for (var i = 0; i < this.events.length; i++)
664 signal.apply(null, this.events[i]);
665 };
666
667 function maybeClipScrollbars(cm) {
668 var display = cm.display;
669 if (!display.scrollbarsClipped && display.scroller.offsetWidth) {
670 display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.c lientWidth;
671 display.heightForcer.style.height = scrollGap(cm) + "px";
672 display.sizer.style.marginBottom = -display.nativeBarWidth + "px";
673 display.sizer.style.borderRightWidth = scrollGap(cm) + "px";
674 display.scrollbarsClipped = true;
675 }
676 }
677
678 // Does the actual updating of the line display. Bails out
679 // (returning false) when there is nothing to be done and forced is
680 // false.
681 function updateDisplayIfNeeded(cm, update) {
682 var display = cm.display, doc = cm.doc;
683
684 if (update.editorIsHidden) {
685 resetView(cm);
686 return false;
687 }
688
689 // Bail out if the visible area is already rendered and nothing changed.
690 if (!update.force &&
691 update.visible.from >= display.viewFrom && update.visible.to <= display. viewTo &&
692 (display.updateLineNumbers == null || display.updateLineNumbers >= displ ay.viewTo) &&
693 display.renderedView == display.view && countDirtyView(cm) == 0)
694 return false;
695
696 if (maybeUpdateLineNumberWidth(cm)) {
697 resetView(cm);
698 update.dims = getDimensions(cm);
699 }
700
701 // Compute a suitable new viewport (from & to)
702 var end = doc.first + doc.size;
703 var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.fir st);
704 var to = Math.min(end, update.visible.to + cm.options.viewportMargin);
705 if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max (doc.first, display.viewFrom);
706 if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, disp lay.viewTo);
707 if (sawCollapsedSpans) {
708 from = visualLineNo(cm.doc, from);
709 to = visualLineEndNo(cm.doc, to);
710 }
711
712 var different = from != display.viewFrom || to != display.viewTo ||
713 display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;
714 adjustView(cm, from, to);
715
716 display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));
717 // Position the mover div to align with the current scroll position
718 cm.display.mover.style.top = display.viewOffset + "px";
719
720 var toUpdate = countDirtyView(cm);
721 if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
722 (display.updateLineNumbers == null || display.updateLineNumbers >= displ ay.viewTo))
723 return false;
724
725 // For big changes, we hide the enclosing element during the
726 // update, since that speeds up the operations on most browsers.
727 var focused = activeElt();
728 if (toUpdate > 4) display.lineDiv.style.display = "none";
729 patchDisplay(cm, display.updateLineNumbers, update.dims);
730 if (toUpdate > 4) display.lineDiv.style.display = "";
731 display.renderedView = display.view;
732 // There might have been a widget with a focused element that got
733 // hidden or updated, if so re-focus it.
734 if (focused && activeElt() != focused && focused.offsetHeight) focused.focus ();
735
736 // Prevent selection and cursors from interfering with the scroll
737 // width and height.
738 removeChildren(display.cursorDiv);
739 removeChildren(display.selectionDiv);
740 display.gutters.style.height = display.sizer.style.minHeight = 0;
741
742 if (different) {
743 display.lastWrapHeight = update.wrapperHeight;
744 display.lastWrapWidth = update.wrapperWidth;
745 startWorker(cm, 400);
746 }
747
748 display.updateLineNumbers = null;
749
750 return true;
751 }
752
753 function postUpdateDisplay(cm, update) {
754 var viewport = update.viewport;
755
756 for (var first = true;; first = false) {
757 if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displa yWidth(cm)) {
758 // Clip forced viewport to actual scrollable area.
759 if (viewport && viewport.top != null)
760 viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - di splayHeight(cm), viewport.top)};
761 // Updated line heights might result in the drawn area not
762 // actually covering the viewport. Keep looping until it does.
763 update.visible = visibleLines(cm.display, cm.doc, viewport);
764 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= c m.display.viewTo)
765 break;
766 }
767 if (!updateDisplayIfNeeded(cm, update)) break;
768 updateHeightsInViewport(cm);
769 var barMeasure = measureForScrollbars(cm);
770 updateSelection(cm);
771 updateScrollbars(cm, barMeasure);
772 setDocumentHeight(cm, barMeasure);
773 }
774
775 update.signal(cm, "update", cm);
776 if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {
777 update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.vi ewTo);
778 cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedView To = cm.display.viewTo;
779 }
780 }
781
782 function updateDisplaySimple(cm, viewport) {
783 var update = new DisplayUpdate(cm, viewport);
784 if (updateDisplayIfNeeded(cm, update)) {
785 updateHeightsInViewport(cm);
786 postUpdateDisplay(cm, update);
787 var barMeasure = measureForScrollbars(cm);
788 updateSelection(cm);
789 updateScrollbars(cm, barMeasure);
790 setDocumentHeight(cm, barMeasure);
791 update.finish();
792 }
793 }
794
795 function setDocumentHeight(cm, measure) {
796 cm.display.sizer.style.minHeight = measure.docHeight + "px";
797 cm.display.heightForcer.style.top = measure.docHeight + "px";
798 cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px";
799 }
800
801 // Read the actual heights of the rendered lines, and update their
802 // stored heights to match.
803 function updateHeightsInViewport(cm) {
804 var display = cm.display;
805 var prevBottom = display.lineDiv.offsetTop;
806 for (var i = 0; i < display.view.length; i++) {
807 var cur = display.view[i], height;
808 if (cur.hidden) continue;
809 if (ie && ie_version < 8) {
810 var bot = cur.node.offsetTop + cur.node.offsetHeight;
811 height = bot - prevBottom;
812 prevBottom = bot;
813 } else {
814 var box = cur.node.getBoundingClientRect();
815 height = box.bottom - box.top;
816 }
817 var diff = cur.line.height - height;
818 if (height < 2) height = textHeight(display);
819 if (diff > .001 || diff < -.001) {
820 updateLineHeight(cur.line, height);
821 updateWidgetHeight(cur.line);
822 if (cur.rest) for (var j = 0; j < cur.rest.length; j++)
823 updateWidgetHeight(cur.rest[j]);
824 }
825 }
826 }
827
828 // Read and store the height of line widgets associated with the
829 // given line.
830 function updateWidgetHeight(line) {
831 if (line.widgets) for (var i = 0; i < line.widgets.length; ++i)
832 line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight;
833 }
834
835 // Do a bulk-read of the DOM positions and sizes needed to draw the
836 // view, so that we don't interleave reading and writing to the DOM.
837 function getDimensions(cm) {
838 var d = cm.display, left = {}, width = {};
839 var gutterLeft = d.gutters.clientLeft;
840 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
841 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft;
842 width[cm.options.gutters[i]] = n.clientWidth;
843 }
844 return {fixedPos: compensateForHScroll(d),
845 gutterTotalWidth: d.gutters.offsetWidth,
846 gutterLeft: left,
847 gutterWidth: width,
848 wrapperWidth: d.wrapper.clientWidth};
849 }
850
851 // Sync the actual display DOM structure with display.view, removing
852 // nodes for lines that are no longer in view, and creating the ones
853 // that are not there yet, and updating the ones that are out of
854 // date.
855 function patchDisplay(cm, updateNumbersFrom, dims) {
856 var display = cm.display, lineNumbers = cm.options.lineNumbers;
857 var container = display.lineDiv, cur = container.firstChild;
858
859 function rm(node) {
860 var next = node.nextSibling;
861 // Works around a throw-scroll bug in OS X Webkit
862 if (webkit && mac && cm.display.currentWheelTarget == node)
863 node.style.display = "none";
864 else
865 node.parentNode.removeChild(node);
866 return next;
867 }
868
869 var view = display.view, lineN = display.viewFrom;
870 // Loop over the elements in the view, syncing cur (the DOM nodes
871 // in display.lineDiv) with the view as we go.
872 for (var i = 0; i < view.length; i++) {
873 var lineView = view[i];
874 if (lineView.hidden) {
875 } else if (!lineView.node || lineView.node.parentNode != container) { // N ot drawn yet
876 var node = buildLineElement(cm, lineView, lineN, dims);
877 container.insertBefore(node, cur);
878 } else { // Already drawn
879 while (cur != lineView.node) cur = rm(cur);
880 var updateNumber = lineNumbers && updateNumbersFrom != null &&
881 updateNumbersFrom <= lineN && lineView.lineNumber;
882 if (lineView.changes) {
883 if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false;
884 updateLineForChanges(cm, lineView, lineN, dims);
628 } 885 }
629 } else { 886 if (updateNumber) {
630 span$1.from += offset 887 removeChildren(lineView.lineNumber);
631 if (sameLine) { (first || (first = [])).push(span$1) } 888 lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor( cm.options, lineN)));
632 }
633 }
634 }
635 // Make sure we didn't create any zero-length spans
636 if (first) { first = clearEmptySpans(first) }
637 if (last && last != first) { last = clearEmptySpans(last) }
638
639 var newMarkers = [first]
640 if (!sameLine) {
641 // Fill gap with whole-line-spans
642 var gap = change.text.length - 2, gapMarkers
643 if (gap > 0 && first)
644 { for (var i$2 = 0; i$2 < first.length; ++i$2)
645 { if (first[i$2].to == null)
646 { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].mar ker, null, null)) } } }
647 for (var i$3 = 0; i$3 < gap; ++i$3)
648 { newMarkers.push(gapMarkers) }
649 newMarkers.push(last)
650 }
651 return newMarkers
652 }
653
654 // Remove spans that are empty and don't have a clearWhenEmpty
655 // option of false.
656 function clearEmptySpans(spans) {
657 for (var i = 0; i < spans.length; ++i) {
658 var span = spans[i]
659 if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)
660 { spans.splice(i--, 1) }
661 }
662 if (!spans.length) { return null }
663 return spans
664 }
665
666 // Used to 'clip' out readOnly ranges when making a change.
667 function removeReadOnlyRanges(doc, from, to) {
668 var markers = null
669 doc.iter(from.line, to.line + 1, function (line) {
670 if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {
671 var mark = line.markedSpans[i].marker
672 if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
673 { (markers || (markers = [])).push(mark) }
674 } }
675 })
676 if (!markers) { return null }
677 var parts = [{from: from, to: to}]
678 for (var i = 0; i < markers.length; ++i) {
679 var mk = markers[i], m = mk.find(0)
680 for (var j = 0; j < parts.length; ++j) {
681 var p = parts[j]
682 if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue }
683 var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to)
684 if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)
685 { newParts.push({from: p.from, to: m.from}) }
686 if (dto > 0 || !mk.inclusiveRight && !dto)
687 { newParts.push({from: m.to, to: p.to}) }
688 parts.splice.apply(parts, newParts)
689 j += newParts.length - 3
690 }
691 }
692 return parts
693 }
694
695 // Connect or disconnect spans from a line.
696 function detachMarkedSpans(line) {
697 var spans = line.markedSpans
698 if (!spans) { return }
699 for (var i = 0; i < spans.length; ++i)
700 { spans[i].marker.detachLine(line) }
701 line.markedSpans = null
702 }
703 function attachMarkedSpans(line, spans) {
704 if (!spans) { return }
705 for (var i = 0; i < spans.length; ++i)
706 { spans[i].marker.attachLine(line) }
707 line.markedSpans = spans
708 }
709
710 // Helpers used when computing which overlapping collapsed span
711 // counts as the larger one.
712 function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 }
713 function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 }
714
715 // Returns a number indicating which of two overlapping collapsed
716 // spans is larger (and thus includes the other). Falls back to
717 // comparing ids when the spans cover exactly the same range.
718 function compareCollapsedMarkers(a, b) {
719 var lenDiff = a.lines.length - b.lines.length
720 if (lenDiff != 0) { return lenDiff }
721 var aPos = a.find(), bPos = b.find()
722 var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b)
723 if (fromCmp) { return -fromCmp }
724 var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b)
725 if (toCmp) { return toCmp }
726 return b.id - a.id
727 }
728
729 // Find out whether a line ends or starts in a collapsed span. If
730 // so, return the marker for that span.
731 function collapsedSpanAtSide(line, start) {
732 var sps = sawCollapsedSpans && line.markedSpans, found
733 if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {
734 sp = sps[i]
735 if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&
736 (!found || compareCollapsedMarkers(found, sp.marker) < 0))
737 { found = sp.marker }
738 } }
739 return found
740 }
741 function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) }
742 function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) }
743
744 // Test whether there exists a collapsed span that partially
745 // overlaps (covers the start or end, but not both) of a new span.
746 // Such overlap is not allowed.
747 function conflictingCollapsedRange(doc, lineNo, from, to, marker) {
748 var line = getLine(doc, lineNo)
749 var sps = sawCollapsedSpans && line.markedSpans
750 if (sps) { for (var i = 0; i < sps.length; ++i) {
751 var sp = sps[i]
752 if (!sp.marker.collapsed) { continue }
753 var found = sp.marker.find(0)
754 var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(mark er)
755 var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker)
756 if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue }
757 if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp( found.to, from) >= 0 : cmp(found.to, from) > 0) ||
758 fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp( found.from, to) <= 0 : cmp(found.from, to) < 0))
759 { return true }
760 } }
761 }
762
763 // A visual line is a line as drawn on the screen. Folding, for
764 // example, can cause multiple logical lines to appear on the same
765 // visual line. This finds the start of the visual line that the
766 // given line is part of (usually that is the line itself).
767 function visualLine(line) {
768 var merged
769 while (merged = collapsedSpanAtStart(line))
770 { line = merged.find(-1, true).line }
771 return line
772 }
773
774 function visualLineEnd(line) {
775 var merged
776 while (merged = collapsedSpanAtEnd(line))
777 { line = merged.find(1, true).line }
778 return line
779 }
780
781 // Returns an array of logical lines that continue the visual line
782 // started by the argument, or undefined if there are no such lines.
783 function visualLineContinued(line) {
784 var merged, lines
785 while (merged = collapsedSpanAtEnd(line)) {
786 line = merged.find(1, true).line
787 ;(lines || (lines = [])).push(line)
788 }
789 return lines
790 }
791
792 // Get the line number of the start of the visual line that the
793 // given line number is part of.
794 function visualLineNo(doc, lineN) {
795 var line = getLine(doc, lineN), vis = visualLine(line)
796 if (line == vis) { return lineN }
797 return lineNo(vis)
798 }
799
800 // Get the line number of the start of the next visual line after
801 // the given line.
802 function visualLineEndNo(doc, lineN) {
803 if (lineN > doc.lastLine()) { return lineN }
804 var line = getLine(doc, lineN), merged
805 if (!lineIsHidden(doc, line)) { return lineN }
806 while (merged = collapsedSpanAtEnd(line))
807 { line = merged.find(1, true).line }
808 return lineNo(line) + 1
809 }
810
811 // Compute whether a line is hidden. Lines count as hidden when they
812 // are part of a visual line that starts with another line, or when
813 // they are entirely covered by collapsed, non-widget span.
814 function lineIsHidden(doc, line) {
815 var sps = sawCollapsedSpans && line.markedSpans
816 if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {
817 sp = sps[i]
818 if (!sp.marker.collapsed) { continue }
819 if (sp.from == null) { return true }
820 if (sp.marker.widgetNode) { continue }
821 if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
822 { return true }
823 } }
824 }
825 function lineIsHiddenInner(doc, line, span) {
826 if (span.to == null) {
827 var end = span.marker.find(1, true)
828 return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpan s, span.marker))
829 }
830 if (span.marker.inclusiveRight && span.to == line.text.length)
831 { return true }
832 for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) {
833 sp = line.markedSpans[i]
834 if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&
835 (sp.to == null || sp.to != span.from) &&
836 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
837 lineIsHiddenInner(doc, line, sp)) { return true }
838 }
839 }
840
841 // Find the height above the given line.
842 function heightAtLine(lineObj) {
843 lineObj = visualLine(lineObj)
844
845 var h = 0, chunk = lineObj.parent
846 for (var i = 0; i < chunk.lines.length; ++i) {
847 var line = chunk.lines[i]
848 if (line == lineObj) { break }
849 else { h += line.height }
850 }
851 for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
852 for (var i$1 = 0; i$1 < p.children.length; ++i$1) {
853 var cur = p.children[i$1]
854 if (cur == chunk) { break }
855 else { h += cur.height }
856 }
857 }
858 return h
859 }
860
861 // Compute the character length of a line, taking into account
862 // collapsed ranges (see markText) that might hide parts, and join
863 // other lines onto it.
864 function lineLength(line) {
865 if (line.height == 0) { return 0 }
866 var len = line.text.length, merged, cur = line
867 while (merged = collapsedSpanAtStart(cur)) {
868 var found = merged.find(0, true)
869 cur = found.from.line
870 len += found.from.ch - found.to.ch
871 }
872 cur = line
873 while (merged = collapsedSpanAtEnd(cur)) {
874 var found$1 = merged.find(0, true)
875 len -= cur.text.length - found$1.from.ch
876 cur = found$1.to.line
877 len += cur.text.length - found$1.to.ch
878 }
879 return len
880 }
881
882 // Find the longest line in the document.
883 function findMaxLine(cm) {
884 var d = cm.display, doc = cm.doc
885 d.maxLine = getLine(doc, doc.first)
886 d.maxLineLength = lineLength(d.maxLine)
887 d.maxLineChanged = true
888 doc.iter(function (line) {
889 var len = lineLength(line)
890 if (len > d.maxLineLength) {
891 d.maxLineLength = len
892 d.maxLine = line
893 }
894 })
895 }
896
897 // BIDI HELPERS
898
899 function iterateBidiSections(order, from, to, f) {
900 if (!order) { return f(from, to, "ltr") }
901 var found = false
902 for (var i = 0; i < order.length; ++i) {
903 var part = order[i]
904 if (part.from < to && part.to > from || from == to && part.to == from) {
905 f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl " : "ltr")
906 found = true
907 }
908 }
909 if (!found) { f(from, to, "ltr") }
910 }
911
912 var bidiOther = null
913 function getBidiPartAt(order, ch, sticky) {
914 var found
915 bidiOther = null
916 for (var i = 0; i < order.length; ++i) {
917 var cur = order[i]
918 if (cur.from < ch && cur.to > ch) { return i }
919 if (cur.to == ch) {
920 if (cur.from != cur.to && sticky == "before") { found = i }
921 else { bidiOther = i }
922 }
923 if (cur.from == ch) {
924 if (cur.from != cur.to && sticky != "before") { found = i }
925 else { bidiOther = i }
926 }
927 }
928 return found != null ? found : bidiOther
929 }
930
931 // Bidirectional ordering algorithm
932 // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
933 // that this (partially) implements.
934
935 // One-char codes used for character types:
936 // L (L): Left-to-Right
937 // R (R): Right-to-Left
938 // r (AL): Right-to-Left Arabic
939 // 1 (EN): European Number
940 // + (ES): European Number Separator
941 // % (ET): European Number Terminator
942 // n (AN): Arabic Number
943 // , (CS): Common Number Separator
944 // m (NSM): Non-Spacing Mark
945 // b (BN): Boundary Neutral
946 // s (B): Paragraph Separator
947 // t (S): Segment Separator
948 // w (WS): Whitespace
949 // N (ON): Other Neutrals
950
951 // Returns null if characters are ordered as they appear
952 // (left-to-right), or an array of sections ({from, to, level}
953 // objects) in the order in which they occur visually.
954 var bidiOrdering = (function() {
955 // Character types for codepoints 0 to 0xff
956 var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNN NNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbb bbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLL LLLLLLLLLLLLLLLLLLLLLLLLLN"
957 // Character types for codepoints 0x600 to 0x6f9
958 var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmm mmnNmmmmmmrrmmNmmmmrr1111111111"
959 function charType(code) {
960 if (code <= 0xf7) { return lowTypes.charAt(code) }
961 else if (0x590 <= code && code <= 0x5f4) { return "R" }
962 else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) }
963 else if (0x6ee <= code && code <= 0x8ac) { return "r" }
964 else if (0x2000 <= code && code <= 0x200b) { return "w" }
965 else if (code == 0x200c) { return "b" }
966 else { return "L" }
967 }
968
969 var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/
970 var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsA sNum = /[1n]/
971
972 function BidiSpan(level, from, to) {
973 this.level = level
974 this.from = from; this.to = to
975 }
976
977 return function(str, direction) {
978 var outerType = direction == "ltr" ? "L" : "R"
979
980 if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return fal se }
981 var len = str.length, types = []
982 for (var i = 0; i < len; ++i)
983 { types.push(charType(str.charCodeAt(i))) }
984
985 // W1. Examine each non-spacing mark (NSM) in the level run, and
986 // change the type of the NSM to the type of the previous
987 // character. If the NSM is at the start of the level run, it will
988 // get the type of sor.
989 for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) {
990 var type = types[i$1]
991 if (type == "m") { types[i$1] = prev }
992 else { prev = type }
993 }
994
995 // W2. Search backwards from each instance of a European number
996 // until the first strong type (R, L, AL, or sor) is found. If an
997 // AL is found, change the type of the European number to Arabic
998 // number.
999 // W3. Change all ALs to R.
1000 for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) {
1001 var type$1 = types[i$2]
1002 if (type$1 == "1" && cur == "r") { types[i$2] = "n" }
1003 else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types [i$2] = "R" } }
1004 }
1005
1006 // W4. A single European separator between two European numbers
1007 // changes to a European number. A single common separator between
1008 // two numbers of the same type changes to that type.
1009 for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) {
1010 var type$2 = types[i$3]
1011 if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1" }
1012 else if (type$2 == "," && prev$1 == types[i$3+1] &&
1013 (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1 }
1014 prev$1 = type$2
1015 }
1016
1017 // W5. A sequence of European terminators adjacent to European
1018 // numbers changes to all European numbers.
1019 // W6. Otherwise, separators and terminators change to Other
1020 // Neutral.
1021 for (var i$4 = 0; i$4 < len; ++i$4) {
1022 var type$3 = types[i$4]
1023 if (type$3 == ",") { types[i$4] = "N" }
1024 else if (type$3 == "%") {
1025 var end = (void 0)
1026 for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {}
1027 var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] = = "1") ? "1" : "N"
1028 for (var j = i$4; j < end; ++j) { types[j] = replace }
1029 i$4 = end - 1
1030 }
1031 }
1032
1033 // W7. Search backwards from each instance of a European number
1034 // until the first strong type (R, L, or sor) is found. If an L is
1035 // found, then change the type of the European number to L.
1036 for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) {
1037 var type$4 = types[i$5]
1038 if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L" }
1039 else if (isStrong.test(type$4)) { cur$1 = type$4 }
1040 }
1041
1042 // N1. A sequence of neutrals takes the direction of the
1043 // surrounding strong text if the text on both sides has the same
1044 // direction. European and Arabic numbers act as if they were R in
1045 // terms of their influence on neutrals. Start-of-level-run (sor)
1046 // and end-of-level-run (eor) are used at level run boundaries.
1047 // N2. Any remaining neutrals take the embedding direction.
1048 for (var i$6 = 0; i$6 < len; ++i$6) {
1049 if (isNeutral.test(types[i$6])) {
1050 var end$1 = (void 0)
1051 for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end $1) {}
1052 var before = (i$6 ? types[i$6-1] : outerType) == "L"
1053 var after = (end$1 < len ? types[end$1] : outerType) == "L"
1054 var replace$1 = before == after ? (before ? "L" : "R") : outerType
1055 for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1 }
1056 i$6 = end$1 - 1
1057 }
1058 }
1059
1060 // Here we depart from the documented algorithm, in order to avoid
1061 // building up an actual levels array. Since there are only three
1062 // levels (0, 1, 2) in an implementation that doesn't take
1063 // explicit embedding into account, we can build up the order on
1064 // the fly, without following the level-based algorithm.
1065 var order = [], m
1066 for (var i$7 = 0; i$7 < len;) {
1067 if (countsAsLeft.test(types[i$7])) {
1068 var start = i$7
1069 for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {}
1070 order.push(new BidiSpan(0, start, i$7))
1071 } else {
1072 var pos = i$7, at = order.length
1073 for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {}
1074 for (var j$2 = pos; j$2 < i$7;) {
1075 if (countsAsNum.test(types[j$2])) {
1076 if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)) }
1077 var nstart = j$2
1078 for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {}
1079 order.splice(at, 0, new BidiSpan(2, nstart, j$2))
1080 pos = j$2
1081 } else { ++j$2 }
1082 } 889 }
1083 if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)) } 890 cur = lineView.node.nextSibling;
1084 } 891 }
1085 } 892 lineN += lineView.size;
1086 if (order[0].level == 1 && (m = str.match(/^\s+/))) { 893 }
1087 order[0].from = m[0].length 894 while (cur) cur = rm(cur);
1088 order.unshift(new BidiSpan(0, 0, m[0].length)) 895 }
1089 } 896
1090 if (lst(order).level == 1 && (m = str.match(/\s+$/))) { 897 // When an aspect of a line changes, a string is added to
1091 lst(order).to -= m[0].length 898 // lineView.changes. This updates the relevant part of the line's
1092 order.push(new BidiSpan(0, len - m[0].length, len)) 899 // DOM structure.
1093 } 900 function updateLineForChanges(cm, lineView, lineN, dims) {
1094 901 for (var j = 0; j < lineView.changes.length; j++) {
1095 return direction == "rtl" ? order.reverse() : order 902 var type = lineView.changes[j];
1096 } 903 if (type == "text") updateLineText(cm, lineView);
1097 })() 904 else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims);
1098 905 else if (type == "class") updateLineClasses(lineView);
1099 // Get the bidi ordering for the given line (and cache it). Returns 906 else if (type == "widget") updateLineWidgets(cm, lineView, dims);
1100 // false for lines that are fully left-to-right, and an array of 907 }
1101 // BidiSpan objects otherwise. 908 lineView.changes = null;
1102 function getOrder(line, direction) { 909 }
1103 var order = line.order 910
1104 if (order == null) { order = line.order = bidiOrdering(line.text, direction) } 911 // Lines with gutter elements, widgets or a background class need to
1105 return order 912 // be wrapped, and have the extra elements added to the wrapper div
1106 } 913 function ensureLineWrapped(lineView) {
1107 914 if (lineView.node == lineView.text) {
1108 function moveCharLogically(line, ch, dir) { 915 lineView.node = elt("div", null, null, "position: relative");
1109 var target = skipExtendingChars(line.text, ch + dir, dir) 916 if (lineView.text.parentNode)
1110 return target < 0 || target > line.text.length ? null : target 917 lineView.text.parentNode.replaceChild(lineView.node, lineView.text);
1111 } 918 lineView.node.appendChild(lineView.text);
1112 919 if (ie && ie_version < 8) lineView.node.style.zIndex = 2;
1113 function moveLogically(line, start, dir) { 920 }
1114 var ch = moveCharLogically(line, start.ch, dir) 921 return lineView.node;
1115 return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before ") 922 }
1116 } 923
1117 924 function updateLineBackground(lineView) {
1118 function endOfLine(visually, cm, lineObj, lineNo, dir) { 925 var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass;
1119 if (visually) { 926 if (cls) cls += " CodeMirror-linebackground";
1120 var order = getOrder(lineObj, cm.doc.direction) 927 if (lineView.background) {
1121 if (order) { 928 if (cls) lineView.background.className = cls;
1122 var part = dir < 0 ? lst(order) : order[0] 929 else { lineView.background.parentNode.removeChild(lineView.background); li neView.background = null; }
1123 var moveInStorageOrder = (dir < 0) == (part.level == 1) 930 } else if (cls) {
1124 var sticky = moveInStorageOrder ? "after" : "before" 931 var wrap = ensureLineWrapped(lineView);
1125 var ch 932 lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstC hild);
1126 // With a wrapped rtl chunk (possibly spanning multiple bidi parts), 933 }
1127 // it could be that the last bidi part is not on the last visual line, 934 }
1128 // since visual lines contain content order-consecutive chunks. 935
1129 // Thus, in rtl, we are looking for the first (content-order) character 936 // Wrapper around buildLineContent which will reuse the structure
1130 // in the rtl chunk that is on the last line (that is, the same line 937 // in display.externalMeasured when possible.
1131 // as the last (content-order) character). 938 function getLineContent(cm, lineView) {
1132 if (part.level > 0) { 939 var ext = cm.display.externalMeasured;
1133 var prep = prepareMeasureForLine(cm, lineObj) 940 if (ext && ext.line == lineView.line) {
1134 ch = dir < 0 ? lineObj.text.length - 1 : 0 941 cm.display.externalMeasured = null;
1135 var targetTop = measureCharPrepared(cm, prep, ch).top 942 lineView.measure = ext.measure;
1136 ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch). top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, c h) 943 return ext.built;
1137 if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1, true) } 944 }
1138 } else { ch = dir < 0 ? part.to : part.from } 945 return buildLineContent(cm, lineView);
1139 return new Pos(lineNo, ch, sticky) 946 }
1140 } 947
1141 } 948 // Redraw the line's text. Interacts with the background and text
1142 return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") 949 // classes because the mode may output tokens that influence these
1143 } 950 // classes.
1144 951 function updateLineText(cm, lineView) {
1145 function moveVisually(cm, line, start, dir) { 952 var cls = lineView.text.className;
1146 var bidi = getOrder(line, cm.doc.direction) 953 var built = getLineContent(cm, lineView);
1147 if (!bidi) { return moveLogically(line, start, dir) } 954 if (lineView.text == lineView.node) lineView.node = built.pre;
1148 if (start.ch >= line.text.length) { 955 lineView.text.parentNode.replaceChild(built.pre, lineView.text);
1149 start.ch = line.text.length 956 lineView.text = built.pre;
1150 start.sticky = "before" 957 if (built.bgClass != lineView.bgClass || built.textClass != lineView.textCla ss) {
1151 } else if (start.ch <= 0) { 958 lineView.bgClass = built.bgClass;
1152 start.ch = 0 959 lineView.textClass = built.textClass;
1153 start.sticky = "after" 960 updateLineClasses(lineView);
1154 } 961 } else if (cls) {
1155 var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos ] 962 lineView.text.className = cls;
1156 if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > s tart.ch : part.from < start.ch)) { 963 }
1157 // Case 1: We move within an ltr part in an ltr editor. Even with wrapped li nes, 964 }
1158 // nothing interesting happens. 965
1159 return moveLogically(line, start, dir) 966 function updateLineClasses(lineView) {
1160 } 967 updateLineBackground(lineView);
1161 968 if (lineView.line.wrapClass)
1162 var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof P os ? pos.ch : pos, dir); } 969 ensureLineWrapped(lineView).className = lineView.line.wrapClass;
1163 var prep 970 else if (lineView.node != lineView.text)
1164 var getWrappedLineExtent = function (ch) { 971 lineView.node.className = "";
1165 if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } 972 var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.li ne.textClass || "") : lineView.line.textClass;
1166 prep = prep || prepareMeasureForLine(cm, line) 973 lineView.text.className = textClass || "";
1167 return wrappedLineExtentChar(cm, line, prep, ch) 974 }
1168 } 975
1169 var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(sta rt, -1) : start.ch) 976 function updateLineGutter(cm, lineView, lineN, dims) {
1170 977 if (lineView.gutter) {
1171 if (cm.doc.direction == "rtl" || part.level == 1) { 978 lineView.node.removeChild(lineView.gutter);
1172 var moveInStorageOrder = (part.level == 1) == (dir < 0) 979 lineView.gutter = null;
1173 var ch = mv(start, moveInStorageOrder ? 1 : -1) 980 }
1174 if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLin eExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { 981 if (lineView.gutterBackground) {
1175 // Case 2: We move within an rtl part or in an rtl editor on the same visu al line 982 lineView.node.removeChild(lineView.gutterBackground);
1176 var sticky = moveInStorageOrder ? "before" : "after" 983 lineView.gutterBackground = null;
1177 return new Pos(start.line, ch, sticky) 984 }
1178 } 985 if (lineView.line.gutterClass) {
1179 } 986 var wrap = ensureLineWrapped(lineView);
1180 987 lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass,
1181 // Case 3: Could not move within this bidi part in this visual line, so leave 988 "left: " + (cm.options.fixedGutter ? dims. fixedPos : -dims.gutterTotalWidth) +
1182 // the current bidi part 989 "px; width: " + dims.gutterTotalWidth + "p x");
1183 990 wrap.insertBefore(lineView.gutterBackground, lineView.text);
1184 var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { 991 }
1185 var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder 992 var markers = lineView.line.gutterMarkers;
1186 ? new Pos(start.line, mv(ch, 1), "before") 993 if (cm.options.lineNumbers || markers) {
1187 : new Pos(start.line, ch, "after"); } 994 var wrap = ensureLineWrapped(lineView);
1188 995 var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wra pper", "left: " +
1189 for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { 996 (cm.options.fixedGutter ? dims.fixe dPos : -dims.gutterTotalWidth) + "px");
1190 var part = bidi[partPos] 997 cm.display.input.setUneditable(gutterWrap);
1191 var moveInStorageOrder = (dir > 0) == (part.level != 1) 998 wrap.insertBefore(gutterWrap, lineView.text);
1192 var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExte nt.end, -1) 999 if (lineView.line.gutterClass)
1193 if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrde r) } 1000 gutterWrap.className += " " + lineView.line.gutterClass;
1194 ch = moveInStorageOrder ? part.from : mv(part.to, -1) 1001 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumber s"]))
1195 if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } 1002 lineView.lineNumber = gutterWrap.appendChild(
1196 } 1003 elt("div", lineNumberFor(cm.options, lineN),
1197 } 1004 "CodeMirror-linenumber CodeMirror-gutter-elt",
1198 1005 "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
1199 // Case 3a: Look for other bidi parts on the same visual line 1006 + cm.display.lineNumInnerWidth + "px"));
1200 var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent) 1007 if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) {
1201 if (res) { return res } 1008 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && ma rkers[id];
1202 1009 if (found)
1203 // Case 3b: Look for other bidi parts on the next visual line 1010 gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "l eft: " +
1204 var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1) 1011 dims.gutterLeft[id] + "px; width: " + dims. gutterWidth[id] + "px"));
1205 if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { 1012 }
1206 res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineE xtent(nextCh)) 1013 }
1207 if (res) { return res } 1014 }
1208 } 1015
1209 1016 function updateLineWidgets(cm, lineView, dims) {
1210 // Case 4: Nowhere to move 1017 if (lineView.alignable) lineView.alignable = null;
1211 return null 1018 for (var node = lineView.node.firstChild, next; node; node = next) {
1212 } 1019 var next = node.nextSibling;
1213 1020 if (node.className == "CodeMirror-linewidget")
1214 // EVENT HANDLING 1021 lineView.node.removeChild(node);
1215 1022 }
1216 // Lightweight event framework. on/off also work on DOM nodes, 1023 insertLineWidgets(cm, lineView, dims);
1217 // registering native DOM handlers. 1024 }
1218 1025
1219 var noHandlers = [] 1026 // Build a line's DOM representation from scratch
1220 1027 function buildLineElement(cm, lineView, lineN, dims) {
1221 var on = function(emitter, type, f) { 1028 var built = getLineContent(cm, lineView);
1222 if (emitter.addEventListener) { 1029 lineView.text = lineView.node = built.pre;
1223 emitter.addEventListener(type, f, false) 1030 if (built.bgClass) lineView.bgClass = built.bgClass;
1224 } else if (emitter.attachEvent) { 1031 if (built.textClass) lineView.textClass = built.textClass;
1225 emitter.attachEvent("on" + type, f) 1032
1226 } else { 1033 updateLineClasses(lineView);
1227 var map = emitter._handlers || (emitter._handlers = {}) 1034 updateLineGutter(cm, lineView, lineN, dims);
1228 map[type] = (map[type] || noHandlers).concat(f) 1035 insertLineWidgets(cm, lineView, dims);
1229 } 1036 return lineView.node;
1230 } 1037 }
1231 1038
1232 function getHandlers(emitter, type) { 1039 // A lineView may contain multiple logical lines (when merged by
1233 return emitter._handlers && emitter._handlers[type] || noHandlers 1040 // collapsed spans). The widgets for all of them need to be drawn.
1234 } 1041 function insertLineWidgets(cm, lineView, dims) {
1235 1042 insertLineWidgetsFor(cm, lineView.line, lineView, dims, true);
1236 function off(emitter, type, f) { 1043 if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
1237 if (emitter.removeEventListener) { 1044 insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false);
1238 emitter.removeEventListener(type, f, false) 1045 }
1239 } else if (emitter.detachEvent) { 1046
1240 emitter.detachEvent("on" + type, f) 1047 function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {
1241 } else { 1048 if (!line.widgets) return;
1242 var map = emitter._handlers, arr = map && map[type] 1049 var wrap = ensureLineWrapped(lineView);
1243 if (arr) { 1050 for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
1244 var index = indexOf(arr, f) 1051 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidge t");
1245 if (index > -1) 1052 if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true ");
1246 { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)) } 1053 positionLineWidget(widget, node, lineView, dims);
1247 } 1054 cm.display.input.setUneditable(node);
1248 } 1055 if (allowAbove && widget.above)
1249 } 1056 wrap.insertBefore(node, lineView.gutter || lineView.text);
1250 1057 else
1251 function signal(emitter, type /*, values...*/) { 1058 wrap.appendChild(node);
1252 var handlers = getHandlers(emitter, type) 1059 signalLater(widget, "redraw");
1253 if (!handlers.length) { return } 1060 }
1254 var args = Array.prototype.slice.call(arguments, 2) 1061 }
1255 for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args) } 1062
1256 } 1063 function positionLineWidget(widget, node, lineView, dims) {
1257 1064 if (widget.noHScroll) {
1258 // The DOM events that CodeMirror handles can be overridden by 1065 (lineView.alignable || (lineView.alignable = [])).push(node);
1259 // registering a (non-DOM) handler on the editor for the event name, 1066 var width = dims.wrapperWidth;
1260 // and preventDefault-ing the event in that handler. 1067 node.style.left = dims.fixedPos + "px";
1261 function signalDOMEvent(cm, e, override) { 1068 if (!widget.coverGutter) {
1262 if (typeof e == "string") 1069 width -= dims.gutterTotalWidth;
1263 { e = {type: e, preventDefault: function() { this.defaultPrevented = true }} } 1070 node.style.paddingLeft = dims.gutterTotalWidth + "px";
1264 signal(cm, override || e.type, cm, e) 1071 }
1265 return e_defaultPrevented(e) || e.codemirrorIgnore 1072 node.style.width = width + "px";
1266 } 1073 }
1267 1074 if (widget.coverGutter) {
1268 function signalCursorActivity(cm) { 1075 node.style.zIndex = 5;
1269 var arr = cm._handlers && cm._handlers.cursorActivity 1076 node.style.position = "relative";
1270 if (!arr) { return } 1077 if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "p x";
1271 var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []) 1078 }
1272 for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) 1079 }
1273 { set.push(arr[i]) } } 1080
1274 } 1081 // POSITION OBJECT
1275 1082
1276 function hasHandler(emitter, type) { 1083 // A Pos instance represents a position within the text.
1277 return getHandlers(emitter, type).length > 0 1084 var Pos = CodeMirror.Pos = function(line, ch) {
1278 } 1085 if (!(this instanceof Pos)) return new Pos(line, ch);
1279 1086 this.line = line; this.ch = ch;
1280 // Add on and off methods to a constructor's prototype, to make 1087 };
1281 // registering events on such objects more convenient. 1088
1282 function eventMixin(ctor) { 1089 // Compare two positions, return 0 if they are the same, a negative
1283 ctor.prototype.on = function(type, f) {on(this, type, f)} 1090 // number when a is less, and a positive number otherwise.
1284 ctor.prototype.off = function(type, f) {off(this, type, f)} 1091 var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; };
1285 } 1092
1286 1093 function copyPos(x) {return Pos(x.line, x.ch);}
1287 // Due to the fact that we still support jurassic IE versions, some 1094 function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
1288 // compatibility wrappers are needed. 1095 function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }
1289 1096
1290 function e_preventDefault(e) { 1097 // INPUT HANDLING
1291 if (e.preventDefault) { e.preventDefault() } 1098
1292 else { e.returnValue = false } 1099 function ensureFocus(cm) {
1293 } 1100 if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
1294 function e_stopPropagation(e) { 1101 }
1295 if (e.stopPropagation) { e.stopPropagation() } 1102
1296 else { e.cancelBubble = true } 1103 // This will be set to a {lineWise: bool, text: [string]} object, so
1297 } 1104 // that, when pasting, we know what kind of selections the copied
1298 function e_defaultPrevented(e) { 1105 // text was made out of.
1299 return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == fals e 1106 var lastCopied = null;
1300 } 1107
1301 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e)} 1108 function applyTextInput(cm, inserted, deleted, sel, origin) {
1302 1109 var doc = cm.doc;
1303 function e_target(e) {return e.target || e.srcElement} 1110 cm.display.shift = false;
1304 function e_button(e) { 1111 if (!sel) sel = doc.sel;
1305 var b = e.which 1112
1306 if (b == null) { 1113 var paste = cm.state.pasteIncoming || origin == "paste";
1307 if (e.button & 1) { b = 1 } 1114 var textLines = doc.splitLines(inserted), multiPaste = null
1308 else if (e.button & 2) { b = 3 } 1115 // When pasing N lines into N selections, insert one line per selection
1309 else if (e.button & 4) { b = 2 } 1116 if (paste && sel.ranges.length > 1) {
1310 } 1117 if (lastCopied && lastCopied.text.join("\n") == inserted) {
1311 if (mac && e.ctrlKey && b == 1) { b = 3 } 1118 if (sel.ranges.length % lastCopied.text.length == 0) {
1312 return b 1119 multiPaste = [];
1313 } 1120 for (var i = 0; i < lastCopied.text.length; i++)
1314 1121 multiPaste.push(doc.splitLines(lastCopied.text[i]));
1315 // Detect drag-and-drop
1316 var dragAndDrop = function() {
1317 // There is *some* kind of drag-and-drop support in IE6-8, but I
1318 // couldn't get it to work yet.
1319 if (ie && ie_version < 9) { return false }
1320 var div = elt('div')
1321 return "draggable" in div || "dragDrop" in div
1322 }()
1323
1324 var zwspSupported
1325 function zeroWidthElement(measure) {
1326 if (zwspSupported == null) {
1327 var test = elt("span", "\u200b")
1328 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x" )]))
1329 if (measure.firstChild.offsetHeight != 0)
1330 { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie & & ie_version < 8) }
1331 }
1332 var node = zwspSupported ? elt("span", "\u200b") :
1333 elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right : -1px")
1334 node.setAttribute("cm-text", "")
1335 return node
1336 }
1337
1338 // Feature-detect IE's crummy client rect reporting for bidi text
1339 var badBidiRects
1340 function hasBadBidiRects(measure) {
1341 if (badBidiRects != null) { return badBidiRects }
1342 var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"))
1343 var r0 = range(txt, 0, 1).getBoundingClientRect()
1344 var r1 = range(txt, 1, 2).getBoundingClientRect()
1345 removeChildren(measure)
1346 if (!r0 || r0.left == r0.right) { return false } // Safari returns null in som e cases (#2780)
1347 return badBidiRects = (r1.right - r0.right < 3)
1348 }
1349
1350 // See if "".split is the broken IE version, if so, provide an
1351 // alternative way to split lines.
1352 var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) {
1353 var pos = 0, result = [], l = string.length
1354 while (pos <= l) {
1355 var nl = string.indexOf("\n", pos)
1356 if (nl == -1) { nl = string.length }
1357 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl)
1358 var rt = line.indexOf("\r")
1359 if (rt != -1) {
1360 result.push(line.slice(0, rt))
1361 pos += rt + 1
1362 } else {
1363 result.push(line)
1364 pos = nl + 1
1365 }
1366 }
1367 return result
1368 } : function (string) { return string.split(/\r\n?|\n/); }
1369
1370 var hasSelection = window.getSelection ? function (te) {
1371 try { return te.selectionStart != te.selectionEnd }
1372 catch(e) { return false }
1373 } : function (te) {
1374 var range
1375 try {range = te.ownerDocument.selection.createRange()}
1376 catch(e) {}
1377 if (!range || range.parentElement() != te) { return false }
1378 return range.compareEndPoints("StartToEnd", range) != 0
1379 }
1380
1381 var hasCopyEvent = (function () {
1382 var e = elt("div")
1383 if ("oncopy" in e) { return true }
1384 e.setAttribute("oncopy", "return;")
1385 return typeof e.oncopy == "function"
1386 })()
1387
1388 var badZoomedRects = null
1389 function hasBadZoomedRects(measure) {
1390 if (badZoomedRects != null) { return badZoomedRects }
1391 var node = removeChildrenAndAdd(measure, elt("span", "x"))
1392 var normal = node.getBoundingClientRect()
1393 var fromRange = range(node, 0, 1).getBoundingClientRect()
1394 return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1
1395 }
1396
1397 var modes = {};
1398 var mimeModes = {};
1399 // Extra arguments are stored as the mode's dependencies, which is
1400 // used by (legacy) mechanisms like loadmode.js to automatically
1401 // load a mode. (Preferred mechanism is the require/define calls.)
1402 function defineMode(name, mode) {
1403 if (arguments.length > 2)
1404 { mode.dependencies = Array.prototype.slice.call(arguments, 2) }
1405 modes[name] = mode
1406 }
1407
1408 function defineMIME(mime, spec) {
1409 mimeModes[mime] = spec
1410 }
1411
1412 // Given a MIME type, a {name, ...options} config object, or a name
1413 // string, return a mode config object.
1414 function resolveMode(spec) {
1415 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
1416 spec = mimeModes[spec]
1417 } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(sp ec.name)) {
1418 var found = mimeModes[spec.name]
1419 if (typeof found == "string") { found = {name: found} }
1420 spec = createObj(found, spec)
1421 spec.name = found.name
1422 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
1423 return resolveMode("application/xml")
1424 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) {
1425 return resolveMode("application/json")
1426 }
1427 if (typeof spec == "string") { return {name: spec} }
1428 else { return spec || {name: "null"} }
1429 }
1430
1431 // Given a mode spec (anything that resolveMode accepts), find and
1432 // initialize an actual mode object.
1433 function getMode(options, spec) {
1434 spec = resolveMode(spec)
1435 var mfactory = modes[spec.name]
1436 if (!mfactory) { return getMode(options, "text/plain") }
1437 var modeObj = mfactory(options, spec)
1438 if (modeExtensions.hasOwnProperty(spec.name)) {
1439 var exts = modeExtensions[spec.name]
1440 for (var prop in exts) {
1441 if (!exts.hasOwnProperty(prop)) { continue }
1442 if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop] }
1443 modeObj[prop] = exts[prop]
1444 }
1445 }
1446 modeObj.name = spec.name
1447 if (spec.helperType) { modeObj.helperType = spec.helperType }
1448 if (spec.modeProps) { for (var prop$1 in spec.modeProps)
1449 { modeObj[prop$1] = spec.modeProps[prop$1] } }
1450
1451 return modeObj
1452 }
1453
1454 // This can be used to attach properties to mode objects from
1455 // outside the actual mode definition.
1456 var modeExtensions = {}
1457 function extendMode(mode, properties) {
1458 var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeE xtensions[mode] = {})
1459 copyObj(properties, exts)
1460 }
1461
1462 function copyState(mode, state) {
1463 if (state === true) { return state }
1464 if (mode.copyState) { return mode.copyState(state) }
1465 var nstate = {}
1466 for (var n in state) {
1467 var val = state[n]
1468 if (val instanceof Array) { val = val.concat([]) }
1469 nstate[n] = val
1470 }
1471 return nstate
1472 }
1473
1474 // Given a mode and a state (for that mode), find the inner mode and
1475 // state at the position that the state refers to.
1476 function innerMode(mode, state) {
1477 var info
1478 while (mode.innerMode) {
1479 info = mode.innerMode(state)
1480 if (!info || info.mode == mode) { break }
1481 state = info.state
1482 mode = info.mode
1483 }
1484 return info || {mode: mode, state: state}
1485 }
1486
1487 function startState(mode, a1, a2) {
1488 return mode.startState ? mode.startState(a1, a2) : true
1489 }
1490
1491 // STRING STREAM
1492
1493 // Fed to the mode parsers, provides helper functions to make
1494 // parsers more succinct.
1495
1496 var StringStream = function StringStream(string, tabSize) {
1497 this.pos = this.start = 0
1498 this.string = string
1499 this.tabSize = tabSize || 8
1500 this.lastColumnPos = this.lastColumnValue = 0
1501 this.lineStart = 0
1502 };
1503
1504 StringStream.prototype.eol = function eol () {return this.pos >= this.string.len gth};
1505 StringStream.prototype.sol = function sol () {return this.pos == this.lineStart} ;
1506 StringStream.prototype.peek = function peek () {return this.string.charAt(this.p os) || undefined};
1507 StringStream.prototype.next = function next () {
1508 if (this.pos < this.string.length)
1509 { return this.string.charAt(this.pos++) }
1510 };
1511 StringStream.prototype.eat = function eat (match) {
1512 var ch = this.string.charAt(this.pos)
1513 var ok
1514 if (typeof match == "string") { ok = ch == match }
1515 else { ok = ch && (match.test ? match.test(ch) : match(ch)) }
1516 if (ok) {++this.pos; return ch}
1517 };
1518 StringStream.prototype.eatWhile = function eatWhile (match) {
1519 var start = this.pos
1520 while (this.eat(match)){}
1521 return this.pos > start
1522 };
1523 StringStream.prototype.eatSpace = function eatSpace () {
1524 var this$1 = this;
1525
1526 var start = this.pos
1527 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos }
1528 return this.pos > start
1529 };
1530 StringStream.prototype.skipToEnd = function skipToEnd () {this.pos = this.string .length};
1531 StringStream.prototype.skipTo = function skipTo (ch) {
1532 var found = this.string.indexOf(ch, this.pos)
1533 if (found > -1) {this.pos = found; return true}
1534 };
1535 StringStream.prototype.backUp = function backUp (n) {this.pos -= n};
1536 StringStream.prototype.column = function column () {
1537 if (this.lastColumnPos < this.start) {
1538 this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, th is.lastColumnPos, this.lastColumnValue)
1539 this.lastColumnPos = this.start
1540 }
1541 return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this. lineStart, this.tabSize) : 0)
1542 };
1543 StringStream.prototype.indentation = function indentation () {
1544 return countColumn(this.string, null, this.tabSize) -
1545 (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0 )
1546 };
1547 StringStream.prototype.match = function match (pattern, consume, caseInsensitive ) {
1548 if (typeof pattern == "string") {
1549 var cased = function (str) { return caseInsensitive ? str.toLowerCase() : st r; }
1550 var substr = this.string.substr(this.pos, pattern.length)
1551 if (cased(substr) == cased(pattern)) {
1552 if (consume !== false) { this.pos += pattern.length }
1553 return true
1554 }
1555 } else {
1556 var match = this.string.slice(this.pos).match(pattern)
1557 if (match && match.index > 0) { return null }
1558 if (match && consume !== false) { this.pos += match[0].length }
1559 return match
1560 }
1561 };
1562 StringStream.prototype.current = function current (){return this.string.slice(th is.start, this.pos)};
1563 StringStream.prototype.hideFirstChars = function hideFirstChars (n, inner) {
1564 this.lineStart += n
1565 try { return inner() }
1566 finally { this.lineStart -= n }
1567 };
1568
1569 // Compute a style array (an array starting with a mode generation
1570 // -- for invalidation -- followed by pairs of end positions and
1571 // style strings), which is used to highlight the tokens on the
1572 // line.
1573 function highlightLine(cm, line, state, forceToEnd) {
1574 // A styles array always starts with a number identifying the
1575 // mode/overlays that it is based on (for easy invalidation).
1576 var st = [cm.state.modeGen], lineClasses = {}
1577 // Compute the base array of styles
1578 runMode(cm, line.text, cm.doc.mode, state, function (end, style) { return st.p ush(end, style); },
1579 lineClasses, forceToEnd)
1580
1581 // Run overlays, adjust style array.
1582 var loop = function ( o ) {
1583 var overlay = cm.state.overlays[o], i = 1, at = 0
1584 runMode(cm, line.text, overlay.mode, true, function (end, style) {
1585 var start = i
1586 // Ensure there's a token end at the current position, and that i points a t it
1587 while (at < end) {
1588 var i_end = st[i]
1589 if (i_end > end)
1590 { st.splice(i, 1, end, st[i+1], i_end) }
1591 i += 2
1592 at = Math.min(end, i_end)
1593 }
1594 if (!style) { return }
1595 if (overlay.opaque) {
1596 st.splice(start, i - start, end, "overlay " + style)
1597 i = start + 2
1598 } else {
1599 for (; start < i; start += 2) {
1600 var cur = st[start+1]
1601 st[start+1] = (cur ? cur + " " : "") + "overlay " + style
1602 } 1122 }
1603 } 1123 } else if (textLines.length == sel.ranges.length) {
1604 }, lineClasses) 1124 multiPaste = map(textLines, function(l) { return [l]; });
1125 }
1126 }
1127
1128 // Normal behavior is to insert the new text into every selection
1129 for (var i = sel.ranges.length - 1; i >= 0; i--) {
1130 var range = sel.ranges[i];
1131 var from = range.from(), to = range.to();
1132 if (range.empty()) {
1133 if (deleted && deleted > 0) // Handle deletion
1134 from = Pos(from.line, from.ch - deleted);
1135 else if (cm.state.overwrite && !paste) // Handle overwrite
1136 to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
1137 else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted)
1138 from = to = Pos(from.line, 0)
1139 }
1140 var updateInput = cm.curOp.updateInput;
1141 var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % m ultiPaste.length] : textLines,
1142 origin: origin || (paste ? "paste" : cm.state.cutIncomi ng ? "cut" : "+input")};
1143 makeChange(cm.doc, changeEvent);
1144 signalLater(cm, "inputRead", cm, changeEvent);
1145 }
1146 if (inserted && !paste)
1147 triggerElectric(cm, inserted);
1148
1149 ensureCursorVisible(cm);
1150 cm.curOp.updateInput = updateInput;
1151 cm.curOp.typing = true;
1152 cm.state.pasteIncoming = cm.state.cutIncoming = false;
1153 }
1154
1155 function handlePaste(e, cm) {
1156 var pasted = e.clipboardData && e.clipboardData.getData("text/plain");
1157 if (pasted) {
1158 e.preventDefault();
1159 if (!cm.isReadOnly() && !cm.options.disableInput)
1160 runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); } );
1161 return true;
1162 }
1163 }
1164
1165 function triggerElectric(cm, inserted) {
1166 // When an 'electric' character is inserted, immediately trigger a reindent
1167 if (!cm.options.electricChars || !cm.options.smartIndent) return;
1168 var sel = cm.doc.sel;
1169
1170 for (var i = sel.ranges.length - 1; i >= 0; i--) {
1171 var range = sel.ranges[i];
1172 if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head .line)) continue;
1173 var mode = cm.getModeAt(range.head);
1174 var indented = false;
1175 if (mode.electricChars) {
1176 for (var j = 0; j < mode.electricChars.length; j++)
1177 if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
1178 indented = indentLine(cm, range.head.line, "smart");
1179 break;
1180 }
1181 } else if (mode.electricInput) {
1182 if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice( 0, range.head.ch)))
1183 indented = indentLine(cm, range.head.line, "smart");
1184 }
1185 if (indented) signalLater(cm, "electricInput", cm, range.head.line);
1186 }
1187 }
1188
1189 function copyableRanges(cm) {
1190 var text = [], ranges = [];
1191 for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
1192 var line = cm.doc.sel.ranges[i].head.line;
1193 var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
1194 ranges.push(lineRange);
1195 text.push(cm.getRange(lineRange.anchor, lineRange.head));
1196 }
1197 return {text: text, ranges: ranges};
1198 }
1199
1200 function disableBrowserMagic(field) {
1201 field.setAttribute("autocorrect", "off");
1202 field.setAttribute("autocapitalize", "off");
1203 field.setAttribute("spellcheck", "false");
1204 }
1205
1206 // TEXTAREA INPUT STYLE
1207
1208 function TextareaInput(cm) {
1209 this.cm = cm;
1210 // See input.poll and input.reset
1211 this.prevInput = "";
1212
1213 // Flag that indicates whether we expect input to appear real soon
1214 // now (after some event like 'keypress' or 'input') and are
1215 // polling intensively.
1216 this.pollingFast = false;
1217 // Self-resetting timeout for the poller
1218 this.polling = new Delayed();
1219 // Tracks when input.reset has punted to just putting a short
1220 // string into the textarea instead of the full selection.
1221 this.inaccurateSelection = false;
1222 // Used to work around IE issue with selection being forgotten when focus mo ves away from textarea
1223 this.hasSelection = false;
1224 this.composing = null;
1605 }; 1225 };
1606 1226
1607 for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); 1227 function hiddenTextarea() {
1608 1228 var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padd ing: 0; width: 1px; height: 1em; outline: none");
1609 return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? li neClasses : null} 1229 var div = elt("div", [te], null, "overflow: hidden; position: relative; widt h: 3px; height: 0px;");
1610 } 1230 // The textarea is kept positioned near the cursor to prevent the
1611 1231 // fact that it'll be scrolled into view on input from scrolling
1612 function getLineStyles(cm, line, updateFrontier) { 1232 // our fake cursor out of view. On webkit, when wrap=off, paste is
1613 if (!line.styles || line.styles[0] != cm.state.modeGen) { 1233 // very slow. So make the area wide instead.
1614 var state = getStateBefore(cm, lineNo(line)) 1234 if (webkit) te.style.width = "1000px";
1615 var result = highlightLine(cm, line, line.text.length > cm.options.maxHighli ghtLength ? copyState(cm.doc.mode, state) : state) 1235 else te.setAttribute("wrap", "off");
1616 line.stateAfter = state 1236 // If border: 0; -- iOS fails to open keyboard (issue #1287)
1617 line.styles = result.styles 1237 if (ios) te.style.border = "1px solid black";
1618 if (result.classes) { line.styleClasses = result.classes } 1238 disableBrowserMagic(te);
1619 else if (line.styleClasses) { line.styleClasses = null } 1239 return div;
1620 if (updateFrontier === cm.doc.frontier) { cm.doc.frontier++ } 1240 }
1621 } 1241
1622 return line.styles 1242 TextareaInput.prototype = copyObj({
1623 } 1243 init: function(display) {
1624 1244 var input = this, cm = this.cm;
1625 function getStateBefore(cm, n, precise) { 1245
1626 var doc = cm.doc, display = cm.display 1246 // Wraps and hides input textarea
1627 if (!doc.mode.startState) { return true } 1247 var div = this.wrapper = hiddenTextarea();
1628 var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(do c, pos-1).stateAfter 1248 // The semihidden textarea that is focused when the editor is
1629 if (!state) { state = startState(doc.mode) } 1249 // focused, and receives input.
1630 else { state = copyState(doc.mode, state) } 1250 var te = this.textarea = div.firstChild;
1631 doc.iter(pos, n, function (line) { 1251 display.wrapper.insertBefore(div, display.wrapper.firstChild);
1632 processLine(cm, line.text, state) 1252
1633 var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo 1253 // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)
1634 line.stateAfter = save ? copyState(doc.mode, state) : null 1254 if (ios) te.style.width = "0px";
1635 ++pos 1255
1636 }) 1256 on(te, "input", function() {
1637 if (precise) { doc.frontier = pos } 1257 if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = nu ll;
1638 return state 1258 input.poll();
1639 } 1259 });
1640 1260
1641 // Lightweight form of highlight -- proceed over this line and 1261 on(te, "paste", function(e) {
1642 // update state, but don't save a style array. Used for lines that 1262 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return
1643 // aren't currently visible. 1263
1644 function processLine(cm, text, state, startAt) { 1264 cm.state.pasteIncoming = true;
1645 var mode = cm.doc.mode 1265 input.fastPoll();
1646 var stream = new StringStream(text, cm.options.tabSize) 1266 });
1647 stream.start = stream.pos = startAt || 0 1267
1648 if (text == "") { callBlankLine(mode, state) } 1268 function prepareCopyCut(e) {
1649 while (!stream.eol()) { 1269 if (signalDOMEvent(cm, e)) return
1650 readToken(mode, stream, state) 1270 if (cm.somethingSelected()) {
1651 stream.start = stream.pos 1271 lastCopied = {lineWise: false, text: cm.getSelections()};
1652 } 1272 if (input.inaccurateSelection) {
1653 } 1273 input.prevInput = "";
1654 1274 input.inaccurateSelection = false;
1655 function callBlankLine(mode, state) { 1275 te.value = lastCopied.text.join("\n");
1656 if (mode.blankLine) { return mode.blankLine(state) } 1276 selectInput(te);
1657 if (!mode.innerMode) { return }
1658 var inner = innerMode(mode, state)
1659 if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) }
1660 }
1661
1662 function readToken(mode, stream, state, inner) {
1663 for (var i = 0; i < 10; i++) {
1664 if (inner) { inner[0] = innerMode(mode, state).mode }
1665 var style = mode.token(stream, state)
1666 if (stream.pos > stream.start) { return style }
1667 }
1668 throw new Error("Mode " + mode.name + " failed to advance stream.")
1669 }
1670
1671 // Utility for getTokenAt and getLineTokens
1672 function takeToken(cm, pos, precise, asArray) {
1673 var getObj = function (copy) { return ({
1674 start: stream.start, end: stream.pos,
1675 string: stream.current(),
1676 type: style || null,
1677 state: copy ? copyState(doc.mode, state) : state
1678 }); }
1679
1680 var doc = cm.doc, mode = doc.mode, style
1681 pos = clipPos(doc, pos)
1682 var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precis e)
1683 var stream = new StringStream(line.text, cm.options.tabSize), tokens
1684 if (asArray) { tokens = [] }
1685 while ((asArray || stream.pos < pos.ch) && !stream.eol()) {
1686 stream.start = stream.pos
1687 style = readToken(mode, stream, state)
1688 if (asArray) { tokens.push(getObj(true)) }
1689 }
1690 return asArray ? tokens : getObj()
1691 }
1692
1693 function extractLineClasses(type, output) {
1694 if (type) { for (;;) {
1695 var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/)
1696 if (!lineClass) { break }
1697 type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineCla ss[0].length)
1698 var prop = lineClass[1] ? "bgClass" : "textClass"
1699 if (output[prop] == null)
1700 { output[prop] = lineClass[2] }
1701 else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[p rop]))
1702 { output[prop] += " " + lineClass[2] }
1703 } }
1704 return type
1705 }
1706
1707 // Run the given mode's parser over a line, calling f for each token.
1708 function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) {
1709 var flattenSpans = mode.flattenSpans
1710 if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans }
1711 var curStart = 0, curStyle = null
1712 var stream = new StringStream(text, cm.options.tabSize), style
1713 var inner = cm.options.addModeClass && [null]
1714 if (text == "") { extractLineClasses(callBlankLine(mode, state), lineClasses) }
1715 while (!stream.eol()) {
1716 if (stream.pos > cm.options.maxHighlightLength) {
1717 flattenSpans = false
1718 if (forceToEnd) { processLine(cm, text, state, stream.pos) }
1719 stream.pos = text.length
1720 style = null
1721 } else {
1722 style = extractLineClasses(readToken(mode, stream, state, inner), lineClas ses)
1723 }
1724 if (inner) {
1725 var mName = inner[0].name
1726 if (mName) { style = "m-" + (style ? mName + " " + style : mName) }
1727 }
1728 if (!flattenSpans || curStyle != style) {
1729 while (curStart < stream.start) {
1730 curStart = Math.min(stream.start, curStart + 5000)
1731 f(curStart, curStyle)
1732 }
1733 curStyle = style
1734 }
1735 stream.start = stream.pos
1736 }
1737 while (curStart < stream.pos) {
1738 // Webkit seems to refuse to render text nodes longer than 57444
1739 // characters, and returns inaccurate measurements in nodes
1740 // starting around 5000 chars.
1741 var pos = Math.min(stream.pos, curStart + 5000)
1742 f(pos, curStyle)
1743 curStart = pos
1744 }
1745 }
1746
1747 // Finds the line to start with when starting a parse. Tries to
1748 // find a line with a stateAfter, so that it can start with a
1749 // valid state. If that fails, it returns the line with the
1750 // smallest indentation, which tends to need the least context to
1751 // parse correctly.
1752 function findStartLine(cm, n, precise) {
1753 var minindent, minline, doc = cm.doc
1754 var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100)
1755 for (var search = n; search > lim; --search) {
1756 if (search <= doc.first) { return doc.first }
1757 var line = getLine(doc, search - 1)
1758 if (line.stateAfter && (!precise || search <= doc.frontier)) { return search }
1759 var indented = countColumn(line.text, null, cm.options.tabSize)
1760 if (minline == null || minindent > indented) {
1761 minline = search - 1
1762 minindent = indented
1763 }
1764 }
1765 return minline
1766 }
1767
1768 // LINE DATA STRUCTURE
1769
1770 // Line objects. These hold state related to a line, including
1771 // highlighting info (the styles array).
1772 var Line = function Line(text, markedSpans, estimateHeight) {
1773 this.text = text
1774 attachMarkedSpans(this, markedSpans)
1775 this.height = estimateHeight ? estimateHeight(this) : 1
1776 };
1777
1778 Line.prototype.lineNo = function lineNo$1 () { return lineNo(this) };
1779 eventMixin(Line)
1780
1781 // Change the content (text, markers) of a line. Automatically
1782 // invalidates cached information and tries to re-estimate the
1783 // line's height.
1784 function updateLine(line, text, markedSpans, estimateHeight) {
1785 line.text = text
1786 if (line.stateAfter) { line.stateAfter = null }
1787 if (line.styles) { line.styles = null }
1788 if (line.order != null) { line.order = null }
1789 detachMarkedSpans(line)
1790 attachMarkedSpans(line, markedSpans)
1791 var estHeight = estimateHeight ? estimateHeight(line) : 1
1792 if (estHeight != line.height) { updateLineHeight(line, estHeight) }
1793 }
1794
1795 // Detach a line from the document tree and its markers.
1796 function cleanUpLine(line) {
1797 line.parent = null
1798 detachMarkedSpans(line)
1799 }
1800
1801 // Convert a style as returned by a mode (either null, or a string
1802 // containing one or more styles) to a CSS style. This is cached,
1803 // and also looks for line-wide styles.
1804 var styleToClassCache = {};
1805 var styleToClassCacheWithMode = {};
1806 function interpretTokenStyle(style, options) {
1807 if (!style || /^\s*$/.test(style)) { return null }
1808 var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCac he
1809 return cache[style] ||
1810 (cache[style] = style.replace(/\S+/g, "cm-$&"))
1811 }
1812
1813 // Render the DOM representation of the text of a line. Also builds
1814 // up a 'line map', which points at the DOM nodes that represent
1815 // specific stretches of text, and is used by the measuring code.
1816 // The returned object contains the DOM node, this map, and
1817 // information about line-wide styles that were set by the mode.
1818 function buildLineContent(cm, lineView) {
1819 // The padding-right forces the element to have a 'border', which
1820 // is needed on Webkit to be able to get line-level bounding
1821 // rectangles for it (in measureChar).
1822 var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null)
1823 var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: conten t,
1824 col: 0, pos: 0, cm: cm,
1825 trailingSpace: false,
1826 splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}
1827 lineView.measure = {}
1828
1829 // Iterate over the logical lines that make up this visual line.
1830 for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {
1831 var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0)
1832 builder.pos = 0
1833 builder.addToken = buildToken
1834 // Optionally wire in some hacks into the token-rendering
1835 // algorithm, to deal with browser quirks.
1836 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.di rection)))
1837 { builder.addToken = buildTokenBadBidi(builder.addToken, order) }
1838 builder.map = []
1839 var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo( line)
1840 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate ))
1841 if (line.styleClasses) {
1842 if (line.styleClasses.bgClass)
1843 { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgCla ss || "") }
1844 if (line.styleClasses.textClass)
1845 { builder.textClass = joinClasses(line.styleClasses.textClass, builder.t extClass || "") }
1846 }
1847
1848 // Ensure at least a single node is present, for measuring.
1849 if (builder.map.length == 0)
1850 { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.d isplay.measure))) }
1851
1852 // Store the map and a cache object for the current logical line
1853 if (i == 0) {
1854 lineView.measure.map = builder.map
1855 lineView.measure.cache = {}
1856 } else {
1857 ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map)
1858 ;(lineView.measure.caches || (lineView.measure.caches = [])).push({})
1859 }
1860 }
1861
1862 // See issue #2901
1863 if (webkit) {
1864 var last = builder.content.lastChild
1865 if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySe lector(".cm-tab")))
1866 { builder.content.className = "cm-tab-wrap-hack" }
1867 }
1868
1869 signal(cm, "renderLine", cm, lineView.line, builder.pre)
1870 if (builder.pre.className)
1871 { builder.textClass = joinClasses(builder.pre.className, builder.textClass | | "") }
1872
1873 return builder
1874 }
1875
1876 function defaultSpecialCharPlaceholder(ch) {
1877 var token = elt("span", "\u2022", "cm-invalidchar")
1878 token.title = "\\u" + ch.charCodeAt(0).toString(16)
1879 token.setAttribute("aria-label", token.title)
1880 return token
1881 }
1882
1883 // Build up the DOM representation for a single token, and add it to
1884 // the line map. Takes care to render special characters separately.
1885 function buildToken(builder, text, style, startStyle, endStyle, title, css) {
1886 if (!text) { return }
1887 var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpac e) : text
1888 var special = builder.cm.state.specialChars, mustWrap = false
1889 var content
1890 if (!special.test(text)) {
1891 builder.col += text.length
1892 content = document.createTextNode(displayText)
1893 builder.map.push(builder.pos, builder.pos + text.length, content)
1894 if (ie && ie_version < 9) { mustWrap = true }
1895 builder.pos += text.length
1896 } else {
1897 content = document.createDocumentFragment()
1898 var pos = 0
1899 while (true) {
1900 special.lastIndex = pos
1901 var m = special.exec(text)
1902 var skipped = m ? m.index - pos : text.length - pos
1903 if (skipped) {
1904 var txt = document.createTextNode(displayText.slice(pos, pos + skipped))
1905 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])) }
1906 else { content.appendChild(txt) }
1907 builder.map.push(builder.pos, builder.pos + skipped, txt)
1908 builder.col += skipped
1909 builder.pos += skipped
1910 }
1911 if (!m) { break }
1912 pos += skipped + 1
1913 var txt$1 = (void 0)
1914 if (m[0] == "\t") {
1915 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.c ol % tabSize
1916 txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"))
1917 txt$1.setAttribute("role", "presentation")
1918 txt$1.setAttribute("cm-text", "\t")
1919 builder.col += tabWidth
1920 } else if (m[0] == "\r" || m[0] == "\n") {
1921 txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u242 4", "cm-invalidchar"))
1922 txt$1.setAttribute("cm-text", m[0])
1923 builder.col += 1
1924 } else {
1925 txt$1 = builder.cm.options.specialCharPlaceholder(m[0])
1926 txt$1.setAttribute("cm-text", m[0])
1927 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])) }
1928 else { content.appendChild(txt$1) }
1929 builder.col += 1
1930 }
1931 builder.map.push(builder.pos, builder.pos + 1, txt$1)
1932 builder.pos++
1933 }
1934 }
1935 builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32
1936 if (style || startStyle || endStyle || mustWrap || css) {
1937 var fullStyle = style || ""
1938 if (startStyle) { fullStyle += startStyle }
1939 if (endStyle) { fullStyle += endStyle }
1940 var token = elt("span", [content], fullStyle, css)
1941 if (title) { token.title = title }
1942 return builder.content.appendChild(token)
1943 }
1944 builder.content.appendChild(content)
1945 }
1946
1947 function splitSpaces(text, trailingBefore) {
1948 if (text.length > 1 && !/ /.test(text)) { return text }
1949 var spaceBefore = trailingBefore, result = ""
1950 for (var i = 0; i < text.length; i++) {
1951 var ch = text.charAt(i)
1952 if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32))
1953 { ch = "\u00a0" }
1954 result += ch
1955 spaceBefore = ch == " "
1956 }
1957 return result
1958 }
1959
1960 // Work around nonsense dimensions being reported for stretches of
1961 // right-to-left text.
1962 function buildTokenBadBidi(inner, order) {
1963 return function (builder, text, style, startStyle, endStyle, title, css) {
1964 style = style ? style + " cm-force-border" : "cm-force-border"
1965 var start = builder.pos, end = start + text.length
1966 for (;;) {
1967 // Find the part that overlaps with the start of this text
1968 var part = (void 0)
1969 for (var i = 0; i < order.length; i++) {
1970 part = order[i]
1971 if (part.to > start && part.from <= start) { break }
1972 }
1973 if (part.to >= end) { return inner(builder, text, style, startStyle, endSt yle, title, css) }
1974 inner(builder, text.slice(0, part.to - start), style, startStyle, null, ti tle, css)
1975 startStyle = null
1976 text = text.slice(part.to - start)
1977 start = part.to
1978 }
1979 }
1980 }
1981
1982 function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
1983 var widget = !ignoreWidget && marker.widgetNode
1984 if (widget) { builder.map.push(builder.pos, builder.pos + size, widget) }
1985 if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) {
1986 if (!widget)
1987 { widget = builder.content.appendChild(document.createElement("span")) }
1988 widget.setAttribute("cm-marker", marker.id)
1989 }
1990 if (widget) {
1991 builder.cm.display.input.setUneditable(widget)
1992 builder.content.appendChild(widget)
1993 }
1994 builder.pos += size
1995 builder.trailingSpace = false
1996 }
1997
1998 // Outputs a number of spans to make up a line, taking highlighting
1999 // and marked text into account.
2000 function insertLineContent(line, builder, styles) {
2001 var spans = line.markedSpans, allText = line.text, at = 0
2002 if (!spans) {
2003 for (var i$1 = 1; i$1 < styles.length; i$1+=2)
2004 { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpret TokenStyle(styles[i$1+1], builder.cm.options)) }
2005 return
2006 }
2007
2008 var len = allText.length, pos = 0, i = 1, text = "", style, css
2009 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed
2010 for (;;) {
2011 if (nextChange == pos) { // Update current marker set
2012 spanStyle = spanEndStyle = spanStartStyle = title = css = ""
2013 collapsed = null; nextChange = Infinity
2014 var foundBookmarks = [], endStyles = (void 0)
2015 for (var j = 0; j < spans.length; ++j) {
2016 var sp = spans[j], m = sp.marker
2017 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) {
2018 foundBookmarks.push(m)
2019 } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapse d && sp.to == pos && sp.from == pos)) {
2020 if (sp.to != null && sp.to != pos && nextChange > sp.to) {
2021 nextChange = sp.to
2022 spanEndStyle = ""
2023 } 1277 }
2024 if (m.className) { spanStyle += " " + m.className } 1278 } else if (!cm.options.lineWiseCopyCut) {
2025 if (m.css) { css = (css ? css + ";" : "") + m.css } 1279 return;
2026 if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startS tyle } 1280 } else {
2027 if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [] )).push(m.endStyle, sp.to) } 1281 var ranges = copyableRanges(cm);
2028 if (m.title && !title) { title = m.title } 1282 lastCopied = {lineWise: true, text: ranges.text};
2029 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.ma rker, m) < 0)) 1283 if (e.type == "cut") {
2030 { collapsed = sp } 1284 cm.setSelections(ranges.ranges, null, sel_dontScroll);
2031 } else if (sp.from > pos && nextChange > sp.from) { 1285 } else {
2032 nextChange = sp.from 1286 input.prevInput = "";
2033 } 1287 te.value = ranges.text.join("\n");
2034 } 1288 selectInput(te);
2035 if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2)
2036 { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyle s[j$1] } } }
2037
2038 if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBo okmarks.length; ++j$2)
2039 { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]) } }
2040 if (collapsed && (collapsed.from || 0) == pos) {
2041 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed. to) - pos,
2042 collapsed.marker, collapsed.from == null)
2043 if (collapsed.to == null) { return }
2044 if (collapsed.to == pos) { collapsed = false }
2045 }
2046 }
2047 if (pos >= len) { break }
2048
2049 var upto = Math.min(len, nextChange)
2050 while (true) {
2051 if (text) {
2052 var end = pos + text.length
2053 if (!collapsed) {
2054 var tokenText = end > upto ? text.slice(0, upto - pos) : text
2055 builder.addToken(builder, tokenText, style ? style + spanStyle : spanS tyle,
2056 spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css)
2057 }
2058 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break}
2059 pos = end
2060 spanStartStyle = ""
2061 }
2062 text = allText.slice(at, at = styles[i++])
2063 style = interpretTokenStyle(styles[i++], builder.cm.options)
2064 }
2065 }
2066 }
2067
2068
2069 // These objects are used to represent the visible (currently drawn)
2070 // part of the document. A LineView may correspond to multiple
2071 // logical lines, if those are connected by collapsed ranges.
2072 function LineView(doc, line, lineN) {
2073 // The starting line
2074 this.line = line
2075 // Continuing lines, if any
2076 this.rest = visualLineContinued(line)
2077 // Number of logical lines in this visual line
2078 this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1
2079 this.node = this.text = null
2080 this.hidden = lineIsHidden(doc, line)
2081 }
2082
2083 // Create a range of LineView objects for the given lines.
2084 function buildViewArray(cm, from, to) {
2085 var array = [], nextPos
2086 for (var pos = from; pos < to; pos = nextPos) {
2087 var view = new LineView(cm.doc, getLine(cm.doc, pos), pos)
2088 nextPos = pos + view.size
2089 array.push(view)
2090 }
2091 return array
2092 }
2093
2094 var operationGroup = null
2095
2096 function pushOperation(op) {
2097 if (operationGroup) {
2098 operationGroup.ops.push(op)
2099 } else {
2100 op.ownsGroup = operationGroup = {
2101 ops: [op],
2102 delayedCallbacks: []
2103 }
2104 }
2105 }
2106
2107 function fireCallbacksForOps(group) {
2108 // Calls delayed callbacks and cursorActivity handlers until no
2109 // new ones appear
2110 var callbacks = group.delayedCallbacks, i = 0
2111 do {
2112 for (; i < callbacks.length; i++)
2113 { callbacks[i].call(null) }
2114 for (var j = 0; j < group.ops.length; j++) {
2115 var op = group.ops[j]
2116 if (op.cursorActivityHandlers)
2117 { while (op.cursorActivityCalled < op.cursorActivityHandlers.length)
2118 { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.c m) } }
2119 }
2120 } while (i < callbacks.length)
2121 }
2122
2123 function finishOperation(op, endCb) {
2124 var group = op.ownsGroup
2125 if (!group) { return }
2126
2127 try { fireCallbacksForOps(group) }
2128 finally {
2129 operationGroup = null
2130 endCb(group)
2131 }
2132 }
2133
2134 var orphanDelayedCallbacks = null
2135
2136 // Often, we want to signal events at a point where we are in the
2137 // middle of some work, but don't want the handler to start calling
2138 // other methods on the editor, which might be in an inconsistent
2139 // state or simply not expect any other events to happen.
2140 // signalLater looks whether there are any handlers, and schedules
2141 // them to be executed when the last operation ends, or, if no
2142 // operation is active, when a timeout fires.
2143 function signalLater(emitter, type /*, values...*/) {
2144 var arr = getHandlers(emitter, type)
2145 if (!arr.length) { return }
2146 var args = Array.prototype.slice.call(arguments, 2), list
2147 if (operationGroup) {
2148 list = operationGroup.delayedCallbacks
2149 } else if (orphanDelayedCallbacks) {
2150 list = orphanDelayedCallbacks
2151 } else {
2152 list = orphanDelayedCallbacks = []
2153 setTimeout(fireOrphanDelayed, 0)
2154 }
2155 var loop = function ( i ) {
2156 list.push(function () { return arr[i].apply(null, args); })
2157 };
2158
2159 for (var i = 0; i < arr.length; ++i)
2160 loop( i );
2161 }
2162
2163 function fireOrphanDelayed() {
2164 var delayed = orphanDelayedCallbacks
2165 orphanDelayedCallbacks = null
2166 for (var i = 0; i < delayed.length; ++i) { delayed[i]() }
2167 }
2168
2169 // When an aspect of a line changes, a string is added to
2170 // lineView.changes. This updates the relevant part of the line's
2171 // DOM structure.
2172 function updateLineForChanges(cm, lineView, lineN, dims) {
2173 for (var j = 0; j < lineView.changes.length; j++) {
2174 var type = lineView.changes[j]
2175 if (type == "text") { updateLineText(cm, lineView) }
2176 else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims) }
2177 else if (type == "class") { updateLineClasses(cm, lineView) }
2178 else if (type == "widget") { updateLineWidgets(cm, lineView, dims) }
2179 }
2180 lineView.changes = null
2181 }
2182
2183 // Lines with gutter elements, widgets or a background class need to
2184 // be wrapped, and have the extra elements added to the wrapper div
2185 function ensureLineWrapped(lineView) {
2186 if (lineView.node == lineView.text) {
2187 lineView.node = elt("div", null, null, "position: relative")
2188 if (lineView.text.parentNode)
2189 { lineView.text.parentNode.replaceChild(lineView.node, lineView.text) }
2190 lineView.node.appendChild(lineView.text)
2191 if (ie && ie_version < 8) { lineView.node.style.zIndex = 2 }
2192 }
2193 return lineView.node
2194 }
2195
2196 function updateLineBackground(cm, lineView) {
2197 var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass | | "") : lineView.line.bgClass
2198 if (cls) { cls += " CodeMirror-linebackground" }
2199 if (lineView.background) {
2200 if (cls) { lineView.background.className = cls }
2201 else { lineView.background.parentNode.removeChild(lineView.background); line View.background = null }
2202 } else if (cls) {
2203 var wrap = ensureLineWrapped(lineView)
2204 lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChi ld)
2205 cm.display.input.setUneditable(lineView.background)
2206 }
2207 }
2208
2209 // Wrapper around buildLineContent which will reuse the structure
2210 // in display.externalMeasured when possible.
2211 function getLineContent(cm, lineView) {
2212 var ext = cm.display.externalMeasured
2213 if (ext && ext.line == lineView.line) {
2214 cm.display.externalMeasured = null
2215 lineView.measure = ext.measure
2216 return ext.built
2217 }
2218 return buildLineContent(cm, lineView)
2219 }
2220
2221 // Redraw the line's text. Interacts with the background and text
2222 // classes because the mode may output tokens that influence these
2223 // classes.
2224 function updateLineText(cm, lineView) {
2225 var cls = lineView.text.className
2226 var built = getLineContent(cm, lineView)
2227 if (lineView.text == lineView.node) { lineView.node = built.pre }
2228 lineView.text.parentNode.replaceChild(built.pre, lineView.text)
2229 lineView.text = built.pre
2230 if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass ) {
2231 lineView.bgClass = built.bgClass
2232 lineView.textClass = built.textClass
2233 updateLineClasses(cm, lineView)
2234 } else if (cls) {
2235 lineView.text.className = cls
2236 }
2237 }
2238
2239 function updateLineClasses(cm, lineView) {
2240 updateLineBackground(cm, lineView)
2241 if (lineView.line.wrapClass)
2242 { ensureLineWrapped(lineView).className = lineView.line.wrapClass }
2243 else if (lineView.node != lineView.text)
2244 { lineView.node.className = "" }
2245 var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line .textClass || "") : lineView.line.textClass
2246 lineView.text.className = textClass || ""
2247 }
2248
2249 function updateLineGutter(cm, lineView, lineN, dims) {
2250 if (lineView.gutter) {
2251 lineView.node.removeChild(lineView.gutter)
2252 lineView.gutter = null
2253 }
2254 if (lineView.gutterBackground) {
2255 lineView.node.removeChild(lineView.gutterBackground)
2256 lineView.gutterBackground = null
2257 }
2258 if (lineView.line.gutterClass) {
2259 var wrap = ensureLineWrapped(lineView)
2260 lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass,
2261 ("left: " + (cm.options.fixedGutter ? dims.f ixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "p x"))
2262 cm.display.input.setUneditable(lineView.gutterBackground)
2263 wrap.insertBefore(lineView.gutterBackground, lineView.text)
2264 }
2265 var markers = lineView.line.gutterMarkers
2266 if (cm.options.lineNumbers || markers) {
2267 var wrap$1 = ensureLineWrapped(lineView)
2268 var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapp er", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidt h) + "px"))
2269 cm.display.input.setUneditable(gutterWrap)
2270 wrap$1.insertBefore(gutterWrap, lineView.text)
2271 if (lineView.line.gutterClass)
2272 { gutterWrap.className += " " + lineView.line.gutterClass }
2273 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers" ]))
2274 { lineView.lineNumber = gutterWrap.appendChild(
2275 elt("div", lineNumberFor(cm.options, lineN),
2276 "CodeMirror-linenumber CodeMirror-gutter-elt",
2277 ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width : " + (cm.display.lineNumInnerWidth) + "px"))) }
2278 if (markers) { for (var k = 0; k < cm.options.gutters.length; ++k) {
2279 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && mark ers[id]
2280 if (found)
2281 { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt",
2282 ("left: " + (dims.gutterLeft[id]) + "px; widt h: " + (dims.gutterWidth[id]) + "px"))) }
2283 } }
2284 }
2285 }
2286
2287 function updateLineWidgets(cm, lineView, dims) {
2288 if (lineView.alignable) { lineView.alignable = null }
2289 for (var node = lineView.node.firstChild, next = (void 0); node; node = next) {
2290 next = node.nextSibling
2291 if (node.className == "CodeMirror-linewidget")
2292 { lineView.node.removeChild(node) }
2293 }
2294 insertLineWidgets(cm, lineView, dims)
2295 }
2296
2297 // Build a line's DOM representation from scratch
2298 function buildLineElement(cm, lineView, lineN, dims) {
2299 var built = getLineContent(cm, lineView)
2300 lineView.text = lineView.node = built.pre
2301 if (built.bgClass) { lineView.bgClass = built.bgClass }
2302 if (built.textClass) { lineView.textClass = built.textClass }
2303
2304 updateLineClasses(cm, lineView)
2305 updateLineGutter(cm, lineView, lineN, dims)
2306 insertLineWidgets(cm, lineView, dims)
2307 return lineView.node
2308 }
2309
2310 // A lineView may contain multiple logical lines (when merged by
2311 // collapsed spans). The widgets for all of them need to be drawn.
2312 function insertLineWidgets(cm, lineView, dims) {
2313 insertLineWidgetsFor(cm, lineView.line, lineView, dims, true)
2314 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)
2315 { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false) } }
2316 }
2317
2318 function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {
2319 if (!line.widgets) { return }
2320 var wrap = ensureLineWrapped(lineView)
2321 for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
2322 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget" )
2323 if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true ") }
2324 positionLineWidget(widget, node, lineView, dims)
2325 cm.display.input.setUneditable(node)
2326 if (allowAbove && widget.above)
2327 { wrap.insertBefore(node, lineView.gutter || lineView.text) }
2328 else
2329 { wrap.appendChild(node) }
2330 signalLater(widget, "redraw")
2331 }
2332 }
2333
2334 function positionLineWidget(widget, node, lineView, dims) {
2335 if (widget.noHScroll) {
2336 ;(lineView.alignable || (lineView.alignable = [])).push(node)
2337 var width = dims.wrapperWidth
2338 node.style.left = dims.fixedPos + "px"
2339 if (!widget.coverGutter) {
2340 width -= dims.gutterTotalWidth
2341 node.style.paddingLeft = dims.gutterTotalWidth + "px"
2342 }
2343 node.style.width = width + "px"
2344 }
2345 if (widget.coverGutter) {
2346 node.style.zIndex = 5
2347 node.style.position = "relative"
2348 if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "p x" }
2349 }
2350 }
2351
2352 function widgetHeight(widget) {
2353 if (widget.height != null) { return widget.height }
2354 var cm = widget.doc.cm
2355 if (!cm) { return 0 }
2356 if (!contains(document.body, widget.node)) {
2357 var parentStyle = "position: relative;"
2358 if (widget.coverGutter)
2359 { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;" }
2360 if (widget.noHScroll)
2361 { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;" }
2362 removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, par entStyle))
2363 }
2364 return widget.height = widget.node.parentNode.offsetHeight
2365 }
2366
2367 // Return true when the given mouse event happened in a widget
2368 function eventInWidget(display, e) {
2369 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
2370 if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") ||
2371 (n.parentNode == display.sizer && n != display.mover))
2372 { return true }
2373 }
2374 }
2375
2376 // POSITION MEASUREMENT
2377
2378 function paddingTop(display) {return display.lineSpace.offsetTop}
2379 function paddingVert(display) {return display.mover.offsetHeight - display.lineS pace.offsetHeight}
2380 function paddingH(display) {
2381 if (display.cachedPaddingH) { return display.cachedPaddingH }
2382 var e = removeChildrenAndAdd(display.measure, elt("pre", "x"))
2383 var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentSt yle
2384 var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRi ght)}
2385 if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data }
2386 return data
2387 }
2388
2389 function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth }
2390 function displayWidth(cm) {
2391 return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth
2392 }
2393 function displayHeight(cm) {
2394 return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight
2395 }
2396
2397 // Ensure the lineView.wrapping.heights array is populated. This is
2398 // an array of bottom offsets for the lines that make up a drawn
2399 // line. When lineWrapping is on, there might be more than one
2400 // height.
2401 function ensureLineHeights(cm, lineView, rect) {
2402 var wrapping = cm.options.lineWrapping
2403 var curWidth = wrapping && displayWidth(cm)
2404 if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidt h) {
2405 var heights = lineView.measure.heights = []
2406 if (wrapping) {
2407 lineView.measure.width = curWidth
2408 var rects = lineView.text.firstChild.getClientRects()
2409 for (var i = 0; i < rects.length - 1; i++) {
2410 var cur = rects[i], next = rects[i + 1]
2411 if (Math.abs(cur.bottom - next.bottom) > 2)
2412 { heights.push((cur.bottom + next.top) / 2 - rect.top) }
2413 }
2414 }
2415 heights.push(rect.bottom - rect.top)
2416 }
2417 }
2418
2419 // Find a line map (mapping character offsets to text nodes) and a
2420 // measurement cache for the given line number. (A line view might
2421 // contain multiple lines when collapsed ranges are present.)
2422 function mapFromLineView(lineView, line, lineN) {
2423 if (lineView.line == line)
2424 { return {map: lineView.measure.map, cache: lineView.measure.cache} }
2425 for (var i = 0; i < lineView.rest.length; i++)
2426 { if (lineView.rest[i] == line)
2427 { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i] } } }
2428 for (var i$1 = 0; i$1 < lineView.rest.length; i$1++)
2429 { if (lineNo(lineView.rest[i$1]) > lineN)
2430 { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[ i$1], before: true} } }
2431 }
2432
2433 // Render a line into the hidden node display.externalMeasured. Used
2434 // when measurement is needed for a line that's not in the viewport.
2435 function updateExternalMeasurement(cm, line) {
2436 line = visualLine(line)
2437 var lineN = lineNo(line)
2438 var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN)
2439 view.lineN = lineN
2440 var built = view.built = buildLineContent(cm, view)
2441 view.text = built.pre
2442 removeChildrenAndAdd(cm.display.lineMeasure, built.pre)
2443 return view
2444 }
2445
2446 // Get a {top, bottom, left, right} box (in line-local coordinates)
2447 // for a given character.
2448 function measureChar(cm, line, ch, bias) {
2449 return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias)
2450 }
2451
2452 // Find a line view that corresponds to the given line number.
2453 function findViewForLine(cm, lineN) {
2454 if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
2455 { return cm.display.view[findViewIndex(cm, lineN)] }
2456 var ext = cm.display.externalMeasured
2457 if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
2458 { return ext }
2459 }
2460
2461 // Measurement can be split in two steps, the set-up work that
2462 // applies to the whole line, and the measurement of the actual
2463 // character. Functions like coordsChar, that need to do a lot of
2464 // measurements in a row, can thus ensure that the set-up work is
2465 // only done once.
2466 function prepareMeasureForLine(cm, line) {
2467 var lineN = lineNo(line)
2468 var view = findViewForLine(cm, lineN)
2469 if (view && !view.text) {
2470 view = null
2471 } else if (view && view.changes) {
2472 updateLineForChanges(cm, view, lineN, getDimensions(cm))
2473 cm.curOp.forceUpdate = true
2474 }
2475 if (!view)
2476 { view = updateExternalMeasurement(cm, line) }
2477
2478 var info = mapFromLineView(view, line, lineN)
2479 return {
2480 line: line, view: view, rect: null,
2481 map: info.map, cache: info.cache, before: info.before,
2482 hasHeights: false
2483 }
2484 }
2485
2486 // Given a prepared measurement object, measures the position of an
2487 // actual character (or fetches it from the cache).
2488 function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
2489 if (prepared.before) { ch = -1 }
2490 var key = ch + (bias || ""), found
2491 if (prepared.cache.hasOwnProperty(key)) {
2492 found = prepared.cache[key]
2493 } else {
2494 if (!prepared.rect)
2495 { prepared.rect = prepared.view.text.getBoundingClientRect() }
2496 if (!prepared.hasHeights) {
2497 ensureLineHeights(cm, prepared.view, prepared.rect)
2498 prepared.hasHeights = true
2499 }
2500 found = measureCharInner(cm, prepared, ch, bias)
2501 if (!found.bogus) { prepared.cache[key] = found }
2502 }
2503 return {left: found.left, right: found.right,
2504 top: varHeight ? found.rtop : found.top,
2505 bottom: varHeight ? found.rbottom : found.bottom}
2506 }
2507
2508 var nullRect = {left: 0, right: 0, top: 0, bottom: 0}
2509
2510 function nodeAndOffsetInLineMap(map, ch, bias) {
2511 var node, start, end, collapse, mStart, mEnd
2512 // First, search the line map for the text node corresponding to,
2513 // or closest to, the target character.
2514 for (var i = 0; i < map.length; i += 3) {
2515 mStart = map[i]
2516 mEnd = map[i + 1]
2517 if (ch < mStart) {
2518 start = 0; end = 1
2519 collapse = "left"
2520 } else if (ch < mEnd) {
2521 start = ch - mStart
2522 end = start + 1
2523 } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {
2524 end = mEnd - mStart
2525 start = end - 1
2526 if (ch >= mEnd) { collapse = "right" }
2527 }
2528 if (start != null) {
2529 node = map[i + 2]
2530 if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
2531 { collapse = bias }
2532 if (bias == "left" && start == 0)
2533 { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {
2534 node = map[(i -= 3) + 2]
2535 collapse = "left"
2536 } }
2537 if (bias == "right" && start == mEnd - mStart)
2538 { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].i nsertLeft) {
2539 node = map[(i += 3) + 2]
2540 collapse = "right"
2541 } }
2542 break
2543 }
2544 }
2545 return {node: node, start: start, end: end, collapse: collapse, coverStart: mS tart, coverEnd: mEnd}
2546 }
2547
2548 function getUsefulRect(rects, bias) {
2549 var rect = nullRect
2550 if (bias == "left") { for (var i = 0; i < rects.length; i++) {
2551 if ((rect = rects[i]).left != rect.right) { break }
2552 } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) {
2553 if ((rect = rects[i$1]).left != rect.right) { break }
2554 } }
2555 return rect
2556 }
2557
2558 function measureCharInner(cm, prepared, ch, bias) {
2559 var place = nodeAndOffsetInLineMap(prepared.map, ch, bias)
2560 var node = place.node, start = place.start, end = place.end, collapse = place. collapse
2561
2562 var rect
2563 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
2564 for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonse nse rectangles are returned
2565 while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start }
2566 while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared .line.text.charAt(place.coverStart + end))) { ++end }
2567 if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.co verStart)
2568 { rect = node.parentNode.getBoundingClientRect() }
2569 else
2570 { rect = getUsefulRect(range(node, start, end).getClientRects(), bias) }
2571 if (rect.left || rect.right || start == 0) { break }
2572 end = start
2573 start = start - 1
2574 collapse = "right"
2575 }
2576 if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.mea sure, rect) }
2577 } else { // If it is a widget, simply get the box for the whole widget.
2578 if (start > 0) { collapse = bias = "right" }
2579 var rects
2580 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
2581 { rect = rects[bias == "right" ? rects.length - 1 : 0] }
2582 else
2583 { rect = node.getBoundingClientRect() }
2584 }
2585 if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {
2586 var rSpan = node.parentNode.getClientRects()[0]
2587 if (rSpan)
2588 { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top : rSpan.top, bottom: rSpan.bottom} }
2589 else
2590 { rect = nullRect }
2591 }
2592
2593 var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.to p
2594 var mid = (rtop + rbot) / 2
2595 var heights = prepared.view.measure.heights
2596 var i = 0
2597 for (; i < heights.length - 1; i++)
2598 { if (mid < heights[i]) { break } }
2599 var top = i ? heights[i - 1] : 0, bot = heights[i]
2600 var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared. rect.left,
2601 right: (collapse == "left" ? rect.left : rect.right) - prepared. rect.left,
2602 top: top, bottom: bot}
2603 if (!rect.left && !rect.right) { result.bogus = true }
2604 if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbotto m = rbot }
2605
2606 return result
2607 }
2608
2609 // Work around problem with bounding client rects on ranges being
2610 // returned incorrectly when zoomed on IE10 and below.
2611 function maybeUpdateRectForZooming(measure, rect) {
2612 if (!window.screen || screen.logicalXDPI == null ||
2613 screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
2614 { return rect }
2615 var scaleX = screen.logicalXDPI / screen.deviceXDPI
2616 var scaleY = screen.logicalYDPI / screen.deviceYDPI
2617 return {left: rect.left * scaleX, right: rect.right * scaleX,
2618 top: rect.top * scaleY, bottom: rect.bottom * scaleY}
2619 }
2620
2621 function clearLineMeasurementCacheFor(lineView) {
2622 if (lineView.measure) {
2623 lineView.measure.cache = {}
2624 lineView.measure.heights = null
2625 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)
2626 { lineView.measure.caches[i] = {} } }
2627 }
2628 }
2629
2630 function clearLineMeasurementCache(cm) {
2631 cm.display.externalMeasure = null
2632 removeChildren(cm.display.lineMeasure)
2633 for (var i = 0; i < cm.display.view.length; i++)
2634 { clearLineMeasurementCacheFor(cm.display.view[i]) }
2635 }
2636
2637 function clearCaches(cm) {
2638 clearLineMeasurementCache(cm)
2639 cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPa ddingH = null
2640 if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true }
2641 cm.display.lineNumChars = null
2642 }
2643
2644 function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft }
2645 function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop }
2646
2647 // Converts a {top, bottom, left, right} box from line-local
2648 // coordinates into another coordinate system. Context may be one of
2649 // "line", "div" (display.lineDiv), "local"./null (editor), "window",
2650 // or "page".
2651 function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) {
2652 if (!includeWidgets && lineObj.widgets) { for (var i = 0; i < lineObj.widgets. length; ++i) { if (lineObj.widgets[i].above) {
2653 var size = widgetHeight(lineObj.widgets[i])
2654 rect.top += size; rect.bottom += size
2655 } } }
2656 if (context == "line") { return rect }
2657 if (!context) { context = "local" }
2658 var yOff = heightAtLine(lineObj)
2659 if (context == "local") { yOff += paddingTop(cm.display) }
2660 else { yOff -= cm.display.viewOffset }
2661 if (context == "page" || context == "window") {
2662 var lOff = cm.display.lineSpace.getBoundingClientRect()
2663 yOff += lOff.top + (context == "window" ? 0 : pageScrollY())
2664 var xOff = lOff.left + (context == "window" ? 0 : pageScrollX())
2665 rect.left += xOff; rect.right += xOff
2666 }
2667 rect.top += yOff; rect.bottom += yOff
2668 return rect
2669 }
2670
2671 // Coverts a box from "div" coords to another coordinate system.
2672 // Context may be "window", "page", "div", or "local"./null.
2673 function fromCoordSystem(cm, coords, context) {
2674 if (context == "div") { return coords }
2675 var left = coords.left, top = coords.top
2676 // First move into "page" coordinate system
2677 if (context == "page") {
2678 left -= pageScrollX()
2679 top -= pageScrollY()
2680 } else if (context == "local" || !context) {
2681 var localBox = cm.display.sizer.getBoundingClientRect()
2682 left += localBox.left
2683 top += localBox.top
2684 }
2685
2686 var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect()
2687 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}
2688 }
2689
2690 function charCoords(cm, pos, context, lineObj, bias) {
2691 if (!lineObj) { lineObj = getLine(cm.doc, pos.line) }
2692 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), co ntext)
2693 }
2694
2695 // Returns a box for a given cursor position, which may have an
2696 // 'other' property containing the position of the secondary cursor
2697 // on a bidi boundary.
2698 // A cursor Pos(line, char, "before") is on the same visual line as `char - 1`
2699 // and after `char - 1` in writing order of `char - 1`
2700 // A cursor Pos(line, char, "after") is on the same visual line as `char`
2701 // and before `char` in writing order of `char`
2702 // Examples (upper-case letters are RTL, lower-case are LTR):
2703 // Pos(0, 1, ...)
2704 // before after
2705 // ab a|b a|b
2706 // aB a|B aB|
2707 // Ab |Ab A|b
2708 // AB B|A B|A
2709 // Every position after the last character on a line is considered to stick
2710 // to the last character on the line.
2711 function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {
2712 lineObj = lineObj || getLine(cm.doc, pos.line)
2713 if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) }
2714 function get(ch, right) {
2715 var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left ", varHeight)
2716 if (right) { m.left = m.right; } else { m.right = m.left }
2717 return intoCoordSystem(cm, lineObj, m, context)
2718 }
2719 var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sti cky
2720 if (ch >= lineObj.text.length) {
2721 ch = lineObj.text.length
2722 sticky = "before"
2723 } else if (ch <= 0) {
2724 ch = 0
2725 sticky = "after"
2726 }
2727 if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") }
2728
2729 function getBidi(ch, partPos, invert) {
2730 var part = order[partPos], right = (part.level % 2) != 0
2731 return get(invert ? ch - 1 : ch, right != invert)
2732 }
2733 var partPos = getBidiPartAt(order, ch, sticky)
2734 var other = bidiOther
2735 var val = getBidi(ch, partPos, sticky == "before")
2736 if (other != null) { val.other = getBidi(ch, other, sticky != "before") }
2737 return val
2738 }
2739
2740 // Used to cheaply estimate the coordinates for a position. Used for
2741 // intermediate scroll updates.
2742 function estimateCoords(cm, pos) {
2743 var left = 0
2744 pos = clipPos(cm.doc, pos)
2745 if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch }
2746 var lineObj = getLine(cm.doc, pos.line)
2747 var top = heightAtLine(lineObj) + paddingTop(cm.display)
2748 return {left: left, right: left, top: top, bottom: top + lineObj.height}
2749 }
2750
2751 // Positions returned by coordsChar contain some extra information.
2752 // xRel is the relative x position of the input coordinates compared
2753 // to the found position (so xRel > 0 means the coordinates are to
2754 // the right of the character position, for example). When outside
2755 // is true, that means the coordinates lie outside the line's
2756 // vertical range.
2757 function PosWithInfo(line, ch, sticky, outside, xRel) {
2758 var pos = Pos(line, ch, sticky)
2759 pos.xRel = xRel
2760 if (outside) { pos.outside = true }
2761 return pos
2762 }
2763
2764 // Compute the character position closest to the given coordinates.
2765 // Input must be lineSpace-local ("div" coordinate system).
2766 function coordsChar(cm, x, y) {
2767 var doc = cm.doc
2768 y += cm.display.viewOffset
2769 if (y < 0) { return PosWithInfo(doc.first, 0, null, true, -1) }
2770 var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1
2771 if (lineN > last)
2772 { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.lengt h, null, true, 1) }
2773 if (x < 0) { x = 0 }
2774
2775 var lineObj = getLine(doc, lineN)
2776 for (;;) {
2777 var found = coordsCharInner(cm, lineObj, lineN, x, y)
2778 var merged = collapsedSpanAtEnd(lineObj)
2779 var mergedPos = merged && merged.find(0, true)
2780 if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
2781 { lineN = lineNo(lineObj = mergedPos.to.line) }
2782 else
2783 { return found }
2784 }
2785 }
2786
2787 function wrappedLineExtent(cm, lineObj, preparedMeasure, y) {
2788 var measure = function (ch) { return intoCoordSystem(cm, lineObj, measureCharP repared(cm, preparedMeasure, ch), "line"); }
2789 var end = lineObj.text.length
2790 var begin = findFirst(function (ch) { return measure(ch - 1).bottom <= y; }, e nd, 0)
2791 end = findFirst(function (ch) { return measure(ch).top > y; }, begin, end)
2792 return {begin: begin, end: end}
2793 }
2794
2795 function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) {
2796 var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedM easure, target), "line").top
2797 return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop)
2798 }
2799
2800 function coordsCharInner(cm, lineObj, lineNo, x, y) {
2801 y -= heightAtLine(lineObj)
2802 var begin = 0, end = lineObj.text.length
2803 var preparedMeasure = prepareMeasureForLine(cm, lineObj)
2804 var pos
2805 var order = getOrder(lineObj, cm.doc.direction)
2806 if (order) {
2807 if (cm.options.lineWrapping) {
2808 ;var assign;
2809 ((assign = wrappedLineExtent(cm, lineObj, preparedMeasure, y), begin = ass ign.begin, end = assign.end))
2810 }
2811 pos = new Pos(lineNo, begin)
2812 var beginLeft = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left
2813 var dir = beginLeft < x ? 1 : -1
2814 var prevDiff, diff = beginLeft - x, prevPos
2815 do {
2816 prevDiff = diff
2817 prevPos = pos
2818 pos = moveVisually(cm, lineObj, pos, dir)
2819 if (pos == null || pos.ch < begin || end <= (pos.sticky == "before" ? pos. ch - 1 : pos.ch)) {
2820 pos = prevPos
2821 break
2822 }
2823 diff = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left - x
2824 } while ((dir < 0) != (diff < 0) && (Math.abs(diff) <= Math.abs(prevDiff)))
2825 if (Math.abs(diff) > Math.abs(prevDiff)) {
2826 if ((diff < 0) == (prevDiff < 0)) { throw new Error("Broke out of infinite loop in coordsCharInner") }
2827 pos = prevPos
2828 }
2829 } else {
2830 var ch = findFirst(function (ch) {
2831 var box = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMea sure, ch), "line")
2832 if (box.top > y) {
2833 // For the cursor stickiness
2834 end = Math.min(ch, end)
2835 return true
2836 }
2837 else if (box.bottom <= y) { return false }
2838 else if (box.left > x) { return true }
2839 else if (box.right < x) { return false }
2840 else { return (x - box.left < box.right - x) }
2841 }, begin, end)
2842 ch = skipExtendingChars(lineObj.text, ch, 1)
2843 pos = new Pos(lineNo, ch, ch == end ? "before" : "after")
2844 }
2845 var coords = cursorCoords(cm, pos, "line", lineObj, preparedMeasure)
2846 if (y < coords.top || coords.bottom < y) { pos.outside = true }
2847 pos.xRel = x < coords.left ? -1 : (x > coords.right ? 1 : 0)
2848 return pos
2849 }
2850
2851 var measureText
2852 // Compute the default text height.
2853 function textHeight(display) {
2854 if (display.cachedTextHeight != null) { return display.cachedTextHeight }
2855 if (measureText == null) {
2856 measureText = elt("pre")
2857 // Measure a bunch of lines, for browsers that compute
2858 // fractional heights.
2859 for (var i = 0; i < 49; ++i) {
2860 measureText.appendChild(document.createTextNode("x"))
2861 measureText.appendChild(elt("br"))
2862 }
2863 measureText.appendChild(document.createTextNode("x"))
2864 }
2865 removeChildrenAndAdd(display.measure, measureText)
2866 var height = measureText.offsetHeight / 50
2867 if (height > 3) { display.cachedTextHeight = height }
2868 removeChildren(display.measure)
2869 return height || 1
2870 }
2871
2872 // Compute the default character width.
2873 function charWidth(display) {
2874 if (display.cachedCharWidth != null) { return display.cachedCharWidth }
2875 var anchor = elt("span", "xxxxxxxxxx")
2876 var pre = elt("pre", [anchor])
2877 removeChildrenAndAdd(display.measure, pre)
2878 var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10
2879 if (width > 2) { display.cachedCharWidth = width }
2880 return width || 10
2881 }
2882
2883 // Do a bulk-read of the DOM positions and sizes needed to draw the
2884 // view, so that we don't interleave reading and writing to the DOM.
2885 function getDimensions(cm) {
2886 var d = cm.display, left = {}, width = {}
2887 var gutterLeft = d.gutters.clientLeft
2888 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
2889 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft
2890 width[cm.options.gutters[i]] = n.clientWidth
2891 }
2892 return {fixedPos: compensateForHScroll(d),
2893 gutterTotalWidth: d.gutters.offsetWidth,
2894 gutterLeft: left,
2895 gutterWidth: width,
2896 wrapperWidth: d.wrapper.clientWidth}
2897 }
2898
2899 // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
2900 // but using getBoundingClientRect to get a sub-pixel-accurate
2901 // result.
2902 function compensateForHScroll(display) {
2903 return display.scroller.getBoundingClientRect().left - display.sizer.getBoundi ngClientRect().left
2904 }
2905
2906 // Returns a function that estimates the height of a line, to use as
2907 // first approximation until the line becomes visible (and is thus
2908 // properly measurable).
2909 function estimateHeight(cm) {
2910 var th = textHeight(cm.display), wrapping = cm.options.lineWrapping
2911 var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWi dth(cm.display) - 3)
2912 return function (line) {
2913 if (lineIsHidden(cm.doc, line)) { return 0 }
2914
2915 var widgetsHeight = 0
2916 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) {
2917 if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height }
2918 } }
2919
2920 if (wrapping)
2921 { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th }
2922 else
2923 { return widgetsHeight + th }
2924 }
2925 }
2926
2927 function estimateLineHeights(cm) {
2928 var doc = cm.doc, est = estimateHeight(cm)
2929 doc.iter(function (line) {
2930 var estHeight = est(line)
2931 if (estHeight != line.height) { updateLineHeight(line, estHeight) }
2932 })
2933 }
2934
2935 // Given a mouse event, find the corresponding position. If liberal
2936 // is false, it checks whether a gutter or scrollbar was clicked,
2937 // and returns null if it was. forRect is used by rectangular
2938 // selections, and tries to estimate a character position even for
2939 // coordinates beyond the right of the text.
2940 function posFromMouse(cm, e, liberal, forRect) {
2941 var display = cm.display
2942 if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null }
2943
2944 var x, y, space = display.lineSpace.getBoundingClientRect()
2945 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
2946 try { x = e.clientX - space.left; y = e.clientY - space.top }
2947 catch (e) { return null }
2948 var coords = coordsChar(cm, x, y), line
2949 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text). length == coords.ch) {
2950 var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.leng th
2951 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).l eft) / charWidth(cm.display)) - colDiff))
2952 }
2953 return coords
2954 }
2955
2956 // Find the view element corresponding to a given line. Return null
2957 // when the line isn't visible.
2958 function findViewIndex(cm, n) {
2959 if (n >= cm.display.viewTo) { return null }
2960 n -= cm.display.viewFrom
2961 if (n < 0) { return null }
2962 var view = cm.display.view
2963 for (var i = 0; i < view.length; i++) {
2964 n -= view[i].size
2965 if (n < 0) { return i }
2966 }
2967 }
2968
2969 function updateSelection(cm) {
2970 cm.display.input.showSelection(cm.display.input.prepareSelection())
2971 }
2972
2973 function prepareSelection(cm, primary) {
2974 var doc = cm.doc, result = {}
2975 var curFragment = result.cursors = document.createDocumentFragment()
2976 var selFragment = result.selection = document.createDocumentFragment()
2977
2978 for (var i = 0; i < doc.sel.ranges.length; i++) {
2979 if (primary === false && i == doc.sel.primIndex) { continue }
2980 var range = doc.sel.ranges[i]
2981 if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.v iewFrom) { continue }
2982 var collapsed = range.empty()
2983 if (collapsed || cm.options.showCursorWhenSelecting)
2984 { drawSelectionCursor(cm, range.head, curFragment) }
2985 if (!collapsed)
2986 { drawSelectionRange(cm, range, selFragment) }
2987 }
2988 return result
2989 }
2990
2991 // Draws a cursor for the given range
2992 function drawSelectionCursor(cm, head, output) {
2993 var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHe ightPerLine)
2994
2995 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"))
2996 cursor.style.left = pos.left + "px"
2997 cursor.style.top = pos.top + "px"
2998 cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHei ght + "px"
2999
3000 if (pos.other) {
3001 // Secondary cursor, shown when on a 'jump' in bi-directional text
3002 var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"))
3003 otherCursor.style.display = ""
3004 otherCursor.style.left = pos.other.left + "px"
3005 otherCursor.style.top = pos.other.top + "px"
3006 otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"
3007 }
3008 }
3009
3010 // Draws the given range as a highlighted selection
3011 function drawSelectionRange(cm, range, output) {
3012 var display = cm.display, doc = cm.doc
3013 var fragment = document.createDocumentFragment()
3014 var padding = paddingH(cm.display), leftSide = padding.left
3015 var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer. offsetLeft) - padding.right
3016
3017 function add(left, top, width, bottom) {
3018 if (top < 0) { top = 0 }
3019 top = Math.round(top)
3020 bottom = Math.round(bottom)
3021 fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: abs olute; left: " + left + "px;\n top: " + top + "px; w idth: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px")))
3022 }
3023
3024 function drawForLine(line, fromArg, toArg) {
3025 var lineObj = getLine(doc, line)
3026 var lineLen = lineObj.text.length
3027 var start, end
3028 function coords(ch, bias) {
3029 return charCoords(cm, Pos(line, ch), "div", lineObj, bias)
3030 }
3031
3032 iterateBidiSections(getOrder(lineObj, doc.direction), fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir) {
3033 var leftPos = coords(from, "left"), rightPos, left, right
3034 if (from == to) {
3035 rightPos = leftPos
3036 left = right = leftPos.left
3037 } else {
3038 rightPos = coords(to - 1, "right")
3039 if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tm p }
3040 left = leftPos.left
3041 right = rightPos.right
3042 }
3043 if (fromArg == null && from == 0) { left = leftSide }
3044 if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
3045 add(left, leftPos.top, null, leftPos.bottom)
3046 left = leftSide
3047 if (leftPos.bottom < rightPos.top) { add(left, leftPos.bottom, null, rig htPos.top) }
3048 }
3049 if (toArg == null && to == lineLen) { right = rightSide }
3050 if (!start || leftPos.top < start.top || leftPos.top == start.top && leftP os.left < start.left)
3051 { start = leftPos }
3052 if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
3053 { end = rightPos }
3054 if (left < leftSide + 1) { left = leftSide }
3055 add(left, rightPos.top, right - left, rightPos.bottom)
3056 })
3057 return {start: start, end: end}
3058 }
3059
3060 var sFrom = range.from(), sTo = range.to()
3061 if (sFrom.line == sTo.line) {
3062 drawForLine(sFrom.line, sFrom.ch, sTo.ch)
3063 } else {
3064 var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line)
3065 var singleVLine = visualLine(fromLine) == visualLine(toLine)
3066 var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text. length + 1 : null).end
3067 var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start
3068 if (singleVLine) {
3069 if (leftEnd.top < rightStart.top - 2) {
3070 add(leftEnd.right, leftEnd.top, null, leftEnd.bottom)
3071 add(leftSide, rightStart.top, rightStart.left, rightStart.bottom)
3072 } else {
3073 add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd .bottom)
3074 }
3075 }
3076 if (leftEnd.bottom < rightStart.top)
3077 { add(leftSide, leftEnd.bottom, null, rightStart.top) }
3078 }
3079
3080 output.appendChild(fragment)
3081 }
3082
3083 // Cursor-blinking
3084 function restartBlink(cm) {
3085 if (!cm.state.focused) { return }
3086 var display = cm.display
3087 clearInterval(display.blinker)
3088 var on = true
3089 display.cursorDiv.style.visibility = ""
3090 if (cm.options.cursorBlinkRate > 0)
3091 { display.blinker = setInterval(function () { return display.cursorDiv.style .visibility = (on = !on) ? "" : "hidden"; },
3092 cm.options.cursorBlinkRate) }
3093 else if (cm.options.cursorBlinkRate < 0)
3094 { display.cursorDiv.style.visibility = "hidden" }
3095 }
3096
3097 function ensureFocus(cm) {
3098 if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm) }
3099 }
3100
3101 function delayBlurEvent(cm) {
3102 cm.state.delayingBlurEvent = true
3103 setTimeout(function () { if (cm.state.delayingBlurEvent) {
3104 cm.state.delayingBlurEvent = false
3105 onBlur(cm)
3106 } }, 100)
3107 }
3108
3109 function onFocus(cm, e) {
3110 if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false }
3111
3112 if (cm.options.readOnly == "nocursor") { return }
3113 if (!cm.state.focused) {
3114 signal(cm, "focus", cm, e)
3115 cm.state.focused = true
3116 addClass(cm.display.wrapper, "CodeMirror-focused")
3117 // This test prevents this from firing when a context
3118 // menu is closed (since the input reset would kill the
3119 // select-all detection hack)
3120 if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
3121 cm.display.input.reset()
3122 if (webkit) { setTimeout(function () { return cm.display.input.reset(true) ; }, 20) } // Issue #1730
3123 }
3124 cm.display.input.receivedFocus()
3125 }
3126 restartBlink(cm)
3127 }
3128 function onBlur(cm, e) {
3129 if (cm.state.delayingBlurEvent) { return }
3130
3131 if (cm.state.focused) {
3132 signal(cm, "blur", cm, e)
3133 cm.state.focused = false
3134 rmClass(cm.display.wrapper, "CodeMirror-focused")
3135 }
3136 clearInterval(cm.display.blinker)
3137 setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false } } , 150)
3138 }
3139
3140 // Re-align line numbers and gutter marks to compensate for
3141 // horizontal scrolling.
3142 function alignHorizontally(cm) {
3143 var display = cm.display, view = display.view
3144 if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixed Gutter)) { return }
3145 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.do c.scrollLeft
3146 var gutterW = display.gutters.offsetWidth, left = comp + "px"
3147 for (var i = 0; i < view.length; i++) { if (!view[i].hidden) {
3148 if (cm.options.fixedGutter) {
3149 if (view[i].gutter)
3150 { view[i].gutter.style.left = left }
3151 if (view[i].gutterBackground)
3152 { view[i].gutterBackground.style.left = left }
3153 }
3154 var align = view[i].alignable
3155 if (align) { for (var j = 0; j < align.length; j++)
3156 { align[j].style.left = left } }
3157 } }
3158 if (cm.options.fixedGutter)
3159 { display.gutters.style.left = (comp + gutterW) + "px" }
3160 }
3161
3162 // Used to ensure that the line number gutter is still the right
3163 // size for the current document size. Returns true when an update
3164 // is needed.
3165 function maybeUpdateLineNumberWidth(cm) {
3166 if (!cm.options.lineNumbers) { return false }
3167 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display
3168 if (last.length != display.lineNumChars) {
3169 var test = display.measure.appendChild(elt("div", [elt("div", last)],
3170 "CodeMirror-linenumber CodeMirror -gutter-elt"))
3171 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - inner W
3172 display.lineGutter.style.width = ""
3173 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1
3174 display.lineNumWidth = display.lineNumInnerWidth + padding
3175 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1
3176 display.lineGutter.style.width = display.lineNumWidth + "px"
3177 updateGutterSpace(cm)
3178 return true
3179 }
3180 return false
3181 }
3182
3183 // Read the actual heights of the rendered lines, and update their
3184 // stored heights to match.
3185 function updateHeightsInViewport(cm) {
3186 var display = cm.display
3187 var prevBottom = display.lineDiv.offsetTop
3188 for (var i = 0; i < display.view.length; i++) {
3189 var cur = display.view[i], height = (void 0)
3190 if (cur.hidden) { continue }
3191 if (ie && ie_version < 8) {
3192 var bot = cur.node.offsetTop + cur.node.offsetHeight
3193 height = bot - prevBottom
3194 prevBottom = bot
3195 } else {
3196 var box = cur.node.getBoundingClientRect()
3197 height = box.bottom - box.top
3198 }
3199 var diff = cur.line.height - height
3200 if (height < 2) { height = textHeight(display) }
3201 if (diff > .001 || diff < -.001) {
3202 updateLineHeight(cur.line, height)
3203 updateWidgetHeight(cur.line)
3204 if (cur.rest) { for (var j = 0; j < cur.rest.length; j++)
3205 { updateWidgetHeight(cur.rest[j]) } }
3206 }
3207 }
3208 }
3209
3210 // Read and store the height of line widgets associated with the
3211 // given line.
3212 function updateWidgetHeight(line) {
3213 if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i)
3214 { line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight } }
3215 }
3216
3217 // Compute the lines that are visible in a given viewport (defaults
3218 // the the current scroll position). viewport may contain top,
3219 // height, and ensure (see op.scrollToPos) properties.
3220 function visibleLines(display, doc, viewport) {
3221 var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : displ ay.scroller.scrollTop
3222 top = Math.floor(top - paddingTop(display))
3223 var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + dis play.wrapper.clientHeight
3224
3225 var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom)
3226 // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
3227 // forces those lines into the viewport (if possible).
3228 if (viewport && viewport.ensure) {
3229 var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.li ne
3230 if (ensureFrom < from) {
3231 from = ensureFrom
3232 to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wr apper.clientHeight)
3233 } else if (Math.min(ensureTo, doc.lastLine()) >= to) {
3234 from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wr apper.clientHeight)
3235 to = ensureTo
3236 }
3237 }
3238 return {from: from, to: Math.max(to, from + 1)}
3239 }
3240
3241 // Sync the scrollable area and scrollbars, ensure the viewport
3242 // covers the visible area.
3243 function setScrollTop(cm, val) {
3244 if (Math.abs(cm.doc.scrollTop - val) < 2) { return }
3245 cm.doc.scrollTop = val
3246 if (!gecko) { updateDisplaySimple(cm, {top: val}) }
3247 if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = va l }
3248 cm.display.scrollbars.setScrollTop(val)
3249 if (gecko) { updateDisplaySimple(cm) }
3250 startWorker(cm, 100)
3251 }
3252 // Sync scroller and scrollbar, ensure the gutter elements are
3253 // aligned.
3254 function setScrollLeft(cm, val, isScroller) {
3255 if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) { return }
3256 val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clie ntWidth)
3257 cm.doc.scrollLeft = val
3258 alignHorizontally(cm)
3259 if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val }
3260 cm.display.scrollbars.setScrollLeft(val)
3261 }
3262
3263 // Since the delta values reported on mouse wheel events are
3264 // unstandardized between browsers and even browser versions, and
3265 // generally horribly unpredictable, this code starts by measuring
3266 // the scroll effect that the first few mouse wheel events have,
3267 // and, from that, detects the way it can convert deltas to pixel
3268 // offsets afterwards.
3269 //
3270 // The reason we want to know the amount a wheel event will scroll
3271 // is that it gives us a chance to update the display before the
3272 // actual scrolling happens, reducing flickering.
3273
3274 var wheelSamples = 0;
3275 var wheelPixelsPerUnit = null;
3276 // Fill in a browser-detected starting value on browsers where we
3277 // know one. These don't have to be accurate -- the result of them
3278 // being wrong would just be a slight flicker on the first wheel
3279 // scroll (if it is large enough).
3280 if (ie) { wheelPixelsPerUnit = -.53 }
3281 else if (gecko) { wheelPixelsPerUnit = 15 }
3282 else if (chrome) { wheelPixelsPerUnit = -.7 }
3283 else if (safari) { wheelPixelsPerUnit = -1/3 }
3284
3285 function wheelEventDelta(e) {
3286 var dx = e.wheelDeltaX, dy = e.wheelDeltaY
3287 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail }
3288 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail }
3289 else if (dy == null) { dy = e.wheelDelta }
3290 return {x: dx, y: dy}
3291 }
3292 function wheelEventPixels(e) {
3293 var delta = wheelEventDelta(e)
3294 delta.x *= wheelPixelsPerUnit
3295 delta.y *= wheelPixelsPerUnit
3296 return delta
3297 }
3298
3299 function onScrollWheel(cm, e) {
3300 var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y
3301
3302 var display = cm.display, scroll = display.scroller
3303 // Quit if there's nothing to scroll here
3304 var canScrollX = scroll.scrollWidth > scroll.clientWidth
3305 var canScrollY = scroll.scrollHeight > scroll.clientHeight
3306 if (!(dx && canScrollX || dy && canScrollY)) { return }
3307
3308 // Webkit browsers on OS X abort momentum scrolls when the target
3309 // of the scroll event is removed from the scrollable element.
3310 // This hack (see related code in patchDisplay) makes sure the
3311 // element is kept around.
3312 if (dy && mac && webkit) {
3313 outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cu r.parentNode) {
3314 for (var i = 0; i < view.length; i++) {
3315 if (view[i].node == cur) {
3316 cm.display.currentWheelTarget = cur
3317 break outer
3318 }
3319 }
3320 }
3321 }
3322
3323 // On some browsers, horizontal scrolling will cause redraws to
3324 // happen before the gutter has been realigned, causing it to
3325 // wriggle around in a most unseemly way. When we have an
3326 // estimated pixels/delta value, we just handle horizontal
3327 // scrolling entirely here. It'll be slightly off from native, but
3328 // better than glitching out.
3329 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
3330 if (dy && canScrollY)
3331 { setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixel sPerUnit, scroll.scrollHeight - scroll.clientHeight))) }
3332 setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsP erUnit, scroll.scrollWidth - scroll.clientWidth)))
3333 // Only prevent default scrolling if vertical scrolling is
3334 // actually possible. Otherwise, it causes vertical scroll
3335 // jitter on OSX trackpads when deltaX is small and deltaY
3336 // is large (issue #3579)
3337 if (!dy || (dy && canScrollY))
3338 { e_preventDefault(e) }
3339 display.wheelStartX = null // Abort measurement, if in progress
3340 return
3341 }
3342
3343 // 'Project' the visible viewport to cover the area that is being
3344 // scrolled into view (if we know enough to estimate it).
3345 if (dy && wheelPixelsPerUnit != null) {
3346 var pixels = dy * wheelPixelsPerUnit
3347 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight
3348 if (pixels < 0) { top = Math.max(0, top + pixels - 50) }
3349 else { bot = Math.min(cm.doc.height, bot + pixels + 50) }
3350 updateDisplaySimple(cm, {top: top, bottom: bot})
3351 }
3352
3353 if (wheelSamples < 20) {
3354 if (display.wheelStartX == null) {
3355 display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scro llTop
3356 display.wheelDX = dx; display.wheelDY = dy
3357 setTimeout(function () {
3358 if (display.wheelStartX == null) { return }
3359 var movedX = scroll.scrollLeft - display.wheelStartX
3360 var movedY = scroll.scrollTop - display.wheelStartY
3361 var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
3362 (movedX && display.wheelDX && movedX / display.wheelDX)
3363 display.wheelStartX = display.wheelStartY = null
3364 if (!sample) { return }
3365 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (whe elSamples + 1)
3366 ++wheelSamples
3367 }, 200)
3368 } else {
3369 display.wheelDX += dx; display.wheelDY += dy
3370 }
3371 }
3372 }
3373
3374 // SCROLLBARS
3375
3376 // Prepare DOM reads needed to update the scrollbars. Done in one
3377 // shot to minimize update/measure roundtrips.
3378 function measureForScrollbars(cm) {
3379 var d = cm.display, gutterW = d.gutters.offsetWidth
3380 var docH = Math.round(cm.doc.height + paddingVert(cm.display))
3381 return {
3382 clientHeight: d.scroller.clientHeight,
3383 viewHeight: d.wrapper.clientHeight,
3384 scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth,
3385 viewWidth: d.wrapper.clientWidth,
3386 barLeft: cm.options.fixedGutter ? gutterW : 0,
3387 docHeight: docH,
3388 scrollHeight: docH + scrollGap(cm) + d.barHeight,
3389 nativeBarWidth: d.nativeBarWidth,
3390 gutterWidth: gutterW
3391 }
3392 }
3393
3394 var NativeScrollbars = function NativeScrollbars(place, scroll, cm) {
3395 this.cm = cm
3396 var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar")
3397 var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min -height: 1px")], "CodeMirror-hscrollbar")
3398 place(vert); place(horiz)
3399
3400 on(vert, "scroll", function () {
3401 if (vert.clientHeight) { scroll(vert.scrollTop, "vertical") }
3402 })
3403 on(horiz, "scroll", function () {
3404 if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal") }
3405 })
3406
3407 this.checkedZeroWidth = false
3408 // Need to set a minimum width to see the scrollbar on IE7 (but must not set i t on IE8).
3409 if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWi dth = "18px" }
3410 };
3411
3412 NativeScrollbars.prototype.update = function update (measure) {
3413 var needsH = measure.scrollWidth > measure.clientWidth + 1
3414 var needsV = measure.scrollHeight > measure.clientHeight + 1
3415 var sWidth = measure.nativeBarWidth
3416
3417 if (needsV) {
3418 this.vert.style.display = "block"
3419 this.vert.style.bottom = needsH ? sWidth + "px" : "0"
3420 var totalHeight = measure.viewHeight - (needsH ? sWidth : 0)
3421 // A bug in IE8 can cause this value to be negative, so guard it.
3422 this.vert.firstChild.style.height =
3423 Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + " px"
3424 } else {
3425 this.vert.style.display = ""
3426 this.vert.firstChild.style.height = "0"
3427 }
3428
3429 if (needsH) {
3430 this.horiz.style.display = "block"
3431 this.horiz.style.right = needsV ? sWidth + "px" : "0"
3432 this.horiz.style.left = measure.barLeft + "px"
3433 var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0)
3434 this.horiz.firstChild.style.width =
3435 Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px"
3436 } else {
3437 this.horiz.style.display = ""
3438 this.horiz.firstChild.style.width = "0"
3439 }
3440
3441 if (!this.checkedZeroWidth && measure.clientHeight > 0) {
3442 if (sWidth == 0) { this.zeroWidthHack() }
3443 this.checkedZeroWidth = true
3444 }
3445
3446 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}
3447 };
3448
3449 NativeScrollbars.prototype.setScrollLeft = function setScrollLeft$1 (pos) {
3450 if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos }
3451 if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz ) }
3452 };
3453
3454 NativeScrollbars.prototype.setScrollTop = function setScrollTop$1 (pos) {
3455 if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos }
3456 if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert) }
3457 };
3458
3459 NativeScrollbars.prototype.zeroWidthHack = function zeroWidthHack () {
3460 var w = mac && !mac_geMountainLion ? "12px" : "18px"
3461 this.horiz.style.height = this.vert.style.width = w
3462 this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"
3463 this.disableHoriz = new Delayed
3464 this.disableVert = new Delayed
3465 };
3466
3467 NativeScrollbars.prototype.enableZeroWidthBar = function enableZeroWidthBar (bar , delay) {
3468 bar.style.pointerEvents = "auto"
3469 function maybeDisable() {
3470 // To find out whether the scrollbar is still visible, we
3471 // check whether the element under the pixel in the bottom
3472 // left corner of the scrollbar box is the scrollbar box
3473 // itself (when the bar is still visible) or its filler child
3474 // (when the bar is hidden). If it is still visible, we keep
3475 // it enabled, if it's hidden, we disable pointer events.
3476 var box = bar.getBoundingClientRect()
3477 var elt = document.elementFromPoint(box.left + 1, box.bottom - 1)
3478 if (elt != bar) { bar.style.pointerEvents = "none" }
3479 else { delay.set(1000, maybeDisable) }
3480 }
3481 delay.set(1000, maybeDisable)
3482 };
3483
3484 NativeScrollbars.prototype.clear = function clear () {
3485 var parent = this.horiz.parentNode
3486 parent.removeChild(this.horiz)
3487 parent.removeChild(this.vert)
3488 };
3489
3490 var NullScrollbars = function NullScrollbars () {};
3491
3492 NullScrollbars.prototype.update = function update () { return {bottom: 0, right: 0} };
3493 NullScrollbars.prototype.setScrollLeft = function setScrollLeft$2 () {};
3494 NullScrollbars.prototype.setScrollTop = function setScrollTop$2 () {};
3495 NullScrollbars.prototype.clear = function clear () {};
3496
3497 function updateScrollbars(cm, measure) {
3498 if (!measure) { measure = measureForScrollbars(cm) }
3499 var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight
3500 updateScrollbarsInner(cm, measure)
3501 for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != c m.display.barHeight; i++) {
3502 if (startWidth != cm.display.barWidth && cm.options.lineWrapping)
3503 { updateHeightsInViewport(cm) }
3504 updateScrollbarsInner(cm, measureForScrollbars(cm))
3505 startWidth = cm.display.barWidth; startHeight = cm.display.barHeight
3506 }
3507 }
3508
3509 // Re-synchronize the fake scrollbars with the actual size of the
3510 // content.
3511 function updateScrollbarsInner(cm, measure) {
3512 var d = cm.display
3513 var sizes = d.scrollbars.update(measure)
3514
3515 d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"
3516 d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"
3517 d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"
3518
3519 if (sizes.right && sizes.bottom) {
3520 d.scrollbarFiller.style.display = "block"
3521 d.scrollbarFiller.style.height = sizes.bottom + "px"
3522 d.scrollbarFiller.style.width = sizes.right + "px"
3523 } else { d.scrollbarFiller.style.display = "" }
3524 if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedG utter) {
3525 d.gutterFiller.style.display = "block"
3526 d.gutterFiller.style.height = sizes.bottom + "px"
3527 d.gutterFiller.style.width = measure.gutterWidth + "px"
3528 } else { d.gutterFiller.style.display = "" }
3529 }
3530
3531 var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}
3532
3533 function initScrollbars(cm) {
3534 if (cm.display.scrollbars) {
3535 cm.display.scrollbars.clear()
3536 if (cm.display.scrollbars.addClass)
3537 { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass) }
3538 }
3539
3540 cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) {
3541 cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller)
3542 // Prevent clicks in the scrollbars from killing focus
3543 on(node, "mousedown", function () {
3544 if (cm.state.focused) { setTimeout(function () { return cm.display.input.f ocus(); }, 0) }
3545 })
3546 node.setAttribute("cm-not-content", "true")
3547 }, function (pos, axis) {
3548 if (axis == "horizontal") { setScrollLeft(cm, pos) }
3549 else { setScrollTop(cm, pos) }
3550 }, cm)
3551 if (cm.display.scrollbars.addClass)
3552 { addClass(cm.display.wrapper, cm.display.scrollbars.addClass) }
3553 }
3554
3555 // SCROLLING THINGS INTO VIEW
3556
3557 // If an editor sits on the top or bottom of the window, partially
3558 // scrolled out of view, this ensures that the cursor is visible.
3559 function maybeScrollWindow(cm, rect) {
3560 if (signalDOMEvent(cm, "scrollCursorIntoView")) { return }
3561
3562 var display = cm.display, box = display.sizer.getBoundingClientRect(), doScrol l = null
3563 if (rect.top + box.top < 0) { doScroll = true }
3564 else if (rect.bottom + box.top > (window.innerHeight || document.documentEleme nt.clientHeight)) { doScroll = false }
3565 if (doScroll != null && !phantom) {
3566 var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display) ) + "px;\n height: " + (rect.bottom - rect.top + scrollG ap(cm) + display.barHeight) + "px;\n left: " + (rect.lef t) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;"))
3567 cm.display.lineSpace.appendChild(scrollNode)
3568 scrollNode.scrollIntoView(doScroll)
3569 cm.display.lineSpace.removeChild(scrollNode)
3570 }
3571 }
3572
3573 // Scroll a given position into view (immediately), verifying that
3574 // it actually became visible (as line heights are accurately
3575 // measured, the position of something may 'drift' during drawing).
3576 function scrollPosIntoView(cm, pos, end, margin) {
3577 if (margin == null) { margin = 0 }
3578 var rect
3579 for (var limit = 0; limit < 5; limit++) {
3580 var changed = false
3581 var coords = cursorCoords(cm, pos)
3582 var endCoords = !end || end == pos ? coords : cursorCoords(cm, end)
3583 rect = {left: Math.min(coords.left, endCoords.left),
3584 top: Math.min(coords.top, endCoords.top) - margin,
3585 right: Math.max(coords.left, endCoords.left),
3586 bottom: Math.max(coords.bottom, endCoords.bottom) + margin}
3587 var scrollPos = calculateScrollPos(cm, rect)
3588 var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft
3589 if (scrollPos.scrollTop != null) {
3590 setScrollTop(cm, scrollPos.scrollTop)
3591 if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true }
3592 }
3593 if (scrollPos.scrollLeft != null) {
3594 setScrollLeft(cm, scrollPos.scrollLeft)
3595 if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true }
3596 }
3597 if (!changed) { break }
3598 }
3599 return rect
3600 }
3601
3602 // Scroll a given set of coordinates into view (immediately).
3603 function scrollIntoView(cm, rect) {
3604 var scrollPos = calculateScrollPos(cm, rect)
3605 if (scrollPos.scrollTop != null) { setScrollTop(cm, scrollPos.scrollTop) }
3606 if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft) }
3607 }
3608
3609 // Calculate a new scroll position needed to scroll the given
3610 // rectangle into view. Returns an object with scrollTop and
3611 // scrollLeft properties. When these are undefined, the
3612 // vertical/horizontal position does not need to be adjusted.
3613 function calculateScrollPos(cm, rect) {
3614 var display = cm.display, snapMargin = textHeight(cm.display)
3615 if (rect.top < 0) { rect.top = 0 }
3616 var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop
3617 var screen = displayHeight(cm), result = {}
3618 if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen }
3619 var docBottom = cm.doc.height + paddingVert(display)
3620 var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMa rgin
3621 if (rect.top < screentop) {
3622 result.scrollTop = atTop ? 0 : rect.top
3623 } else if (rect.bottom > screentop + screen) {
3624 var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - scree n)
3625 if (newTop != screentop) { result.scrollTop = newTop }
3626 }
3627
3628 var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft
3629 var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.off setWidth : 0)
3630 var tooWide = rect.right - rect.left > screenw
3631 if (tooWide) { rect.right = rect.left + screenw }
3632 if (rect.left < 10)
3633 { result.scrollLeft = 0 }
3634 else if (rect.left < screenleft)
3635 { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)) }
3636 else if (rect.right > screenw + screenleft - 3)
3637 { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw }
3638 return result
3639 }
3640
3641 // Store a relative adjustment to the scroll position in the current
3642 // operation (to be applied when the operation finishes).
3643 function addToScrollPos(cm, left, top) {
3644 if (left != null || top != null) { resolveScrollToPos(cm) }
3645 if (left != null)
3646 { cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : c m.curOp.scrollLeft) + left }
3647 if (top != null)
3648 { cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.c urOp.scrollTop) + top }
3649 }
3650
3651 // Make sure that at the end of the operation the current cursor is
3652 // shown.
3653 function ensureCursorVisible(cm) {
3654 resolveScrollToPos(cm)
3655 var cur = cm.getCursor(), from = cur, to = cur
3656 if (!cm.options.lineWrapping) {
3657 from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur
3658 to = Pos(cur.line, cur.ch + 1)
3659 }
3660 cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMar gin}
3661 }
3662
3663 // When an operation has its scrollToPos property set, and another
3664 // scroll action is applied before the end of the operation, this
3665 // 'simulates' scrolling that position into view in a cheap way, so
3666 // that the effect of intermediate scroll commands is not ignored.
3667 function resolveScrollToPos(cm) {
3668 var range = cm.curOp.scrollToPos
3669 if (range) {
3670 cm.curOp.scrollToPos = null
3671 var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to)
3672 var sPos = calculateScrollPos(cm, {
3673 left: Math.min(from.left, to.left),
3674 top: Math.min(from.top, to.top) - range.margin,
3675 right: Math.max(from.right, to.right),
3676 bottom: Math.max(from.bottom, to.bottom) + range.margin
3677 })
3678 cm.scrollTo(sPos.scrollLeft, sPos.scrollTop)
3679 }
3680 }
3681
3682 // Operations are used to wrap a series of changes to the editor
3683 // state in such a way that each change won't have to update the
3684 // cursor and display (which would be awkward, slow, and
3685 // error-prone). Instead, display updates are batched and then all
3686 // combined and executed at once.
3687
3688 var nextOpId = 0
3689 // Start a new operation.
3690 function startOperation(cm) {
3691 cm.curOp = {
3692 cm: cm,
3693 viewChanged: false, // Flag that indicates that lines might need to be redrawn
3694 startHeight: cm.doc.height, // Used to detect need to update scrollbar
3695 forceUpdate: false, // Used to force a redraw
3696 updateInput: null, // Whether to reset the input textarea
3697 typing: false, // Whether this reset should be careful to leave ex isting text (for compositing)
3698 changeObjs: null, // Accumulated changes, for firing change events
3699 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
3700 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been c alled already
3701 selectionChanged: false, // Whether the selection needs to be redrawn
3702 updateMaxLine: false, // Set when the widest line needs to be determined anew
3703 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not push ed to DOM yet
3704 scrollToPos: null, // Used to scroll to a specific position
3705 focus: false,
3706 id: ++nextOpId // Unique ID
3707 }
3708 pushOperation(cm.curOp)
3709 }
3710
3711 // Finish an operation, updating the display and signalling delayed events
3712 function endOperation(cm) {
3713 var op = cm.curOp
3714 finishOperation(op, function (group) {
3715 for (var i = 0; i < group.ops.length; i++)
3716 { group.ops[i].cm.curOp = null }
3717 endOperations(group)
3718 })
3719 }
3720
3721 // The DOM updates done when an operation finishes are batched so
3722 // that the minimum number of relayouts are required.
3723 function endOperations(group) {
3724 var ops = group.ops
3725 for (var i = 0; i < ops.length; i++) // Read DOM
3726 { endOperation_R1(ops[i]) }
3727 for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe)
3728 { endOperation_W1(ops[i$1]) }
3729 for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM
3730 { endOperation_R2(ops[i$2]) }
3731 for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe)
3732 { endOperation_W2(ops[i$3]) }
3733 for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM
3734 { endOperation_finish(ops[i$4]) }
3735 }
3736
3737 function endOperation_R1(op) {
3738 var cm = op.cm, display = cm.display
3739 maybeClipScrollbars(cm)
3740 if (op.updateMaxLine) { findMaxLine(cm) }
3741
3742 op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||
3743 op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
3744 op.scrollToPos.to.line >= display.viewTo) ||
3745 display.maxLineChanged && cm.options.lineWrapping
3746 op.update = op.mustUpdate &&
3747 new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scroll ToPos}, op.forceUpdate)
3748 }
3749
3750 function endOperation_W1(op) {
3751 op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update)
3752 }
3753
3754 function endOperation_R2(op) {
3755 var cm = op.cm, display = cm.display
3756 if (op.updatedDisplay) { updateHeightsInViewport(cm) }
3757
3758 op.barMeasure = measureForScrollbars(cm)
3759
3760 // If the max line changed since it was last measured, measure it,
3761 // and ensure the document's width matches it.
3762 // updateDisplay_W2 will use these properties to do the actual resizing
3763 if (display.maxLineChanged && !cm.options.lineWrapping) {
3764 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.len gth).left + 3
3765 cm.display.sizerWidth = op.adjustWidthTo
3766 op.barMeasure.scrollWidth =
3767 Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjus tWidthTo + scrollGap(cm) + cm.display.barWidth)
3768 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm))
3769 }
3770
3771 if (op.updatedDisplay || op.selectionChanged)
3772 { op.preparedSelection = display.input.prepareSelection(op.focus) }
3773 }
3774
3775 function endOperation_W2(op) {
3776 var cm = op.cm
3777
3778 if (op.adjustWidthTo != null) {
3779 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"
3780 if (op.maxScrollLeft < cm.doc.scrollLeft)
3781 { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollL eft), true) }
3782 cm.display.maxLineChanged = false
3783 }
3784
3785 var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus())
3786 if (op.preparedSelection)
3787 { cm.display.input.showSelection(op.preparedSelection, takeFocus) }
3788 if (op.updatedDisplay || op.startHeight != cm.doc.height)
3789 { updateScrollbars(cm, op.barMeasure) }
3790 if (op.updatedDisplay)
3791 { setDocumentHeight(cm, op.barMeasure) }
3792
3793 if (op.selectionChanged) { restartBlink(cm) }
3794
3795 if (cm.state.focused && op.updateInput)
3796 { cm.display.input.reset(op.typing) }
3797 if (takeFocus) { ensureFocus(op.cm) }
3798 }
3799
3800 function endOperation_finish(op) {
3801 var cm = op.cm, display = cm.display, doc = cm.doc
3802
3803 if (op.updatedDisplay) { postUpdateDisplay(cm, op.update) }
3804
3805 // Abort mouse wheel delta measurement, when scrolling explicitly
3806 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != n ull || op.scrollToPos))
3807 { display.wheelStartX = display.wheelStartY = null }
3808
3809 // Propagate the scroll position to the actual DOM scroller
3810 if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op. forceScroll)) {
3811 doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display .scroller.clientHeight, op.scrollTop))
3812 display.scrollbars.setScrollTop(doc.scrollTop)
3813 display.scroller.scrollTop = doc.scrollTop
3814 }
3815 if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) {
3816 doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display .scroller.clientWidth, op.scrollLeft))
3817 display.scrollbars.setScrollLeft(doc.scrollLeft)
3818 display.scroller.scrollLeft = doc.scrollLeft
3819 alignHorizontally(cm)
3820 }
3821 // If we need to scroll a specific position into view, do so.
3822 if (op.scrollToPos) {
3823 var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
3824 clipPos(doc, op.scrollToPos.to), op.scrollToPos .margin)
3825 maybeScrollWindow(cm, rect)
3826 }
3827
3828 // Fire events for markers that are hidden/unidden by editing or
3829 // undoing
3830 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers
3831 if (hidden) { for (var i = 0; i < hidden.length; ++i)
3832 { if (!hidden[i].lines.length) { signal(hidden[i], "hide") } } }
3833 if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1)
3834 { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide") } } }
3835
3836 if (display.wrapper.offsetHeight)
3837 { doc.scrollTop = cm.display.scroller.scrollTop }
3838
3839 // Fire change events, and delayed event handlers
3840 if (op.changeObjs)
3841 { signal(cm, "changes", cm, op.changeObjs) }
3842 if (op.update)
3843 { op.update.finish() }
3844 }
3845
3846 // Run the given function in an operation
3847 function runInOp(cm, f) {
3848 if (cm.curOp) { return f() }
3849 startOperation(cm)
3850 try { return f() }
3851 finally { endOperation(cm) }
3852 }
3853 // Wraps a function in an operation. Returns the wrapped function.
3854 function operation(cm, f) {
3855 return function() {
3856 if (cm.curOp) { return f.apply(cm, arguments) }
3857 startOperation(cm)
3858 try { return f.apply(cm, arguments) }
3859 finally { endOperation(cm) }
3860 }
3861 }
3862 // Used to add methods to editor and doc instances, wrapping them in
3863 // operations.
3864 function methodOp(f) {
3865 return function() {
3866 if (this.curOp) { return f.apply(this, arguments) }
3867 startOperation(this)
3868 try { return f.apply(this, arguments) }
3869 finally { endOperation(this) }
3870 }
3871 }
3872 function docMethodOp(f) {
3873 return function() {
3874 var cm = this.cm
3875 if (!cm || cm.curOp) { return f.apply(this, arguments) }
3876 startOperation(cm)
3877 try { return f.apply(this, arguments) }
3878 finally { endOperation(cm) }
3879 }
3880 }
3881
3882 // Updates the display.view data structure for a given change to the
3883 // document. From and to are in pre-change coordinates. Lendiff is
3884 // the amount of lines added or subtracted by the change. This is
3885 // used for changes that span multiple lines, or change the way
3886 // lines are divided into visual lines. regLineChange (below)
3887 // registers single-line changes.
3888 function regChange(cm, from, to, lendiff) {
3889 if (from == null) { from = cm.doc.first }
3890 if (to == null) { to = cm.doc.first + cm.doc.size }
3891 if (!lendiff) { lendiff = 0 }
3892
3893 var display = cm.display
3894 if (lendiff && to < display.viewTo &&
3895 (display.updateLineNumbers == null || display.updateLineNumbers > from))
3896 { display.updateLineNumbers = from }
3897
3898 cm.curOp.viewChanged = true
3899
3900 if (from >= display.viewTo) { // Change after
3901 if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
3902 { resetView(cm) }
3903 } else if (to <= display.viewFrom) { // Change before
3904 if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.vie wFrom) {
3905 resetView(cm)
3906 } else {
3907 display.viewFrom += lendiff
3908 display.viewTo += lendiff
3909 }
3910 } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
3911 resetView(cm)
3912 } else if (from <= display.viewFrom) { // Top overlap
3913 var cut = viewCuttingPoint(cm, to, to + lendiff, 1)
3914 if (cut) {
3915 display.view = display.view.slice(cut.index)
3916 display.viewFrom = cut.lineN
3917 display.viewTo += lendiff
3918 } else {
3919 resetView(cm)
3920 }
3921 } else if (to >= display.viewTo) { // Bottom overlap
3922 var cut$1 = viewCuttingPoint(cm, from, from, -1)
3923 if (cut$1) {
3924 display.view = display.view.slice(0, cut$1.index)
3925 display.viewTo = cut$1.lineN
3926 } else {
3927 resetView(cm)
3928 }
3929 } else { // Gap in the middle
3930 var cutTop = viewCuttingPoint(cm, from, from, -1)
3931 var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1)
3932 if (cutTop && cutBot) {
3933 display.view = display.view.slice(0, cutTop.index)
3934 .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
3935 .concat(display.view.slice(cutBot.index))
3936 display.viewTo += lendiff
3937 } else {
3938 resetView(cm)
3939 }
3940 }
3941
3942 var ext = display.externalMeasured
3943 if (ext) {
3944 if (to < ext.lineN)
3945 { ext.lineN += lendiff }
3946 else if (from < ext.lineN + ext.size)
3947 { display.externalMeasured = null }
3948 }
3949 }
3950
3951 // Register a change to a single line. Type must be one of "text",
3952 // "gutter", "class", "widget"
3953 function regLineChange(cm, line, type) {
3954 cm.curOp.viewChanged = true
3955 var display = cm.display, ext = cm.display.externalMeasured
3956 if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
3957 { display.externalMeasured = null }
3958
3959 if (line < display.viewFrom || line >= display.viewTo) { return }
3960 var lineView = display.view[findViewIndex(cm, line)]
3961 if (lineView.node == null) { return }
3962 var arr = lineView.changes || (lineView.changes = [])
3963 if (indexOf(arr, type) == -1) { arr.push(type) }
3964 }
3965
3966 // Clear the view.
3967 function resetView(cm) {
3968 cm.display.viewFrom = cm.display.viewTo = cm.doc.first
3969 cm.display.view = []
3970 cm.display.viewOffset = 0
3971 }
3972
3973 function viewCuttingPoint(cm, oldN, newN, dir) {
3974 var index = findViewIndex(cm, oldN), diff, view = cm.display.view
3975 if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
3976 { return {index: index, lineN: newN} }
3977 var n = cm.display.viewFrom
3978 for (var i = 0; i < index; i++)
3979 { n += view[i].size }
3980 if (n != oldN) {
3981 if (dir > 0) {
3982 if (index == view.length - 1) { return null }
3983 diff = (n + view[index].size) - oldN
3984 index++
3985 } else {
3986 diff = n - oldN
3987 }
3988 oldN += diff; newN += diff
3989 }
3990 while (visualLineNo(cm.doc, newN) != newN) {
3991 if (index == (dir < 0 ? 0 : view.length - 1)) { return null }
3992 newN += dir * view[index - (dir < 0 ? 1 : 0)].size
3993 index += dir
3994 }
3995 return {index: index, lineN: newN}
3996 }
3997
3998 // Force the view to cover a given range, adding empty view element
3999 // or clipping off existing ones as needed.
4000 function adjustView(cm, from, to) {
4001 var display = cm.display, view = display.view
4002 if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
4003 display.view = buildViewArray(cm, from, to)
4004 display.viewFrom = from
4005 } else {
4006 if (display.viewFrom > from)
4007 { display.view = buildViewArray(cm, from, display.viewFrom).concat(display .view) }
4008 else if (display.viewFrom < from)
4009 { display.view = display.view.slice(findViewIndex(cm, from)) }
4010 display.viewFrom = from
4011 if (display.viewTo < to)
4012 { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to )) }
4013 else if (display.viewTo > to)
4014 { display.view = display.view.slice(0, findViewIndex(cm, to)) }
4015 }
4016 display.viewTo = to
4017 }
4018
4019 // Count the number of lines in the view whose DOM representation is
4020 // out of date (or nonexistent).
4021 function countDirtyView(cm) {
4022 var view = cm.display.view, dirty = 0
4023 for (var i = 0; i < view.length; i++) {
4024 var lineView = view[i]
4025 if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty }
4026 }
4027 return dirty
4028 }
4029
4030 // HIGHLIGHT WORKER
4031
4032 function startWorker(cm, time) {
4033 if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo)
4034 { cm.state.highlight.set(time, bind(highlightWorker, cm)) }
4035 }
4036
4037 function highlightWorker(cm) {
4038 var doc = cm.doc
4039 if (doc.frontier < doc.first) { doc.frontier = doc.first }
4040 if (doc.frontier >= cm.display.viewTo) { return }
4041 var end = +new Date + cm.options.workTime
4042 var state = copyState(doc.mode, getStateBefore(cm, doc.frontier))
4043 var changedLines = []
4044
4045 doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500) , function (line) {
4046 if (doc.frontier >= cm.display.viewFrom) { // Visible
4047 var oldStyles = line.styles, tooLong = line.text.length > cm.options.maxHi ghlightLength
4048 var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, st ate) : state, true)
4049 line.styles = highlighted.styles
4050 var oldCls = line.styleClasses, newCls = highlighted.classes
4051 if (newCls) { line.styleClasses = newCls }
4052 else if (oldCls) { line.styleClasses = null }
4053 var ischange = !oldStyles || oldStyles.length != line.styles.length ||
4054 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgCl ass || oldCls.textClass != newCls.textClass)
4055 for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldSt yles[i] != line.styles[i] }
4056 if (ischange) { changedLines.push(doc.frontier) }
4057 line.stateAfter = tooLong ? state : copyState(doc.mode, state)
4058 } else {
4059 if (line.text.length <= cm.options.maxHighlightLength)
4060 { processLine(cm, line.text, state) }
4061 line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : nul l
4062 }
4063 ++doc.frontier
4064 if (+new Date > end) {
4065 startWorker(cm, cm.options.workDelay)
4066 return true
4067 }
4068 })
4069 if (changedLines.length) { runInOp(cm, function () {
4070 for (var i = 0; i < changedLines.length; i++)
4071 { regLineChange(cm, changedLines[i], "text") }
4072 }) }
4073 }
4074
4075 // DISPLAY DRAWING
4076
4077 var DisplayUpdate = function DisplayUpdate(cm, viewport, force) {
4078 var display = cm.display
4079
4080 this.viewport = viewport
4081 // Store some values that we'll need later (but don't want to force a relayout for)
4082 this.visible = visibleLines(display, cm.doc, viewport)
4083 this.editorIsHidden = !display.wrapper.offsetWidth
4084 this.wrapperHeight = display.wrapper.clientHeight
4085 this.wrapperWidth = display.wrapper.clientWidth
4086 this.oldDisplayWidth = displayWidth(cm)
4087 this.force = force
4088 this.dims = getDimensions(cm)
4089 this.events = []
4090 };
4091
4092 DisplayUpdate.prototype.signal = function signal$1 (emitter, type) {
4093 if (hasHandler(emitter, type))
4094 { this.events.push(arguments) }
4095 };
4096 DisplayUpdate.prototype.finish = function finish () {
4097 var this$1 = this;
4098
4099 for (var i = 0; i < this.events.length; i++)
4100 { signal.apply(null, this$1.events[i]) }
4101 };
4102
4103 function maybeClipScrollbars(cm) {
4104 var display = cm.display
4105 if (!display.scrollbarsClipped && display.scroller.offsetWidth) {
4106 display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.cli entWidth
4107 display.heightForcer.style.height = scrollGap(cm) + "px"
4108 display.sizer.style.marginBottom = -display.nativeBarWidth + "px"
4109 display.sizer.style.borderRightWidth = scrollGap(cm) + "px"
4110 display.scrollbarsClipped = true
4111 }
4112 }
4113
4114 // Does the actual updating of the line display. Bails out
4115 // (returning false) when there is nothing to be done and forced is
4116 // false.
4117 function updateDisplayIfNeeded(cm, update) {
4118 var display = cm.display, doc = cm.doc
4119
4120 if (update.editorIsHidden) {
4121 resetView(cm)
4122 return false
4123 }
4124
4125 // Bail out if the visible area is already rendered and nothing changed.
4126 if (!update.force &&
4127 update.visible.from >= display.viewFrom && update.visible.to <= display.vi ewTo &&
4128 (display.updateLineNumbers == null || display.updateLineNumbers >= display .viewTo) &&
4129 display.renderedView == display.view && countDirtyView(cm) == 0)
4130 { return false }
4131
4132 if (maybeUpdateLineNumberWidth(cm)) {
4133 resetView(cm)
4134 update.dims = getDimensions(cm)
4135 }
4136
4137 // Compute a suitable new viewport (from & to)
4138 var end = doc.first + doc.size
4139 var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first )
4140 var to = Math.min(end, update.visible.to + cm.options.viewportMargin)
4141 if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max (doc.first, display.viewFrom) }
4142 if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, disp lay.viewTo) }
4143 if (sawCollapsedSpans) {
4144 from = visualLineNo(cm.doc, from)
4145 to = visualLineEndNo(cm.doc, to)
4146 }
4147
4148 var different = from != display.viewFrom || to != display.viewTo ||
4149 display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != u pdate.wrapperWidth
4150 adjustView(cm, from, to)
4151
4152 display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom))
4153 // Position the mover div to align with the current scroll position
4154 cm.display.mover.style.top = display.viewOffset + "px"
4155
4156 var toUpdate = countDirtyView(cm)
4157 if (!different && toUpdate == 0 && !update.force && display.renderedView == di splay.view &&
4158 (display.updateLineNumbers == null || display.updateLineNumbers >= display .viewTo))
4159 { return false }
4160
4161 // For big changes, we hide the enclosing element during the
4162 // update, since that speeds up the operations on most browsers.
4163 var focused = activeElt()
4164 if (toUpdate > 4) { display.lineDiv.style.display = "none" }
4165 patchDisplay(cm, display.updateLineNumbers, update.dims)
4166 if (toUpdate > 4) { display.lineDiv.style.display = "" }
4167 display.renderedView = display.view
4168 // There might have been a widget with a focused element that got
4169 // hidden or updated, if so re-focus it.
4170 if (focused && activeElt() != focused && focused.offsetHeight) { focused.focus () }
4171
4172 // Prevent selection and cursors from interfering with the scroll
4173 // width and height.
4174 removeChildren(display.cursorDiv)
4175 removeChildren(display.selectionDiv)
4176 display.gutters.style.height = display.sizer.style.minHeight = 0
4177
4178 if (different) {
4179 display.lastWrapHeight = update.wrapperHeight
4180 display.lastWrapWidth = update.wrapperWidth
4181 startWorker(cm, 400)
4182 }
4183
4184 display.updateLineNumbers = null
4185
4186 return true
4187 }
4188
4189 function postUpdateDisplay(cm, update) {
4190 var viewport = update.viewport
4191
4192 for (var first = true;; first = false) {
4193 if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayW idth(cm)) {
4194 // Clip forced viewport to actual scrollable area.
4195 if (viewport && viewport.top != null)
4196 { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - di splayHeight(cm), viewport.top)} }
4197 // Updated line heights might result in the drawn area not
4198 // actually covering the viewport. Keep looping until it does.
4199 update.visible = visibleLines(cm.display, cm.doc, viewport)
4200 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm. display.viewTo)
4201 { break }
4202 }
4203 if (!updateDisplayIfNeeded(cm, update)) { break }
4204 updateHeightsInViewport(cm)
4205 var barMeasure = measureForScrollbars(cm)
4206 updateSelection(cm)
4207 updateScrollbars(cm, barMeasure)
4208 setDocumentHeight(cm, barMeasure)
4209 }
4210
4211 update.signal(cm, "update", cm)
4212 if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {
4213 update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.view To)
4214 cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo
4215 }
4216 }
4217
4218 function updateDisplaySimple(cm, viewport) {
4219 var update = new DisplayUpdate(cm, viewport)
4220 if (updateDisplayIfNeeded(cm, update)) {
4221 updateHeightsInViewport(cm)
4222 postUpdateDisplay(cm, update)
4223 var barMeasure = measureForScrollbars(cm)
4224 updateSelection(cm)
4225 updateScrollbars(cm, barMeasure)
4226 setDocumentHeight(cm, barMeasure)
4227 update.finish()
4228 }
4229 }
4230
4231 // Sync the actual display DOM structure with display.view, removing
4232 // nodes for lines that are no longer in view, and creating the ones
4233 // that are not there yet, and updating the ones that are out of
4234 // date.
4235 function patchDisplay(cm, updateNumbersFrom, dims) {
4236 var display = cm.display, lineNumbers = cm.options.lineNumbers
4237 var container = display.lineDiv, cur = container.firstChild
4238
4239 function rm(node) {
4240 var next = node.nextSibling
4241 // Works around a throw-scroll bug in OS X Webkit
4242 if (webkit && mac && cm.display.currentWheelTarget == node)
4243 { node.style.display = "none" }
4244 else
4245 { node.parentNode.removeChild(node) }
4246 return next
4247 }
4248
4249 var view = display.view, lineN = display.viewFrom
4250 // Loop over the elements in the view, syncing cur (the DOM nodes
4251 // in display.lineDiv) with the view as we go.
4252 for (var i = 0; i < view.length; i++) {
4253 var lineView = view[i]
4254 if (lineView.hidden) {
4255 } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet
4256 var node = buildLineElement(cm, lineView, lineN, dims)
4257 container.insertBefore(node, cur)
4258 } else { // Already drawn
4259 while (cur != lineView.node) { cur = rm(cur) }
4260 var updateNumber = lineNumbers && updateNumbersFrom != null &&
4261 updateNumbersFrom <= lineN && lineView.lineNumber
4262 if (lineView.changes) {
4263 if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false }
4264 updateLineForChanges(cm, lineView, lineN, dims)
4265 }
4266 if (updateNumber) {
4267 removeChildren(lineView.lineNumber)
4268 lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm .options, lineN)))
4269 }
4270 cur = lineView.node.nextSibling
4271 }
4272 lineN += lineView.size
4273 }
4274 while (cur) { cur = rm(cur) }
4275 }
4276
4277 function updateGutterSpace(cm) {
4278 var width = cm.display.gutters.offsetWidth
4279 cm.display.sizer.style.marginLeft = width + "px"
4280 }
4281
4282 function setDocumentHeight(cm, measure) {
4283 cm.display.sizer.style.minHeight = measure.docHeight + "px"
4284 cm.display.heightForcer.style.top = measure.docHeight + "px"
4285 cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"
4286 }
4287
4288 // Rebuild the gutter elements, ensure the margin to the left of the
4289 // code matches their width.
4290 function updateGutters(cm) {
4291 var gutters = cm.display.gutters, specs = cm.options.gutters
4292 removeChildren(gutters)
4293 var i = 0
4294 for (; i < specs.length; ++i) {
4295 var gutterClass = specs[i]
4296 var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutte rClass))
4297 if (gutterClass == "CodeMirror-linenumbers") {
4298 cm.display.lineGutter = gElt
4299 gElt.style.width = (cm.display.lineNumWidth || 1) + "px"
4300 }
4301 }
4302 gutters.style.display = i ? "" : "none"
4303 updateGutterSpace(cm)
4304 }
4305
4306 // Make sure the gutters options contains the element
4307 // "CodeMirror-linenumbers" when the lineNumbers option is true.
4308 function setGuttersForLineNumbers(options) {
4309 var found = indexOf(options.gutters, "CodeMirror-linenumbers")
4310 if (found == -1 && options.lineNumbers) {
4311 options.gutters = options.gutters.concat(["CodeMirror-linenumbers"])
4312 } else if (found > -1 && !options.lineNumbers) {
4313 options.gutters = options.gutters.slice(0)
4314 options.gutters.splice(found, 1)
4315 }
4316 }
4317
4318 // Selection objects are immutable. A new one is created every time
4319 // the selection changes. A selection is one or more non-overlapping
4320 // (and non-touching) ranges, sorted, and an integer that indicates
4321 // which one is the primary selection (the one that's scrolled into
4322 // view, that getCursor returns, etc).
4323 var Selection = function Selection(ranges, primIndex) {
4324 this.ranges = ranges
4325 this.primIndex = primIndex
4326 };
4327
4328 Selection.prototype.primary = function primary () { return this.ranges[this.prim Index] };
4329
4330 Selection.prototype.equals = function equals (other) {
4331 var this$1 = this;
4332
4333 if (other == this) { return true }
4334 if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.le ngth) { return false }
4335 for (var i = 0; i < this.ranges.length; i++) {
4336 var here = this$1.ranges[i], there = other.ranges[i]
4337 if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false }
4338 }
4339 return true
4340 };
4341
4342 Selection.prototype.deepCopy = function deepCopy () {
4343 var this$1 = this;
4344
4345 var out = []
4346 for (var i = 0; i < this.ranges.length; i++)
4347 { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges [i].head)) }
4348 return new Selection(out, this.primIndex)
4349 };
4350
4351 Selection.prototype.somethingSelected = function somethingSelected () {
4352 var this$1 = this;
4353
4354 for (var i = 0; i < this.ranges.length; i++)
4355 { if (!this$1.ranges[i].empty()) { return true } }
4356 return false
4357 };
4358
4359 Selection.prototype.contains = function contains (pos, end) {
4360 var this$1 = this;
4361
4362 if (!end) { end = pos }
4363 for (var i = 0; i < this.ranges.length; i++) {
4364 var range = this$1.ranges[i]
4365 if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
4366 { return i }
4367 }
4368 return -1
4369 };
4370
4371 var Range = function Range(anchor, head) {
4372 this.anchor = anchor; this.head = head
4373 };
4374
4375 Range.prototype.from = function from () { return minPos(this.anchor, this.head) };
4376 Range.prototype.to = function to () { return maxPos(this.anchor, this.head) };
4377 Range.prototype.empty = function empty () { return this.head.line == this.anchor .line && this.head.ch == this.anchor.ch };
4378
4379 // Take an unsorted, potentially overlapping set of ranges, and
4380 // build a selection out of it. 'Consumes' ranges array (modifying
4381 // it).
4382 function normalizeSelection(ranges, primIndex) {
4383 var prim = ranges[primIndex]
4384 ranges.sort(function (a, b) { return cmp(a.from(), b.from()); })
4385 primIndex = indexOf(ranges, prim)
4386 for (var i = 1; i < ranges.length; i++) {
4387 var cur = ranges[i], prev = ranges[i - 1]
4388 if (cmp(prev.to(), cur.from()) >= 0) {
4389 var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to( ))
4390 var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head
4391 if (i <= primIndex) { --primIndex }
4392 ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to))
4393 }
4394 }
4395 return new Selection(ranges, primIndex)
4396 }
4397
4398 function simpleSelection(anchor, head) {
4399 return new Selection([new Range(anchor, head || anchor)], 0)
4400 }
4401
4402 // Compute the position of the end of a change (its 'to' property
4403 // refers to the pre-change end).
4404 function changeEnd(change) {
4405 if (!change.text) { return change.to }
4406 return Pos(change.from.line + change.text.length - 1,
4407 lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0))
4408 }
4409
4410 // Adjust a position to refer to the post-change position of the
4411 // same text, or the end of the change if the change covers it.
4412 function adjustForChange(pos, change) {
4413 if (cmp(pos, change.from) < 0) { return pos }
4414 if (cmp(pos, change.to) <= 0) { return changeEnd(change) }
4415
4416 var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch
4417 if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch }
4418 return Pos(line, ch)
4419 }
4420
4421 function computeSelAfterChange(doc, change) {
4422 var out = []
4423 for (var i = 0; i < doc.sel.ranges.length; i++) {
4424 var range = doc.sel.ranges[i]
4425 out.push(new Range(adjustForChange(range.anchor, change),
4426 adjustForChange(range.head, change)))
4427 }
4428 return normalizeSelection(out, doc.sel.primIndex)
4429 }
4430
4431 function offsetPos(pos, old, nw) {
4432 if (pos.line == old.line)
4433 { return Pos(nw.line, pos.ch - old.ch + nw.ch) }
4434 else
4435 { return Pos(nw.line + (pos.line - old.line), pos.ch) }
4436 }
4437
4438 // Used by replaceSelections to allow moving the selection to the
4439 // start or around the replaced test. Hint may be "start" or "around".
4440 function computeReplacedSel(doc, changes, hint) {
4441 var out = []
4442 var oldPrev = Pos(doc.first, 0), newPrev = oldPrev
4443 for (var i = 0; i < changes.length; i++) {
4444 var change = changes[i]
4445 var from = offsetPos(change.from, oldPrev, newPrev)
4446 var to = offsetPos(changeEnd(change), oldPrev, newPrev)
4447 oldPrev = change.to
4448 newPrev = to
4449 if (hint == "around") {
4450 var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0
4451 out[i] = new Range(inv ? to : from, inv ? from : to)
4452 } else {
4453 out[i] = new Range(from, from)
4454 }
4455 }
4456 return new Selection(out, doc.sel.primIndex)
4457 }
4458
4459 // Used to get the editor into a consistent state again when options change.
4460
4461 function loadMode(cm) {
4462 cm.doc.mode = getMode(cm.options, cm.doc.modeOption)
4463 resetModeState(cm)
4464 }
4465
4466 function resetModeState(cm) {
4467 cm.doc.iter(function (line) {
4468 if (line.stateAfter) { line.stateAfter = null }
4469 if (line.styles) { line.styles = null }
4470 })
4471 cm.doc.frontier = cm.doc.first
4472 startWorker(cm, 100)
4473 cm.state.modeGen++
4474 if (cm.curOp) { regChange(cm) }
4475 }
4476
4477 // DOCUMENT DATA STRUCTURE
4478
4479 // By default, updates that start and end at the beginning of a line
4480 // are treated specially, in order to make the association of line
4481 // widgets and marker elements with the text behave more intuitive.
4482 function isWholeLineUpdate(doc, change) {
4483 return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" &&
4484 (!doc.cm || doc.cm.options.wholeLineUpdateBefore)
4485 }
4486
4487 // Perform a change on the document data structure.
4488 function updateDoc(doc, change, markedSpans, estimateHeight) {
4489 function spansFor(n) {return markedSpans ? markedSpans[n] : null}
4490 function update(line, text, spans) {
4491 updateLine(line, text, spans, estimateHeight)
4492 signalLater(line, "change", line, change)
4493 }
4494 function linesFor(start, end) {
4495 var result = []
4496 for (var i = start; i < end; ++i)
4497 { result.push(new Line(text[i], spansFor(i), estimateHeight)) }
4498 return result
4499 }
4500
4501 var from = change.from, to = change.to, text = change.text
4502 var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line)
4503 var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.l ine - from.line
4504
4505 // Adjust the line structure
4506 if (change.full) {
4507 doc.insert(0, linesFor(0, text.length))
4508 doc.remove(text.length, doc.size - text.length)
4509 } else if (isWholeLineUpdate(doc, change)) {
4510 // This is a whole-line replace. Treated specially to make
4511 // sure line objects move the way they are supposed to.
4512 var added = linesFor(0, text.length - 1)
4513 update(lastLine, lastLine.text, lastSpans)
4514 if (nlines) { doc.remove(from.line, nlines) }
4515 if (added.length) { doc.insert(from.line, added) }
4516 } else if (firstLine == lastLine) {
4517 if (text.length == 1) {
4518 update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine. text.slice(to.ch), lastSpans)
4519 } else {
4520 var added$1 = linesFor(1, text.length - 1)
4521 added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, e stimateHeight))
4522 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0))
4523 doc.insert(from.line + 1, added$1)
4524 }
4525 } else if (text.length == 1) {
4526 update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text .slice(to.ch), spansFor(0))
4527 doc.remove(from.line + 1, nlines)
4528 } else {
4529 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0))
4530 update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans)
4531 var added$2 = linesFor(1, text.length - 1)
4532 if (nlines > 1) { doc.remove(from.line + 1, nlines - 1) }
4533 doc.insert(from.line + 1, added$2)
4534 }
4535
4536 signalLater(doc, "change", doc, change)
4537 }
4538
4539 // Call f for all linked documents.
4540 function linkedDocs(doc, f, sharedHistOnly) {
4541 function propagate(doc, skip, sharedHist) {
4542 if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) {
4543 var rel = doc.linked[i]
4544 if (rel.doc == skip) { continue }
4545 var shared = sharedHist && rel.sharedHist
4546 if (sharedHistOnly && !shared) { continue }
4547 f(rel.doc, shared)
4548 propagate(rel.doc, doc, shared)
4549 } }
4550 }
4551 propagate(doc, null, true)
4552 }
4553
4554 // Attach a document to an editor.
4555 function attachDoc(cm, doc) {
4556 if (doc.cm) { throw new Error("This document is already in use.") }
4557 cm.doc = doc
4558 doc.cm = cm
4559 estimateLineHeights(cm)
4560 loadMode(cm)
4561 setDirectionClass(cm)
4562 if (!cm.options.lineWrapping) { findMaxLine(cm) }
4563 cm.options.mode = doc.modeOption
4564 regChange(cm)
4565 }
4566
4567 function setDirectionClass(cm) {
4568 ;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMir ror-rtl")
4569 }
4570
4571 function directionChanged(cm) {
4572 runInOp(cm, function () {
4573 setDirectionClass(cm)
4574 regChange(cm)
4575 })
4576 }
4577
4578 function History(startGen) {
4579 // Arrays of change events and selections. Doing something adds an
4580 // event to done and clears undo. Undoing moves events from done
4581 // to undone, redoing moves them in the other direction.
4582 this.done = []; this.undone = []
4583 this.undoDepth = Infinity
4584 // Used to track when changes can be merged into a single undo
4585 // event
4586 this.lastModTime = this.lastSelTime = 0
4587 this.lastOp = this.lastSelOp = null
4588 this.lastOrigin = this.lastSelOrigin = null
4589 // Used by the isClean() method
4590 this.generation = this.maxGeneration = startGen || 1
4591 }
4592
4593 // Create a history change event from an updateDoc-style change
4594 // object.
4595 function historyChangeFromChange(doc, change) {
4596 var histChange = {from: copyPos(change.from), to: changeEnd(change), text: get Between(doc, change.from, change.to)}
4597 attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1)
4598 linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, chan ge.from.line, change.to.line + 1); }, true)
4599 return histChange
4600 }
4601
4602 // Pop all selection events off the end of a history array. Stop at
4603 // a change event.
4604 function clearSelectionEvents(array) {
4605 while (array.length) {
4606 var last = lst(array)
4607 if (last.ranges) { array.pop() }
4608 else { break }
4609 }
4610 }
4611
4612 // Find the top change event in the history. Pop off selection
4613 // events that are in the way.
4614 function lastChangeEvent(hist, force) {
4615 if (force) {
4616 clearSelectionEvents(hist.done)
4617 return lst(hist.done)
4618 } else if (hist.done.length && !lst(hist.done).ranges) {
4619 return lst(hist.done)
4620 } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {
4621 hist.done.pop()
4622 return lst(hist.done)
4623 }
4624 }
4625
4626 // Register a change in the history. Merges changes that are within
4627 // a single operation, or are close together with an origin that
4628 // allows merging (starting with "+") into a single event.
4629 function addChangeToHistory(doc, change, selAfter, opId) {
4630 var hist = doc.history
4631 hist.undone.length = 0
4632 var time = +new Date, cur
4633 var last
4634
4635 if ((hist.lastOp == opId ||
4636 hist.lastOrigin == change.origin && change.origin &&
4637 ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - d oc.cm.options.historyEventDelay) ||
4638 change.origin.charAt(0) == "*")) &&
4639 (cur = lastChangeEvent(hist, hist.lastOp == opId))) {
4640 // Merge this change into the last event
4641 last = lst(cur.changes)
4642 if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {
4643 // Optimized case for simple insertion -- don't want to add
4644 // new changesets for every character typed
4645 last.to = changeEnd(change)
4646 } else {
4647 // Add new sub-event
4648 cur.changes.push(historyChangeFromChange(doc, change))
4649 }
4650 } else {
4651 // Can not be merged, start a new event.
4652 var before = lst(hist.done)
4653 if (!before || !before.ranges)
4654 { pushSelectionToHistory(doc.sel, hist.done) }
4655 cur = {changes: [historyChangeFromChange(doc, change)],
4656 generation: hist.generation}
4657 hist.done.push(cur)
4658 while (hist.done.length > hist.undoDepth) {
4659 hist.done.shift()
4660 if (!hist.done[0].ranges) { hist.done.shift() }
4661 }
4662 }
4663 hist.done.push(selAfter)
4664 hist.generation = ++hist.maxGeneration
4665 hist.lastModTime = hist.lastSelTime = time
4666 hist.lastOp = hist.lastSelOp = opId
4667 hist.lastOrigin = hist.lastSelOrigin = change.origin
4668
4669 if (!last) { signal(doc, "historyAdded") }
4670 }
4671
4672 function selectionEventCanBeMerged(doc, origin, prev, sel) {
4673 var ch = origin.charAt(0)
4674 return ch == "*" ||
4675 ch == "+" &&
4676 prev.ranges.length == sel.ranges.length &&
4677 prev.somethingSelected() == sel.somethingSelected() &&
4678 new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventD elay : 500)
4679 }
4680
4681 // Called whenever the selection changes, sets the new selection as
4682 // the pending selection in the history, and pushes the old pending
4683 // selection into the 'done' array when it was significantly
4684 // different (in number of selected ranges, emptiness, or time).
4685 function addSelectionToHistory(doc, sel, opId, options) {
4686 var hist = doc.history, origin = options && options.origin
4687
4688 // A new event is started when the previous origin does not match
4689 // the current, or the origins don't allow matching. Origins
4690 // starting with * are always merged, those starting with + are
4691 // merged when similar and close together in time.
4692 if (opId == hist.lastSelOp ||
4693 (origin && hist.lastSelOrigin == origin &&
4694 (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
4695 selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
4696 { hist.done[hist.done.length - 1] = sel }
4697 else
4698 { pushSelectionToHistory(sel, hist.done) }
4699
4700 hist.lastSelTime = +new Date
4701 hist.lastSelOrigin = origin
4702 hist.lastSelOp = opId
4703 if (options && options.clearRedo !== false)
4704 { clearSelectionEvents(hist.undone) }
4705 }
4706
4707 function pushSelectionToHistory(sel, dest) {
4708 var top = lst(dest)
4709 if (!(top && top.ranges && top.equals(sel)))
4710 { dest.push(sel) }
4711 }
4712
4713 // Used to store marked span information in the history.
4714 function attachLocalSpans(doc, change, from, to) {
4715 var existing = change["spans_" + doc.id], n = 0
4716 doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), functi on (line) {
4717 if (line.markedSpans)
4718 { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.mark edSpans }
4719 ++n
4720 })
4721 }
4722
4723 // When un/re-doing restores text containing marked spans, those
4724 // that have been explicitly cleared should not be restored.
4725 function removeClearedSpans(spans) {
4726 if (!spans) { return null }
4727 var out
4728 for (var i = 0; i < spans.length; ++i) {
4729 if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i) } }
4730 else if (out) { out.push(spans[i]) }
4731 }
4732 return !out ? spans : out.length ? out : null
4733 }
4734
4735 // Retrieve and filter the old marked spans stored in a change event.
4736 function getOldSpans(doc, change) {
4737 var found = change["spans_" + doc.id]
4738 if (!found) { return null }
4739 var nw = []
4740 for (var i = 0; i < change.text.length; ++i)
4741 { nw.push(removeClearedSpans(found[i])) }
4742 return nw
4743 }
4744
4745 // Used for un/re-doing changes from the history. Combines the
4746 // result of computing the existing spans with the set of spans that
4747 // existed in the history (so that deleting around a span and then
4748 // undoing brings back the span).
4749 function mergeOldSpans(doc, change) {
4750 var old = getOldSpans(doc, change)
4751 var stretched = stretchSpansOverChange(doc, change)
4752 if (!old) { return stretched }
4753 if (!stretched) { return old }
4754
4755 for (var i = 0; i < old.length; ++i) {
4756 var oldCur = old[i], stretchCur = stretched[i]
4757 if (oldCur && stretchCur) {
4758 spans: for (var j = 0; j < stretchCur.length; ++j) {
4759 var span = stretchCur[j]
4760 for (var k = 0; k < oldCur.length; ++k)
4761 { if (oldCur[k].marker == span.marker) { continue spans } }
4762 oldCur.push(span)
4763 }
4764 } else if (stretchCur) {
4765 old[i] = stretchCur
4766 }
4767 }
4768 return old
4769 }
4770
4771 // Used both to provide a JSON-safe object in .getHistory, and, when
4772 // detaching a document, to split the history in two
4773 function copyHistoryArray(events, newGroup, instantiateSel) {
4774 var copy = []
4775 for (var i = 0; i < events.length; ++i) {
4776 var event = events[i]
4777 if (event.ranges) {
4778 copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : even t)
4779 continue
4780 }
4781 var changes = event.changes, newChanges = []
4782 copy.push({changes: newChanges})
4783 for (var j = 0; j < changes.length; ++j) {
4784 var change = changes[j], m = (void 0)
4785 newChanges.push({from: change.from, to: change.to, text: change.text})
4786 if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+ )$/)) {
4787 if (indexOf(newGroup, Number(m[1])) > -1) {
4788 lst(newChanges)[prop] = change[prop]
4789 delete change[prop]
4790 }
4791 } } }
4792 }
4793 }
4794 return copy
4795 }
4796
4797 // The 'scroll' parameter given to many of these indicated whether
4798 // the new cursor position should be scrolled into view after
4799 // modifying the selection.
4800
4801 // If shift is held or the extend flag is set, extends a range to
4802 // include a given position (and optionally a second position).
4803 // Otherwise, simply returns the range between the given positions.
4804 // Used for cursor motion and such.
4805 function extendRange(doc, range, head, other) {
4806 if (doc.cm && doc.cm.display.shift || doc.extend) {
4807 var anchor = range.anchor
4808 if (other) {
4809 var posBefore = cmp(head, anchor) < 0
4810 if (posBefore != (cmp(other, anchor) < 0)) {
4811 anchor = head
4812 head = other
4813 } else if (posBefore != (cmp(head, other) < 0)) {
4814 head = other
4815 }
4816 }
4817 return new Range(anchor, head)
4818 } else {
4819 return new Range(other || head, head)
4820 }
4821 }
4822
4823 // Extend the primary selection range, discard the rest.
4824 function extendSelection(doc, head, other, options) {
4825 setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, oth er)], 0), options)
4826 }
4827
4828 // Extend all selections (pos is an array of selections with length
4829 // equal the number of selections)
4830 function extendSelections(doc, heads, options) {
4831 var out = []
4832 for (var i = 0; i < doc.sel.ranges.length; i++)
4833 { out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null) }
4834 var newSel = normalizeSelection(out, doc.sel.primIndex)
4835 setSelection(doc, newSel, options)
4836 }
4837
4838 // Updates a single range in the selection.
4839 function replaceOneSelection(doc, i, range, options) {
4840 var ranges = doc.sel.ranges.slice(0)
4841 ranges[i] = range
4842 setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options)
4843 }
4844
4845 // Reset the selection to a single range.
4846 function setSimpleSelection(doc, anchor, head, options) {
4847 setSelection(doc, simpleSelection(anchor, head), options)
4848 }
4849
4850 // Give beforeSelectionChange handlers a change to influence a
4851 // selection update.
4852 function filterSelectionChange(doc, sel, options) {
4853 var obj = {
4854 ranges: sel.ranges,
4855 update: function(ranges) {
4856 var this$1 = this;
4857
4858 this.ranges = []
4859 for (var i = 0; i < ranges.length; i++)
4860 { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
4861 clipPos(doc, ranges[i].head)) }
4862 },
4863 origin: options && options.origin
4864 }
4865 signal(doc, "beforeSelectionChange", doc, obj)
4866 if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj) }
4867 if (obj.ranges != sel.ranges) { return normalizeSelection(obj.ranges, obj.rang es.length - 1) }
4868 else { return sel }
4869 }
4870
4871 function setSelectionReplaceHistory(doc, sel, options) {
4872 var done = doc.history.done, last = lst(done)
4873 if (last && last.ranges) {
4874 done[done.length - 1] = sel
4875 setSelectionNoUndo(doc, sel, options)
4876 } else {
4877 setSelection(doc, sel, options)
4878 }
4879 }
4880
4881 // Set a new selection.
4882 function setSelection(doc, sel, options) {
4883 setSelectionNoUndo(doc, sel, options)
4884 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options)
4885 }
4886
4887 function setSelectionNoUndo(doc, sel, options) {
4888 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, " beforeSelectionChange"))
4889 { sel = filterSelectionChange(doc, sel, options) }
4890
4891 var bias = options && options.bias ||
4892 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1)
4893 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true))
4894
4895 if (!(options && options.scroll === false) && doc.cm)
4896 { ensureCursorVisible(doc.cm) }
4897 }
4898
4899 function setSelectionInner(doc, sel) {
4900 if (sel.equals(doc.sel)) { return }
4901
4902 doc.sel = sel
4903
4904 if (doc.cm) {
4905 doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true
4906 signalCursorActivity(doc.cm)
4907 }
4908 signalLater(doc, "cursorActivity", doc)
4909 }
4910
4911 // Verify that the selection does not partially select any atomic
4912 // marked ranges.
4913 function reCheckSelection(doc) {
4914 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_d ontScroll)
4915 }
4916
4917 // Return a selection that does not partially select any atomic
4918 // ranges.
4919 function skipAtomicInSelection(doc, sel, bias, mayClear) {
4920 var out
4921 for (var i = 0; i < sel.ranges.length; i++) {
4922 var range = sel.ranges[i]
4923 var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]
4924 var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayCl ear)
4925 var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear)
4926 if (out || newAnchor != range.anchor || newHead != range.head) {
4927 if (!out) { out = sel.ranges.slice(0, i) }
4928 out[i] = new Range(newAnchor, newHead)
4929 }
4930 }
4931 return out ? normalizeSelection(out, sel.primIndex) : sel
4932 }
4933
4934 function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
4935 var line = getLine(doc, pos.line)
4936 if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {
4937 var sp = line.markedSpans[i], m = sp.marker
4938 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos .ch)) &&
4939 (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch)) ) {
4940 if (mayClear) {
4941 signal(m, "beforeCursorEnter")
4942 if (m.explicitlyCleared) {
4943 if (!line.markedSpans) { break }
4944 else {--i; continue}
4945 }
4946 }
4947 if (!m.atomic) { continue }
4948
4949 if (oldPos) {
4950 var near = m.find(dir < 0 ? 1 : -1), diff = (void 0)
4951 if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft)
4952 { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null) }
4953 if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))
4954 { return skipAtomicInner(doc, near, pos, dir, mayClear) }
4955 }
4956
4957 var far = m.find(dir < 0 ? -1 : 1)
4958 if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight)
4959 { far = movePos(doc, far, dir, far.line == pos.line ? line : null) }
4960 return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null
4961 }
4962 } }
4963 return pos
4964 }
4965
4966 // Ensure a given position is not inside an atomic range.
4967 function skipAtomic(doc, pos, oldPos, bias, mayClear) {
4968 var dir = bias || 1
4969 var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
4970 (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
4971 skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
4972 (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true))
4973 if (!found) {
4974 doc.cantEdit = true
4975 return Pos(doc.first, 0)
4976 }
4977 return found
4978 }
4979
4980 function movePos(doc, pos, dir, line) {
4981 if (dir < 0 && pos.ch == 0) {
4982 if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) }
4983 else { return null }
4984 } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {
4985 if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) }
4986 else { return null }
4987 } else {
4988 return new Pos(pos.line, pos.ch + dir)
4989 }
4990 }
4991
4992 function selectAll(cm) {
4993 cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll)
4994 }
4995
4996 // UPDATING
4997
4998 // Allow "beforeChange" event handlers to influence a change
4999 function filterChange(doc, change, update) {
5000 var obj = {
5001 canceled: false,
5002 from: change.from,
5003 to: change.to,
5004 text: change.text,
5005 origin: change.origin,
5006 cancel: function () { return obj.canceled = true; }
5007 }
5008 if (update) { obj.update = function (from, to, text, origin) {
5009 if (from) { obj.from = clipPos(doc, from) }
5010 if (to) { obj.to = clipPos(doc, to) }
5011 if (text) { obj.text = text }
5012 if (origin !== undefined) { obj.origin = origin }
5013 } }
5014 signal(doc, "beforeChange", doc, obj)
5015 if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj) }
5016
5017 if (obj.canceled) { return null }
5018 return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}
5019 }
5020
5021 // Apply a change to a document, and add it to the document's
5022 // history, and propagating it to all linked documents.
5023 function makeChange(doc, change, ignoreReadOnly) {
5024 if (doc.cm) {
5025 if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignor eReadOnly) }
5026 if (doc.cm.state.suppressEdits) { return }
5027 }
5028
5029 if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeCha nge")) {
5030 change = filterChange(doc, change, true)
5031 if (!change) { return }
5032 }
5033
5034 // Possibly split or suppress the update based on the presence
5035 // of read-only spans in its range.
5036 var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, c hange.from, change.to)
5037 if (split) {
5038 for (var i = split.length - 1; i >= 0; --i)
5039 { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [" "] : change.text}) }
5040 } else {
5041 makeChangeInner(doc, change)
5042 }
5043 }
5044
5045 function makeChangeInner(doc, change) {
5046 if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change .to) == 0) { return }
5047 var selAfter = computeSelAfterChange(doc, change)
5048 addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN)
5049
5050 makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change) )
5051 var rebased = []
5052
5053 linkedDocs(doc, function (doc, sharedHist) {
5054 if (!sharedHist && indexOf(rebased, doc.history) == -1) {
5055 rebaseHist(doc.history, change)
5056 rebased.push(doc.history)
5057 }
5058 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change))
5059 })
5060 }
5061
5062 // Revert a change stored in a document's history.
5063 function makeChangeFromHistory(doc, type, allowSelectionOnly) {
5064 if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) { return }
5065
5066 var hist = doc.history, event, selAfter = doc.sel
5067 var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done
5068
5069 // Verify that there is a useable event (so that ctrl-z won't
5070 // needlessly clear selection events)
5071 var i = 0
5072 for (; i < source.length; i++) {
5073 event = source[i]
5074 if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ran ges)
5075 { break }
5076 }
5077 if (i == source.length) { return }
5078 hist.lastOrigin = hist.lastSelOrigin = null
5079
5080 for (;;) {
5081 event = source.pop()
5082 if (event.ranges) {
5083 pushSelectionToHistory(event, dest)
5084 if (allowSelectionOnly && !event.equals(doc.sel)) {
5085 setSelection(doc, event, {clearRedo: false})
5086 return
5087 }
5088 selAfter = event
5089 }
5090 else { break }
5091 }
5092
5093 // Build up a reverse change object to add to the opposite history
5094 // stack (redo when undoing, and vice versa).
5095 var antiChanges = []
5096 pushSelectionToHistory(selAfter, dest)
5097 dest.push({changes: antiChanges, generation: hist.generation})
5098 hist.generation = event.generation || ++hist.maxGeneration
5099
5100 var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, " beforeChange")
5101
5102 var loop = function ( i ) {
5103 var change = event.changes[i]
5104 change.origin = type
5105 if (filter && !filterChange(doc, change, false)) {
5106 source.length = 0
5107 return {}
5108 }
5109
5110 antiChanges.push(historyChangeFromChange(doc, change))
5111
5112 var after = i ? computeSelAfterChange(doc, change) : lst(source)
5113 makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change))
5114 if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd( change)}) }
5115 var rebased = []
5116
5117 // Propagate to the linked documents
5118 linkedDocs(doc, function (doc, sharedHist) {
5119 if (!sharedHist && indexOf(rebased, doc.history) == -1) {
5120 rebaseHist(doc.history, change)
5121 rebased.push(doc.history)
5122 }
5123 makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change))
5124 })
5125 };
5126
5127 for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) {
5128 var returned = loop( i$1 );
5129
5130 if ( returned ) return returned.v;
5131 }
5132 }
5133
5134 // Sub-views need their line numbers shifted when text is added
5135 // above or below them in the parent document.
5136 function shiftDoc(doc, distance) {
5137 if (distance == 0) { return }
5138 doc.first += distance
5139 doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Rang e(
5140 Pos(range.anchor.line + distance, range.anchor.ch),
5141 Pos(range.head.line + distance, range.head.ch)
5142 ); }), doc.sel.primIndex)
5143 if (doc.cm) {
5144 regChange(doc.cm, doc.first, doc.first - distance, distance)
5145 for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)
5146 { regLineChange(doc.cm, l, "gutter") }
5147 }
5148 }
5149
5150 // More lower-level change function, handling only a single document
5151 // (not linked ones).
5152 function makeChangeSingleDoc(doc, change, selAfter, spans) {
5153 if (doc.cm && !doc.cm.curOp)
5154 { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans ) }
5155
5156 if (change.to.line < doc.first) {
5157 shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line))
5158 return
5159 }
5160 if (change.from.line > doc.lastLine()) { return }
5161
5162 // Clip the change to the size of this doc
5163 if (change.from.line < doc.first) {
5164 var shift = change.text.length - 1 - (doc.first - change.from.line)
5165 shiftDoc(doc, shift)
5166 change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to .ch),
5167 text: [lst(change.text)], origin: change.origin}
5168 }
5169 var last = doc.lastLine()
5170 if (change.to.line > last) {
5171 change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
5172 text: [change.text[0]], origin: change.origin}
5173 }
5174
5175 change.removed = getBetween(doc, change.from, change.to)
5176
5177 if (!selAfter) { selAfter = computeSelAfterChange(doc, change) }
5178 if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans) }
5179 else { updateDoc(doc, change, spans) }
5180 setSelectionNoUndo(doc, selAfter, sel_dontScroll)
5181 }
5182
5183 // Handle the interaction of a change to a document with the editor
5184 // that this document is part of.
5185 function makeChangeSingleDocInEditor(cm, change, spans) {
5186 var doc = cm.doc, display = cm.display, from = change.from, to = change.to
5187
5188 var recomputeMaxLength = false, checkWidthStart = from.line
5189 if (!cm.options.lineWrapping) {
5190 checkWidthStart = lineNo(visualLine(getLine(doc, from.line)))
5191 doc.iter(checkWidthStart, to.line + 1, function (line) {
5192 if (line == display.maxLine) {
5193 recomputeMaxLength = true
5194 return true
5195 }
5196 })
5197 }
5198
5199 if (doc.sel.contains(change.from, change.to) > -1)
5200 { signalCursorActivity(cm) }
5201
5202 updateDoc(doc, change, spans, estimateHeight(cm))
5203
5204 if (!cm.options.lineWrapping) {
5205 doc.iter(checkWidthStart, from.line + change.text.length, function (line) {
5206 var len = lineLength(line)
5207 if (len > display.maxLineLength) {
5208 display.maxLine = line
5209 display.maxLineLength = len
5210 display.maxLineChanged = true
5211 recomputeMaxLength = false
5212 }
5213 })
5214 if (recomputeMaxLength) { cm.curOp.updateMaxLine = true }
5215 }
5216
5217 // Adjust frontier, schedule worker
5218 doc.frontier = Math.min(doc.frontier, from.line)
5219 startWorker(cm, 400)
5220
5221 var lendiff = change.text.length - (to.line - from.line) - 1
5222 // Remember that these lines changed, for updating the display
5223 if (change.full)
5224 { regChange(cm) }
5225 else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate (cm.doc, change))
5226 { regLineChange(cm, from.line, "text") }
5227 else
5228 { regChange(cm, from.line, to.line + 1, lendiff) }
5229
5230 var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change")
5231 if (changeHandler || changesHandler) {
5232 var obj = {
5233 from: from, to: to,
5234 text: change.text,
5235 removed: change.removed,
5236 origin: change.origin
5237 }
5238 if (changeHandler) { signalLater(cm, "change", cm, obj) }
5239 if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).pu sh(obj) }
5240 }
5241 cm.display.selForContextMenu = null
5242 }
5243
5244 function replaceRange(doc, code, from, to, origin) {
5245 if (!to) { to = from }
5246 if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp }
5247 if (typeof code == "string") { code = doc.splitLines(code) }
5248 makeChange(doc, {from: from, to: to, text: code, origin: origin})
5249 }
5250
5251 // Rebasing/resetting history to deal with externally-sourced changes
5252
5253 function rebaseHistSelSingle(pos, from, to, diff) {
5254 if (to < pos.line) {
5255 pos.line += diff
5256 } else if (from < pos.line) {
5257 pos.line = from
5258 pos.ch = 0
5259 }
5260 }
5261
5262 // Tries to rebase an array of history events given a change in the
5263 // document. If the change touches the same lines as the event, the
5264 // event, and everything 'behind' it, is discarded. If the change is
5265 // before the event, the event's positions are updated. Uses a
5266 // copy-on-write scheme for the positions, to avoid having to
5267 // reallocate them all on every rebase, but also avoid problems with
5268 // shared position objects being unsafely updated.
5269 function rebaseHistArray(array, from, to, diff) {
5270 for (var i = 0; i < array.length; ++i) {
5271 var sub = array[i], ok = true
5272 if (sub.ranges) {
5273 if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true }
5274 for (var j = 0; j < sub.ranges.length; j++) {
5275 rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff)
5276 rebaseHistSelSingle(sub.ranges[j].head, from, to, diff)
5277 }
5278 continue
5279 }
5280 for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) {
5281 var cur = sub.changes[j$1]
5282 if (to < cur.from.line) {
5283 cur.from = Pos(cur.from.line + diff, cur.from.ch)
5284 cur.to = Pos(cur.to.line + diff, cur.to.ch)
5285 } else if (from <= cur.to.line) {
5286 ok = false
5287 break
5288 }
5289 }
5290 if (!ok) {
5291 array.splice(0, i + 1)
5292 i = 0
5293 }
5294 }
5295 }
5296
5297 function rebaseHist(hist, change) {
5298 var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1
5299 rebaseHistArray(hist.done, from, to, diff)
5300 rebaseHistArray(hist.undone, from, to, diff)
5301 }
5302
5303 // Utility for applying a change to a line by handle or number,
5304 // returning the number and optionally registering the line as
5305 // changed.
5306 function changeLine(doc, handle, changeType, op) {
5307 var no = handle, line = handle
5308 if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)) }
5309 else { no = lineNo(handle) }
5310 if (no == null) { return null }
5311 if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType) }
5312 return line
5313 }
5314
5315 // The document is represented as a BTree consisting of leaves, with
5316 // chunk of lines in them, and branches, with up to ten leaves or
5317 // other branch nodes below them. The top node is always a branch
5318 // node, and is the document object itself (meaning it has
5319 // additional methods and properties).
5320 //
5321 // All nodes have parent links. The tree is used both to go from
5322 // line numbers to line objects, and to go from objects to numbers.
5323 // It also indexes by height, and is used to convert between height
5324 // and line object, and to find the total height of the document.
5325 //
5326 // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
5327
5328 var LeafChunk = function LeafChunk(lines) {
5329 var this$1 = this;
5330
5331 this.lines = lines
5332 this.parent = null
5333 var height = 0
5334 for (var i = 0; i < lines.length; ++i) {
5335 lines[i].parent = this$1
5336 height += lines[i].height
5337 }
5338 this.height = height
5339 };
5340
5341 LeafChunk.prototype.chunkSize = function chunkSize () { return this.lines.length };
5342
5343 // Remove the n lines at offset 'at'.
5344 LeafChunk.prototype.removeInner = function removeInner (at, n) {
5345 var this$1 = this;
5346
5347 for (var i = at, e = at + n; i < e; ++i) {
5348 var line = this$1.lines[i]
5349 this$1.height -= line.height
5350 cleanUpLine(line)
5351 signalLater(line, "delete")
5352 }
5353 this.lines.splice(at, n)
5354 };
5355
5356 // Helper used to collapse a small branch into a single leaf.
5357 LeafChunk.prototype.collapse = function collapse (lines) {
5358 lines.push.apply(lines, this.lines)
5359 };
5360
5361 // Insert the given array of lines at offset 'at', count them as
5362 // having the given height.
5363 LeafChunk.prototype.insertInner = function insertInner (at, lines, height) {
5364 var this$1 = this;
5365
5366 this.height += height
5367 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at) )
5368 for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1 }
5369 };
5370
5371 // Used to iterate over a part of the tree.
5372 LeafChunk.prototype.iterN = function iterN (at, n, op) {
5373 var this$1 = this;
5374
5375 for (var e = at + n; at < e; ++at)
5376 { if (op(this$1.lines[at])) { return true } }
5377 };
5378
5379 var BranchChunk = function BranchChunk(children) {
5380 var this$1 = this;
5381
5382 this.children = children
5383 var size = 0, height = 0
5384 for (var i = 0; i < children.length; ++i) {
5385 var ch = children[i]
5386 size += ch.chunkSize(); height += ch.height
5387 ch.parent = this$1
5388 }
5389 this.size = size
5390 this.height = height
5391 this.parent = null
5392 };
5393
5394 BranchChunk.prototype.chunkSize = function chunkSize () { return this.size };
5395
5396 BranchChunk.prototype.removeInner = function removeInner (at, n) {
5397 var this$1 = this;
5398
5399 this.size -= n
5400 for (var i = 0; i < this.children.length; ++i) {
5401 var child = this$1.children[i], sz = child.chunkSize()
5402 if (at < sz) {
5403 var rm = Math.min(n, sz - at), oldHeight = child.height
5404 child.removeInner(at, rm)
5405 this$1.height -= oldHeight - child.height
5406 if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null }
5407 if ((n -= rm) == 0) { break }
5408 at = 0
5409 } else { at -= sz }
5410 }
5411 // If the result is smaller than 25 lines, ensure that it is a
5412 // single leaf node.
5413 if (this.size - n < 25 &&
5414 (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {
5415 var lines = []
5416 this.collapse(lines)
5417 this.children = [new LeafChunk(lines)]
5418 this.children[0].parent = this
5419 }
5420 };
5421
5422 BranchChunk.prototype.collapse = function collapse (lines) {
5423 var this$1 = this;
5424
5425 for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(l ines) }
5426 };
5427
5428 BranchChunk.prototype.insertInner = function insertInner (at, lines, height) {
5429 var this$1 = this;
5430
5431 this.size += lines.length
5432 this.height += height
5433 for (var i = 0; i < this.children.length; ++i) {
5434 var child = this$1.children[i], sz = child.chunkSize()
5435 if (at <= sz) {
5436 child.insertInner(at, lines, height)
5437 if (child.lines && child.lines.length > 50) {
5438 // To avoid memory thrashing when child.lines is huge (e.g. first view o f a large file), it's never spliced.
5439 // Instead, small slices are taken. They're taken in order because seque ntial memory accesses are fastest.
5440 var remaining = child.lines.length % 25 + 25
5441 for (var pos = remaining; pos < child.lines.length;) {
5442 var leaf = new LeafChunk(child.lines.slice(pos, pos += 25))
5443 child.height -= leaf.height
5444 this$1.children.splice(++i, 0, leaf)
5445 leaf.parent = this$1
5446 }
5447 child.lines = child.lines.slice(0, remaining)
5448 this$1.maybeSpill()
5449 }
5450 break
5451 }
5452 at -= sz
5453 }
5454 };
5455
5456 // When a node has grown, check whether it should be split.
5457 BranchChunk.prototype.maybeSpill = function maybeSpill () {
5458 if (this.children.length <= 10) { return }
5459 var me = this
5460 do {
5461 var spilled = me.children.splice(me.children.length - 5, 5)
5462 var sibling = new BranchChunk(spilled)
5463 if (!me.parent) { // Become the parent node
5464 var copy = new BranchChunk(me.children)
5465 copy.parent = me
5466 me.children = [copy, sibling]
5467 me = copy
5468 } else {
5469 me.size -= sibling.size
5470 me.height -= sibling.height
5471 var myIndex = indexOf(me.parent.children, me)
5472 me.parent.children.splice(myIndex + 1, 0, sibling)
5473 }
5474 sibling.parent = me.parent
5475 } while (me.children.length > 10)
5476 me.parent.maybeSpill()
5477 };
5478
5479 BranchChunk.prototype.iterN = function iterN (at, n, op) {
5480 var this$1 = this;
5481
5482 for (var i = 0; i < this.children.length; ++i) {
5483 var child = this$1.children[i], sz = child.chunkSize()
5484 if (at < sz) {
5485 var used = Math.min(n, sz - at)
5486 if (child.iterN(at, used, op)) { return true }
5487 if ((n -= used) == 0) { break }
5488 at = 0
5489 } else { at -= sz }
5490 }
5491 };
5492
5493 // Line widgets are block elements displayed above or below a line.
5494
5495 var LineWidget = function LineWidget(doc, node, options) {
5496 var this$1 = this;
5497
5498 if (options) { for (var opt in options) { if (options.hasOwnProperty(opt))
5499 { this$1[opt] = options[opt] } } }
5500 this.doc = doc
5501 this.node = node
5502 };
5503
5504 LineWidget.prototype.clear = function clear () {
5505 var this$1 = this;
5506
5507 var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(li ne)
5508 if (no == null || !ws) { return }
5509 for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1) } }
5510 if (!ws.length) { line.widgets = null }
5511 var height = widgetHeight(this)
5512 updateLineHeight(line, Math.max(0, line.height - height))
5513 if (cm) {
5514 runInOp(cm, function () {
5515 adjustScrollWhenAboveVisible(cm, line, -height)
5516 regLineChange(cm, no, "widget")
5517 })
5518 signalLater(cm, "lineWidgetCleared", cm, this, no)
5519 }
5520 };
5521
5522 LineWidget.prototype.changed = function changed () {
5523 var this$1 = this;
5524
5525 var oldH = this.height, cm = this.doc.cm, line = this.line
5526 this.height = null
5527 var diff = widgetHeight(this) - oldH
5528 if (!diff) { return }
5529 updateLineHeight(line, line.height + diff)
5530 if (cm) {
5531 runInOp(cm, function () {
5532 cm.curOp.forceUpdate = true
5533 adjustScrollWhenAboveVisible(cm, line, diff)
5534 signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line))
5535 })
5536 }
5537 };
5538 eventMixin(LineWidget)
5539
5540 function adjustScrollWhenAboveVisible(cm, line, diff) {
5541 if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop ))
5542 { addToScrollPos(cm, null, diff) }
5543 }
5544
5545 function addLineWidget(doc, handle, node, options) {
5546 var widget = new LineWidget(doc, node, options)
5547 var cm = doc.cm
5548 if (cm && widget.noHScroll) { cm.display.alignWidgets = true }
5549 changeLine(doc, handle, "widget", function (line) {
5550 var widgets = line.widgets || (line.widgets = [])
5551 if (widget.insertAt == null) { widgets.push(widget) }
5552 else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insert At)), 0, widget) }
5553 widget.line = line
5554 if (cm && !lineIsHidden(doc, line)) {
5555 var aboveVisible = heightAtLine(line) < doc.scrollTop
5556 updateLineHeight(line, line.height + widgetHeight(widget))
5557 if (aboveVisible) { addToScrollPos(cm, null, widget.height) }
5558 cm.curOp.forceUpdate = true
5559 }
5560 return true
5561 })
5562 signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? han dle : lineNo(handle))
5563 return widget
5564 }
5565
5566 // TEXTMARKERS
5567
5568 // Created with markText and setBookmark methods. A TextMarker is a
5569 // handle that can be used to clear or find a marked position in the
5570 // document. Line objects hold arrays (markedSpans) containing
5571 // {from, to, marker} object pointing to such marker objects, and
5572 // indicating that such a marker is present on that line. Multiple
5573 // lines may point to the same marker when it spans across lines.
5574 // The spans will have null for their from/to properties when the
5575 // marker continues beyond the start/end of the line. Markers have
5576 // links back to the lines they currently touch.
5577
5578 // Collapsed markers have unique ids, in order to be able to order
5579 // them, which is needed for uniquely determining an outer marker
5580 // when they overlap (they may nest, but not partially overlap).
5581 var nextMarkerId = 0
5582
5583 var TextMarker = function TextMarker(doc, type) {
5584 this.lines = []
5585 this.type = type
5586 this.doc = doc
5587 this.id = ++nextMarkerId
5588 };
5589
5590 // Clear the marker.
5591 TextMarker.prototype.clear = function clear () {
5592 var this$1 = this;
5593
5594 if (this.explicitlyCleared) { return }
5595 var cm = this.doc.cm, withOp = cm && !cm.curOp
5596 if (withOp) { startOperation(cm) }
5597 if (hasHandler(this, "clear")) {
5598 var found = this.find()
5599 if (found) { signalLater(this, "clear", found.from, found.to) }
5600 }
5601 var min = null, max = null
5602 for (var i = 0; i < this.lines.length; ++i) {
5603 var line = this$1.lines[i]
5604 var span = getMarkedSpanFor(line.markedSpans, this$1)
5605 if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text") }
5606 else if (cm) {
5607 if (span.to != null) { max = lineNo(line) }
5608 if (span.from != null) { min = lineNo(line) }
5609 }
5610 line.markedSpans = removeMarkedSpan(line.markedSpans, span)
5611 if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm)
5612 { updateLineHeight(line, textHeight(cm.display)) }
5613 }
5614 if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) {
5615 var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual)
5616 if (len > cm.display.maxLineLength) {
5617 cm.display.maxLine = visual
5618 cm.display.maxLineLength = len
5619 cm.display.maxLineChanged = true
5620 }
5621 } }
5622
5623 if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1) }
5624 this.lines.length = 0
5625 this.explicitlyCleared = true
5626 if (this.atomic && this.doc.cantEdit) {
5627 this.doc.cantEdit = false
5628 if (cm) { reCheckSelection(cm.doc) }
5629 }
5630 if (cm) { signalLater(cm, "markerCleared", cm, this, min, max) }
5631 if (withOp) { endOperation(cm) }
5632 if (this.parent) { this.parent.clear() }
5633 };
5634
5635 // Find the position of the marker in the document. Returns a {from,
5636 // to} object by default. Side can be passed to get a specific side
5637 // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
5638 // Pos objects returned contain a line object, rather than a line
5639 // number (used to prevent looking up the same line twice).
5640 TextMarker.prototype.find = function find (side, lineObj) {
5641 var this$1 = this;
5642
5643 if (side == null && this.type == "bookmark") { side = 1 }
5644 var from, to
5645 for (var i = 0; i < this.lines.length; ++i) {
5646 var line = this$1.lines[i]
5647 var span = getMarkedSpanFor(line.markedSpans, this$1)
5648 if (span.from != null) {
5649 from = Pos(lineObj ? line : lineNo(line), span.from)
5650 if (side == -1) { return from }
5651 }
5652 if (span.to != null) {
5653 to = Pos(lineObj ? line : lineNo(line), span.to)
5654 if (side == 1) { return to }
5655 }
5656 }
5657 return from && {from: from, to: to}
5658 };
5659
5660 // Signals that the marker's widget changed, and surrounding layout
5661 // should be recomputed.
5662 TextMarker.prototype.changed = function changed () {
5663 var this$1 = this;
5664
5665 var pos = this.find(-1, true), widget = this, cm = this.doc.cm
5666 if (!pos || !cm) { return }
5667 runInOp(cm, function () {
5668 var line = pos.line, lineN = lineNo(pos.line)
5669 var view = findViewForLine(cm, lineN)
5670 if (view) {
5671 clearLineMeasurementCacheFor(view)
5672 cm.curOp.selectionChanged = cm.curOp.forceUpdate = true
5673 }
5674 cm.curOp.updateMaxLine = true
5675 if (!lineIsHidden(widget.doc, line) && widget.height != null) {
5676 var oldHeight = widget.height
5677 widget.height = null
5678 var dHeight = widgetHeight(widget) - oldHeight
5679 if (dHeight)
5680 { updateLineHeight(line, line.height + dHeight) }
5681 }
5682 signalLater(cm, "markerChanged", cm, this$1)
5683 })
5684 };
5685
5686 TextMarker.prototype.attachLine = function attachLine (line) {
5687 if (!this.lines.length && this.doc.cm) {
5688 var op = this.doc.cm.curOp
5689 if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
5690 { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) }
5691 }
5692 this.lines.push(line)
5693 };
5694
5695 TextMarker.prototype.detachLine = function detachLine (line) {
5696 this.lines.splice(indexOf(this.lines, line), 1)
5697 if (!this.lines.length && this.doc.cm) {
5698 var op = this.doc.cm.curOp
5699 ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this)
5700 }
5701 };
5702 eventMixin(TextMarker)
5703
5704 // Create a marker, wire it up to the right lines, and
5705 function markText(doc, from, to, options, type) {
5706 // Shared markers (across linked documents) are handled separately
5707 // (markTextShared will call out to this again, once per
5708 // document).
5709 if (options && options.shared) { return markTextShared(doc, from, to, options, type) }
5710 // Ensure we are in an operation.
5711 if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, t o, options, type) }
5712
5713 var marker = new TextMarker(doc, type), diff = cmp(from, to)
5714 if (options) { copyObj(options, marker, false) }
5715 // Don't connect empty markers unless clearWhenEmpty is false
5716 if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
5717 { return marker }
5718 if (marker.replacedWith) {
5719 // Showing up as a widget implies collapsed (widget replaces text)
5720 marker.collapsed = true
5721 marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget")
5722 if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore- events", "true") }
5723 if (options.insertLeft) { marker.widgetNode.insertLeft = true }
5724 }
5725 if (marker.collapsed) {
5726 if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
5727 from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to , marker))
5728 { throw new Error("Inserting collapsed marker partially overlapping an exi sting one") }
5729 seeCollapsedSpans()
5730 }
5731
5732 if (marker.addToHistory)
5733 { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN) }
5734
5735 var curLine = from.line, cm = doc.cm, updateMaxLine
5736 doc.iter(curLine, to.line + 1, function (line) {
5737 if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) = = cm.display.maxLine)
5738 { updateMaxLine = true }
5739 if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0) }
5740 addMarkedSpan(line, new MarkedSpan(marker,
5741 curLine == from.line ? from.ch : null,
5742 curLine == to.line ? to.ch : null))
5743 ++curLine
5744 })
5745 // lineIsHidden depends on the presence of the spans, so needs a second pass
5746 if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) {
5747 if (lineIsHidden(doc, line)) { updateLineHeight(line, 0) }
5748 }) }
5749
5750 if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { retur n marker.clear(); }) }
5751
5752 if (marker.readOnly) {
5753 seeReadOnlySpans()
5754 if (doc.history.done.length || doc.history.undone.length)
5755 { doc.clearHistory() }
5756 }
5757 if (marker.collapsed) {
5758 marker.id = ++nextMarkerId
5759 marker.atomic = true
5760 }
5761 if (cm) {
5762 // Sync editor state
5763 if (updateMaxLine) { cm.curOp.updateMaxLine = true }
5764 if (marker.collapsed)
5765 { regChange(cm, from.line, to.line + 1) }
5766 else if (marker.className || marker.title || marker.startStyle || marker.end Style || marker.css)
5767 { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text" ) } }
5768 if (marker.atomic) { reCheckSelection(cm.doc) }
5769 signalLater(cm, "markerAdded", cm, marker)
5770 }
5771 return marker
5772 }
5773
5774 // SHARED TEXTMARKERS
5775
5776 // A shared marker spans multiple linked documents. It is
5777 // implemented as a meta-marker-object controlling multiple normal
5778 // markers.
5779 var SharedTextMarker = function SharedTextMarker(markers, primary) {
5780 var this$1 = this;
5781
5782 this.markers = markers
5783 this.primary = primary
5784 for (var i = 0; i < markers.length; ++i)
5785 { markers[i].parent = this$1 }
5786 };
5787
5788 SharedTextMarker.prototype.clear = function clear () {
5789 var this$1 = this;
5790
5791 if (this.explicitlyCleared) { return }
5792 this.explicitlyCleared = true
5793 for (var i = 0; i < this.markers.length; ++i)
5794 { this$1.markers[i].clear() }
5795 signalLater(this, "clear")
5796 };
5797
5798 SharedTextMarker.prototype.find = function find (side, lineObj) {
5799 return this.primary.find(side, lineObj)
5800 };
5801 eventMixin(SharedTextMarker)
5802
5803 function markTextShared(doc, from, to, options, type) {
5804 options = copyObj(options)
5805 options.shared = false
5806 var markers = [markText(doc, from, to, options, type)], primary = markers[0]
5807 var widget = options.widgetNode
5808 linkedDocs(doc, function (doc) {
5809 if (widget) { options.widgetNode = widget.cloneNode(true) }
5810 markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, ty pe))
5811 for (var i = 0; i < doc.linked.length; ++i)
5812 { if (doc.linked[i].isParent) { return } }
5813 primary = lst(markers)
5814 })
5815 return new SharedTextMarker(markers, primary)
5816 }
5817
5818 function findSharedMarkers(doc) {
5819 return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), func tion (m) { return m.parent; })
5820 }
5821
5822 function copySharedMarkers(doc, markers) {
5823 for (var i = 0; i < markers.length; i++) {
5824 var marker = markers[i], pos = marker.find()
5825 var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to)
5826 if (cmp(mFrom, mTo)) {
5827 var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.typ e)
5828 marker.markers.push(subMark)
5829 subMark.parent = marker
5830 }
5831 }
5832 }
5833
5834 function detachSharedMarkers(markers) {
5835 var loop = function ( i ) {
5836 var marker = markers[i], linked = [marker.primary.doc]
5837 linkedDocs(marker.primary.doc, function (d) { return linked.push(d); })
5838 for (var j = 0; j < marker.markers.length; j++) {
5839 var subMarker = marker.markers[j]
5840 if (indexOf(linked, subMarker.doc) == -1) {
5841 subMarker.parent = null
5842 marker.markers.splice(j--, 1)
5843 }
5844 }
5845 };
5846
5847 for (var i = 0; i < markers.length; i++) loop( i );
5848 }
5849
5850 var nextDocId = 0
5851 var Doc = function(text, mode, firstLine, lineSep, direction) {
5852 if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, d irection) }
5853 if (firstLine == null) { firstLine = 0 }
5854
5855 BranchChunk.call(this, [new LeafChunk([new Line("", null)])])
5856 this.first = firstLine
5857 this.scrollTop = this.scrollLeft = 0
5858 this.cantEdit = false
5859 this.cleanGeneration = 1
5860 this.frontier = firstLine
5861 var start = Pos(firstLine, 0)
5862 this.sel = simpleSelection(start)
5863 this.history = new History(null)
5864 this.id = ++nextDocId
5865 this.modeOption = mode
5866 this.lineSep = lineSep
5867 this.direction = (direction == "rtl") ? "rtl" : "ltr"
5868 this.extend = false
5869
5870 if (typeof text == "string") { text = this.splitLines(text) }
5871 updateDoc(this, {from: start, to: start, text: text})
5872 setSelection(this, simpleSelection(start), sel_dontScroll)
5873 }
5874
5875 Doc.prototype = createObj(BranchChunk.prototype, {
5876 constructor: Doc,
5877 // Iterate over the document. Supports two forms -- with only one
5878 // argument, it calls that for each line in the document. With
5879 // three, it iterates over the range given by the first two (with
5880 // the second being non-inclusive).
5881 iter: function(from, to, op) {
5882 if (op) { this.iterN(from - this.first, to - from, op) }
5883 else { this.iterN(this.first, this.first + this.size, from) }
5884 },
5885
5886 // Non-public interface for adding and removing lines.
5887 insert: function(at, lines) {
5888 var height = 0
5889 for (var i = 0; i < lines.length; ++i) { height += lines[i].height }
5890 this.insertInner(at - this.first, lines, height)
5891 },
5892 remove: function(at, n) { this.removeInner(at - this.first, n) },
5893
5894 // From here, the methods are part of the public interface. Most
5895 // are also available from CodeMirror (editor) instances.
5896
5897 getValue: function(lineSep) {
5898 var lines = getLines(this, this.first, this.first + this.size)
5899 if (lineSep === false) { return lines }
5900 return lines.join(lineSep || this.lineSeparator())
5901 },
5902 setValue: docMethodOp(function(code) {
5903 var top = Pos(this.first, 0), last = this.first + this.size - 1
5904 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
5905 text: this.splitLines(code), origin: "setValue", full: tru e}, true)
5906 setSelection(this, simpleSelection(top))
5907 }),
5908 replaceRange: function(code, from, to, origin) {
5909 from = clipPos(this, from)
5910 to = to ? clipPos(this, to) : from
5911 replaceRange(this, code, from, to, origin)
5912 },
5913 getRange: function(from, to, lineSep) {
5914 var lines = getBetween(this, clipPos(this, from), clipPos(this, to))
5915 if (lineSep === false) { return lines }
5916 return lines.join(lineSep || this.lineSeparator())
5917 },
5918
5919 getLine: function(line) {var l = this.getLineHandle(line); return l && l.text} ,
5920
5921 getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }},
5922 getLineNumber: function(line) {return lineNo(line)},
5923
5924 getLineHandleVisualStart: function(line) {
5925 if (typeof line == "number") { line = getLine(this, line) }
5926 return visualLine(line)
5927 },
5928
5929 lineCount: function() {return this.size},
5930 firstLine: function() {return this.first},
5931 lastLine: function() {return this.first + this.size - 1},
5932
5933 clipPos: function(pos) {return clipPos(this, pos)},
5934
5935 getCursor: function(start) {
5936 var range = this.sel.primary(), pos
5937 if (start == null || start == "head") { pos = range.head }
5938 else if (start == "anchor") { pos = range.anchor }
5939 else if (start == "end" || start == "to" || start === false) { pos = range.t o() }
5940 else { pos = range.from() }
5941 return pos
5942 },
5943 listSelections: function() { return this.sel.ranges },
5944 somethingSelected: function() {return this.sel.somethingSelected()},
5945
5946 setCursor: docMethodOp(function(line, ch, options) {
5947 setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, c h || 0) : line), null, options)
5948 }),
5949 setSelection: docMethodOp(function(anchor, head, options) {
5950 setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor ), options)
5951 }),
5952 extendSelection: docMethodOp(function(head, other, options) {
5953 extendSelection(this, clipPos(this, head), other && clipPos(this, other), op tions)
5954 }),
5955 extendSelections: docMethodOp(function(heads, options) {
5956 extendSelections(this, clipPosArray(this, heads), options)
5957 }),
5958 extendSelectionsBy: docMethodOp(function(f, options) {
5959 var heads = map(this.sel.ranges, f)
5960 extendSelections(this, clipPosArray(this, heads), options)
5961 }),
5962 setSelections: docMethodOp(function(ranges, primary, options) {
5963 var this$1 = this;
5964
5965 if (!ranges.length) { return }
5966 var out = []
5967 for (var i = 0; i < ranges.length; i++)
5968 { out[i] = new Range(clipPos(this$1, ranges[i].anchor),
5969 clipPos(this$1, ranges[i].head)) }
5970 if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIn dex) }
5971 setSelection(this, normalizeSelection(out, primary), options)
5972 }),
5973 addSelection: docMethodOp(function(anchor, head, options) {
5974 var ranges = this.sel.ranges.slice(0)
5975 ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)))
5976 setSelection(this, normalizeSelection(ranges, ranges.length - 1), options)
5977 }),
5978
5979 getSelection: function(lineSep) {
5980 var this$1 = this;
5981
5982 var ranges = this.sel.ranges, lines
5983 for (var i = 0; i < ranges.length; i++) {
5984 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to())
5985 lines = lines ? lines.concat(sel) : sel
5986 }
5987 if (lineSep === false) { return lines }
5988 else { return lines.join(lineSep || this.lineSeparator()) }
5989 },
5990 getSelections: function(lineSep) {
5991 var this$1 = this;
5992
5993 var parts = [], ranges = this.sel.ranges
5994 for (var i = 0; i < ranges.length; i++) {
5995 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to())
5996 if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()) }
5997 parts[i] = sel
5998 }
5999 return parts
6000 },
6001 replaceSelection: function(code, collapse, origin) {
6002 var dup = []
6003 for (var i = 0; i < this.sel.ranges.length; i++)
6004 { dup[i] = code }
6005 this.replaceSelections(dup, collapse, origin || "+input")
6006 },
6007 replaceSelections: docMethodOp(function(code, collapse, origin) {
6008 var this$1 = this;
6009
6010 var changes = [], sel = this.sel
6011 for (var i = 0; i < sel.ranges.length; i++) {
6012 var range = sel.ranges[i]
6013 changes[i] = {from: range.from(), to: range.to(), text: this$1.splitLines( code[i]), origin: origin}
6014 }
6015 var newSel = collapse && collapse != "end" && computeReplacedSel(this, chang es, collapse)
6016 for (var i$1 = changes.length - 1; i$1 >= 0; i$1--)
6017 { makeChange(this$1, changes[i$1]) }
6018 if (newSel) { setSelectionReplaceHistory(this, newSel) }
6019 else if (this.cm) { ensureCursorVisible(this.cm) }
6020 }),
6021 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}),
6022 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}),
6023 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", tru e)}),
6024 redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", tru e)}),
6025
6026 setExtending: function(val) {this.extend = val},
6027 getExtending: function() {return this.extend},
6028
6029 historySize: function() {
6030 var hist = this.history, done = 0, undone = 0
6031 for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++d one } }
6032 for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].r anges) { ++undone } }
6033 return {undo: done, redo: undone}
6034 },
6035 clearHistory: function() {this.history = new History(this.history.maxGeneratio n)},
6036
6037 markClean: function() {
6038 this.cleanGeneration = this.changeGeneration(true)
6039 },
6040 changeGeneration: function(forceSplit) {
6041 if (forceSplit)
6042 { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null }
6043 return this.history.generation
6044 },
6045 isClean: function (gen) {
6046 return this.history.generation == (gen || this.cleanGeneration)
6047 },
6048
6049 getHistory: function() {
6050 return {done: copyHistoryArray(this.history.done),
6051 undone: copyHistoryArray(this.history.undone)}
6052 },
6053 setHistory: function(histData) {
6054 var hist = this.history = new History(this.history.maxGeneration)
6055 hist.done = copyHistoryArray(histData.done.slice(0), null, true)
6056 hist.undone = copyHistoryArray(histData.undone.slice(0), null, true)
6057 },
6058
6059 setGutterMarker: docMethodOp(function(line, gutterID, value) {
6060 return changeLine(this, line, "gutter", function (line) {
6061 var markers = line.gutterMarkers || (line.gutterMarkers = {})
6062 markers[gutterID] = value
6063 if (!value && isEmpty(markers)) { line.gutterMarkers = null }
6064 return true
6065 })
6066 }),
6067
6068 clearGutter: docMethodOp(function(gutterID) {
6069 var this$1 = this;
6070
6071 this.iter(function (line) {
6072 if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
6073 changeLine(this$1, line, "gutter", function () {
6074 line.gutterMarkers[gutterID] = null
6075 if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null }
6076 return true
6077 })
6078 }
6079 })
6080 }),
6081
6082 lineInfo: function(line) {
6083 var n
6084 if (typeof line == "number") {
6085 if (!isLine(this, line)) { return null }
6086 n = line
6087 line = getLine(this, line)
6088 if (!line) { return null }
6089 } else {
6090 n = lineNo(line)
6091 if (n == null) { return null }
6092 }
6093 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMa rkers,
6094 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wr apClass,
6095 widgets: line.widgets}
6096 },
6097
6098 addLineClass: docMethodOp(function(handle, where, cls) {
6099 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", func tion (line) {
6100 var prop = where == "text" ? "textClass"
6101 : where == "background" ? "bgClass"
6102 : where == "gutter" ? "gutterClass" : "wrapClass"
6103 if (!line[prop]) { line[prop] = cls }
6104 else if (classTest(cls).test(line[prop])) { return false }
6105 else { line[prop] += " " + cls }
6106 return true
6107 })
6108 }),
6109 removeLineClass: docMethodOp(function(handle, where, cls) {
6110 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", func tion (line) {
6111 var prop = where == "text" ? "textClass"
6112 : where == "background" ? "bgClass"
6113 : where == "gutter" ? "gutterClass" : "wrapClass"
6114 var cur = line[prop]
6115 if (!cur) { return false }
6116 else if (cls == null) { line[prop] = null }
6117 else {
6118 var found = cur.match(classTest(cls))
6119 if (!found) { return false }
6120 var end = found.index + found[0].length
6121 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.len gth ? "" : " ") + cur.slice(end) || null
6122 }
6123 return true
6124 })
6125 }),
6126
6127 addLineWidget: docMethodOp(function(handle, node, options) {
6128 return addLineWidget(this, handle, node, options)
6129 }),
6130 removeLineWidget: function(widget) { widget.clear() },
6131
6132 markText: function(from, to, options) {
6133 return markText(this, clipPos(this, from), clipPos(this, to), options, optio ns && options.type || "range")
6134 },
6135 setBookmark: function(pos, options) {
6136 var realOpts = {replacedWith: options && (options.nodeType == null ? options .widget : options),
6137 insertLeft: options && options.insertLeft,
6138 clearWhenEmpty: false, shared: options && options.shared,
6139 handleMouseEvents: options && options.handleMouseEvents}
6140 pos = clipPos(this, pos)
6141 return markText(this, pos, pos, realOpts, "bookmark")
6142 },
6143 findMarksAt: function(pos) {
6144 pos = clipPos(this, pos)
6145 var markers = [], spans = getLine(this, pos.line).markedSpans
6146 if (spans) { for (var i = 0; i < spans.length; ++i) {
6147 var span = spans[i]
6148 if ((span.from == null || span.from <= pos.ch) &&
6149 (span.to == null || span.to >= pos.ch))
6150 { markers.push(span.marker.parent || span.marker) }
6151 } }
6152 return markers
6153 },
6154 findMarks: function(from, to, filter) {
6155 from = clipPos(this, from); to = clipPos(this, to)
6156 var found = [], lineNo = from.line
6157 this.iter(from.line, to.line + 1, function (line) {
6158 var spans = line.markedSpans
6159 if (spans) { for (var i = 0; i < spans.length; i++) {
6160 var span = spans[i]
6161 if (!(span.to != null && lineNo == from.line && from.ch >= span.to ||
6162 span.from == null && lineNo != from.line ||
6163 span.from != null && lineNo == to.line && span.from >= to.ch) &&
6164 (!filter || filter(span.marker)))
6165 { found.push(span.marker.parent || span.marker) }
6166 } }
6167 ++lineNo
6168 })
6169 return found
6170 },
6171 getAllMarks: function() {
6172 var markers = []
6173 this.iter(function (line) {
6174 var sps = line.markedSpans
6175 if (sps) { for (var i = 0; i < sps.length; ++i)
6176 { if (sps[i].from != null) { markers.push(sps[i].marker) } } }
6177 })
6178 return markers
6179 },
6180
6181 posFromIndex: function(off) {
6182 var ch, lineNo = this.first, sepSize = this.lineSeparator().length
6183 this.iter(function (line) {
6184 var sz = line.text.length + sepSize
6185 if (sz > off) { ch = off; return true }
6186 off -= sz
6187 ++lineNo
6188 })
6189 return clipPos(this, Pos(lineNo, ch))
6190 },
6191 indexFromPos: function (coords) {
6192 coords = clipPos(this, coords)
6193 var index = coords.ch
6194 if (coords.line < this.first || coords.ch < 0) { return 0 }
6195 var sepSize = this.lineSeparator().length
6196 this.iter(this.first, coords.line, function (line) { // iter aborts when cal lback returns a truthy value
6197 index += line.text.length + sepSize
6198 })
6199 return index
6200 },
6201
6202 copy: function(copyHistory) {
6203 var doc = new Doc(getLines(this, this.first, this.first + this.size),
6204 this.modeOption, this.first, this.lineSep, this.direction)
6205 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft
6206 doc.sel = this.sel
6207 doc.extend = false
6208 if (copyHistory) {
6209 doc.history.undoDepth = this.history.undoDepth
6210 doc.setHistory(this.getHistory())
6211 }
6212 return doc
6213 },
6214
6215 linkedDoc: function(options) {
6216 if (!options) { options = {} }
6217 var from = this.first, to = this.first + this.size
6218 if (options.from != null && options.from > from) { from = options.from }
6219 if (options.to != null && options.to < to) { to = options.to }
6220 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption , from, this.lineSep, this.direction)
6221 if (options.sharedHist) { copy.history = this.history
6222 ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options. sharedHist})
6223 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]
6224 copySharedMarkers(copy, findSharedMarkers(this))
6225 return copy
6226 },
6227 unlinkDoc: function(other) {
6228 var this$1 = this;
6229
6230 if (other instanceof CodeMirror) { other = other.doc }
6231 if (this.linked) { for (var i = 0; i < this.linked.length; ++i) {
6232 var link = this$1.linked[i]
6233 if (link.doc != other) { continue }
6234 this$1.linked.splice(i, 1)
6235 other.unlinkDoc(this$1)
6236 detachSharedMarkers(findSharedMarkers(this$1))
6237 break
6238 } }
6239 // If the histories were shared, split them again
6240 if (other.history == this.history) {
6241 var splitIds = [other.id]
6242 linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true)
6243 other.history = new History(null)
6244 other.history.done = copyHistoryArray(this.history.done, splitIds)
6245 other.history.undone = copyHistoryArray(this.history.undone, splitIds)
6246 }
6247 },
6248 iterLinkedDocs: function(f) {linkedDocs(this, f)},
6249
6250 getMode: function() {return this.mode},
6251 getEditor: function() {return this.cm},
6252
6253 splitLines: function(str) {
6254 if (this.lineSep) { return str.split(this.lineSep) }
6255 return splitLinesAuto(str)
6256 },
6257 lineSeparator: function() { return this.lineSep || "\n" },
6258
6259 setDirection: docMethodOp(function (dir) {
6260 if (dir != "rtl") { dir = "ltr" }
6261 if (dir == this.direction) { return }
6262 this.direction = dir
6263 this.iter(function (line) { return line.order = null; })
6264 if (this.cm) { directionChanged(this.cm) }
6265 })
6266 })
6267
6268 // Public alias.
6269 Doc.prototype.eachLine = Doc.prototype.iter
6270
6271 // Kludge to work around strange IE behavior where it'll sometimes
6272 // re-fire a series of drag-related events right after the drop (#1551)
6273 var lastDrop = 0
6274
6275 function onDrop(e) {
6276 var cm = this
6277 clearDragCursor(cm)
6278 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
6279 { return }
6280 e_preventDefault(e)
6281 if (ie) { lastDrop = +new Date }
6282 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files
6283 if (!pos || cm.isReadOnly()) { return }
6284 // Might be a file drop, in which case we simply extract the text
6285 // and insert it.
6286 if (files && files.length && window.FileReader && window.File) {
6287 var n = files.length, text = Array(n), read = 0
6288 var loadFile = function (file, i) {
6289 if (cm.options.allowDropFileTypes &&
6290 indexOf(cm.options.allowDropFileTypes, file.type) == -1)
6291 { return }
6292
6293 var reader = new FileReader
6294 reader.onload = operation(cm, function () {
6295 var content = reader.result
6296 if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = "" }
6297 text[i] = content
6298 if (++read == n) {
6299 pos = clipPos(cm.doc, pos)
6300 var change = {from: pos, to: pos,
6301 text: cm.doc.splitLines(text.join(cm.doc.lineSeparator() )),
6302 origin: "paste"}
6303 makeChange(cm.doc, change)
6304 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(chan ge)))
6305 }
6306 })
6307 reader.readAsText(file)
6308 }
6309 for (var i = 0; i < n; ++i) { loadFile(files[i], i) }
6310 } else { // Normal drop
6311 // Don't do a replace if the drop happened inside of the selected text.
6312 if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
6313 cm.state.draggingText(e)
6314 // Ensure the editor is re-focused
6315 setTimeout(function () { return cm.display.input.focus(); }, 20)
6316 return
6317 }
6318 try {
6319 var text$1 = e.dataTransfer.getData("Text")
6320 if (text$1) {
6321 var selected
6322 if (cm.state.draggingText && !cm.state.draggingText.copy)
6323 { selected = cm.listSelections() }
6324 setSelectionNoUndo(cm.doc, simpleSelection(pos, pos))
6325 if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1)
6326 { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, " drag") } }
6327 cm.replaceSelection(text$1, "around", "paste")
6328 cm.display.input.focus()
6329 }
6330 }
6331 catch(e){}
6332 }
6333 }
6334
6335 function onDragStart(cm, e) {
6336 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return }
6337 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return }
6338
6339 e.dataTransfer.setData("Text", cm.getSelection())
6340 e.dataTransfer.effectAllowed = "copyMove"
6341
6342 // Use dummy image instead of default browsers image.
6343 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
6344 if (e.dataTransfer.setDragImage && !safari) {
6345 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;")
6346 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAI CTAEAOw=="
6347 if (presto) {
6348 img.width = img.height = 1
6349 cm.display.wrapper.appendChild(img)
6350 // Force a relayout, or Opera won't use our image for some obscure reason
6351 img._top = img.offsetTop
6352 }
6353 e.dataTransfer.setDragImage(img, 0, 0)
6354 if (presto) { img.parentNode.removeChild(img) }
6355 }
6356 }
6357
6358 function onDragOver(cm, e) {
6359 var pos = posFromMouse(cm, e)
6360 if (!pos) { return }
6361 var frag = document.createDocumentFragment()
6362 drawSelectionCursor(cm, pos, frag)
6363 if (!cm.display.dragCursor) {
6364 cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-drag cursors")
6365 cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDi v)
6366 }
6367 removeChildrenAndAdd(cm.display.dragCursor, frag)
6368 }
6369
6370 function clearDragCursor(cm) {
6371 if (cm.display.dragCursor) {
6372 cm.display.lineSpace.removeChild(cm.display.dragCursor)
6373 cm.display.dragCursor = null
6374 }
6375 }
6376
6377 // These must be handled carefully, because naively registering a
6378 // handler for each editor will cause the editors to never be
6379 // garbage collected.
6380
6381 function forEachCodeMirror(f) {
6382 if (!document.body.getElementsByClassName) { return }
6383 var byClass = document.body.getElementsByClassName("CodeMirror")
6384 for (var i = 0; i < byClass.length; i++) {
6385 var cm = byClass[i].CodeMirror
6386 if (cm) { f(cm) }
6387 }
6388 }
6389
6390 var globalsRegistered = false
6391 function ensureGlobalHandlers() {
6392 if (globalsRegistered) { return }
6393 registerGlobalHandlers()
6394 globalsRegistered = true
6395 }
6396 function registerGlobalHandlers() {
6397 // When the window resizes, we need to refresh active editors.
6398 var resizeTimer
6399 on(window, "resize", function () {
6400 if (resizeTimer == null) { resizeTimer = setTimeout(function () {
6401 resizeTimer = null
6402 forEachCodeMirror(onResize)
6403 }, 100) }
6404 })
6405 // When the window loses focus, we want to show the editor as blurred
6406 on(window, "blur", function () { return forEachCodeMirror(onBlur); })
6407 }
6408 // Called when the window resizes
6409 function onResize(cm) {
6410 var d = cm.display
6411 if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper .clientWidth)
6412 { return }
6413 // Might be a text scaling operation, clear size caches.
6414 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null
6415 d.scrollbarsClipped = false
6416 cm.setSize()
6417 }
6418
6419 var keyNames = {
6420 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18 : "Alt",
6421 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDo wn", 35: "End",
6422 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45 : "Insert",
6423 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod",
6424 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete",
6425 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`" , 219: "[", 220: "\\",
6426 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
6427 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Inser t"
6428 }
6429
6430 // Number keys
6431 for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i) }
6432 // Alphabetic keys
6433 for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1) }
6434 // Function keys
6435 for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235 ] = "F" + i$2 }
6436
6437 var keyMap = {}
6438
6439 keyMap.basic = {
6440 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLin eDown",
6441 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDow n": "goPageDown",
6442 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "de lCharBefore",
6443 "Tab": "defaultTab", "Shift-Tab": "indentAuto",
6444 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
6445 "Esc": "singleSelection"
6446 }
6447 // Note that the save and find-related commands aren't defined by
6448 // default. User code or addons can define them. Unknown commands
6449 // are simply ignored.
6450 keyMap.pcDefault = {
6451 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z ": "redo", "Ctrl-Y": "redo",
6452 "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctr l-Down": "goLineDown",
6453 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineS tart", "Alt-Right": "goLineEnd",
6454 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
6455 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", " Shift-Ctrl-R": "replaceAll",
6456 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
6457 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSele ction",
6458 fallthrough: "basic"
6459 }
6460 // Very basic readline/emacs-style bindings, which are standard on Mac.
6461 keyMap.emacsy = {
6462 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N ": "goLineDown",
6463 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl- E": "goLineEnd",
6464 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
6465 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine ", "Ctrl-T": "transposeChars",
6466 "Ctrl-O": "openLine"
6467 }
6468 keyMap.macDefault = {
6469 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": " redo", "Cmd-Y": "redo",
6470 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd- Down": "goDocEnd", "Alt-Left": "goGroupLeft",
6471 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRig ht", "Alt-Backspace": "delGroupBefore",
6472 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
6473 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift -Cmd-Alt-F": "replaceAll",
6474 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLine Left", "Cmd-Delete": "delWrappedLineRight",
6475 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocSta rt", "Ctrl-Down": "goDocEnd",
6476 fallthrough: ["basic", "emacsy"]
6477 }
6478 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault
6479
6480 // KEYMAP DISPATCH
6481
6482 function normalizeKeyName(name) {
6483 var parts = name.split(/-(?!$)/)
6484 name = parts[parts.length - 1]
6485 var alt, ctrl, shift, cmd
6486 for (var i = 0; i < parts.length - 1; i++) {
6487 var mod = parts[i]
6488 if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true }
6489 else if (/^a(lt)?$/i.test(mod)) { alt = true }
6490 else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true }
6491 else if (/^s(hift)?$/i.test(mod)) { shift = true }
6492 else { throw new Error("Unrecognized modifier name: " + mod) }
6493 }
6494 if (alt) { name = "Alt-" + name }
6495 if (ctrl) { name = "Ctrl-" + name }
6496 if (cmd) { name = "Cmd-" + name }
6497 if (shift) { name = "Shift-" + name }
6498 return name
6499 }
6500
6501 // This is a kludge to keep keymaps mostly working as raw objects
6502 // (backwards compatibility) while at the same time support features
6503 // like normalization and multi-stroke key bindings. It compiles a
6504 // new normalized keymap, and then updates the old object to reflect
6505 // this.
6506 function normalizeKeyMap(keymap) {
6507 var copy = {}
6508 for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) {
6509 var value = keymap[keyname]
6510 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue }
6511 if (value == "...") { delete keymap[keyname]; continue }
6512
6513 var keys = map(keyname.split(" "), normalizeKeyName)
6514 for (var i = 0; i < keys.length; i++) {
6515 var val = (void 0), name = (void 0)
6516 if (i == keys.length - 1) {
6517 name = keys.join(" ")
6518 val = value
6519 } else {
6520 name = keys.slice(0, i + 1).join(" ")
6521 val = "..."
6522 }
6523 var prev = copy[name]
6524 if (!prev) { copy[name] = val }
6525 else if (prev != val) { throw new Error("Inconsistent bindings for " + nam e) }
6526 }
6527 delete keymap[keyname]
6528 } }
6529 for (var prop in copy) { keymap[prop] = copy[prop] }
6530 return keymap
6531 }
6532
6533 function lookupKey(key, map, handle, context) {
6534 map = getKeyMap(map)
6535 var found = map.call ? map.call(key, context) : map[key]
6536 if (found === false) { return "nothing" }
6537 if (found === "...") { return "multi" }
6538 if (found != null && handle(found)) { return "handled" }
6539
6540 if (map.fallthrough) {
6541 if (Object.prototype.toString.call(map.fallthrough) != "[object Array]")
6542 { return lookupKey(key, map.fallthrough, handle, context) }
6543 for (var i = 0; i < map.fallthrough.length; i++) {
6544 var result = lookupKey(key, map.fallthrough[i], handle, context)
6545 if (result) { return result }
6546 }
6547 }
6548 }
6549
6550 // Modifier key presses don't count as 'real' key presses for the
6551 // purpose of keymap fallthrough.
6552 function isModifierKey(value) {
6553 var name = typeof value == "string" ? value : keyNames[value.keyCode]
6554 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"
6555 }
6556
6557 // Look up the name of a key as indicated by an event object.
6558 function keyName(event, noShift) {
6559 if (presto && event.keyCode == 34 && event["char"]) { return false }
6560 var base = keyNames[event.keyCode], name = base
6561 if (name == null || event.altGraphKey) { return false }
6562 if (event.altKey && base != "Alt") { name = "Alt-" + name }
6563 if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name }
6564 if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = " Cmd-" + name }
6565 if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name }
6566 return name
6567 }
6568
6569 function getKeyMap(val) {
6570 return typeof val == "string" ? keyMap[val] : val
6571 }
6572
6573 // Helper for deleting text near the selection(s), used to implement
6574 // backspace, delete, and similar functionality.
6575 function deleteNearSelection(cm, compute) {
6576 var ranges = cm.doc.sel.ranges, kill = []
6577 // Build up a set of ranges to kill first, merging overlapping
6578 // ranges.
6579 for (var i = 0; i < ranges.length; i++) {
6580 var toKill = compute(ranges[i])
6581 while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {
6582 var replaced = kill.pop()
6583 if (cmp(replaced.from, toKill.from) < 0) {
6584 toKill.from = replaced.from
6585 break
6586 }
6587 }
6588 kill.push(toKill)
6589 }
6590 // Next, remove those actual ranges.
6591 runInOp(cm, function () {
6592 for (var i = kill.length - 1; i >= 0; i--)
6593 { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete") }
6594 ensureCursorVisible(cm)
6595 })
6596 }
6597
6598 // Commands are parameter-less actions that can be performed on an
6599 // editor, mostly used for keybindings.
6600 var commands = {
6601 selectAll: selectAll,
6602 singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor") , cm.getCursor("head"), sel_dontScroll); },
6603 killLine: function (cm) { return deleteNearSelection(cm, function (range) {
6604 if (range.empty()) {
6605 var len = getLine(cm.doc, range.head.line).text.length
6606 if (range.head.ch == len && range.head.line < cm.lastLine())
6607 { return {from: range.head, to: Pos(range.head.line + 1, 0)} }
6608 else
6609 { return {from: range.head, to: Pos(range.head.line, len)} }
6610 } else {
6611 return {from: range.from(), to: range.to()}
6612 }
6613 }); },
6614 deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({
6615 from: Pos(range.from().line, 0),
6616 to: clipPos(cm.doc, Pos(range.to().line + 1, 0))
6617 }); }); },
6618 delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({
6619 from: Pos(range.from().line, 0), to: range.from()
6620 }); }); },
6621 delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (r ange) {
6622 var top = cm.charCoords(range.head, "div").top + 5
6623 var leftPos = cm.coordsChar({left: 0, top: top}, "div")
6624 return {from: leftPos, to: range.from()}
6625 }); },
6626 delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function ( range) {
6627 var top = cm.charCoords(range.head, "div").top + 5
6628 var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, to p: top}, "div")
6629 return {from: range.from(), to: rightPos }
6630 }); },
6631 undo: function (cm) { return cm.undo(); },
6632 redo: function (cm) { return cm.redo(); },
6633 undoSelection: function (cm) { return cm.undoSelection(); },
6634 redoSelection: function (cm) { return cm.redoSelection(); },
6635 goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); },
6636 goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); },
6637 goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { r eturn lineStart(cm, range.head.line); },
6638 {origin: "+move", bias: 1}
6639 ); },
6640 goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range ) { return lineStartSmart(cm, range.head); },
6641 {origin: "+move", bias: 1}
6642 ); },
6643 goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { ret urn lineEnd(cm, range.head.line); },
6644 {origin: "+move", bias: -1}
6645 ); },
6646 goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) {
6647 var top = cm.charCoords(range.head, "div").top + 5
6648 return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")
6649 }, sel_move); },
6650 goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) {
6651 var top = cm.charCoords(range.head, "div").top + 5
6652 return cm.coordsChar({left: 0, top: top}, "div")
6653 }, sel_move); },
6654 goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) {
6655 var top = cm.charCoords(range.head, "div").top + 5
6656 var pos = cm.coordsChar({left: 0, top: top}, "div")
6657 if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) }
6658 return pos
6659 }, sel_move); },
6660 goLineUp: function (cm) { return cm.moveV(-1, "line"); },
6661 goLineDown: function (cm) { return cm.moveV(1, "line"); },
6662 goPageUp: function (cm) { return cm.moveV(-1, "page"); },
6663 goPageDown: function (cm) { return cm.moveV(1, "page"); },
6664 goCharLeft: function (cm) { return cm.moveH(-1, "char"); },
6665 goCharRight: function (cm) { return cm.moveH(1, "char"); },
6666 goColumnLeft: function (cm) { return cm.moveH(-1, "column"); },
6667 goColumnRight: function (cm) { return cm.moveH(1, "column"); },
6668 goWordLeft: function (cm) { return cm.moveH(-1, "word"); },
6669 goGroupRight: function (cm) { return cm.moveH(1, "group"); },
6670 goGroupLeft: function (cm) { return cm.moveH(-1, "group"); },
6671 goWordRight: function (cm) { return cm.moveH(1, "word"); },
6672 delCharBefore: function (cm) { return cm.deleteH(-1, "char"); },
6673 delCharAfter: function (cm) { return cm.deleteH(1, "char"); },
6674 delWordBefore: function (cm) { return cm.deleteH(-1, "word"); },
6675 delWordAfter: function (cm) { return cm.deleteH(1, "word"); },
6676 delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); },
6677 delGroupAfter: function (cm) { return cm.deleteH(1, "group"); },
6678 indentAuto: function (cm) { return cm.indentSelection("smart"); },
6679 indentMore: function (cm) { return cm.indentSelection("add"); },
6680 indentLess: function (cm) { return cm.indentSelection("subtract"); },
6681 insertTab: function (cm) { return cm.replaceSelection("\t"); },
6682 insertSoftTab: function (cm) {
6683 var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize
6684 for (var i = 0; i < ranges.length; i++) {
6685 var pos = ranges[i].from()
6686 var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize)
6687 spaces.push(spaceStr(tabSize - col % tabSize))
6688 }
6689 cm.replaceSelections(spaces)
6690 },
6691 defaultTab: function (cm) {
6692 if (cm.somethingSelected()) { cm.indentSelection("add") }
6693 else { cm.execCommand("insertTab") }
6694 },
6695 // Swap the two chars left and right of each selection's head.
6696 // Move cursor behind the two swapped characters afterwards.
6697 //
6698 // Doesn't consider line feeds a character.
6699 // Doesn't scan more than one line above to find a character.
6700 // Doesn't do anything on an empty line.
6701 // Doesn't do anything with non-empty selections.
6702 transposeChars: function (cm) { return runInOp(cm, function () {
6703 var ranges = cm.listSelections(), newSel = []
6704 for (var i = 0; i < ranges.length; i++) {
6705 if (!ranges[i].empty()) { continue }
6706 var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text
6707 if (line) {
6708 if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1) }
6709 if (cur.ch > 0) {
6710 cur = new Pos(cur.line, cur.ch + 1)
6711 cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),
6712 Pos(cur.line, cur.ch - 2), cur, "+transpose")
6713 } else if (cur.line > cm.doc.first) {
6714 var prev = getLine(cm.doc, cur.line - 1).text
6715 if (prev) {
6716 cur = new Pos(cur.line, 1)
6717 cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +
6718 prev.charAt(prev.length - 1),
6719 Pos(cur.line - 1, prev.length - 1), cur, "+transpose ")
6720 } 1289 }
6721 } 1290 }
6722 } 1291 if (e.type == "cut") cm.state.cutIncoming = true;
6723 newSel.push(new Range(cur, cur)) 1292 }
6724 } 1293 on(te, "cut", prepareCopyCut);
6725 cm.setSelections(newSel) 1294 on(te, "copy", prepareCopyCut);
6726 }); }, 1295
6727 newlineAndIndent: function (cm) { return runInOp(cm, function () { 1296 on(display.scroller, "paste", function(e) {
6728 var sels = cm.listSelections() 1297 if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return;
6729 for (var i = sels.length - 1; i >= 0; i--) 1298 cm.state.pasteIncoming = true;
6730 { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+ input") } 1299 input.focus();
6731 sels = cm.listSelections() 1300 });
6732 for (var i$1 = 0; i$1 < sels.length; i$1++) 1301
6733 { cm.indentLine(sels[i$1].from().line, null, true) } 1302 // Prevent normal selection in the editor (we handle our own)
6734 ensureCursorVisible(cm) 1303 on(display.lineSpace, "selectstart", function(e) {
6735 }); }, 1304 if (!eventInWidget(display, e)) e_preventDefault(e);
6736 openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, 1305 });
6737 toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } 1306
6738 } 1307 on(te, "compositionstart", function() {
6739 1308 var start = cm.getCursor("from");
6740 1309 if (input.composing) input.composing.range.clear()
6741 function lineStart(cm, lineN) { 1310 input.composing = {
6742 var line = getLine(cm.doc, lineN) 1311 start: start,
6743 var visual = visualLine(line) 1312 range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror- composing"})
6744 if (visual != line) { lineN = lineNo(visual) } 1313 };
6745 return endOfLine(true, cm, visual, lineN, 1) 1314 });
6746 } 1315 on(te, "compositionend", function() {
6747 function lineEnd(cm, lineN) { 1316 if (input.composing) {
6748 var line = getLine(cm.doc, lineN) 1317 input.poll();
6749 var visual = visualLineEnd(line) 1318 input.composing.range.clear();
6750 if (visual != line) { lineN = lineNo(visual) } 1319 input.composing = null;
6751 return endOfLine(true, cm, line, lineN, -1) 1320 }
6752 } 1321 });
6753 function lineStartSmart(cm, pos) { 1322 },
6754 var start = lineStart(cm, pos.line) 1323
6755 var line = getLine(cm.doc, start.line) 1324 prepareSelection: function() {
6756 var order = getOrder(line, cm.doc.direction) 1325 // Redraw the selection and/or cursor
6757 if (!order || order[0].level == 0) { 1326 var cm = this.cm, display = cm.display, doc = cm.doc;
6758 var firstNonWS = Math.max(0, line.text.search(/\S/)) 1327 var result = prepareSelection(cm);
6759 var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch 1328
6760 return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) 1329 // Move the hidden textarea near the cursor to prevent scrolling artifacts
6761 } 1330 if (cm.options.moveInputWithCursor) {
6762 return start 1331 var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
6763 } 1332 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display .lineDiv.getBoundingClientRect();
6764 1333 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
6765 // Run a handler that was bound to a key. 1334 headPos.top + lineOff.top - wrapOff. top));
6766 function doHandleBinding(cm, bound, dropShift) { 1335 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
6767 if (typeof bound == "string") { 1336 headPos.left + lineOff.left - wrapO ff.left));
6768 bound = commands[bound] 1337 }
6769 if (!bound) { return false } 1338
6770 } 1339 return result;
6771 // Ensure previous input has been read, so that the handler sees a 1340 },
6772 // consistent view of the document 1341
6773 cm.display.input.ensurePolled() 1342 showSelection: function(drawn) {
6774 var prevShift = cm.display.shift, done = false 1343 var cm = this.cm, display = cm.display;
6775 try { 1344 removeChildrenAndAdd(display.cursorDiv, drawn.cursors);
6776 if (cm.isReadOnly()) { cm.state.suppressEdits = true } 1345 removeChildrenAndAdd(display.selectionDiv, drawn.selection);
6777 if (dropShift) { cm.display.shift = false } 1346 if (drawn.teTop != null) {
6778 done = bound(cm) != Pass 1347 this.wrapper.style.top = drawn.teTop + "px";
6779 } finally { 1348 this.wrapper.style.left = drawn.teLeft + "px";
6780 cm.display.shift = prevShift 1349 }
6781 cm.state.suppressEdits = false 1350 },
6782 } 1351
6783 return done 1352 // Reset the input to correspond to the selection (or to be empty,
6784 } 1353 // when not typing and nothing is selected)
6785 1354 reset: function(typing) {
6786 function lookupKeyForEditor(cm, name, handle) { 1355 if (this.contextMenuPending) return;
6787 for (var i = 0; i < cm.state.keyMaps.length; i++) { 1356 var minimal, selected, cm = this.cm, doc = cm.doc;
6788 var result = lookupKey(name, cm.state.keyMaps[i], handle, cm) 1357 if (cm.somethingSelected()) {
6789 if (result) { return result } 1358 this.prevInput = "";
6790 } 1359 var range = doc.sel.primary();
6791 return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) 1360 minimal = hasCopyEvent &&
6792 || lookupKey(name, cm.options.keyMap, handle, cm) 1361 (range.to().line - range.from().line > 100 || (selected = cm.getSelect ion()).length > 1000);
6793 } 1362 var content = minimal ? "-" : selected || cm.getSelection();
6794 1363 this.textarea.value = content;
6795 var stopSeq = new Delayed 1364 if (cm.state.focused) selectInput(this.textarea);
6796 function dispatchKey(cm, name, e, handle) { 1365 if (ie && ie_version >= 9) this.hasSelection = content;
6797 var seq = cm.state.keySeq 1366 } else if (!typing) {
6798 if (seq) { 1367 this.prevInput = this.textarea.value = "";
6799 if (isModifierKey(name)) { return "handled" } 1368 if (ie && ie_version >= 9) this.hasSelection = null;
6800 stopSeq.set(50, function () { 1369 }
6801 if (cm.state.keySeq == seq) { 1370 this.inaccurateSelection = minimal;
6802 cm.state.keySeq = null 1371 },
6803 cm.display.input.reset() 1372
6804 } 1373 getField: function() { return this.textarea; },
6805 }) 1374
6806 name = seq + " " + name 1375 supportsTouch: function() { return false; },
6807 } 1376
6808 var result = lookupKeyForEditor(cm, name, handle) 1377 focus: function() {
6809 1378 if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != t his.textarea)) {
6810 if (result == "multi") 1379 try { this.textarea.focus(); }
6811 { cm.state.keySeq = name } 1380 catch (e) {} // IE8 will throw if the textarea is display: none or not i n DOM
6812 if (result == "handled") 1381 }
6813 { signalLater(cm, "keyHandled", cm, name, e) } 1382 },
6814 1383
6815 if (result == "handled" || result == "multi") { 1384 blur: function() { this.textarea.blur(); },
6816 e_preventDefault(e) 1385
6817 restartBlink(cm) 1386 resetPosition: function() {
6818 } 1387 this.wrapper.style.top = this.wrapper.style.left = 0;
6819 1388 },
6820 if (seq && !result && /\'$/.test(name)) { 1389
6821 e_preventDefault(e) 1390 receivedFocus: function() { this.slowPoll(); },
6822 return true 1391
6823 } 1392 // Poll for input changes, using the normal rate of polling. This
6824 return !!result 1393 // runs as long as the editor is focused.
6825 } 1394 slowPoll: function() {
6826 1395 var input = this;
6827 // Handle a key from the keydown event. 1396 if (input.pollingFast) return;
6828 function handleKeyBinding(cm, e) { 1397 input.polling.set(this.cm.options.pollInterval, function() {
6829 var name = keyName(e, true) 1398 input.poll();
6830 if (!name) { return false } 1399 if (input.cm.state.focused) input.slowPoll();
6831 1400 });
6832 if (e.shiftKey && !cm.state.keySeq) { 1401 },
6833 // First try to resolve full name (including 'Shift-'). Failing 1402
6834 // that, see if there is a cursor-motion command (starting with 1403 // When an event has just come in that is likely to add or change
6835 // 'go') bound to the keyname without 'Shift-'. 1404 // something in the input textarea, we poll faster, to ensure that
6836 return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBin ding(cm, b, true); }) 1405 // the change appears on the screen quickly.
6837 || dispatchKey(cm, name, e, function (b) { 1406 fastPoll: function() {
6838 if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) 1407 var missed = false, input = this;
6839 { return doHandleBinding(cm, b) } 1408 input.pollingFast = true;
6840 }) 1409 function p() {
6841 } else { 1410 var changed = input.poll();
6842 return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b) ; }) 1411 if (!changed && !missed) {missed = true; input.polling.set(60, p);}
6843 } 1412 else {input.pollingFast = false; input.slowPoll();}
6844 } 1413 }
6845 1414 input.polling.set(20, p);
6846 // Handle a key from the keypress event 1415 },
6847 function handleCharBinding(cm, e, ch) { 1416
6848 return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBindin g(cm, b, true); }) 1417 // Read input from the textarea, and update the document to match.
6849 } 1418 // When something is selected, it is present in the textarea, and
6850 1419 // selected (unless it is huge, in which case a placeholder is
6851 var lastStoppedKey = null 1420 // used). When nothing is selected, the cursor sits after previously
6852 function onKeyDown(e) { 1421 // seen text (can be empty), which is stored in prevInput (we must
6853 var cm = this 1422 // not reset the textarea when typing, because that breaks IME).
6854 cm.curOp.focus = activeElt() 1423 poll: function() {
6855 if (signalDOMEvent(cm, e)) { return } 1424 var cm = this.cm, input = this.textarea, prevInput = this.prevInput;
6856 // IE does strange things with escape. 1425 // Since this is called a *lot*, try to bail out as cheaply as
6857 if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false } 1426 // possible when it is clear that nothing happened. hasSelection
6858 var code = e.keyCode 1427 // will be the case when there is a lot of text in the textarea,
6859 cm.display.shift = code == 16 || e.shiftKey 1428 // in which case reading its value would be expensive.
6860 var handled = handleKeyBinding(cm, e) 1429 if (this.contextMenuPending || !cm.state.focused ||
6861 if (presto) { 1430 (hasSelection(input) && !prevInput && !this.composing) ||
6862 lastStoppedKey = handled ? code : null 1431 cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
6863 // Opera has no cut event... we try to at least catch the key combo 1432 return false;
6864 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey) ) 1433
6865 { cm.replaceSelection("", null, "cut") } 1434 var text = input.value;
6866 } 1435 // If nothing changed, bail.
6867 1436 if (text == prevInput && !cm.somethingSelected()) return false;
6868 // Turn mouse into crosshair when Alt is held on Mac. 1437 // Work around nonsensical selection resetting in IE9/10, and
6869 if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.classNam e)) 1438 // inexplicable appearance of private area unicode characters on
6870 { showCrossHair(cm) } 1439 // some key combos in Mac (#2689).
6871 } 1440 if (ie && ie_version >= 9 && this.hasSelection === text ||
6872 1441 mac && /[\uf700-\uf7ff]/.test(text)) {
6873 function showCrossHair(cm) { 1442 cm.display.input.reset();
6874 var lineDiv = cm.display.lineDiv 1443 return false;
6875 addClass(lineDiv, "CodeMirror-crosshair") 1444 }
6876 1445
6877 function up(e) { 1446 if (cm.doc.sel == cm.display.selForContextMenu) {
6878 if (e.keyCode == 18 || !e.altKey) { 1447 var first = text.charCodeAt(0);
6879 rmClass(lineDiv, "CodeMirror-crosshair") 1448 if (first == 0x200b && !prevInput) prevInput = "\u200b";
6880 off(document, "keyup", up) 1449 if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); }
6881 off(document, "mouseover", up) 1450 }
6882 } 1451 // Find the part of the input that is actually new
6883 } 1452 var same = 0, l = Math.min(prevInput.length, text.length);
6884 on(document, "keyup", up) 1453 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++ same;
6885 on(document, "mouseover", up) 1454
6886 } 1455 var self = this;
6887 1456 runInOp(cm, function() {
6888 function onKeyUp(e) { 1457 applyTextInput(cm, text.slice(same), prevInput.length - same,
6889 if (e.keyCode == 16) { this.doc.sel.shift = false } 1458 null, self.composing ? "*compose" : null);
6890 signalDOMEvent(this, e) 1459
6891 } 1460 // Don't leave long text in the textarea, since it makes further polling slow
6892 1461 if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.pr evInput = "";
6893 function onKeyPress(e) { 1462 else self.prevInput = text;
6894 var cm = this 1463
6895 if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.a ltKey || mac && e.metaKey) { return } 1464 if (self.composing) {
6896 var keyCode = e.keyCode, charCode = e.charCode 1465 self.composing.range.clear();
6897 if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefa ult(e); return} 1466 self.composing.range = cm.markText(self.composing.start, cm.getCursor( "to"),
6898 if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { retur n } 1467 {className: "CodeMirror-composing"} );
6899 var ch = String.fromCharCode(charCode == null ? keyCode : charCode) 1468 }
6900 // Some browsers fire keypress events for backspace 1469 });
6901 if (ch == "\x08") { return } 1470 return true;
6902 if (handleCharBinding(cm, e, ch)) { return } 1471 },
6903 cm.display.input.onKeyPress(e) 1472
6904 } 1473 ensurePolled: function() {
6905 1474 if (this.pollingFast && this.poll()) this.pollingFast = false;
6906 // A mouse down can be a single click, double click, triple click, 1475 },
6907 // start of selection drag, start of text drag, new cursor 1476
6908 // (ctrl-click), rectangle drag (alt-drag), or xwin 1477 onKeyPress: function() {
6909 // middle-click-paste. Or it might be a click on something we should 1478 if (ie && ie_version >= 9) this.hasSelection = null;
6910 // not interfere with, such as a scrollbar or widget. 1479 this.fastPoll();
6911 function onMouseDown(e) { 1480 },
6912 var cm = this, display = cm.display 1481
6913 if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouc h()) { return } 1482 onContextMenu: function(e) {
6914 display.input.ensurePolled() 1483 var input = this, cm = input.cm, display = cm.display, te = input.textarea ;
6915 display.shift = e.shiftKey 1484 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
6916 1485 if (!pos || presto) return; // Opera is difficult.
6917 if (eventInWidget(display, e)) { 1486
6918 if (!webkit) { 1487 // Reset the current text selection only if the click is done outside of t he selection
6919 // Briefly turn off draggability, to allow widgets to do 1488 // and 'resetSelectionOnContextMenu' option is true.
6920 // normal dragging things. 1489 var reset = cm.options.resetSelectionOnContextMenu;
6921 display.scroller.draggable = false 1490 if (reset && cm.doc.sel.contains(pos) == -1)
6922 setTimeout(function () { return display.scroller.draggable = true; }, 100) 1491 operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll );
6923 } 1492
6924 return 1493 var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText ;
6925 } 1494 input.wrapper.style.cssText = "position: absolute"
6926 if (clickInGutter(cm, e)) { return } 1495 var wrapperBox = input.wrapper.getBoundingClientRect()
6927 var start = posFromMouse(cm, e) 1496 te.style.cssText = "position: absolute; width: 30px; height: 30px; top: " + (e.clientY - wrapperBox.top - 5) +
6928 window.focus() 1497 "px; left: " + (e.clientX - wrapperBox.left - 5) + "px; z-index: 1000; b ackground: " +
6929 1498 (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
6930 switch (e_button(e)) { 1499 "; outline: none; border-width: 0; outline: none; overflow: hidden; opac ity: .05; filter: alpha(opacity=5);";
6931 case 1: 1500 if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue ( #2712)
6932 // #3261: make sure, that we're not starting a second selection 1501 display.input.focus();
6933 if (cm.state.selectingText) 1502 if (webkit) window.scrollTo(null, oldScrollY);
6934 { cm.state.selectingText(e) } 1503 display.input.reset();
6935 else if (start) 1504 // Adds "Select all" to context menu in FF
6936 { leftButtonDown(cm, e, start) } 1505 if (!cm.somethingSelected()) te.value = input.prevInput = " ";
6937 else if (e_target(e) == display.scroller) 1506 input.contextMenuPending = true;
6938 { e_preventDefault(e) } 1507 display.selForContextMenu = cm.doc.sel;
6939 break 1508 clearTimeout(display.detectingSelectAll);
6940 case 2: 1509
6941 if (webkit) { cm.state.lastMiddleDown = +new Date } 1510 // Select-all will be greyed out if there's nothing to select, so
6942 if (start) { extendSelection(cm.doc, start) } 1511 // this adds a zero-width space so that we can later check whether
6943 setTimeout(function () { return display.input.focus(); }, 20) 1512 // it got selected.
6944 e_preventDefault(e) 1513 function prepareSelectAllHack() {
6945 break 1514 if (te.selectionStart != null) {
6946 case 3: 1515 var selected = cm.somethingSelected();
6947 if (captureRightClick) { onContextMenu(cm, e) } 1516 var extval = "\u200b" + (selected ? te.value : "");
6948 else { delayBlurEvent(cm) } 1517 te.value = "\u21da"; // Used to catch context-menu undo
6949 break 1518 te.value = extval;
6950 } 1519 input.prevInput = selected ? "" : "\u200b";
6951 } 1520 te.selectionStart = 1; te.selectionEnd = extval.length;
6952 1521 // Re-set this, in case some other handler touched the
6953 var lastClick; 1522 // selection in the meantime.
6954 var lastDoubleClick; 1523 display.selForContextMenu = cm.doc.sel;
6955 function leftButtonDown(cm, e, start) { 1524 }
6956 if (ie) { setTimeout(bind(ensureFocus, cm), 0) } 1525 }
6957 else { cm.curOp.focus = activeElt() } 1526 function rehide() {
6958 1527 input.contextMenuPending = false;
6959 var now = +new Date, type 1528 input.wrapper.style.cssText = oldWrapperCSS
6960 if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick .pos, start) == 0) { 1529 te.style.cssText = oldCSS;
6961 type = "triple" 1530 if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroll er.scrollTop = scrollPos);
6962 } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start ) == 0) { 1531
6963 type = "double" 1532 // Try to detect the user choosing select-all
6964 lastDoubleClick = {time: now, pos: start} 1533 if (te.selectionStart != null) {
6965 } else { 1534 if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
6966 type = "single" 1535 var i = 0, poll = function() {
6967 lastClick = {time: now, pos: start} 1536 if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&
6968 } 1537 te.selectionEnd > 0 && input.prevInput == "\u200b")
6969 1538 operation(cm, commands.selectAll)(cm);
6970 var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained 1539 else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500 );
6971 if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && 1540 else display.input.reset();
6972 type == "single" && (contained = sel.contains(start)) > -1 && 1541 };
6973 (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) && 1542 display.detectingSelectAll = setTimeout(poll, 200);
6974 (cmp(contained.to(), start) > 0 || start.xRel < 0)) 1543 }
6975 { leftButtonStartDrag(cm, e, start, modifier) } 1544 }
6976 else 1545
6977 { leftButtonSelect(cm, e, start, type, modifier) } 1546 if (ie && ie_version >= 9) prepareSelectAllHack();
6978 } 1547 if (captureRightClick) {
6979 1548 e_stop(e);
6980 // Start a text drag. When it ends, see if any dragging actually 1549 var mouseup = function() {
6981 // happen, and treat as a click if it didn't. 1550 off(window, "mouseup", mouseup);
6982 function leftButtonStartDrag(cm, e, start, modifier) { 1551 setTimeout(rehide, 20);
6983 var display = cm.display, startTime = +new Date 1552 };
6984 var dragEnd = operation(cm, function (e2) { 1553 on(window, "mouseup", mouseup);
6985 if (webkit) { display.scroller.draggable = false } 1554 } else {
6986 cm.state.draggingText = false 1555 setTimeout(rehide, 50);
6987 off(document, "mouseup", dragEnd) 1556 }
6988 off(display.scroller, "drop", dragEnd) 1557 },
6989 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10 ) { 1558
6990 e_preventDefault(e2) 1559 readOnlyChanged: function(val) {
6991 if (!modifier && +new Date - 200 < startTime) 1560 if (!val) this.reset();
6992 { extendSelection(cm.doc, start) } 1561 },
6993 // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#308 1) 1562
6994 if (webkit || ie && ie_version == 9) 1563 setUneditable: nothing,
6995 { setTimeout(function () {document.body.focus(); display.input.focus()}, 20) } 1564
1565 needsContentAttribute: false
1566 }, TextareaInput.prototype);
1567
1568 // CONTENTEDITABLE INPUT STYLE
1569
1570 function ContentEditableInput(cm) {
1571 this.cm = cm;
1572 this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.last FocusOffset = null;
1573 this.polling = new Delayed();
1574 this.gracePeriod = false;
1575 }
1576
1577 ContentEditableInput.prototype = copyObj({
1578 init: function(display) {
1579 var input = this, cm = input.cm;
1580 var div = input.div = display.lineDiv;
1581 disableBrowserMagic(div);
1582
1583 on(div, "paste", function(e) {
1584 if (!signalDOMEvent(cm, e)) handlePaste(e, cm);
1585 })
1586
1587 on(div, "compositionstart", function(e) {
1588 var data = e.data;
1589 input.composing = {sel: cm.doc.sel, data: data, startData: data};
1590 if (!data) return;
1591 var prim = cm.doc.sel.primary();
1592 var line = cm.getLine(prim.head.line);
1593 var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length));
1594 if (found > -1 && found <= prim.head.ch)
1595 input.composing.sel = simpleSelection(Pos(prim.head.line, found),
1596 Pos(prim.head.line, found + data .length));
1597 });
1598 on(div, "compositionupdate", function(e) {
1599 input.composing.data = e.data;
1600 });
1601 on(div, "compositionend", function(e) {
1602 var ours = input.composing;
1603 if (!ours) return;
1604 if (e.data != ours.startData && !/\u200b/.test(e.data))
1605 ours.data = e.data;
1606 // Need a small delay to prevent other code (input event,
1607 // selection polling) from doing damage when fired right after
1608 // compositionend.
1609 setTimeout(function() {
1610 if (!ours.handled)
1611 input.applyComposition(ours);
1612 if (input.composing == ours)
1613 input.composing = null;
1614 }, 50);
1615 });
1616
1617 on(div, "touchstart", function() {
1618 input.forceCompositionEnd();
1619 });
1620
1621 on(div, "input", function() {
1622 if (input.composing) return;
1623 if (cm.isReadOnly() || !input.pollContent())
1624 runInOp(input.cm, function() {regChange(cm);});
1625 });
1626
1627 function onCopyCut(e) {
1628 if (signalDOMEvent(cm, e)) return
1629 if (cm.somethingSelected()) {
1630 lastCopied = {lineWise: false, text: cm.getSelections()};
1631 if (e.type == "cut") cm.replaceSelection("", null, "cut");
1632 } else if (!cm.options.lineWiseCopyCut) {
1633 return;
1634 } else {
1635 var ranges = copyableRanges(cm);
1636 lastCopied = {lineWise: true, text: ranges.text};
1637 if (e.type == "cut") {
1638 cm.operation(function() {
1639 cm.setSelections(ranges.ranges, 0, sel_dontScroll);
1640 cm.replaceSelection("", null, "cut");
1641 });
1642 }
1643 }
1644 // iOS exposes the clipboard API, but seems to discard content inserted into it
1645 if (e.clipboardData && !ios) {
1646 e.preventDefault();
1647 e.clipboardData.clearData();
1648 e.clipboardData.setData("text/plain", lastCopied.text.join("\n"));
1649 } else {
1650 // Old-fashioned briefly-focus-a-textarea hack
1651 var kludge = hiddenTextarea(), te = kludge.firstChild;
1652 cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstCh ild);
1653 te.value = lastCopied.text.join("\n");
1654 var hadFocus = document.activeElement;
1655 selectInput(te);
1656 setTimeout(function() {
1657 cm.display.lineSpace.removeChild(kludge);
1658 hadFocus.focus();
1659 }, 50);
1660 }
1661 }
1662 on(div, "copy", onCopyCut);
1663 on(div, "cut", onCopyCut);
1664 },
1665
1666 prepareSelection: function() {
1667 var result = prepareSelection(this.cm, false);
1668 result.focus = this.cm.state.focused;
1669 return result;
1670 },
1671
1672 showSelection: function(info, takeFocus) {
1673 if (!info || !this.cm.display.view.length) return;
1674 if (info.focus || takeFocus) this.showPrimarySelection();
1675 this.showMultipleSelections(info);
1676 },
1677
1678 showPrimarySelection: function() {
1679 var sel = window.getSelection(), prim = this.cm.doc.sel.primary();
1680 var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset);
1681 var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset);
1682 if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&
1683 cmp(minPos(curAnchor, curFocus), prim.from()) == 0 &&
1684 cmp(maxPos(curAnchor, curFocus), prim.to()) == 0)
1685 return;
1686
1687 var start = posToDOM(this.cm, prim.from());
1688 var end = posToDOM(this.cm, prim.to());
1689 if (!start && !end) return;
1690
1691 var view = this.cm.display.view;
1692 var old = sel.rangeCount && sel.getRangeAt(0);
1693 if (!start) {
1694 start = {node: view[0].measure.map[2], offset: 0};
1695 } else if (!end) { // FIXME dangerously hacky
1696 var measure = view[view.length - 1].measure;
1697 var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure .map;
1698 end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map. length - 3]};
1699 }
1700
1701 try { var rng = range(start.node, start.offset, end.offset, end.node); }
1702 catch(e) {} // Our model of the DOM might be outdated, in which case the r ange we try to set can be impossible
1703 if (rng) {
1704 if (!gecko && this.cm.state.focused) {
1705 sel.collapse(start.node, start.offset);
1706 if (!rng.collapsed) sel.addRange(rng);
1707 } else {
1708 sel.removeAllRanges();
1709 sel.addRange(rng);
1710 }
1711 if (old && sel.anchorNode == null) sel.addRange(old);
1712 else if (gecko) this.startGracePeriod();
1713 }
1714 this.rememberSelection();
1715 },
1716
1717 startGracePeriod: function() {
1718 var input = this;
1719 clearTimeout(this.gracePeriod);
1720 this.gracePeriod = setTimeout(function() {
1721 input.gracePeriod = false;
1722 if (input.selectionChanged())
1723 input.cm.operation(function() { input.cm.curOp.selectionChanged = true ; });
1724 }, 20);
1725 },
1726
1727 showMultipleSelections: function(info) {
1728 removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors);
1729 removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection);
1730 },
1731
1732 rememberSelection: function() {
1733 var sel = window.getSelection();
1734 this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOf fset;
1735 this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset ;
1736 },
1737
1738 selectionInEditor: function() {
1739 var sel = window.getSelection();
1740 if (!sel.rangeCount) return false;
1741 var node = sel.getRangeAt(0).commonAncestorContainer;
1742 return contains(this.div, node);
1743 },
1744
1745 focus: function() {
1746 if (this.cm.options.readOnly != "nocursor") this.div.focus();
1747 },
1748 blur: function() { this.div.blur(); },
1749 getField: function() { return this.div; },
1750
1751 supportsTouch: function() { return true; },
1752
1753 receivedFocus: function() {
1754 var input = this;
1755 if (this.selectionInEditor())
1756 this.pollSelection();
6996 else 1757 else
6997 { display.input.focus() } 1758 runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; }) ;
6998 } 1759
6999 }) 1760 function poll() {
7000 // Let the drag handler handle this. 1761 if (input.cm.state.focused) {
7001 if (webkit) { display.scroller.draggable = true } 1762 input.pollSelection();
7002 cm.state.draggingText = dragEnd 1763 input.polling.set(input.cm.options.pollInterval, poll);
7003 dragEnd.copy = mac ? e.altKey : e.ctrlKey 1764 }
7004 // IE's approach to draggable 1765 }
7005 if (display.scroller.dragDrop) { display.scroller.dragDrop() } 1766 this.polling.set(this.cm.options.pollInterval, poll);
7006 on(document, "mouseup", dragEnd) 1767 },
7007 on(display.scroller, "drop", dragEnd) 1768
7008 } 1769 selectionChanged: function() {
7009 1770 var sel = window.getSelection();
7010 // Normal selection, as opposed to text dragging. 1771 return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.l astAnchorOffset ||
7011 function leftButtonSelect(cm, e, start, type, addNew) { 1772 sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocus Offset;
7012 var display = cm.display, doc = cm.doc 1773 },
7013 e_preventDefault(e) 1774
7014 1775 pollSelection: function() {
7015 var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges 1776 if (!this.composing && !this.gracePeriod && this.selectionChanged()) {
7016 if (addNew && !e.shiftKey) { 1777 var sel = window.getSelection(), cm = this.cm;
7017 ourIndex = doc.sel.contains(start) 1778 this.rememberSelection();
7018 if (ourIndex > -1) 1779 var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);
7019 { ourRange = ranges[ourIndex] } 1780 var head = domToPos(cm, sel.focusNode, sel.focusOffset);
1781 if (anchor && head) runInOp(cm, function() {
1782 setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll);
1783 if (anchor.bad || head.bad) cm.curOp.selectionChanged = true;
1784 });
1785 }
1786 },
1787
1788 pollContent: function() {
1789 var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary();
1790 var from = sel.from(), to = sel.to();
1791 if (from.line < display.viewFrom || to.line > display.viewTo - 1) return f alse;
1792
1793 var fromIndex;
1794 if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.l ine)) == 0) {
1795 var fromLine = lineNo(display.view[0].line);
1796 var fromNode = display.view[0].node;
1797 } else {
1798 var fromLine = lineNo(display.view[fromIndex].line);
1799 var fromNode = display.view[fromIndex - 1].node.nextSibling;
1800 }
1801 var toIndex = findViewIndex(cm, to.line);
1802 if (toIndex == display.view.length - 1) {
1803 var toLine = display.viewTo - 1;
1804 var toNode = display.lineDiv.lastChild;
1805 } else {
1806 var toLine = lineNo(display.view[toIndex + 1].line) - 1;
1807 var toNode = display.view[toIndex + 1].node.previousSibling;
1808 }
1809
1810 var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromL ine, toLine));
1811 var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm. doc, toLine).text.length));
1812 while (newText.length > 1 && oldText.length > 1) {
1813 if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine --; }
1814 else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); f romLine++; }
1815 else break;
1816 }
1817
1818 var cutFront = 0, cutEnd = 0;
1819 var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTo p.length, oldTop.length);
1820 while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.cha rCodeAt(cutFront))
1821 ++cutFront;
1822 var newBot = lst(newText), oldBot = lst(oldText);
1823 var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),
1824 oldBot.length - (oldText.length == 1 ? cutFront : 0));
1825 while (cutEnd < maxCutEnd &&
1826 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt( oldBot.length - cutEnd - 1))
1827 ++cutEnd;
1828
1829 newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd);
1830 newText[0] = newText[0].slice(cutFront);
1831
1832 var chFrom = Pos(fromLine, cutFront);
1833 var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0);
1834 if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {
1835 replaceRange(cm.doc, newText, chFrom, chTo, "+input");
1836 return true;
1837 }
1838 },
1839
1840 ensurePolled: function() {
1841 this.forceCompositionEnd();
1842 },
1843 reset: function() {
1844 this.forceCompositionEnd();
1845 },
1846 forceCompositionEnd: function() {
1847 if (!this.composing || this.composing.handled) return;
1848 this.applyComposition(this.composing);
1849 this.composing.handled = true;
1850 this.div.blur();
1851 this.div.focus();
1852 },
1853 applyComposition: function(composing) {
1854 if (this.cm.isReadOnly())
1855 operation(this.cm, regChange)(this.cm)
1856 else if (composing.data && composing.data != composing.startData)
1857 operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing .sel);
1858 },
1859
1860 setUneditable: function(node) {
1861 node.contentEditable = "false"
1862 },
1863
1864 onKeyPress: function(e) {
1865 e.preventDefault();
1866 if (!this.cm.isReadOnly())
1867 operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCo de == null ? e.keyCode : e.charCode), 0);
1868 },
1869
1870 readOnlyChanged: function(val) {
1871 this.div.contentEditable = String(val != "nocursor")
1872 },
1873
1874 onContextMenu: nothing,
1875 resetPosition: nothing,
1876
1877 needsContentAttribute: true
1878 }, ContentEditableInput.prototype);
1879
1880 function posToDOM(cm, pos) {
1881 var view = findViewForLine(cm, pos.line);
1882 if (!view || view.hidden) return null;
1883 var line = getLine(cm.doc, pos.line);
1884 var info = mapFromLineView(view, line, pos.line);
1885
1886 var order = getOrder(line), side = "left";
1887 if (order) {
1888 var partPos = getBidiPartAt(order, pos.ch);
1889 side = partPos % 2 ? "right" : "left";
1890 }
1891 var result = nodeAndOffsetInLineMap(info.map, pos.ch, side);
1892 result.offset = result.collapse == "right" ? result.end : result.start;
1893 return result;
1894 }
1895
1896 function badPos(pos, bad) { if (bad) pos.bad = true; return pos; }
1897
1898 function domToPos(cm, node, offset) {
1899 var lineNode;
1900 if (node == cm.display.lineDiv) {
1901 lineNode = cm.display.lineDiv.childNodes[offset];
1902 if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) ;
1903 node = null; offset = 0;
1904 } else {
1905 for (lineNode = node;; lineNode = lineNode.parentNode) {
1906 if (!lineNode || lineNode == cm.display.lineDiv) return null;
1907 if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) br eak;
1908 }
1909 }
1910 for (var i = 0; i < cm.display.view.length; i++) {
1911 var lineView = cm.display.view[i];
1912 if (lineView.node == lineNode)
1913 return locateNodeInLineView(lineView, node, offset);
1914 }
1915 }
1916
1917 function locateNodeInLineView(lineView, node, offset) {
1918 var wrapper = lineView.text.firstChild, bad = false;
1919 if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.lin e), 0), true);
1920 if (node == wrapper) {
1921 bad = true;
1922 node = wrapper.childNodes[offset];
1923 offset = 0;
1924 if (!node) {
1925 var line = lineView.rest ? lst(lineView.rest) : lineView.line;
1926 return badPos(Pos(lineNo(line), line.text.length), bad);
1927 }
1928 }
1929
1930 var textNode = node.nodeType == 3 ? node : null, topNode = node;
1931 if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
1932 textNode = node.firstChild;
1933 if (offset) offset = textNode.nodeValue.length;
1934 }
1935 while (topNode.parentNode != wrapper) topNode = topNode.parentNode;
1936 var measure = lineView.measure, maps = measure.maps;
1937
1938 function find(textNode, topNode, offset) {
1939 for (var i = -1; i < (maps ? maps.length : 0); i++) {
1940 var map = i < 0 ? measure.map : maps[i];
1941 for (var j = 0; j < map.length; j += 3) {
1942 var curNode = map[j + 2];
1943 if (curNode == textNode || curNode == topNode) {
1944 var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]);
1945 var ch = map[j] + offset;
1946 if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0) ];
1947 return Pos(line, ch);
1948 }
1949 }
1950 }
1951 }
1952 var found = find(textNode, topNode, offset);
1953 if (found) return badPos(found, bad);
1954
1955 // FIXME this is all really shaky. might handle the few cases it needs to ha ndle, but likely to cause problems
1956 for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.l ength - offset : 0; after; after = after.nextSibling) {
1957 found = find(after, after.firstChild, 0);
1958 if (found)
1959 return badPos(Pos(found.line, found.ch - dist), bad);
1960 else
1961 dist += after.textContent.length;
1962 }
1963 for (var before = topNode.previousSibling, dist = offset; before; before = b efore.previousSibling) {
1964 found = find(before, before.firstChild, -1);
1965 if (found)
1966 return badPos(Pos(found.line, found.ch + dist), bad);
1967 else
1968 dist += after.textContent.length;
1969 }
1970 }
1971
1972 function domTextBetween(cm, from, to, fromLine, toLine) {
1973 var text = "", closing = false, lineSep = cm.doc.lineSeparator();
1974 function recognizeMarker(id) { return function(marker) { return marker.id == id; }; }
1975 function walk(node) {
1976 if (node.nodeType == 1) {
1977 var cmText = node.getAttribute("cm-text");
1978 if (cmText != null) {
1979 if (cmText == "") cmText = node.textContent.replace(/\u200b/g, "");
1980 text += cmText;
1981 return;
1982 }
1983 var markerID = node.getAttribute("cm-marker"), range;
1984 if (markerID) {
1985 var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recogni zeMarker(+markerID));
1986 if (found.length && (range = found[0].find()))
1987 text += getBetween(cm.doc, range.from, range.to).join(lineSep);
1988 return;
1989 }
1990 if (node.getAttribute("contenteditable") == "false") return;
1991 for (var i = 0; i < node.childNodes.length; i++)
1992 walk(node.childNodes[i]);
1993 if (/^(pre|div|p)$/i.test(node.nodeName))
1994 closing = true;
1995 } else if (node.nodeType == 3) {
1996 var val = node.nodeValue;
1997 if (!val) return;
1998 if (closing) {
1999 text += lineSep;
2000 closing = false;
2001 }
2002 text += val;
2003 }
2004 }
2005 for (;;) {
2006 walk(from);
2007 if (from == to) break;
2008 from = from.nextSibling;
2009 }
2010 return text;
2011 }
2012
2013 CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": Conten tEditableInput};
2014
2015 // SELECTION / CURSOR
2016
2017 // Selection objects are immutable. A new one is created every time
2018 // the selection changes. A selection is one or more non-overlapping
2019 // (and non-touching) ranges, sorted, and an integer that indicates
2020 // which one is the primary selection (the one that's scrolled into
2021 // view, that getCursor returns, etc).
2022 function Selection(ranges, primIndex) {
2023 this.ranges = ranges;
2024 this.primIndex = primIndex;
2025 }
2026
2027 Selection.prototype = {
2028 primary: function() { return this.ranges[this.primIndex]; },
2029 equals: function(other) {
2030 if (other == this) return true;
2031 if (other.primIndex != this.primIndex || other.ranges.length != this.range s.length) return false;
2032 for (var i = 0; i < this.ranges.length; i++) {
2033 var here = this.ranges[i], there = other.ranges[i];
2034 if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false;
2035 }
2036 return true;
2037 },
2038 deepCopy: function() {
2039 for (var out = [], i = 0; i < this.ranges.length; i++)
2040 out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i ].head));
2041 return new Selection(out, this.primIndex);
2042 },
2043 somethingSelected: function() {
2044 for (var i = 0; i < this.ranges.length; i++)
2045 if (!this.ranges[i].empty()) return true;
2046 return false;
2047 },
2048 contains: function(pos, end) {
2049 if (!end) end = pos;
2050 for (var i = 0; i < this.ranges.length; i++) {
2051 var range = this.ranges[i];
2052 if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
2053 return i;
2054 }
2055 return -1;
2056 }
2057 };
2058
2059 function Range(anchor, head) {
2060 this.anchor = anchor; this.head = head;
2061 }
2062
2063 Range.prototype = {
2064 from: function() { return minPos(this.anchor, this.head); },
2065 to: function() { return maxPos(this.anchor, this.head); },
2066 empty: function() {
2067 return this.head.line == this.anchor.line && this.head.ch == this.anchor.c h;
2068 }
2069 };
2070
2071 // Take an unsorted, potentially overlapping set of ranges, and
2072 // build a selection out of it. 'Consumes' ranges array (modifying
2073 // it).
2074 function normalizeSelection(ranges, primIndex) {
2075 var prim = ranges[primIndex];
2076 ranges.sort(function(a, b) { return cmp(a.from(), b.from()); });
2077 primIndex = indexOf(ranges, prim);
2078 for (var i = 1; i < ranges.length; i++) {
2079 var cur = ranges[i], prev = ranges[i - 1];
2080 if (cmp(prev.to(), cur.from()) >= 0) {
2081 var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.t o());
2082 var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.he ad;
2083 if (i <= primIndex) --primIndex;
2084 ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));
2085 }
2086 }
2087 return new Selection(ranges, primIndex);
2088 }
2089
2090 function simpleSelection(anchor, head) {
2091 return new Selection([new Range(anchor, head || anchor)], 0);
2092 }
2093
2094 // Most of the external API clips given positions to make sure they
2095 // actually exist within the document.
2096 function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
2097 function clipPos(doc, pos) {
2098 if (pos.line < doc.first) return Pos(doc.first, 0);
2099 var last = doc.first + doc.size - 1;
2100 if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
2101 return clipToLen(pos, getLine(doc, pos.line).text.length);
2102 }
2103 function clipToLen(pos, linelen) {
2104 var ch = pos.ch;
2105 if (ch == null || ch > linelen) return Pos(pos.line, linelen);
2106 else if (ch < 0) return Pos(pos.line, 0);
2107 else return pos;
2108 }
2109 function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
2110 function clipPosArray(doc, array) {
2111 for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array [i]);
2112 return out;
2113 }
2114
2115 // SELECTION UPDATES
2116
2117 // The 'scroll' parameter given to many of these indicated whether
2118 // the new cursor position should be scrolled into view after
2119 // modifying the selection.
2120
2121 // If shift is held or the extend flag is set, extends a range to
2122 // include a given position (and optionally a second position).
2123 // Otherwise, simply returns the range between the given positions.
2124 // Used for cursor motion and such.
2125 function extendRange(doc, range, head, other) {
2126 if (doc.cm && doc.cm.display.shift || doc.extend) {
2127 var anchor = range.anchor;
2128 if (other) {
2129 var posBefore = cmp(head, anchor) < 0;
2130 if (posBefore != (cmp(other, anchor) < 0)) {
2131 anchor = head;
2132 head = other;
2133 } else if (posBefore != (cmp(head, other) < 0)) {
2134 head = other;
2135 }
2136 }
2137 return new Range(anchor, head);
2138 } else {
2139 return new Range(other || head, head);
2140 }
2141 }
2142
2143 // Extend the primary selection range, discard the rest.
2144 function extendSelection(doc, head, other, options) {
2145 setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, o ther)], 0), options);
2146 }
2147
2148 // Extend all selections (pos is an array of selections with length
2149 // equal the number of selections)
2150 function extendSelections(doc, heads, options) {
2151 for (var out = [], i = 0; i < doc.sel.ranges.length; i++)
2152 out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null);
2153 var newSel = normalizeSelection(out, doc.sel.primIndex);
2154 setSelection(doc, newSel, options);
2155 }
2156
2157 // Updates a single range in the selection.
2158 function replaceOneSelection(doc, i, range, options) {
2159 var ranges = doc.sel.ranges.slice(0);
2160 ranges[i] = range;
2161 setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options);
2162 }
2163
2164 // Reset the selection to a single range.
2165 function setSimpleSelection(doc, anchor, head, options) {
2166 setSelection(doc, simpleSelection(anchor, head), options);
2167 }
2168
2169 // Give beforeSelectionChange handlers a change to influence a
2170 // selection update.
2171 function filterSelectionChange(doc, sel, options) {
2172 var obj = {
2173 ranges: sel.ranges,
2174 update: function(ranges) {
2175 this.ranges = [];
2176 for (var i = 0; i < ranges.length; i++)
2177 this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
2178 clipPos(doc, ranges[i].head));
2179 },
2180 origin: options && options.origin
2181 };
2182 signal(doc, "beforeSelectionChange", doc, obj);
2183 if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
2184 if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.rang es.length - 1);
2185 else return sel;
2186 }
2187
2188 function setSelectionReplaceHistory(doc, sel, options) {
2189 var done = doc.history.done, last = lst(done);
2190 if (last && last.ranges) {
2191 done[done.length - 1] = sel;
2192 setSelectionNoUndo(doc, sel, options);
2193 } else {
2194 setSelection(doc, sel, options);
2195 }
2196 }
2197
2198 // Set a new selection.
2199 function setSelection(doc, sel, options) {
2200 setSelectionNoUndo(doc, sel, options);
2201 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options) ;
2202 }
2203
2204 function setSelectionNoUndo(doc, sel, options) {
2205 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
2206 sel = filterSelectionChange(doc, sel, options);
2207
2208 var bias = options && options.bias ||
2209 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
2210 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
2211
2212 if (!(options && options.scroll === false) && doc.cm)
2213 ensureCursorVisible(doc.cm);
2214 }
2215
2216 function setSelectionInner(doc, sel) {
2217 if (sel.equals(doc.sel)) return;
2218
2219 doc.sel = sel;
2220
2221 if (doc.cm) {
2222 doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
2223 signalCursorActivity(doc.cm);
2224 }
2225 signalLater(doc, "cursorActivity", doc);
2226 }
2227
2228 // Verify that the selection does not partially select any atomic
2229 // marked ranges.
2230 function reCheckSelection(doc) {
2231 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel _dontScroll);
2232 }
2233
2234 // Return a selection that does not partially select any atomic
2235 // ranges.
2236 function skipAtomicInSelection(doc, sel, bias, mayClear) {
2237 var out;
2238 for (var i = 0; i < sel.ranges.length; i++) {
2239 var range = sel.ranges[i];
2240 var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];
2241 var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, may Clear);
2242 var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear) ;
2243 if (out || newAnchor != range.anchor || newHead != range.head) {
2244 if (!out) out = sel.ranges.slice(0, i);
2245 out[i] = new Range(newAnchor, newHead);
2246 }
2247 }
2248 return out ? normalizeSelection(out, sel.primIndex) : sel;
2249 }
2250
2251 function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
2252 var line = getLine(doc, pos.line);
2253 if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
2254 var sp = line.markedSpans[i], m = sp.marker;
2255 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < p os.ch)) &&
2256 (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch ))) {
2257 if (mayClear) {
2258 signal(m, "beforeCursorEnter");
2259 if (m.explicitlyCleared) {
2260 if (!line.markedSpans) break;
2261 else {--i; continue;}
2262 }
2263 }
2264 if (!m.atomic) continue;
2265
2266 if (oldPos) {
2267 var near = m.find(dir < 0 ? 1 : -1), diff;
2268 if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft)
2269 near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null);
2270 if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (di r < 0 ? diff < 0 : diff > 0))
2271 return skipAtomicInner(doc, near, pos, dir, mayClear);
2272 }
2273
2274 var far = m.find(dir < 0 ? -1 : 1);
2275 if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight)
2276 far = movePos(doc, far, dir, far.line == pos.line ? line : null);
2277 return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null;
2278 }
2279 }
2280 return pos;
2281 }
2282
2283 // Ensure a given position is not inside an atomic range.
2284 function skipAtomic(doc, pos, oldPos, bias, mayClear) {
2285 var dir = bias || 1;
2286 var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
2287 (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
2288 skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
2289 (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true));
2290 if (!found) {
2291 doc.cantEdit = true;
2292 return Pos(doc.first, 0);
2293 }
2294 return found;
2295 }
2296
2297 function movePos(doc, pos, dir, line) {
2298 if (dir < 0 && pos.ch == 0) {
2299 if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1));
2300 else return null;
2301 } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length ) {
2302 if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0);
2303 else return null;
2304 } else {
2305 return new Pos(pos.line, pos.ch + dir);
2306 }
2307 }
2308
2309 // SELECTION DRAWING
2310
2311 function updateSelection(cm) {
2312 cm.display.input.showSelection(cm.display.input.prepareSelection());
2313 }
2314
2315 function prepareSelection(cm, primary) {
2316 var doc = cm.doc, result = {};
2317 var curFragment = result.cursors = document.createDocumentFragment();
2318 var selFragment = result.selection = document.createDocumentFragment();
2319
2320 for (var i = 0; i < doc.sel.ranges.length; i++) {
2321 if (primary === false && i == doc.sel.primIndex) continue;
2322 var range = doc.sel.ranges[i];
2323 if (range.from().line >= cm.display.viewTo || range.to().line < cm.display .viewFrom) continue;
2324 var collapsed = range.empty();
2325 if (collapsed || cm.options.showCursorWhenSelecting)
2326 drawSelectionCursor(cm, range.head, curFragment);
2327 if (!collapsed)
2328 drawSelectionRange(cm, range, selFragment);
2329 }
2330 return result;
2331 }
2332
2333 // Draws a cursor for the given range
2334 function drawSelectionCursor(cm, head, output) {
2335 var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursor HeightPerLine);
2336
2337 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
2338 cursor.style.left = pos.left + "px";
2339 cursor.style.top = pos.top + "px";
2340 cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorH eight + "px";
2341
2342 if (pos.other) {
2343 // Secondary cursor, shown when on a 'jump' in bi-directional text
2344 var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-curs or CodeMirror-secondarycursor"));
2345 otherCursor.style.display = "";
2346 otherCursor.style.left = pos.other.left + "px";
2347 otherCursor.style.top = pos.other.top + "px";
2348 otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px" ;
2349 }
2350 }
2351
2352 // Draws the given range as a highlighted selection
2353 function drawSelectionRange(cm, range, output) {
2354 var display = cm.display, doc = cm.doc;
2355 var fragment = document.createDocumentFragment();
2356 var padding = paddingH(cm.display), leftSide = padding.left;
2357 var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.size r.offsetLeft) - padding.right;
2358
2359 function add(left, top, width, bottom) {
2360 if (top < 0) top = 0;
2361 top = Math.round(top);
2362 bottom = Math.round(bottom);
2363 fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: ab solute; left: " + left +
2364 "px; top: " + top + "px; width: " + (width == nul l ? rightSide - left : width) +
2365 "px; height: " + (bottom - top) + "px"));
2366 }
2367
2368 function drawForLine(line, fromArg, toArg) {
2369 var lineObj = getLine(doc, line);
2370 var lineLen = lineObj.text.length;
2371 var start, end;
2372 function coords(ch, bias) {
2373 return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
2374 }
2375
2376 iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineL en : toArg, function(from, to, dir) {
2377 var leftPos = coords(from, "left"), rightPos, left, right;
2378 if (from == to) {
2379 rightPos = leftPos;
2380 left = right = leftPos.left;
2381 } else {
2382 rightPos = coords(to - 1, "right");
2383 if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
2384 left = leftPos.left;
2385 right = rightPos.right;
2386 }
2387 if (fromArg == null && from == 0) left = leftSide;
2388 if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
2389 add(left, leftPos.top, null, leftPos.bottom);
2390 left = leftSide;
2391 if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rig htPos.top);
2392 }
2393 if (toArg == null && to == lineLen) right = rightSide;
2394 if (!start || leftPos.top < start.top || leftPos.top == start.top && lef tPos.left < start.left)
2395 start = leftPos;
2396 if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.botto m && rightPos.right > end.right)
2397 end = rightPos;
2398 if (left < leftSide + 1) left = leftSide;
2399 add(left, rightPos.top, right - left, rightPos.bottom);
2400 });
2401 return {start: start, end: end};
2402 }
2403
2404 var sFrom = range.from(), sTo = range.to();
2405 if (sFrom.line == sTo.line) {
2406 drawForLine(sFrom.line, sFrom.ch, sTo.ch);
2407 } else {
2408 var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);
2409 var singleVLine = visualLine(fromLine) == visualLine(toLine);
2410 var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.tex t.length + 1 : null).end;
2411 var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).sta rt;
2412 if (singleVLine) {
2413 if (leftEnd.top < rightStart.top - 2) {
2414 add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
2415 add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);
2416 } else {
2417 add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftE nd.bottom);
2418 }
2419 }
2420 if (leftEnd.bottom < rightStart.top)
2421 add(leftSide, leftEnd.bottom, null, rightStart.top);
2422 }
2423
2424 output.appendChild(fragment);
2425 }
2426
2427 // Cursor-blinking
2428 function restartBlink(cm) {
2429 if (!cm.state.focused) return;
2430 var display = cm.display;
2431 clearInterval(display.blinker);
2432 var on = true;
2433 display.cursorDiv.style.visibility = "";
2434 if (cm.options.cursorBlinkRate > 0)
2435 display.blinker = setInterval(function() {
2436 display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden";
2437 }, cm.options.cursorBlinkRate);
2438 else if (cm.options.cursorBlinkRate < 0)
2439 display.cursorDiv.style.visibility = "hidden";
2440 }
2441
2442 // HIGHLIGHT WORKER
2443
2444 function startWorker(cm, time) {
2445 if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo)
2446 cm.state.highlight.set(time, bind(highlightWorker, cm));
2447 }
2448
2449 function highlightWorker(cm) {
2450 var doc = cm.doc;
2451 if (doc.frontier < doc.first) doc.frontier = doc.first;
2452 if (doc.frontier >= cm.display.viewTo) return;
2453 var end = +new Date + cm.options.workTime;
2454 var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
2455 var changedLines = [];
2456
2457 doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 50 0), function(line) {
2458 if (doc.frontier >= cm.display.viewFrom) { // Visible
2459 var oldStyles = line.styles, tooLong = line.text.length > cm.options.max HighlightLength;
2460 var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, state) : state, true);
2461 line.styles = highlighted.styles;
2462 var oldCls = line.styleClasses, newCls = highlighted.classes;
2463 if (newCls) line.styleClasses = newCls;
2464 else if (oldCls) line.styleClasses = null;
2465 var ischange = !oldStyles || oldStyles.length != line.styles.length ||
2466 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bg Class || oldCls.textClass != newCls.textClass);
2467 for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldSt yles[i] != line.styles[i];
2468 if (ischange) changedLines.push(doc.frontier);
2469 line.stateAfter = tooLong ? state : copyState(doc.mode, state);
2470 } else {
2471 if (line.text.length <= cm.options.maxHighlightLength)
2472 processLine(cm, line.text, state);
2473 line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : n ull;
2474 }
2475 ++doc.frontier;
2476 if (+new Date > end) {
2477 startWorker(cm, cm.options.workDelay);
2478 return true;
2479 }
2480 });
2481 if (changedLines.length) runInOp(cm, function() {
2482 for (var i = 0; i < changedLines.length; i++)
2483 regLineChange(cm, changedLines[i], "text");
2484 });
2485 }
2486
2487 // Finds the line to start with when starting a parse. Tries to
2488 // find a line with a stateAfter, so that it can start with a
2489 // valid state. If that fails, it returns the line with the
2490 // smallest indentation, which tends to need the least context to
2491 // parse correctly.
2492 function findStartLine(cm, n, precise) {
2493 var minindent, minline, doc = cm.doc;
2494 var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);
2495 for (var search = n; search > lim; --search) {
2496 if (search <= doc.first) return doc.first;
2497 var line = getLine(doc, search - 1);
2498 if (line.stateAfter && (!precise || search <= doc.frontier)) return search ;
2499 var indented = countColumn(line.text, null, cm.options.tabSize);
2500 if (minline == null || minindent > indented) {
2501 minline = search - 1;
2502 minindent = indented;
2503 }
2504 }
2505 return minline;
2506 }
2507
2508 function getStateBefore(cm, n, precise) {
2509 var doc = cm.doc, display = cm.display;
2510 if (!doc.mode.startState) return true;
2511 var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine( doc, pos-1).stateAfter;
2512 if (!state) state = startState(doc.mode);
2513 else state = copyState(doc.mode, state);
2514 doc.iter(pos, n, function(line) {
2515 processLine(cm, line.text, state);
2516 var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo;
2517 line.stateAfter = save ? copyState(doc.mode, state) : null;
2518 ++pos;
2519 });
2520 if (precise) doc.frontier = pos;
2521 return state;
2522 }
2523
2524 // POSITION MEASUREMENT
2525
2526 function paddingTop(display) {return display.lineSpace.offsetTop;}
2527 function paddingVert(display) {return display.mover.offsetHeight - display.lin eSpace.offsetHeight;}
2528 function paddingH(display) {
2529 if (display.cachedPaddingH) return display.cachedPaddingH;
2530 var e = removeChildrenAndAdd(display.measure, elt("pre", "x"));
2531 var style = window.getComputedStyle ? window.getComputedStyle(e) : e.current Style;
2532 var data = {left: parseInt(style.paddingLeft), right: parseInt(style.padding Right)};
2533 if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data;
2534 return data;
2535 }
2536
2537 function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; }
2538 function displayWidth(cm) {
2539 return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth ;
2540 }
2541 function displayHeight(cm) {
2542 return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeig ht;
2543 }
2544
2545 // Ensure the lineView.wrapping.heights array is populated. This is
2546 // an array of bottom offsets for the lines that make up a drawn
2547 // line. When lineWrapping is on, there might be more than one
2548 // height.
2549 function ensureLineHeights(cm, lineView, rect) {
2550 var wrapping = cm.options.lineWrapping;
2551 var curWidth = wrapping && displayWidth(cm);
2552 if (!lineView.measure.heights || wrapping && lineView.measure.width != curWi dth) {
2553 var heights = lineView.measure.heights = [];
2554 if (wrapping) {
2555 lineView.measure.width = curWidth;
2556 var rects = lineView.text.firstChild.getClientRects();
2557 for (var i = 0; i < rects.length - 1; i++) {
2558 var cur = rects[i], next = rects[i + 1];
2559 if (Math.abs(cur.bottom - next.bottom) > 2)
2560 heights.push((cur.bottom + next.top) / 2 - rect.top);
2561 }
2562 }
2563 heights.push(rect.bottom - rect.top);
2564 }
2565 }
2566
2567 // Find a line map (mapping character offsets to text nodes) and a
2568 // measurement cache for the given line number. (A line view might
2569 // contain multiple lines when collapsed ranges are present.)
2570 function mapFromLineView(lineView, line, lineN) {
2571 if (lineView.line == line)
2572 return {map: lineView.measure.map, cache: lineView.measure.cache};
2573 for (var i = 0; i < lineView.rest.length; i++)
2574 if (lineView.rest[i] == line)
2575 return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i] };
2576 for (var i = 0; i < lineView.rest.length; i++)
2577 if (lineNo(lineView.rest[i]) > lineN)
2578 return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i] , before: true};
2579 }
2580
2581 // Render a line into the hidden node display.externalMeasured. Used
2582 // when measurement is needed for a line that's not in the viewport.
2583 function updateExternalMeasurement(cm, line) {
2584 line = visualLine(line);
2585 var lineN = lineNo(line);
2586 var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);
2587 view.lineN = lineN;
2588 var built = view.built = buildLineContent(cm, view);
2589 view.text = built.pre;
2590 removeChildrenAndAdd(cm.display.lineMeasure, built.pre);
2591 return view;
2592 }
2593
2594 // Get a {top, bottom, left, right} box (in line-local coordinates)
2595 // for a given character.
2596 function measureChar(cm, line, ch, bias) {
2597 return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias);
2598 }
2599
2600 // Find a line view that corresponds to the given line number.
2601 function findViewForLine(cm, lineN) {
2602 if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
2603 return cm.display.view[findViewIndex(cm, lineN)];
2604 var ext = cm.display.externalMeasured;
2605 if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
2606 return ext;
2607 }
2608
2609 // Measurement can be split in two steps, the set-up work that
2610 // applies to the whole line, and the measurement of the actual
2611 // character. Functions like coordsChar, that need to do a lot of
2612 // measurements in a row, can thus ensure that the set-up work is
2613 // only done once.
2614 function prepareMeasureForLine(cm, line) {
2615 var lineN = lineNo(line);
2616 var view = findViewForLine(cm, lineN);
2617 if (view && !view.text) {
2618 view = null;
2619 } else if (view && view.changes) {
2620 updateLineForChanges(cm, view, lineN, getDimensions(cm));
2621 cm.curOp.forceUpdate = true;
2622 }
2623 if (!view)
2624 view = updateExternalMeasurement(cm, line);
2625
2626 var info = mapFromLineView(view, line, lineN);
2627 return {
2628 line: line, view: view, rect: null,
2629 map: info.map, cache: info.cache, before: info.before,
2630 hasHeights: false
2631 };
2632 }
2633
2634 // Given a prepared measurement object, measures the position of an
2635 // actual character (or fetches it from the cache).
2636 function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
2637 if (prepared.before) ch = -1;
2638 var key = ch + (bias || ""), found;
2639 if (prepared.cache.hasOwnProperty(key)) {
2640 found = prepared.cache[key];
2641 } else {
2642 if (!prepared.rect)
2643 prepared.rect = prepared.view.text.getBoundingClientRect();
2644 if (!prepared.hasHeights) {
2645 ensureLineHeights(cm, prepared.view, prepared.rect);
2646 prepared.hasHeights = true;
2647 }
2648 found = measureCharInner(cm, prepared, ch, bias);
2649 if (!found.bogus) prepared.cache[key] = found;
2650 }
2651 return {left: found.left, right: found.right,
2652 top: varHeight ? found.rtop : found.top,
2653 bottom: varHeight ? found.rbottom : found.bottom};
2654 }
2655
2656 var nullRect = {left: 0, right: 0, top: 0, bottom: 0};
2657
2658 function nodeAndOffsetInLineMap(map, ch, bias) {
2659 var node, start, end, collapse;
2660 // First, search the line map for the text node corresponding to,
2661 // or closest to, the target character.
2662 for (var i = 0; i < map.length; i += 3) {
2663 var mStart = map[i], mEnd = map[i + 1];
2664 if (ch < mStart) {
2665 start = 0; end = 1;
2666 collapse = "left";
2667 } else if (ch < mEnd) {
2668 start = ch - mStart;
2669 end = start + 1;
2670 } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {
2671 end = mEnd - mStart;
2672 start = end - 1;
2673 if (ch >= mEnd) collapse = "right";
2674 }
2675 if (start != null) {
2676 node = map[i + 2];
2677 if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
2678 collapse = bias;
2679 if (bias == "left" && start == 0)
2680 while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {
2681 node = map[(i -= 3) + 2];
2682 collapse = "left";
2683 }
2684 if (bias == "right" && start == mEnd - mStart)
2685 while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].i nsertLeft) {
2686 node = map[(i += 3) + 2];
2687 collapse = "right";
2688 }
2689 break;
2690 }
2691 }
2692 return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd};
2693 }
2694
2695 function getUsefulRect(rects, bias) {
2696 var rect = nullRect
2697 if (bias == "left") for (var i = 0; i < rects.length; i++) {
2698 if ((rect = rects[i]).left != rect.right) break
2699 } else for (var i = rects.length - 1; i >= 0; i--) {
2700 if ((rect = rects[i]).left != rect.right) break
2701 }
2702 return rect
2703 }
2704
2705 function measureCharInner(cm, prepared, ch, bias) {
2706 var place = nodeAndOffsetInLineMap(prepared.map, ch, bias);
2707 var node = place.node, start = place.start, end = place.end, collapse = plac e.collapse;
2708
2709 var rect;
2710 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve t he coordinates.
2711 for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned
2712 while (start && isExtendingChar(prepared.line.text.charAt(place.coverSta rt + start))) --start;
2713 while (place.coverStart + end < place.coverEnd && isExtendingChar(prepar ed.line.text.charAt(place.coverStart + end))) ++end;
2714 if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place. coverStart)
2715 rect = node.parentNode.getBoundingClientRect();
2716 else
2717 rect = getUsefulRect(range(node, start, end).getClientRects(), bias)
2718 if (rect.left || rect.right || start == 0) break;
2719 end = start;
2720 start = start - 1;
2721 collapse = "right";
2722 }
2723 if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.mea sure, rect);
2724 } else { // If it is a widget, simply get the box for the whole widget.
2725 if (start > 0) collapse = bias = "right";
2726 var rects;
2727 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
2728 rect = rects[bias == "right" ? rects.length - 1 : 0];
2729 else
2730 rect = node.getBoundingClientRect();
2731 }
2732 if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {
2733 var rSpan = node.parentNode.getClientRects()[0];
2734 if (rSpan)
2735 rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top : rSpan.top, bottom: rSpan.bottom};
2736 else
2737 rect = nullRect;
2738 }
2739
2740 var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect. top;
2741 var mid = (rtop + rbot) / 2;
2742 var heights = prepared.view.measure.heights;
2743 for (var i = 0; i < heights.length - 1; i++)
2744 if (mid < heights[i]) break;
2745 var top = i ? heights[i - 1] : 0, bot = heights[i];
2746 var result = {left: (collapse == "right" ? rect.right : rect.left) - prepare d.rect.left,
2747 right: (collapse == "left" ? rect.left : rect.right) - prepare d.rect.left,
2748 top: top, bottom: bot};
2749 if (!rect.left && !rect.right) result.bogus = true;
2750 if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbot tom = rbot; }
2751
2752 return result;
2753 }
2754
2755 // Work around problem with bounding client rects on ranges being
2756 // returned incorrectly when zoomed on IE10 and below.
2757 function maybeUpdateRectForZooming(measure, rect) {
2758 if (!window.screen || screen.logicalXDPI == null ||
2759 screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
2760 return rect;
2761 var scaleX = screen.logicalXDPI / screen.deviceXDPI;
2762 var scaleY = screen.logicalYDPI / screen.deviceYDPI;
2763 return {left: rect.left * scaleX, right: rect.right * scaleX,
2764 top: rect.top * scaleY, bottom: rect.bottom * scaleY};
2765 }
2766
2767 function clearLineMeasurementCacheFor(lineView) {
2768 if (lineView.measure) {
2769 lineView.measure.cache = {};
2770 lineView.measure.heights = null;
2771 if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
2772 lineView.measure.caches[i] = {};
2773 }
2774 }
2775
2776 function clearLineMeasurementCache(cm) {
2777 cm.display.externalMeasure = null;
2778 removeChildren(cm.display.lineMeasure);
2779 for (var i = 0; i < cm.display.view.length; i++)
2780 clearLineMeasurementCacheFor(cm.display.view[i]);
2781 }
2782
2783 function clearCaches(cm) {
2784 clearLineMeasurementCache(cm);
2785 cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cached PaddingH = null;
2786 if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
2787 cm.display.lineNumChars = null;
2788 }
2789
2790 function pageScrollX() { return window.pageXOffset || (document.documentElemen t || document.body).scrollLeft; }
2791 function pageScrollY() { return window.pageYOffset || (document.documentElemen t || document.body).scrollTop; }
2792
2793 // Converts a {top, bottom, left, right} box from line-local
2794 // coordinates into another coordinate system. Context may be one of
2795 // "line", "div" (display.lineDiv), "local"/null (editor), "window",
2796 // or "page".
2797 function intoCoordSystem(cm, lineObj, rect, context) {
2798 if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (li neObj.widgets[i].above) {
2799 var size = widgetHeight(lineObj.widgets[i]);
2800 rect.top += size; rect.bottom += size;
2801 }
2802 if (context == "line") return rect;
2803 if (!context) context = "local";
2804 var yOff = heightAtLine(lineObj);
2805 if (context == "local") yOff += paddingTop(cm.display);
2806 else yOff -= cm.display.viewOffset;
2807 if (context == "page" || context == "window") {
2808 var lOff = cm.display.lineSpace.getBoundingClientRect();
2809 yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
2810 var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
2811 rect.left += xOff; rect.right += xOff;
2812 }
2813 rect.top += yOff; rect.bottom += yOff;
2814 return rect;
2815 }
2816
2817 // Coverts a box from "div" coords to another coordinate system.
2818 // Context may be "window", "page", "div", or "local"/null.
2819 function fromCoordSystem(cm, coords, context) {
2820 if (context == "div") return coords;
2821 var left = coords.left, top = coords.top;
2822 // First move into "page" coordinate system
2823 if (context == "page") {
2824 left -= pageScrollX();
2825 top -= pageScrollY();
2826 } else if (context == "local" || !context) {
2827 var localBox = cm.display.sizer.getBoundingClientRect();
2828 left += localBox.left;
2829 top += localBox.top;
2830 }
2831
2832 var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();
2833 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
2834 }
2835
2836 function charCoords(cm, pos, context, lineObj, bias) {
2837 if (!lineObj) lineObj = getLine(cm.doc, pos.line);
2838 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context);
2839 }
2840
2841 // Returns a box for a given cursor position, which may have an
2842 // 'other' property containing the position of the secondary cursor
2843 // on a bidi boundary.
2844 function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {
2845 lineObj = lineObj || getLine(cm.doc, pos.line);
2846 if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj);
2847 function get(ch, right) {
2848 var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "le ft", varHeight);
2849 if (right) m.left = m.right; else m.right = m.left;
2850 return intoCoordSystem(cm, lineObj, m, context);
2851 }
2852 function getBidi(ch, partPos) {
2853 var part = order[partPos], right = part.level % 2;
2854 if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].lev el) {
2855 part = order[--partPos];
2856 ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
2857 right = true;
2858 } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.lev el < order[partPos + 1].level) {
2859 part = order[++partPos];
2860 ch = bidiLeft(part) - part.level % 2;
2861 right = false;
2862 }
2863 if (right && ch == part.to && ch > part.from) return get(ch - 1);
2864 return get(ch, right);
2865 }
2866 var order = getOrder(lineObj), ch = pos.ch;
2867 if (!order) return get(ch);
2868 var partPos = getBidiPartAt(order, ch);
2869 var val = getBidi(ch, partPos);
2870 if (bidiOther != null) val.other = getBidi(ch, bidiOther);
2871 return val;
2872 }
2873
2874 // Used to cheaply estimate the coordinates for a position. Used for
2875 // intermediate scroll updates.
2876 function estimateCoords(cm, pos) {
2877 var left = 0, pos = clipPos(cm.doc, pos);
2878 if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch;
2879 var lineObj = getLine(cm.doc, pos.line);
2880 var top = heightAtLine(lineObj) + paddingTop(cm.display);
2881 return {left: left, right: left, top: top, bottom: top + lineObj.height};
2882 }
2883
2884 // Positions returned by coordsChar contain some extra information.
2885 // xRel is the relative x position of the input coordinates compared
2886 // to the found position (so xRel > 0 means the coordinates are to
2887 // the right of the character position, for example). When outside
2888 // is true, that means the coordinates lie outside the line's
2889 // vertical range.
2890 function PosWithInfo(line, ch, outside, xRel) {
2891 var pos = Pos(line, ch);
2892 pos.xRel = xRel;
2893 if (outside) pos.outside = true;
2894 return pos;
2895 }
2896
2897 // Compute the character position closest to the given coordinates.
2898 // Input must be lineSpace-local ("div" coordinate system).
2899 function coordsChar(cm, x, y) {
2900 var doc = cm.doc;
2901 y += cm.display.viewOffset;
2902 if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
2903 var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
2904 if (lineN > last)
2905 return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.lengt h, true, 1);
2906 if (x < 0) x = 0;
2907
2908 var lineObj = getLine(doc, lineN);
2909 for (;;) {
2910 var found = coordsCharInner(cm, lineObj, lineN, x, y);
2911 var merged = collapsedSpanAtEnd(lineObj);
2912 var mergedPos = merged && merged.find(0, true);
2913 if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from. ch && found.xRel > 0))
2914 lineN = lineNo(lineObj = mergedPos.to.line);
2915 else
2916 return found;
2917 }
2918 }
2919
2920 function coordsCharInner(cm, lineObj, lineNo, x, y) {
2921 var innerOff = y - heightAtLine(lineObj);
2922 var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
2923 var preparedMeasure = prepareMeasureForLine(cm, lineObj);
2924
2925 function getX(ch) {
2926 var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasur e);
2927 wrongLine = true;
2928 if (innerOff > sp.bottom) return sp.left - adjust;
2929 else if (innerOff < sp.top) return sp.left + adjust;
2930 else wrongLine = false;
2931 return sp.left;
2932 }
2933
2934 var bidi = getOrder(lineObj), dist = lineObj.text.length;
2935 var from = lineLeft(lineObj), to = lineRight(lineObj);
2936 var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
2937
2938 if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
2939 // Do a binary search between these bounds.
2940 for (;;) {
2941 if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
2942 var ch = x < fromX || x - fromX <= toX - x ? from : to;
2943 var outside = ch == from ? fromOutside : toOutside
2944 var xDiff = x - (ch == from ? fromX : toX);
2945 // This is a kludge to handle the case where the coordinates
2946 // are after a line-wrapped line. We should replace it with a
2947 // more general handling of cursor positions around line
2948 // breaks. (Issue #4078)
2949 if (toOutside && !bidi && !/\s/.test(lineObj.text.charAt(ch)) && xDiff > 0 &&
2950 ch < lineObj.text.length && preparedMeasure.view.measure.heights.len gth > 1) {
2951 var charSize = measureCharPrepared(cm, preparedMeasure, ch, "right");
2952 if (innerOff <= charSize.bottom && innerOff >= charSize.top && Math.ab s(x - charSize.right) < xDiff) {
2953 outside = false
2954 ch++
2955 xDiff = x - charSize.right
2956 }
2957 }
2958 while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
2959 var pos = PosWithInfo(lineNo, ch, outside, xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
2960 return pos;
2961 }
2962 var step = Math.ceil(dist / 2), middle = from + step;
2963 if (bidi) {
2964 middle = from;
2965 for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1) ;
2966 }
2967 var middleX = getX(middle);
2968 if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) t oX += 1000; dist = step;}
2969 else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= ste p;}
2970 }
2971 }
2972
2973 var measureText;
2974 // Compute the default text height.
2975 function textHeight(display) {
2976 if (display.cachedTextHeight != null) return display.cachedTextHeight;
2977 if (measureText == null) {
2978 measureText = elt("pre");
2979 // Measure a bunch of lines, for browsers that compute
2980 // fractional heights.
2981 for (var i = 0; i < 49; ++i) {
2982 measureText.appendChild(document.createTextNode("x"));
2983 measureText.appendChild(elt("br"));
2984 }
2985 measureText.appendChild(document.createTextNode("x"));
2986 }
2987 removeChildrenAndAdd(display.measure, measureText);
2988 var height = measureText.offsetHeight / 50;
2989 if (height > 3) display.cachedTextHeight = height;
2990 removeChildren(display.measure);
2991 return height || 1;
2992 }
2993
2994 // Compute the default character width.
2995 function charWidth(display) {
2996 if (display.cachedCharWidth != null) return display.cachedCharWidth;
2997 var anchor = elt("span", "xxxxxxxxxx");
2998 var pre = elt("pre", [anchor]);
2999 removeChildrenAndAdd(display.measure, pre);
3000 var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;
3001 if (width > 2) display.cachedCharWidth = width;
3002 return width || 10;
3003 }
3004
3005 // OPERATIONS
3006
3007 // Operations are used to wrap a series of changes to the editor
3008 // state in such a way that each change won't have to update the
3009 // cursor and display (which would be awkward, slow, and
3010 // error-prone). Instead, display updates are batched and then all
3011 // combined and executed at once.
3012
3013 var operationGroup = null;
3014
3015 var nextOpId = 0;
3016 // Start a new operation.
3017 function startOperation(cm) {
3018 cm.curOp = {
3019 cm: cm,
3020 viewChanged: false, // Flag that indicates that lines might need to b e redrawn
3021 startHeight: cm.doc.height, // Used to detect need to update scrollbar
3022 forceUpdate: false, // Used to force a redraw
3023 updateInput: null, // Whether to reset the input textarea
3024 typing: false, // Whether this reset should be careful to leave existing text (for compositing)
3025 changeObjs: null, // Accumulated changes, for firing change events
3026 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
3027 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already
3028 selectionChanged: false, // Whether the selection needs to be redrawn
3029 updateMaxLine: false, // Set when the widest line needs to be determine d anew
3030 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pu shed to DOM yet
3031 scrollToPos: null, // Used to scroll to a specific position
3032 focus: false,
3033 id: ++nextOpId // Unique ID
3034 };
3035 if (operationGroup) {
3036 operationGroup.ops.push(cm.curOp);
3037 } else {
3038 cm.curOp.ownsGroup = operationGroup = {
3039 ops: [cm.curOp],
3040 delayedCallbacks: []
3041 };
3042 }
3043 }
3044
3045 function fireCallbacksForOps(group) {
3046 // Calls delayed callbacks and cursorActivity handlers until no
3047 // new ones appear
3048 var callbacks = group.delayedCallbacks, i = 0;
3049 do {
3050 for (; i < callbacks.length; i++)
3051 callbacks[i].call(null);
3052 for (var j = 0; j < group.ops.length; j++) {
3053 var op = group.ops[j];
3054 if (op.cursorActivityHandlers)
3055 while (op.cursorActivityCalled < op.cursorActivityHandlers.length)
3056 op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.c m);
3057 }
3058 } while (i < callbacks.length);
3059 }
3060
3061 // Finish an operation, updating the display and signalling delayed events
3062 function endOperation(cm) {
3063 var op = cm.curOp, group = op.ownsGroup;
3064 if (!group) return;
3065
3066 try { fireCallbacksForOps(group); }
3067 finally {
3068 operationGroup = null;
3069 for (var i = 0; i < group.ops.length; i++)
3070 group.ops[i].cm.curOp = null;
3071 endOperations(group);
3072 }
3073 }
3074
3075 // The DOM updates done when an operation finishes are batched so
3076 // that the minimum number of relayouts are required.
3077 function endOperations(group) {
3078 var ops = group.ops;
3079 for (var i = 0; i < ops.length; i++) // Read DOM
3080 endOperation_R1(ops[i]);
3081 for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
3082 endOperation_W1(ops[i]);
3083 for (var i = 0; i < ops.length; i++) // Read DOM
3084 endOperation_R2(ops[i]);
3085 for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
3086 endOperation_W2(ops[i]);
3087 for (var i = 0; i < ops.length; i++) // Read DOM
3088 endOperation_finish(ops[i]);
3089 }
3090
3091 function endOperation_R1(op) {
3092 var cm = op.cm, display = cm.display;
3093 maybeClipScrollbars(cm);
3094 if (op.updateMaxLine) findMaxLine(cm);
3095
3096 op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||
3097 op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
3098 op.scrollToPos.to.line >= display.viewTo) ||
3099 display.maxLineChanged && cm.options.lineWrapping;
3100 op.update = op.mustUpdate &&
3101 new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scro llToPos}, op.forceUpdate);
3102 }
3103
3104 function endOperation_W1(op) {
3105 op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update) ;
3106 }
3107
3108 function endOperation_R2(op) {
3109 var cm = op.cm, display = cm.display;
3110 if (op.updatedDisplay) updateHeightsInViewport(cm);
3111
3112 op.barMeasure = measureForScrollbars(cm);
3113
3114 // If the max line changed since it was last measured, measure it,
3115 // and ensure the document's width matches it.
3116 // updateDisplay_W2 will use these properties to do the actual resizing
3117 if (display.maxLineChanged && !cm.options.lineWrapping) {
3118 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.l ength).left + 3;
3119 cm.display.sizerWidth = op.adjustWidthTo;
3120 op.barMeasure.scrollWidth =
3121 Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adj ustWidthTo + scrollGap(cm) + cm.display.barWidth);
3122 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm));
3123 }
3124
3125 if (op.updatedDisplay || op.selectionChanged)
3126 op.preparedSelection = display.input.prepareSelection(op.focus);
3127 }
3128
3129 function endOperation_W2(op) {
3130 var cm = op.cm;
3131
3132 if (op.adjustWidthTo != null) {
3133 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px";
3134 if (op.maxScrollLeft < cm.doc.scrollLeft)
3135 setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollL eft), true);
3136 cm.display.maxLineChanged = false;
3137 }
3138
3139 var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus | | document.hasFocus())
3140 if (op.preparedSelection)
3141 cm.display.input.showSelection(op.preparedSelection, takeFocus);
3142 if (op.updatedDisplay || op.startHeight != cm.doc.height)
3143 updateScrollbars(cm, op.barMeasure);
3144 if (op.updatedDisplay)
3145 setDocumentHeight(cm, op.barMeasure);
3146
3147 if (op.selectionChanged) restartBlink(cm);
3148
3149 if (cm.state.focused && op.updateInput)
3150 cm.display.input.reset(op.typing);
3151 if (takeFocus) ensureFocus(op.cm);
3152 }
3153
3154 function endOperation_finish(op) {
3155 var cm = op.cm, display = cm.display, doc = cm.doc;
3156
3157 if (op.updatedDisplay) postUpdateDisplay(cm, op.update);
3158
3159 // Abort mouse wheel delta measurement, when scrolling explicitly
3160 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))
3161 display.wheelStartX = display.wheelStartY = null;
3162
3163 // Propagate the scroll position to the actual DOM scroller
3164 if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || o p.forceScroll)) {
3165 doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - displ ay.scroller.clientHeight, op.scrollTop));
3166 display.scrollbars.setScrollTop(doc.scrollTop);
3167 display.scroller.scrollTop = doc.scrollTop;
3168 }
3169 if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft | | op.forceScroll)) {
3170 doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displ ay.scroller.clientWidth, op.scrollLeft));
3171 display.scrollbars.setScrollLeft(doc.scrollLeft);
3172 display.scroller.scrollLeft = doc.scrollLeft;
3173 alignHorizontally(cm);
3174 }
3175 // If we need to scroll a specific position into view, do so.
3176 if (op.scrollToPos) {
3177 var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
3178 clipPos(doc, op.scrollToPos.to), op.scrollT oPos.margin);
3179 if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coo rds);
3180 }
3181
3182 // Fire events for markers that are hidden/unidden by editing or
3183 // undoing
3184 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
3185 if (hidden) for (var i = 0; i < hidden.length; ++i)
3186 if (!hidden[i].lines.length) signal(hidden[i], "hide");
3187 if (unhidden) for (var i = 0; i < unhidden.length; ++i)
3188 if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
3189
3190 if (display.wrapper.offsetHeight)
3191 doc.scrollTop = cm.display.scroller.scrollTop;
3192
3193 // Fire change events, and delayed event handlers
3194 if (op.changeObjs)
3195 signal(cm, "changes", cm, op.changeObjs);
3196 if (op.update)
3197 op.update.finish();
3198 }
3199
3200 // Run the given function in an operation
3201 function runInOp(cm, f) {
3202 if (cm.curOp) return f();
3203 startOperation(cm);
3204 try { return f(); }
3205 finally { endOperation(cm); }
3206 }
3207 // Wraps a function in an operation. Returns the wrapped function.
3208 function operation(cm, f) {
3209 return function() {
3210 if (cm.curOp) return f.apply(cm, arguments);
3211 startOperation(cm);
3212 try { return f.apply(cm, arguments); }
3213 finally { endOperation(cm); }
3214 };
3215 }
3216 // Used to add methods to editor and doc instances, wrapping them in
3217 // operations.
3218 function methodOp(f) {
3219 return function() {
3220 if (this.curOp) return f.apply(this, arguments);
3221 startOperation(this);
3222 try { return f.apply(this, arguments); }
3223 finally { endOperation(this); }
3224 };
3225 }
3226 function docMethodOp(f) {
3227 return function() {
3228 var cm = this.cm;
3229 if (!cm || cm.curOp) return f.apply(this, arguments);
3230 startOperation(cm);
3231 try { return f.apply(this, arguments); }
3232 finally { endOperation(cm); }
3233 };
3234 }
3235
3236 // VIEW TRACKING
3237
3238 // These objects are used to represent the visible (currently drawn)
3239 // part of the document. A LineView may correspond to multiple
3240 // logical lines, if those are connected by collapsed ranges.
3241 function LineView(doc, line, lineN) {
3242 // The starting line
3243 this.line = line;
3244 // Continuing lines, if any
3245 this.rest = visualLineContinued(line);
3246 // Number of logical lines in this visual line
3247 this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;
3248 this.node = this.text = null;
3249 this.hidden = lineIsHidden(doc, line);
3250 }
3251
3252 // Create a range of LineView objects for the given lines.
3253 function buildViewArray(cm, from, to) {
3254 var array = [], nextPos;
3255 for (var pos = from; pos < to; pos = nextPos) {
3256 var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);
3257 nextPos = pos + view.size;
3258 array.push(view);
3259 }
3260 return array;
3261 }
3262
3263 // Updates the display.view data structure for a given change to the
3264 // document. From and to are in pre-change coordinates. Lendiff is
3265 // the amount of lines added or subtracted by the change. This is
3266 // used for changes that span multiple lines, or change the way
3267 // lines are divided into visual lines. regLineChange (below)
3268 // registers single-line changes.
3269 function regChange(cm, from, to, lendiff) {
3270 if (from == null) from = cm.doc.first;
3271 if (to == null) to = cm.doc.first + cm.doc.size;
3272 if (!lendiff) lendiff = 0;
3273
3274 var display = cm.display;
3275 if (lendiff && to < display.viewTo &&
3276 (display.updateLineNumbers == null || display.updateLineNumbers > from))
3277 display.updateLineNumbers = from;
3278
3279 cm.curOp.viewChanged = true;
3280
3281 if (from >= display.viewTo) { // Change after
3282 if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
3283 resetView(cm);
3284 } else if (to <= display.viewFrom) { // Change before
3285 if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.v iewFrom) {
3286 resetView(cm);
3287 } else {
3288 display.viewFrom += lendiff;
3289 display.viewTo += lendiff;
3290 }
3291 } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overl ap
3292 resetView(cm);
3293 } else if (from <= display.viewFrom) { // Top overlap
3294 var cut = viewCuttingPoint(cm, to, to + lendiff, 1);
3295 if (cut) {
3296 display.view = display.view.slice(cut.index);
3297 display.viewFrom = cut.lineN;
3298 display.viewTo += lendiff;
3299 } else {
3300 resetView(cm);
3301 }
3302 } else if (to >= display.viewTo) { // Bottom overlap
3303 var cut = viewCuttingPoint(cm, from, from, -1);
3304 if (cut) {
3305 display.view = display.view.slice(0, cut.index);
3306 display.viewTo = cut.lineN;
3307 } else {
3308 resetView(cm);
3309 }
3310 } else { // Gap in the middle
3311 var cutTop = viewCuttingPoint(cm, from, from, -1);
3312 var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);
3313 if (cutTop && cutBot) {
3314 display.view = display.view.slice(0, cutTop.index)
3315 .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
3316 .concat(display.view.slice(cutBot.index));
3317 display.viewTo += lendiff;
3318 } else {
3319 resetView(cm);
3320 }
3321 }
3322
3323 var ext = display.externalMeasured;
3324 if (ext) {
3325 if (to < ext.lineN)
3326 ext.lineN += lendiff;
3327 else if (from < ext.lineN + ext.size)
3328 display.externalMeasured = null;
3329 }
3330 }
3331
3332 // Register a change to a single line. Type must be one of "text",
3333 // "gutter", "class", "widget"
3334 function regLineChange(cm, line, type) {
3335 cm.curOp.viewChanged = true;
3336 var display = cm.display, ext = cm.display.externalMeasured;
3337 if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
3338 display.externalMeasured = null;
3339
3340 if (line < display.viewFrom || line >= display.viewTo) return;
3341 var lineView = display.view[findViewIndex(cm, line)];
3342 if (lineView.node == null) return;
3343 var arr = lineView.changes || (lineView.changes = []);
3344 if (indexOf(arr, type) == -1) arr.push(type);
3345 }
3346
3347 // Clear the view.
3348 function resetView(cm) {
3349 cm.display.viewFrom = cm.display.viewTo = cm.doc.first;
3350 cm.display.view = [];
3351 cm.display.viewOffset = 0;
3352 }
3353
3354 // Find the view element corresponding to a given line. Return null
3355 // when the line isn't visible.
3356 function findViewIndex(cm, n) {
3357 if (n >= cm.display.viewTo) return null;
3358 n -= cm.display.viewFrom;
3359 if (n < 0) return null;
3360 var view = cm.display.view;
3361 for (var i = 0; i < view.length; i++) {
3362 n -= view[i].size;
3363 if (n < 0) return i;
3364 }
3365 }
3366
3367 function viewCuttingPoint(cm, oldN, newN, dir) {
3368 var index = findViewIndex(cm, oldN), diff, view = cm.display.view;
3369 if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
3370 return {index: index, lineN: newN};
3371 for (var i = 0, n = cm.display.viewFrom; i < index; i++)
3372 n += view[i].size;
3373 if (n != oldN) {
3374 if (dir > 0) {
3375 if (index == view.length - 1) return null;
3376 diff = (n + view[index].size) - oldN;
3377 index++;
3378 } else {
3379 diff = n - oldN;
3380 }
3381 oldN += diff; newN += diff;
3382 }
3383 while (visualLineNo(cm.doc, newN) != newN) {
3384 if (index == (dir < 0 ? 0 : view.length - 1)) return null;
3385 newN += dir * view[index - (dir < 0 ? 1 : 0)].size;
3386 index += dir;
3387 }
3388 return {index: index, lineN: newN};
3389 }
3390
3391 // Force the view to cover a given range, adding empty view element
3392 // or clipping off existing ones as needed.
3393 function adjustView(cm, from, to) {
3394 var display = cm.display, view = display.view;
3395 if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
3396 display.view = buildViewArray(cm, from, to);
3397 display.viewFrom = from;
3398 } else {
3399 if (display.viewFrom > from)
3400 display.view = buildViewArray(cm, from, display.viewFrom).concat(display .view);
3401 else if (display.viewFrom < from)
3402 display.view = display.view.slice(findViewIndex(cm, from));
3403 display.viewFrom = from;
3404 if (display.viewTo < to)
3405 display.view = display.view.concat(buildViewArray(cm, display.viewTo, to ));
3406 else if (display.viewTo > to)
3407 display.view = display.view.slice(0, findViewIndex(cm, to));
3408 }
3409 display.viewTo = to;
3410 }
3411
3412 // Count the number of lines in the view whose DOM representation is
3413 // out of date (or nonexistent).
3414 function countDirtyView(cm) {
3415 var view = cm.display.view, dirty = 0;
3416 for (var i = 0; i < view.length; i++) {
3417 var lineView = view[i];
3418 if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty;
3419 }
3420 return dirty;
3421 }
3422
3423 // EVENT HANDLERS
3424
3425 // Attach the necessary event handlers when initializing the editor
3426 function registerEventHandlers(cm) {
3427 var d = cm.display;
3428 on(d.scroller, "mousedown", operation(cm, onMouseDown));
3429 // Older IE's will not fire a second mousedown for a double click
3430 if (ie && ie_version < 11)
3431 on(d.scroller, "dblclick", operation(cm, function(e) {
3432 if (signalDOMEvent(cm, e)) return;
3433 var pos = posFromMouse(cm, e);
3434 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return ;
3435 e_preventDefault(e);
3436 var word = cm.findWordAt(pos);
3437 extendSelection(cm.doc, word.anchor, word.head);
3438 }));
7020 else 3439 else
7021 { ourRange = new Range(start, start) } 3440 on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preven tDefault(e); });
7022 } else { 3441 // Some browsers fire contextmenu *after* opening the menu, at
7023 ourRange = doc.sel.primary() 3442 // which point we can't mess with it anymore. Context menu is
7024 ourIndex = doc.sel.primIndex 3443 // handled in onMouseDown for these browsers.
7025 } 3444 if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContext Menu(cm, e);});
7026 3445
7027 if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) { 3446 // Used to suppress mouse event handling when a touch happens
7028 type = "rect" 3447 var touchFinished, prevTouch = {end: 0};
7029 if (!addNew) { ourRange = new Range(start, start) } 3448 function finishTouch() {
7030 start = posFromMouse(cm, e, true, true) 3449 if (d.activeTouch) {
7031 ourIndex = -1 3450 touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000);
7032 } else if (type == "double") { 3451 prevTouch = d.activeTouch;
7033 var word = cm.findWordAt(start) 3452 prevTouch.end = +new Date;
7034 if (cm.display.shift || doc.extend) 3453 }
7035 { ourRange = extendRange(doc, ourRange, word.anchor, word.head) } 3454 };
3455 function isMouseLikeTouchEvent(e) {
3456 if (e.touches.length != 1) return false;
3457 var touch = e.touches[0];
3458 return touch.radiusX <= 1 && touch.radiusY <= 1;
3459 }
3460 function farAway(touch, other) {
3461 if (other.left == null) return true;
3462 var dx = other.left - touch.left, dy = other.top - touch.top;
3463 return dx * dx + dy * dy > 20 * 20;
3464 }
3465 on(d.scroller, "touchstart", function(e) {
3466 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) {
3467 clearTimeout(touchFinished);
3468 var now = +new Date;
3469 d.activeTouch = {start: now, moved: false,
3470 prev: now - prevTouch.end <= 300 ? prevTouch : null};
3471 if (e.touches.length == 1) {
3472 d.activeTouch.left = e.touches[0].pageX;
3473 d.activeTouch.top = e.touches[0].pageY;
3474 }
3475 }
3476 });
3477 on(d.scroller, "touchmove", function() {
3478 if (d.activeTouch) d.activeTouch.moved = true;
3479 });
3480 on(d.scroller, "touchend", function(e) {
3481 var touch = d.activeTouch;
3482 if (touch && !eventInWidget(d, e) && touch.left != null &&
3483 !touch.moved && new Date - touch.start < 300) {
3484 var pos = cm.coordsChar(d.activeTouch, "page"), range;
3485 if (!touch.prev || farAway(touch, touch.prev)) // Single tap
3486 range = new Range(pos, pos);
3487 else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap
3488 range = cm.findWordAt(pos);
3489 else // Triple tap
3490 range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0)));
3491 cm.setSelection(range.anchor, range.head);
3492 cm.focus();
3493 e_preventDefault(e);
3494 }
3495 finishTouch();
3496 });
3497 on(d.scroller, "touchcancel", finishTouch);
3498
3499 // Sync scrolling between fake scrollbars and real scrollable
3500 // area, ensure viewport is updated when scrolling.
3501 on(d.scroller, "scroll", function() {
3502 if (d.scroller.clientHeight) {
3503 setScrollTop(cm, d.scroller.scrollTop);
3504 setScrollLeft(cm, d.scroller.scrollLeft, true);
3505 signal(cm, "scroll", cm);
3506 }
3507 });
3508
3509 // Listen to wheel events in order to try and update the viewport on time.
3510 on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
3511 on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
3512
3513 // Prevent wrapper from ever scrolling
3514 on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollL eft = 0; });
3515
3516 d.dragFunctions = {
3517 enter: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);},
3518 over: function(e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop (e); }},
3519 start: function(e){onDragStart(cm, e);},
3520 drop: operation(cm, onDrop),
3521 leave: function(e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }}
3522 };
3523
3524 var inp = d.input.getField();
3525 on(inp, "keyup", function(e) { onKeyUp.call(cm, e); });
3526 on(inp, "keydown", operation(cm, onKeyDown));
3527 on(inp, "keypress", operation(cm, onKeyPress));
3528 on(inp, "focus", bind(onFocus, cm));
3529 on(inp, "blur", bind(onBlur, cm));
3530 }
3531
3532 function dragDropChanged(cm, value, old) {
3533 var wasOn = old && old != CodeMirror.Init;
3534 if (!value != !wasOn) {
3535 var funcs = cm.display.dragFunctions;
3536 var toggle = value ? on : off;
3537 toggle(cm.display.scroller, "dragstart", funcs.start);
3538 toggle(cm.display.scroller, "dragenter", funcs.enter);
3539 toggle(cm.display.scroller, "dragover", funcs.over);
3540 toggle(cm.display.scroller, "dragleave", funcs.leave);
3541 toggle(cm.display.scroller, "drop", funcs.drop);
3542 }
3543 }
3544
3545 // Called when the window resizes
3546 function onResize(cm) {
3547 var d = cm.display;
3548 if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapp er.clientWidth)
3549 return;
3550 // Might be a text scaling operation, clear size caches.
3551 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
3552 d.scrollbarsClipped = false;
3553 cm.setSize();
3554 }
3555
3556 // MOUSE EVENTS
3557
3558 // Return true when the given mouse event happened in a widget
3559 function eventInWidget(display, e) {
3560 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
3561 if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true" ) ||
3562 (n.parentNode == display.sizer && n != display.mover))
3563 return true;
3564 }
3565 }
3566
3567 // Given a mouse event, find the corresponding position. If liberal
3568 // is false, it checks whether a gutter or scrollbar was clicked,
3569 // and returns null if it was. forRect is used by rectangular
3570 // selections, and tries to estimate a character position even for
3571 // coordinates beyond the right of the text.
3572 function posFromMouse(cm, e, liberal, forRect) {
3573 var display = cm.display;
3574 if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null;
3575
3576 var x, y, space = display.lineSpace.getBoundingClientRect();
3577 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
3578 try { x = e.clientX - space.left; y = e.clientY - space.top; }
3579 catch (e) { return null; }
3580 var coords = coordsChar(cm, x, y), line;
3581 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text ).length == coords.ch) {
3582 var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.le ngth;
3583 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display) .left) / charWidth(cm.display)) - colDiff));
3584 }
3585 return coords;
3586 }
3587
3588 // A mouse down can be a single click, double click, triple click,
3589 // start of selection drag, start of text drag, new cursor
3590 // (ctrl-click), rectangle drag (alt-drag), or xwin
3591 // middle-click-paste. Or it might be a click on something we should
3592 // not interfere with, such as a scrollbar or widget.
3593 function onMouseDown(e) {
3594 var cm = this, display = cm.display;
3595 if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTo uch()) return;
3596 display.shift = e.shiftKey;
3597
3598 if (eventInWidget(display, e)) {
3599 if (!webkit) {
3600 // Briefly turn off draggability, to allow widgets to do
3601 // normal dragging things.
3602 display.scroller.draggable = false;
3603 setTimeout(function(){display.scroller.draggable = true;}, 100);
3604 }
3605 return;
3606 }
3607 if (clickInGutter(cm, e)) return;
3608 var start = posFromMouse(cm, e);
3609 window.focus();
3610
3611 switch (e_button(e)) {
3612 case 1:
3613 // #3261: make sure, that we're not starting a second selection
3614 if (cm.state.selectingText)
3615 cm.state.selectingText(e);
3616 else if (start)
3617 leftButtonDown(cm, e, start);
3618 else if (e_target(e) == display.scroller)
3619 e_preventDefault(e);
3620 break;
3621 case 2:
3622 if (webkit) cm.state.lastMiddleDown = +new Date;
3623 if (start) extendSelection(cm.doc, start);
3624 setTimeout(function() {display.input.focus();}, 20);
3625 e_preventDefault(e);
3626 break;
3627 case 3:
3628 if (captureRightClick) onContextMenu(cm, e);
3629 else delayBlurEvent(cm);
3630 break;
3631 }
3632 }
3633
3634 var lastClick, lastDoubleClick;
3635 function leftButtonDown(cm, e, start) {
3636 if (ie) setTimeout(bind(ensureFocus, cm), 0);
3637 else cm.curOp.focus = activeElt();
3638
3639 var now = +new Date, type;
3640 if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleCli ck.pos, start) == 0) {
3641 type = "triple";
3642 } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, sta rt) == 0) {
3643 type = "double";
3644 lastDoubleClick = {time: now, pos: start};
3645 } else {
3646 type = "single";
3647 lastClick = {time: now, pos: start};
3648 }
3649
3650 var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained;
3651 if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&
3652 type == "single" && (contained = sel.contains(start)) > -1 &&
3653 (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRe l > 0) &&
3654 (cmp(contained.to(), start) > 0 || start.xRel < 0))
3655 leftButtonStartDrag(cm, e, start, modifier);
7036 else 3656 else
7037 { ourRange = word } 3657 leftButtonSelect(cm, e, start, type, modifier);
7038 } else if (type == "triple") { 3658 }
7039 var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0) )) 3659
7040 if (cm.display.shift || doc.extend) 3660 // Start a text drag. When it ends, see if any dragging actually
7041 { ourRange = extendRange(doc, ourRange, line.anchor, line.head) } 3661 // happen, and treat as a click if it didn't.
3662 function leftButtonStartDrag(cm, e, start, modifier) {
3663 var display = cm.display, startTime = +new Date;
3664 var dragEnd = operation(cm, function(e2) {
3665 if (webkit) display.scroller.draggable = false;
3666 cm.state.draggingText = false;
3667 off(document, "mouseup", dragEnd);
3668 off(display.scroller, "drop", dragEnd);
3669 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
3670 e_preventDefault(e2);
3671 if (!modifier && +new Date - 200 < startTime)
3672 extendSelection(cm.doc, start);
3673 // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3 081)
3674 if (webkit || ie && ie_version == 9)
3675 setTimeout(function() {document.body.focus(); display.input.focus();}, 20);
3676 else
3677 display.input.focus();
3678 }
3679 });
3680 // Let the drag handler handle this.
3681 if (webkit) display.scroller.draggable = true;
3682 cm.state.draggingText = dragEnd;
3683 dragEnd.copy = mac ? e.altKey : e.ctrlKey
3684 // IE's approach to draggable
3685 if (display.scroller.dragDrop) display.scroller.dragDrop();
3686 on(document, "mouseup", dragEnd);
3687 on(display.scroller, "drop", dragEnd);
3688 }
3689
3690 // Normal selection, as opposed to text dragging.
3691 function leftButtonSelect(cm, e, start, type, addNew) {
3692 var display = cm.display, doc = cm.doc;
3693 e_preventDefault(e);
3694
3695 var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges;
3696 if (addNew && !e.shiftKey) {
3697 ourIndex = doc.sel.contains(start);
3698 if (ourIndex > -1)
3699 ourRange = ranges[ourIndex];
3700 else
3701 ourRange = new Range(start, start);
3702 } else {
3703 ourRange = doc.sel.primary();
3704 ourIndex = doc.sel.primIndex;
3705 }
3706
3707 if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) {
3708 type = "rect";
3709 if (!addNew) ourRange = new Range(start, start);
3710 start = posFromMouse(cm, e, true, true);
3711 ourIndex = -1;
3712 } else if (type == "double") {
3713 var word = cm.findWordAt(start);
3714 if (cm.display.shift || doc.extend)
3715 ourRange = extendRange(doc, ourRange, word.anchor, word.head);
3716 else
3717 ourRange = word;
3718 } else if (type == "triple") {
3719 var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0)));
3720 if (cm.display.shift || doc.extend)
3721 ourRange = extendRange(doc, ourRange, line.anchor, line.head);
3722 else
3723 ourRange = line;
3724 } else {
3725 ourRange = extendRange(doc, ourRange, start);
3726 }
3727
3728 if (!addNew) {
3729 ourIndex = 0;
3730 setSelection(doc, new Selection([ourRange], 0), sel_mouse);
3731 startSel = doc.sel;
3732 } else if (ourIndex == -1) {
3733 ourIndex = ranges.length;
3734 setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex),
3735 {scroll: false, origin: "*mouse"});
3736 } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) {
3737 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(rang es.slice(ourIndex + 1)), 0),
3738 {scroll: false, origin: "*mouse"});
3739 startSel = doc.sel;
3740 } else {
3741 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
3742 }
3743
3744 var lastPos = start;
3745 function extendTo(pos) {
3746 if (cmp(lastPos, pos) == 0) return;
3747 lastPos = pos;
3748
3749 if (type == "rect") {
3750 var ranges = [], tabSize = cm.options.tabSize;
3751 var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabS ize);
3752 var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);
3753 var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol );
3754 for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLi ne(), Math.max(start.line, pos.line));
3755 line <= end; line++) {
3756 var text = getLine(doc, line).text, leftPos = findColumn(text, left, t abSize);
3757 if (left == right)
3758 ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos)));
3759 else if (text.length > leftPos)
3760 ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize))));
3761 }
3762 if (!ranges.length) ranges.push(new Range(start, start));
3763 setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex). concat(ranges), ourIndex),
3764 {origin: "*mouse", scroll: false});
3765 cm.scrollIntoView(pos);
3766 } else {
3767 var oldRange = ourRange;
3768 var anchor = oldRange.anchor, head = pos;
3769 if (type != "single") {
3770 if (type == "double")
3771 var range = cm.findWordAt(pos);
3772 else
3773 var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0)));
3774 if (cmp(range.anchor, anchor) > 0) {
3775 head = range.head;
3776 anchor = minPos(oldRange.from(), range.anchor);
3777 } else {
3778 head = range.anchor;
3779 anchor = maxPos(oldRange.to(), range.head);
3780 }
3781 }
3782 var ranges = startSel.ranges.slice(0);
3783 ranges[ourIndex] = new Range(clipPos(doc, anchor), head);
3784 setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse);
3785 }
3786 }
3787
3788 var editorSize = display.wrapper.getBoundingClientRect();
3789 // Used to ensure timeout re-tries don't fire when another extend
3790 // happened in the meantime (clearTimeout isn't reliable -- at
3791 // least on Chrome, the timeouts still happen even when cleared,
3792 // if the clear happens after their scheduled firing time).
3793 var counter = 0;
3794
3795 function extend(e) {
3796 var curCount = ++counter;
3797 var cur = posFromMouse(cm, e, true, type == "rect");
3798 if (!cur) return;
3799 if (cmp(cur, lastPos) != 0) {
3800 cm.curOp.focus = activeElt();
3801 extendTo(cur);
3802 var visible = visibleLines(display, doc);
3803 if (cur.line >= visible.to || cur.line < visible.from)
3804 setTimeout(operation(cm, function(){if (counter == curCount) extend(e) ;}), 150);
3805 } else {
3806 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize. bottom ? 20 : 0;
3807 if (outside) setTimeout(operation(cm, function() {
3808 if (counter != curCount) return;
3809 display.scroller.scrollTop += outside;
3810 extend(e);
3811 }), 50);
3812 }
3813 }
3814
3815 function done(e) {
3816 cm.state.selectingText = false;
3817 counter = Infinity;
3818 e_preventDefault(e);
3819 display.input.focus();
3820 off(document, "mousemove", move);
3821 off(document, "mouseup", up);
3822 doc.history.lastSelOrigin = null;
3823 }
3824
3825 var move = operation(cm, function(e) {
3826 if (!e_button(e)) done(e);
3827 else extend(e);
3828 });
3829 var up = operation(cm, done);
3830 cm.state.selectingText = up;
3831 on(document, "mousemove", move);
3832 on(document, "mouseup", up);
3833 }
3834
3835 // Determines whether an event happened in the gutter, and fires the
3836 // handlers for the corresponding event.
3837 function gutterEvent(cm, e, type, prevent) {
3838 try { var mX = e.clientX, mY = e.clientY; }
3839 catch(e) { return false; }
3840 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) retu rn false;
3841 if (prevent) e_preventDefault(e);
3842
3843 var display = cm.display;
3844 var lineBox = display.lineDiv.getBoundingClientRect();
3845
3846 if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented( e);
3847 mY -= lineBox.top - display.viewOffset;
3848
3849 for (var i = 0; i < cm.options.gutters.length; ++i) {
3850 var g = display.gutters.childNodes[i];
3851 if (g && g.getBoundingClientRect().right >= mX) {
3852 var line = lineAtHeight(cm.doc, mY);
3853 var gutter = cm.options.gutters[i];
3854 signal(cm, type, cm, line, gutter, e);
3855 return e_defaultPrevented(e);
3856 }
3857 }
3858 }
3859
3860 function clickInGutter(cm, e) {
3861 return gutterEvent(cm, e, "gutterClick", true);
3862 }
3863
3864 // Kludge to work around strange IE behavior where it'll sometimes
3865 // re-fire a series of drag-related events right after the drop (#1551)
3866 var lastDrop = 0;
3867
3868 function onDrop(e) {
3869 var cm = this;
3870 clearDragCursor(cm);
3871 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
3872 return;
3873 e_preventDefault(e);
3874 if (ie) lastDrop = +new Date;
3875 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
3876 if (!pos || cm.isReadOnly()) return;
3877 // Might be a file drop, in which case we simply extract the text
3878 // and insert it.
3879 if (files && files.length && window.FileReader && window.File) {
3880 var n = files.length, text = Array(n), read = 0;
3881 var loadFile = function(file, i) {
3882 if (cm.options.allowDropFileTypes &&
3883 indexOf(cm.options.allowDropFileTypes, file.type) == -1)
3884 return;
3885
3886 var reader = new FileReader;
3887 reader.onload = operation(cm, function() {
3888 var content = reader.result;
3889 if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = "";
3890 text[i] = content;
3891 if (++read == n) {
3892 pos = clipPos(cm.doc, pos);
3893 var change = {from: pos, to: pos,
3894 text: cm.doc.splitLines(text.join(cm.doc.lineSeparator ())),
3895 origin: "paste"};
3896 makeChange(cm.doc, change);
3897 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(ch ange)));
3898 }
3899 });
3900 reader.readAsText(file);
3901 };
3902 for (var i = 0; i < n; ++i) loadFile(files[i], i);
3903 } else { // Normal drop
3904 // Don't do a replace if the drop happened inside of the selected text.
3905 if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
3906 cm.state.draggingText(e);
3907 // Ensure the editor is re-focused
3908 setTimeout(function() {cm.display.input.focus();}, 20);
3909 return;
3910 }
3911 try {
3912 var text = e.dataTransfer.getData("Text");
3913 if (text) {
3914 if (cm.state.draggingText && !cm.state.draggingText.copy)
3915 var selected = cm.listSelections();
3916 setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
3917 if (selected) for (var i = 0; i < selected.length; ++i)
3918 replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag ");
3919 cm.replaceSelection(text, "around", "paste");
3920 cm.display.input.focus();
3921 }
3922 }
3923 catch(e){}
3924 }
3925 }
3926
3927 function onDragStart(cm, e) {
3928 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e ); return; }
3929 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
3930
3931 e.dataTransfer.setData("Text", cm.getSelection());
3932 e.dataTransfer.effectAllowed = "copyMove"
3933
3934 // Use dummy image instead of default browsers image.
3935 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
3936 if (e.dataTransfer.setDragImage && !safari) {
3937 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
3938 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAA AICTAEAOw==";
3939 if (presto) {
3940 img.width = img.height = 1;
3941 cm.display.wrapper.appendChild(img);
3942 // Force a relayout, or Opera won't use our image for some obscure reaso n
3943 img._top = img.offsetTop;
3944 }
3945 e.dataTransfer.setDragImage(img, 0, 0);
3946 if (presto) img.parentNode.removeChild(img);
3947 }
3948 }
3949
3950 function onDragOver(cm, e) {
3951 var pos = posFromMouse(cm, e);
3952 if (!pos) return;
3953 var frag = document.createDocumentFragment();
3954 drawSelectionCursor(cm, pos, frag);
3955 if (!cm.display.dragCursor) {
3956 cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dr agcursors");
3957 cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursor Div);
3958 }
3959 removeChildrenAndAdd(cm.display.dragCursor, frag);
3960 }
3961
3962 function clearDragCursor(cm) {
3963 if (cm.display.dragCursor) {
3964 cm.display.lineSpace.removeChild(cm.display.dragCursor);
3965 cm.display.dragCursor = null;
3966 }
3967 }
3968
3969 // SCROLL EVENTS
3970
3971 // Sync the scrollable area and scrollbars, ensure the viewport
3972 // covers the visible area.
3973 function setScrollTop(cm, val) {
3974 if (Math.abs(cm.doc.scrollTop - val) < 2) return;
3975 cm.doc.scrollTop = val;
3976 if (!gecko) updateDisplaySimple(cm, {top: val});
3977 if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = va l;
3978 cm.display.scrollbars.setScrollTop(val);
3979 if (gecko) updateDisplaySimple(cm);
3980 startWorker(cm, 100);
3981 }
3982 // Sync scroller and scrollbar, ensure the gutter elements are
3983 // aligned.
3984 function setScrollLeft(cm, val, isScroller) {
3985 if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val ) < 2) return;
3986 val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.cl ientWidth);
3987 cm.doc.scrollLeft = val;
3988 alignHorizontally(cm);
3989 if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
3990 cm.display.scrollbars.setScrollLeft(val);
3991 }
3992
3993 // Since the delta values reported on mouse wheel events are
3994 // unstandardized between browsers and even browser versions, and
3995 // generally horribly unpredictable, this code starts by measuring
3996 // the scroll effect that the first few mouse wheel events have,
3997 // and, from that, detects the way it can convert deltas to pixel
3998 // offsets afterwards.
3999 //
4000 // The reason we want to know the amount a wheel event will scroll
4001 // is that it gives us a chance to update the display before the
4002 // actual scrolling happens, reducing flickering.
4003
4004 var wheelSamples = 0, wheelPixelsPerUnit = null;
4005 // Fill in a browser-detected starting value on browsers where we
4006 // know one. These don't have to be accurate -- the result of them
4007 // being wrong would just be a slight flicker on the first wheel
4008 // scroll (if it is large enough).
4009 if (ie) wheelPixelsPerUnit = -.53;
4010 else if (gecko) wheelPixelsPerUnit = 15;
4011 else if (chrome) wheelPixelsPerUnit = -.7;
4012 else if (safari) wheelPixelsPerUnit = -1/3;
4013
4014 var wheelEventDelta = function(e) {
4015 var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
4016 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
4017 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
4018 else if (dy == null) dy = e.wheelDelta;
4019 return {x: dx, y: dy};
4020 };
4021 CodeMirror.wheelEventPixels = function(e) {
4022 var delta = wheelEventDelta(e);
4023 delta.x *= wheelPixelsPerUnit;
4024 delta.y *= wheelPixelsPerUnit;
4025 return delta;
4026 };
4027
4028 function onScrollWheel(cm, e) {
4029 var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y;
4030
4031 var display = cm.display, scroll = display.scroller;
4032 // Quit if there's nothing to scroll here
4033 var canScrollX = scroll.scrollWidth > scroll.clientWidth;
4034 var canScrollY = scroll.scrollHeight > scroll.clientHeight;
4035 if (!(dx && canScrollX || dy && canScrollY)) return;
4036
4037 // Webkit browsers on OS X abort momentum scrolls when the target
4038 // of the scroll event is removed from the scrollable element.
4039 // This hack (see related code in patchDisplay) makes sure the
4040 // element is kept around.
4041 if (dy && mac && webkit) {
4042 outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {
4043 for (var i = 0; i < view.length; i++) {
4044 if (view[i].node == cur) {
4045 cm.display.currentWheelTarget = cur;
4046 break outer;
4047 }
4048 }
4049 }
4050 }
4051
4052 // On some browsers, horizontal scrolling will cause redraws to
4053 // happen before the gutter has been realigned, causing it to
4054 // wriggle around in a most unseemly way. When we have an
4055 // estimated pixels/delta value, we just handle horizontal
4056 // scrolling entirely here. It'll be slightly off from native, but
4057 // better than glitching out.
4058 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
4059 if (dy && canScrollY)
4060 setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixel sPerUnit, scroll.scrollHeight - scroll.clientHeight)));
4061 setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixel sPerUnit, scroll.scrollWidth - scroll.clientWidth)));
4062 // Only prevent default scrolling if vertical scrolling is
4063 // actually possible. Otherwise, it causes vertical scroll
4064 // jitter on OSX trackpads when deltaX is small and deltaY
4065 // is large (issue #3579)
4066 if (!dy || (dy && canScrollY))
4067 e_preventDefault(e);
4068 display.wheelStartX = null; // Abort measurement, if in progress
4069 return;
4070 }
4071
4072 // 'Project' the visible viewport to cover the area that is being
4073 // scrolled into view (if we know enough to estimate it).
4074 if (dy && wheelPixelsPerUnit != null) {
4075 var pixels = dy * wheelPixelsPerUnit;
4076 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
4077 if (pixels < 0) top = Math.max(0, top + pixels - 50);
4078 else bot = Math.min(cm.doc.height, bot + pixels + 50);
4079 updateDisplaySimple(cm, {top: top, bottom: bot});
4080 }
4081
4082 if (wheelSamples < 20) {
4083 if (display.wheelStartX == null) {
4084 display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.sc rollTop;
4085 display.wheelDX = dx; display.wheelDY = dy;
4086 setTimeout(function() {
4087 if (display.wheelStartX == null) return;
4088 var movedX = scroll.scrollLeft - display.wheelStartX;
4089 var movedY = scroll.scrollTop - display.wheelStartY;
4090 var sample = (movedY && display.wheelDY && movedY / display.wheelDY) | |
4091 (movedX && display.wheelDX && movedX / display.wheelDX);
4092 display.wheelStartX = display.wheelStartY = null;
4093 if (!sample) return;
4094 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (w heelSamples + 1);
4095 ++wheelSamples;
4096 }, 200);
4097 } else {
4098 display.wheelDX += dx; display.wheelDY += dy;
4099 }
4100 }
4101 }
4102
4103 // KEY EVENTS
4104
4105 // Run a handler that was bound to a key.
4106 function doHandleBinding(cm, bound, dropShift) {
4107 if (typeof bound == "string") {
4108 bound = commands[bound];
4109 if (!bound) return false;
4110 }
4111 // Ensure previous input has been read, so that the handler sees a
4112 // consistent view of the document
4113 cm.display.input.ensurePolled();
4114 var prevShift = cm.display.shift, done = false;
4115 try {
4116 if (cm.isReadOnly()) cm.state.suppressEdits = true;
4117 if (dropShift) cm.display.shift = false;
4118 done = bound(cm) != Pass;
4119 } finally {
4120 cm.display.shift = prevShift;
4121 cm.state.suppressEdits = false;
4122 }
4123 return done;
4124 }
4125
4126 function lookupKeyForEditor(cm, name, handle) {
4127 for (var i = 0; i < cm.state.keyMaps.length; i++) {
4128 var result = lookupKey(name, cm.state.keyMaps[i], handle, cm);
4129 if (result) return result;
4130 }
4131 return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle , cm))
4132 || lookupKey(name, cm.options.keyMap, handle, cm);
4133 }
4134
4135 var stopSeq = new Delayed;
4136 function dispatchKey(cm, name, e, handle) {
4137 var seq = cm.state.keySeq;
4138 if (seq) {
4139 if (isModifierKey(name)) return "handled";
4140 stopSeq.set(50, function() {
4141 if (cm.state.keySeq == seq) {
4142 cm.state.keySeq = null;
4143 cm.display.input.reset();
4144 }
4145 });
4146 name = seq + " " + name;
4147 }
4148 var result = lookupKeyForEditor(cm, name, handle);
4149
4150 if (result == "multi")
4151 cm.state.keySeq = name;
4152 if (result == "handled")
4153 signalLater(cm, "keyHandled", cm, name, e);
4154
4155 if (result == "handled" || result == "multi") {
4156 e_preventDefault(e);
4157 restartBlink(cm);
4158 }
4159
4160 if (seq && !result && /\'$/.test(name)) {
4161 e_preventDefault(e);
4162 return true;
4163 }
4164 return !!result;
4165 }
4166
4167 // Handle a key from the keydown event.
4168 function handleKeyBinding(cm, e) {
4169 var name = keyName(e, true);
4170 if (!name) return false;
4171
4172 if (e.shiftKey && !cm.state.keySeq) {
4173 // First try to resolve full name (including 'Shift-'). Failing
4174 // that, see if there is a cursor-motion command (starting with
4175 // 'go') bound to the keyname without 'Shift-'.
4176 return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBin ding(cm, b, true);})
4177 || dispatchKey(cm, name, e, function(b) {
4178 if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
4179 return doHandleBinding(cm, b);
4180 });
4181 } else {
4182 return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b ); });
4183 }
4184 }
4185
4186 // Handle a key from the keypress event
4187 function handleCharBinding(cm, e, ch) {
4188 return dispatchKey(cm, "'" + ch + "'", e,
4189 function(b) { return doHandleBinding(cm, b, true); });
4190 }
4191
4192 var lastStoppedKey = null;
4193 function onKeyDown(e) {
4194 var cm = this;
4195 cm.curOp.focus = activeElt();
4196 if (signalDOMEvent(cm, e)) return;
4197 // IE does strange things with escape.
4198 if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false;
4199 var code = e.keyCode;
4200 cm.display.shift = code == 16 || e.shiftKey;
4201 var handled = handleKeyBinding(cm, e);
4202 if (presto) {
4203 lastStoppedKey = handled ? code : null;
4204 // Opera has no cut event... we try to at least catch the key combo
4205 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKe y))
4206 cm.replaceSelection("", null, "cut");
4207 }
4208
4209 // Turn mouse into crosshair when Alt is held on Mac.
4210 if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.classN ame))
4211 showCrossHair(cm);
4212 }
4213
4214 function showCrossHair(cm) {
4215 var lineDiv = cm.display.lineDiv;
4216 addClass(lineDiv, "CodeMirror-crosshair");
4217
4218 function up(e) {
4219 if (e.keyCode == 18 || !e.altKey) {
4220 rmClass(lineDiv, "CodeMirror-crosshair");
4221 off(document, "keyup", up);
4222 off(document, "mouseover", up);
4223 }
4224 }
4225 on(document, "keyup", up);
4226 on(document, "mouseover", up);
4227 }
4228
4229 function onKeyUp(e) {
4230 if (e.keyCode == 16) this.doc.sel.shift = false;
4231 signalDOMEvent(this, e);
4232 }
4233
4234 function onKeyPress(e) {
4235 var cm = this;
4236 if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e .altKey || mac && e.metaKey) return;
4237 var keyCode = e.keyCode, charCode = e.charCode;
4238 if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDe fault(e); return;}
4239 if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) retur n;
4240 var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
4241 if (handleCharBinding(cm, e, ch)) return;
4242 cm.display.input.onKeyPress(e);
4243 }
4244
4245 // FOCUS/BLUR EVENTS
4246
4247 function delayBlurEvent(cm) {
4248 cm.state.delayingBlurEvent = true;
4249 setTimeout(function() {
4250 if (cm.state.delayingBlurEvent) {
4251 cm.state.delayingBlurEvent = false;
4252 onBlur(cm);
4253 }
4254 }, 100);
4255 }
4256
4257 function onFocus(cm) {
4258 if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false;
4259
4260 if (cm.options.readOnly == "nocursor") return;
4261 if (!cm.state.focused) {
4262 signal(cm, "focus", cm);
4263 cm.state.focused = true;
4264 addClass(cm.display.wrapper, "CodeMirror-focused");
4265 // This test prevents this from firing when a context
4266 // menu is closed (since the input reset would kill the
4267 // select-all detection hack)
4268 if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
4269 cm.display.input.reset();
4270 if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20) ; // Issue #1730
4271 }
4272 cm.display.input.receivedFocus();
4273 }
4274 restartBlink(cm);
4275 }
4276 function onBlur(cm) {
4277 if (cm.state.delayingBlurEvent) return;
4278
4279 if (cm.state.focused) {
4280 signal(cm, "blur", cm);
4281 cm.state.focused = false;
4282 rmClass(cm.display.wrapper, "CodeMirror-focused");
4283 }
4284 clearInterval(cm.display.blinker);
4285 setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 15 0);
4286 }
4287
4288 // CONTEXT MENU HANDLING
4289
4290 // To make the context menu work, we need to briefly unhide the
4291 // textarea (making it as unobtrusive as possible) to let the
4292 // right-click take effect on it.
4293 function onContextMenu(cm, e) {
4294 if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return;
4295 if (signalDOMEvent(cm, e, "contextmenu")) return;
4296 cm.display.input.onContextMenu(e);
4297 }
4298
4299 function contextMenuInGutter(cm, e) {
4300 if (!hasHandler(cm, "gutterContextMenu")) return false;
4301 return gutterEvent(cm, e, "gutterContextMenu", false);
4302 }
4303
4304 // UPDATING
4305
4306 // Compute the position of the end of a change (its 'to' property
4307 // refers to the pre-change end).
4308 var changeEnd = CodeMirror.changeEnd = function(change) {
4309 if (!change.text) return change.to;
4310 return Pos(change.from.line + change.text.length - 1,
4311 lst(change.text).length + (change.text.length == 1 ? change.from. ch : 0));
4312 };
4313
4314 // Adjust a position to refer to the post-change position of the
4315 // same text, or the end of the change if the change covers it.
4316 function adjustForChange(pos, change) {
4317 if (cmp(pos, change.from) < 0) return pos;
4318 if (cmp(pos, change.to) <= 0) return changeEnd(change);
4319
4320 var line = pos.line + change.text.length - (change.to.line - change.from.lin e) - 1, ch = pos.ch;
4321 if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch;
4322 return Pos(line, ch);
4323 }
4324
4325 function computeSelAfterChange(doc, change) {
4326 var out = [];
4327 for (var i = 0; i < doc.sel.ranges.length; i++) {
4328 var range = doc.sel.ranges[i];
4329 out.push(new Range(adjustForChange(range.anchor, change),
4330 adjustForChange(range.head, change)));
4331 }
4332 return normalizeSelection(out, doc.sel.primIndex);
4333 }
4334
4335 function offsetPos(pos, old, nw) {
4336 if (pos.line == old.line)
4337 return Pos(nw.line, pos.ch - old.ch + nw.ch);
7042 else 4338 else
7043 { ourRange = line } 4339 return Pos(nw.line + (pos.line - old.line), pos.ch);
7044 } else { 4340 }
7045 ourRange = extendRange(doc, ourRange, start) 4341
7046 } 4342 // Used by replaceSelections to allow moving the selection to the
7047 4343 // start or around the replaced test. Hint may be "start" or "around".
7048 if (!addNew) { 4344 function computeReplacedSel(doc, changes, hint) {
7049 ourIndex = 0 4345 var out = [];
7050 setSelection(doc, new Selection([ourRange], 0), sel_mouse) 4346 var oldPrev = Pos(doc.first, 0), newPrev = oldPrev;
7051 startSel = doc.sel 4347 for (var i = 0; i < changes.length; i++) {
7052 } else if (ourIndex == -1) { 4348 var change = changes[i];
7053 ourIndex = ranges.length 4349 var from = offsetPos(change.from, oldPrev, newPrev);
7054 setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), 4350 var to = offsetPos(changeEnd(change), oldPrev, newPrev);
7055 {scroll: false, origin: "*mouse"}) 4351 oldPrev = change.to;
7056 } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" & & !e.shiftKey) { 4352 newPrev = to;
7057 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges .slice(ourIndex + 1)), 0), 4353 if (hint == "around") {
7058 {scroll: false, origin: "*mouse"}) 4354 var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0;
7059 startSel = doc.sel 4355 out[i] = new Range(inv ? to : from, inv ? from : to);
7060 } else { 4356 } else {
7061 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse) 4357 out[i] = new Range(from, from);
7062 } 4358 }
7063 4359 }
7064 var lastPos = start 4360 return new Selection(out, doc.sel.primIndex);
7065 function extendTo(pos) { 4361 }
7066 if (cmp(lastPos, pos) == 0) { return } 4362
7067 lastPos = pos 4363 // Allow "beforeChange" event handlers to influence a change
7068 4364 function filterChange(doc, change, update) {
7069 if (type == "rect") { 4365 var obj = {
7070 var ranges = [], tabSize = cm.options.tabSize 4366 canceled: false,
7071 var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSiz e) 4367 from: change.from,
7072 var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize) 4368 to: change.to,
7073 var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol) 4369 text: change.text,
7074 for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine (), Math.max(start.line, pos.line)); 4370 origin: change.origin,
7075 line <= end; line++) { 4371 cancel: function() { this.canceled = true; }
7076 var text = getLine(doc, line).text, leftPos = findColumn(text, left, tab Size) 4372 };
7077 if (left == right) 4373 if (update) obj.update = function(from, to, text, origin) {
7078 { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))) } 4374 if (from) this.from = clipPos(doc, from);
7079 else if (text.length > leftPos) 4375 if (to) this.to = clipPos(doc, to);
7080 { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))) } 4376 if (text) this.text = text;
7081 } 4377 if (origin !== undefined) this.origin = origin;
7082 if (!ranges.length) { ranges.push(new Range(start, start)) } 4378 };
7083 setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).co ncat(ranges), ourIndex), 4379 signal(doc, "beforeChange", doc, obj);
7084 {origin: "*mouse", scroll: false}) 4380 if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
7085 cm.scrollIntoView(pos) 4381
4382 if (obj.canceled) return null;
4383 return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
4384 }
4385
4386 // Apply a change to a document, and add it to the document's
4387 // history, and propagating it to all linked documents.
4388 function makeChange(doc, change, ignoreReadOnly) {
4389 if (doc.cm) {
4390 if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignor eReadOnly);
4391 if (doc.cm.state.suppressEdits) return;
4392 }
4393
4394 if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeC hange")) {
4395 change = filterChange(doc, change, true);
4396 if (!change) return;
4397 }
4398
4399 // Possibly split or suppress the update based on the presence
4400 // of read-only spans in its range.
4401 var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
4402 if (split) {
4403 for (var i = split.length - 1; i >= 0; --i)
4404 makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [" "] : change.text});
7086 } else { 4405 } else {
7087 var oldRange = ourRange 4406 makeChangeInner(doc, change);
7088 var anchor = oldRange.anchor, head = pos 4407 }
7089 if (type != "single") { 4408 }
7090 var range 4409
7091 if (type == "double") 4410 function makeChangeInner(doc, change) {
7092 { range = cm.findWordAt(pos) } 4411 if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, chan ge.to) == 0) return;
7093 else 4412 var selAfter = computeSelAfterChange(doc, change);
7094 { range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0 ))) } 4413 addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
7095 if (cmp(range.anchor, anchor) > 0) { 4414
7096 head = range.head 4415 makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, chang e));
7097 anchor = minPos(oldRange.from(), range.anchor) 4416 var rebased = [];
7098 } else { 4417
7099 head = range.anchor 4418 linkedDocs(doc, function(doc, sharedHist) {
7100 anchor = maxPos(oldRange.to(), range.head) 4419 if (!sharedHist && indexOf(rebased, doc.history) == -1) {
4420 rebaseHist(doc.history, change);
4421 rebased.push(doc.history);
4422 }
4423 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change) );
4424 });
4425 }
4426
4427 // Revert a change stored in a document's history.
4428 function makeChangeFromHistory(doc, type, allowSelectionOnly) {
4429 if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) return;
4430
4431 var hist = doc.history, event, selAfter = doc.sel;
4432 var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done;
4433
4434 // Verify that there is a useable event (so that ctrl-z won't
4435 // needlessly clear selection events)
4436 for (var i = 0; i < source.length; i++) {
4437 event = source[i];
4438 if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.r anges)
4439 break;
4440 }
4441 if (i == source.length) return;
4442 hist.lastOrigin = hist.lastSelOrigin = null;
4443
4444 for (;;) {
4445 event = source.pop();
4446 if (event.ranges) {
4447 pushSelectionToHistory(event, dest);
4448 if (allowSelectionOnly && !event.equals(doc.sel)) {
4449 setSelection(doc, event, {clearRedo: false});
4450 return;
7101 } 4451 }
7102 } 4452 selAfter = event;
7103 var ranges$1 = startSel.ranges.slice(0) 4453 }
7104 ranges$1[ourIndex] = new Range(clipPos(doc, anchor), head) 4454 else break;
7105 setSelection(doc, normalizeSelection(ranges$1, ourIndex), sel_mouse) 4455 }
7106 } 4456
7107 } 4457 // Build up a reverse change object to add to the opposite history
7108 4458 // stack (redo when undoing, and vice versa).
7109 var editorSize = display.wrapper.getBoundingClientRect() 4459 var antiChanges = [];
7110 // Used to ensure timeout re-tries don't fire when another extend 4460 pushSelectionToHistory(selAfter, dest);
7111 // happened in the meantime (clearTimeout isn't reliable -- at 4461 dest.push({changes: antiChanges, generation: hist.generation});
7112 // least on Chrome, the timeouts still happen even when cleared, 4462 hist.generation = event.generation || ++hist.maxGeneration;
7113 // if the clear happens after their scheduled firing time). 4463
7114 var counter = 0 4464 var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
7115 4465
7116 function extend(e) { 4466 for (var i = event.changes.length - 1; i >= 0; --i) {
7117 var curCount = ++counter 4467 var change = event.changes[i];
7118 var cur = posFromMouse(cm, e, true, type == "rect") 4468 change.origin = type;
7119 if (!cur) { return } 4469 if (filter && !filterChange(doc, change, false)) {
7120 if (cmp(cur, lastPos) != 0) { 4470 source.length = 0;
7121 cm.curOp.focus = activeElt() 4471 return;
7122 extendTo(cur) 4472 }
7123 var visible = visibleLines(display, doc) 4473
7124 if (cur.line >= visible.to || cur.line < visible.from) 4474 antiChanges.push(historyChangeFromChange(doc, change));
7125 { setTimeout(operation(cm, function () {if (counter == curCount) { exten d(e) }}), 150) } 4475
4476 var after = i ? computeSelAfterChange(doc, change) : lst(source);
4477 makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
4478 if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd( change)});
4479 var rebased = [];
4480
4481 // Propagate to the linked documents
4482 linkedDocs(doc, function(doc, sharedHist) {
4483 if (!sharedHist && indexOf(rebased, doc.history) == -1) {
4484 rebaseHist(doc.history, change);
4485 rebased.push(doc.history);
4486 }
4487 makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
4488 });
4489 }
4490 }
4491
4492 // Sub-views need their line numbers shifted when text is added
4493 // above or below them in the parent document.
4494 function shiftDoc(doc, distance) {
4495 if (distance == 0) return;
4496 doc.first += distance;
4497 doc.sel = new Selection(map(doc.sel.ranges, function(range) {
4498 return new Range(Pos(range.anchor.line + distance, range.anchor.ch),
4499 Pos(range.head.line + distance, range.head.ch));
4500 }), doc.sel.primIndex);
4501 if (doc.cm) {
4502 regChange(doc.cm, doc.first, doc.first - distance, distance);
4503 for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)
4504 regLineChange(doc.cm, l, "gutter");
4505 }
4506 }
4507
4508 // More lower-level change function, handling only a single document
4509 // (not linked ones).
4510 function makeChangeSingleDoc(doc, change, selAfter, spans) {
4511 if (doc.cm && !doc.cm.curOp)
4512 return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans );
4513
4514 if (change.to.line < doc.first) {
4515 shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line) );
4516 return;
4517 }
4518 if (change.from.line > doc.lastLine()) return;
4519
4520 // Clip the change to the size of this doc
4521 if (change.from.line < doc.first) {
4522 var shift = change.text.length - 1 - (doc.first - change.from.line);
4523 shiftDoc(doc, shift);
4524 change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change. to.ch),
4525 text: [lst(change.text)], origin: change.origin};
4526 }
4527 var last = doc.lastLine();
4528 if (change.to.line > last) {
4529 change = {from: change.from, to: Pos(last, getLine(doc, last).text.length) ,
4530 text: [change.text[0]], origin: change.origin};
4531 }
4532
4533 change.removed = getBetween(doc, change.from, change.to);
4534
4535 if (!selAfter) selAfter = computeSelAfterChange(doc, change);
4536 if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans);
4537 else updateDoc(doc, change, spans);
4538 setSelectionNoUndo(doc, selAfter, sel_dontScroll);
4539 }
4540
4541 // Handle the interaction of a change to a document with the editor
4542 // that this document is part of.
4543 function makeChangeSingleDocInEditor(cm, change, spans) {
4544 var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
4545
4546 var recomputeMaxLength = false, checkWidthStart = from.line;
4547 if (!cm.options.lineWrapping) {
4548 checkWidthStart = lineNo(visualLine(getLine(doc, from.line)));
4549 doc.iter(checkWidthStart, to.line + 1, function(line) {
4550 if (line == display.maxLine) {
4551 recomputeMaxLength = true;
4552 return true;
4553 }
4554 });
4555 }
4556
4557 if (doc.sel.contains(change.from, change.to) > -1)
4558 signalCursorActivity(cm);
4559
4560 updateDoc(doc, change, spans, estimateHeight(cm));
4561
4562 if (!cm.options.lineWrapping) {
4563 doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
4564 var len = lineLength(line);
4565 if (len > display.maxLineLength) {
4566 display.maxLine = line;
4567 display.maxLineLength = len;
4568 display.maxLineChanged = true;
4569 recomputeMaxLength = false;
4570 }
4571 });
4572 if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
4573 }
4574
4575 // Adjust frontier, schedule worker
4576 doc.frontier = Math.min(doc.frontier, from.line);
4577 startWorker(cm, 400);
4578
4579 var lendiff = change.text.length - (to.line - from.line) - 1;
4580 // Remember that these lines changed, for updating the display
4581 if (change.full)
4582 regChange(cm);
4583 else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpda te(cm.doc, change))
4584 regLineChange(cm, from.line, "text");
4585 else
4586 regChange(cm, from.line, to.line + 1, lendiff);
4587
4588 var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(c m, "change");
4589 if (changeHandler || changesHandler) {
4590 var obj = {
4591 from: from, to: to,
4592 text: change.text,
4593 removed: change.removed,
4594 origin: change.origin
4595 };
4596 if (changeHandler) signalLater(cm, "change", cm, obj);
4597 if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).pu sh(obj);
4598 }
4599 cm.display.selForContextMenu = null;
4600 }
4601
4602 function replaceRange(doc, code, from, to, origin) {
4603 if (!to) to = from;
4604 if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; }
4605 if (typeof code == "string") code = doc.splitLines(code);
4606 makeChange(doc, {from: from, to: to, text: code, origin: origin});
4607 }
4608
4609 // SCROLLING THINGS INTO VIEW
4610
4611 // If an editor sits on the top or bottom of the window, partially
4612 // scrolled out of view, this ensures that the cursor is visible.
4613 function maybeScrollWindow(cm, coords) {
4614 if (signalDOMEvent(cm, "scrollCursorIntoView")) return;
4615
4616 var display = cm.display, box = display.sizer.getBoundingClientRect(), doScr oll = null;
4617 if (coords.top + box.top < 0) doScroll = true;
4618 else if (coords.bottom + box.top > (window.innerHeight || document.documentE lement.clientHeight)) doScroll = false;
4619 if (doScroll != null && !phantom) {
4620 var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " +
4621 (coords.top - display.viewOffset - paddingTop(cm.disp lay)) + "px; height: " +
4622 (coords.bottom - coords.top + scrollGap(cm) + display .barHeight) + "px; left: " +
4623 coords.left + "px; width: 2px;");
4624 cm.display.lineSpace.appendChild(scrollNode);
4625 scrollNode.scrollIntoView(doScroll);
4626 cm.display.lineSpace.removeChild(scrollNode);
4627 }
4628 }
4629
4630 // Scroll a given position into view (immediately), verifying that
4631 // it actually became visible (as line heights are accurately
4632 // measured, the position of something may 'drift' during drawing).
4633 function scrollPosIntoView(cm, pos, end, margin) {
4634 if (margin == null) margin = 0;
4635 for (var limit = 0; limit < 5; limit++) {
4636 var changed = false, coords = cursorCoords(cm, pos);
4637 var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);
4638 var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.lef t),
4639 Math.min(coords.top, endCoords.top) - m argin,
4640 Math.max(coords.left, endCoords.left),
4641 Math.max(coords.bottom, endCoords.botto m) + margin);
4642 var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
4643 if (scrollPos.scrollTop != null) {
4644 setScrollTop(cm, scrollPos.scrollTop);
4645 if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
4646 }
4647 if (scrollPos.scrollLeft != null) {
4648 setScrollLeft(cm, scrollPos.scrollLeft);
4649 if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
4650 }
4651 if (!changed) break;
4652 }
4653 return coords;
4654 }
4655
4656 // Scroll a given set of coordinates into view (immediately).
4657 function scrollIntoView(cm, x1, y1, x2, y2) {
4658 var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
4659 if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
4660 if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
4661 }
4662
4663 // Calculate a new scroll position needed to scroll the given
4664 // rectangle into view. Returns an object with scrollTop and
4665 // scrollLeft properties. When these are undefined, the
4666 // vertical/horizontal position does not need to be adjusted.
4667 function calculateScrollPos(cm, x1, y1, x2, y2) {
4668 var display = cm.display, snapMargin = textHeight(cm.display);
4669 if (y1 < 0) y1 = 0;
4670 var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;
4671 var screen = displayHeight(cm), result = {};
4672 if (y2 - y1 > screen) y2 = y1 + screen;
4673 var docBottom = cm.doc.height + paddingVert(display);
4674 var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
4675 if (y1 < screentop) {
4676 result.scrollTop = atTop ? 0 : y1;
4677 } else if (y2 > screentop + screen) {
4678 var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
4679 if (newTop != screentop) result.scrollTop = newTop;
4680 }
4681
4682 var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLe ft : display.scroller.scrollLeft;
4683 var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.o ffsetWidth : 0);
4684 var tooWide = x2 - x1 > screenw;
4685 if (tooWide) x2 = x1 + screenw;
4686 if (x1 < 10)
4687 result.scrollLeft = 0;
4688 else if (x1 < screenleft)
4689 result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10));
4690 else if (x2 > screenw + screenleft - 3)
4691 result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw;
4692 return result;
4693 }
4694
4695 // Store a relative adjustment to the scroll position in the current
4696 // operation (to be applied when the operation finishes).
4697 function addToScrollPos(cm, left, top) {
4698 if (left != null || top != null) resolveScrollToPos(cm);
4699 if (left != null)
4700 cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : c m.curOp.scrollLeft) + left;
4701 if (top != null)
4702 cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.c urOp.scrollTop) + top;
4703 }
4704
4705 // Make sure that at the end of the operation the current cursor is
4706 // shown.
4707 function ensureCursorVisible(cm) {
4708 resolveScrollToPos(cm);
4709 var cur = cm.getCursor(), from = cur, to = cur;
4710 if (!cm.options.lineWrapping) {
4711 from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur;
4712 to = Pos(cur.line, cur.ch + 1);
4713 }
4714 cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollM argin, isCursor: true};
4715 }
4716
4717 // When an operation has its scrollToPos property set, and another
4718 // scroll action is applied before the end of the operation, this
4719 // 'simulates' scrolling that position into view in a cheap way, so
4720 // that the effect of intermediate scroll commands is not ignored.
4721 function resolveScrollToPos(cm) {
4722 var range = cm.curOp.scrollToPos;
4723 if (range) {
4724 cm.curOp.scrollToPos = null;
4725 var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.t o);
4726 var sPos = calculateScrollPos(cm, Math.min(from.left, to.left),
4727 Math.min(from.top, to.top) - range.margin,
4728 Math.max(from.right, to.right),
4729 Math.max(from.bottom, to.bottom) + range.mar gin);
4730 cm.scrollTo(sPos.scrollLeft, sPos.scrollTop);
4731 }
4732 }
4733
4734 // API UTILITIES
4735
4736 // Indent the given line. The how parameter can be "smart",
4737 // "add"/null, "subtract", or "prev". When aggressive is false
4738 // (typically set to true for forced single-line indents), empty
4739 // lines are not indented, and places where the mode returns Pass
4740 // are left alone.
4741 function indentLine(cm, n, how, aggressive) {
4742 var doc = cm.doc, state;
4743 if (how == null) how = "add";
4744 if (how == "smart") {
4745 // Fall back to "prev" when the mode doesn't have an indentation
4746 // method.
4747 if (!doc.mode.indent) how = "prev";
4748 else state = getStateBefore(cm, n);
4749 }
4750
4751 var tabSize = cm.options.tabSize;
4752 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize) ;
4753 if (line.stateAfter) line.stateAfter = null;
4754 var curSpaceString = line.text.match(/^\s*/)[0], indentation;
4755 if (!aggressive && !/\S/.test(line.text)) {
4756 indentation = 0;
4757 how = "not";
4758 } else if (how == "smart") {
4759 indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length ), line.text);
4760 if (indentation == Pass || indentation > 150) {
4761 if (!aggressive) return;
4762 how = "prev";
4763 }
4764 }
4765 if (how == "prev") {
4766 if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
4767 else indentation = 0;
4768 } else if (how == "add") {
4769 indentation = curSpace + cm.options.indentUnit;
4770 } else if (how == "subtract") {
4771 indentation = curSpace - cm.options.indentUnit;
4772 } else if (typeof how == "number") {
4773 indentation = curSpace + how;
4774 }
4775 indentation = Math.max(0, indentation);
4776
4777 var indentString = "", pos = 0;
4778 if (cm.options.indentWithTabs)
4779 for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; i ndentString += "\t";}
4780 if (pos < indentation) indentString += spaceStr(indentation - pos);
4781
4782 if (indentString != curSpaceString) {
4783 replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
4784 line.stateAfter = null;
4785 return true;
7126 } else { 4786 } else {
7127 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bo ttom ? 20 : 0 4787 // Ensure that, if the cursor was in the whitespace at the start
7128 if (outside) { setTimeout(operation(cm, function () { 4788 // of the line, it is moved to the end of that space.
7129 if (counter != curCount) { return } 4789 for (var i = 0; i < doc.sel.ranges.length; i++) {
7130 display.scroller.scrollTop += outside 4790 var range = doc.sel.ranges[i];
7131 extend(e) 4791 if (range.head.line == n && range.head.ch < curSpaceString.length) {
7132 }), 50) } 4792 var pos = Pos(n, curSpaceString.length);
7133 } 4793 replaceOneSelection(doc, i, new Range(pos, pos));
7134 } 4794 break;
7135 4795 }
7136 function done(e) { 4796 }
7137 cm.state.selectingText = false 4797 }
7138 counter = Infinity 4798 }
7139 e_preventDefault(e) 4799
7140 display.input.focus() 4800 // Utility for applying a change to a line by handle or number,
7141 off(document, "mousemove", move) 4801 // returning the number and optionally registering the line as
7142 off(document, "mouseup", up) 4802 // changed.
7143 doc.history.lastSelOrigin = null 4803 function changeLine(doc, handle, changeType, op) {
7144 } 4804 var no = handle, line = handle;
7145 4805 if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
7146 var move = operation(cm, function (e) { 4806 else no = lineNo(handle);
7147 if (!e_button(e)) { done(e) } 4807 if (no == null) return null;
7148 else { extend(e) } 4808 if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType);
7149 }) 4809 return line;
7150 var up = operation(cm, done) 4810 }
7151 cm.state.selectingText = up 4811
7152 on(document, "mousemove", move) 4812 // Helper for deleting text near the selection(s), used to implement
7153 on(document, "mouseup", up) 4813 // backspace, delete, and similar functionality.
7154 } 4814 function deleteNearSelection(cm, compute) {
7155 4815 var ranges = cm.doc.sel.ranges, kill = [];
7156 4816 // Build up a set of ranges to kill first, merging overlapping
7157 // Determines whether an event happened in the gutter, and fires the 4817 // ranges.
7158 // handlers for the corresponding event. 4818 for (var i = 0; i < ranges.length; i++) {
7159 function gutterEvent(cm, e, type, prevent) { 4819 var toKill = compute(ranges[i]);
7160 var mX, mY 4820 while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {
7161 try { mX = e.clientX; mY = e.clientY } 4821 var replaced = kill.pop();
7162 catch(e) { return false } 4822 if (cmp(replaced.from, toKill.from) < 0) {
7163 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { retu rn false } 4823 toKill.from = replaced.from;
7164 if (prevent) { e_preventDefault(e) } 4824 break;
7165 4825 }
7166 var display = cm.display 4826 }
7167 var lineBox = display.lineDiv.getBoundingClientRect() 4827 kill.push(toKill);
7168 4828 }
7169 if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented( e) } 4829 // Next, remove those actual ranges.
7170 mY -= lineBox.top - display.viewOffset 4830 runInOp(cm, function() {
7171 4831 for (var i = kill.length - 1; i >= 0; i--)
7172 for (var i = 0; i < cm.options.gutters.length; ++i) { 4832 replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete");
7173 var g = display.gutters.childNodes[i] 4833 ensureCursorVisible(cm);
7174 if (g && g.getBoundingClientRect().right >= mX) { 4834 });
7175 var line = lineAtHeight(cm.doc, mY) 4835 }
7176 var gutter = cm.options.gutters[i] 4836
7177 signal(cm, type, cm, line, gutter, e) 4837 // Used for horizontal relative motion. Dir is -1 or 1 (left or
7178 return e_defaultPrevented(e) 4838 // right), unit can be "char", "column" (like char, but doesn't
7179 } 4839 // cross line boundaries), "word" (across next word), or "group" (to
7180 } 4840 // the start of next group of word or non-word-non-whitespace
7181 } 4841 // chars). The visually param controls whether, in right-to-left
7182 4842 // text, direction 1 means to move towards the next index in the
7183 function clickInGutter(cm, e) { 4843 // string, or towards the character to the right of the current
7184 return gutterEvent(cm, e, "gutterClick", true) 4844 // position. The resulting position will have a hitSide=true
7185 } 4845 // property if it reached the end of the document.
7186 4846 function findPosH(doc, pos, dir, unit, visually) {
7187 // CONTEXT MENU HANDLING 4847 var line = pos.line, ch = pos.ch, origDir = dir;
7188 4848 var lineObj = getLine(doc, line);
7189 // To make the context menu work, we need to briefly unhide the 4849 function findNextLine() {
7190 // textarea (making it as unobtrusive as possible) to let the 4850 var l = line + dir;
7191 // right-click take effect on it. 4851 if (l < doc.first || l >= doc.first + doc.size) return false
7192 function onContextMenu(cm, e) { 4852 line = l;
7193 if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } 4853 return lineObj = getLine(doc, l);
7194 if (signalDOMEvent(cm, e, "contextmenu")) { return } 4854 }
7195 cm.display.input.onContextMenu(e) 4855 function moveOnce(boundToLine) {
7196 } 4856 var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, tru e);
7197 4857 if (next == null) {
7198 function contextMenuInGutter(cm, e) { 4858 if (!boundToLine && findNextLine()) {
7199 if (!hasHandler(cm, "gutterContextMenu")) { return false } 4859 if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
7200 return gutterEvent(cm, e, "gutterContextMenu", false) 4860 else ch = dir < 0 ? lineObj.text.length : 0;
7201 } 4861 } else return false
7202 4862 } else ch = next;
7203 function themeChanged(cm) { 4863 return true;
7204 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\ S+/g, "") + 4864 }
7205 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-") 4865
7206 clearCaches(cm) 4866 if (unit == "char") {
7207 } 4867 moveOnce()
7208 4868 } else if (unit == "column") {
7209 var Init = {toString: function(){return "CodeMirror.Init"}} 4869 moveOnce(true)
7210 4870 } else if (unit == "word" || unit == "group") {
7211 var defaults = {} 4871 var sawType = null, group = unit == "group";
7212 var optionHandlers = {} 4872 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars");
7213 4873 for (var first = true;; first = false) {
7214 function defineOptions(CodeMirror) { 4874 if (dir < 0 && !moveOnce(!first)) break;
7215 var optionHandlers = CodeMirror.optionHandlers 4875 var cur = lineObj.text.charAt(ch) || "\n";
7216 4876 var type = isWordChar(cur, helper) ? "w"
7217 function option(name, deflt, handle, notOnInit) { 4877 : group && cur == "\n" ? "n"
7218 CodeMirror.defaults[name] = deflt 4878 : !group || /\s/.test(cur) ? null
7219 if (handle) { optionHandlers[name] = 4879 : "p";
7220 notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, ol d) }} : handle } 4880 if (group && !first && !type) type = "s";
7221 } 4881 if (sawType && sawType != type) {
7222 4882 if (dir < 0) {dir = 1; moveOnce();}
7223 CodeMirror.defineOption = option 4883 break;
7224 4884 }
7225 // Passed to option handlers when there is no old value. 4885
7226 CodeMirror.Init = Init 4886 if (type) sawType = type;
7227 4887 if (dir > 0 && !moveOnce(!first)) break;
7228 // These two are, on init, called from the constructor because they 4888 }
7229 // have to be initialized before the editor can start at all. 4889 }
7230 option("value", "", function (cm, val) { return cm.setValue(val); }, true) 4890 var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true);
7231 option("mode", null, function (cm, val) { 4891 if (!cmp(pos, result)) result.hitSide = true;
7232 cm.doc.modeOption = val 4892 return result;
7233 loadMode(cm) 4893 }
7234 }, true) 4894
7235 4895 // For relative vertical movement. Dir may be -1 or 1. Unit can be
7236 option("indentUnit", 2, loadMode, true) 4896 // "page" or "line". The resulting position will have a hitSide=true
7237 option("indentWithTabs", false) 4897 // property if it reached the end of the document.
7238 option("smartIndent", true) 4898 function findPosV(cm, pos, dir, unit) {
7239 option("tabSize", 4, function (cm) { 4899 var doc = cm.doc, x = pos.left, y;
7240 resetModeState(cm) 4900 if (unit == "page") {
7241 clearCaches(cm) 4901 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeigh t || document.documentElement.clientHeight);
7242 regChange(cm) 4902 y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.displ ay));
7243 }, true) 4903 } else if (unit == "line") {
7244 option("lineSeparator", null, function (cm, val) { 4904 y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
7245 cm.doc.lineSep = val 4905 }
7246 if (!val) { return } 4906 for (;;) {
7247 var newBreaks = [], lineNo = cm.doc.first 4907 var target = coordsChar(cm, x, y);
7248 cm.doc.iter(function (line) { 4908 if (!target.outside) break;
7249 for (var pos = 0;;) { 4909 if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
7250 var found = line.text.indexOf(val, pos) 4910 y += dir * 5;
7251 if (found == -1) { break } 4911 }
7252 pos = found + val.length 4912 return target;
7253 newBreaks.push(Pos(lineNo, found)) 4913 }
7254 } 4914
7255 lineNo++ 4915 // EDITOR METHODS
7256 }) 4916
7257 for (var i = newBreaks.length - 1; i >= 0; i--) 4917 // The publicly visible API. Note that methodOp(f) means
7258 { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks [i].ch + val.length)) } 4918 // 'wrap f in an operation, performed on its `this` parameter'.
7259 }) 4919
7260 option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u 2028\u2029\ufeff]/g, function (cm, val, old) { 4920 // This is not the complete set of editor methods. Most of the
7261 cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t" ), "g") 4921 // methods defined on the Doc type are also injected into
7262 if (old != Init) { cm.refresh() } 4922 // CodeMirror.prototype, for backwards compatibility and
7263 }) 4923 // convenience.
7264 option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true)
7265 option("electricChars", true)
7266 option("inputStyle", mobile ? "contenteditable" : "textarea", function () {
7267 throw new Error("inputStyle can not (yet) be changed in a running editor") / / FIXME
7268 }, true)
7269 option("spellcheck", false, function (cm, val) { return cm.getInputField().spe llcheck = val; }, true)
7270 option("rtlMoveVisually", !windows)
7271 option("wholeLineUpdateBefore", true)
7272
7273 option("theme", "default", function (cm) {
7274 themeChanged(cm)
7275 guttersChanged(cm)
7276 }, true)
7277 option("keyMap", "default", function (cm, val, old) {
7278 var next = getKeyMap(val)
7279 var prev = old != Init && getKeyMap(old)
7280 if (prev && prev.detach) { prev.detach(cm, next) }
7281 if (next.attach) { next.attach(cm, prev || null) }
7282 })
7283 option("extraKeys", null)
7284
7285 option("lineWrapping", false, wrappingChanged, true)
7286 option("gutters", [], function (cm) {
7287 setGuttersForLineNumbers(cm.options)
7288 guttersChanged(cm)
7289 }, true)
7290 option("fixedGutter", true, function (cm, val) {
7291 cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px " : "0"
7292 cm.refresh()
7293 }, true)
7294 option("coverGutterNextToScrollbar", false, function (cm) { return updateScrol lbars(cm); }, true)
7295 option("scrollbarStyle", "native", function (cm) {
7296 initScrollbars(cm)
7297 updateScrollbars(cm)
7298 cm.display.scrollbars.setScrollTop(cm.doc.scrollTop)
7299 cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft)
7300 }, true)
7301 option("lineNumbers", false, function (cm) {
7302 setGuttersForLineNumbers(cm.options)
7303 guttersChanged(cm)
7304 }, true)
7305 option("firstLineNumber", 1, guttersChanged, true)
7306 option("lineNumberFormatter", function (integer) { return integer; }, guttersC hanged, true)
7307 option("showCursorWhenSelecting", false, updateSelection, true)
7308
7309 option("resetSelectionOnContextMenu", true)
7310 option("lineWiseCopyCut", true)
7311
7312 option("readOnly", false, function (cm, val) {
7313 if (val == "nocursor") {
7314 onBlur(cm)
7315 cm.display.input.blur()
7316 cm.display.disabled = true
7317 } else {
7318 cm.display.disabled = false
7319 }
7320 cm.display.input.readOnlyChanged(val)
7321 })
7322 option("disableInput", false, function (cm, val) {if (!val) { cm.display.input .reset() }}, true)
7323 option("dragDrop", true, dragDropChanged)
7324 option("allowDropFileTypes", null)
7325
7326 option("cursorBlinkRate", 530)
7327 option("cursorScrollMargin", 0)
7328 option("cursorHeight", 1, updateSelection, true)
7329 option("singleCursorHeightPerLine", true, updateSelection, true)
7330 option("workTime", 100)
7331 option("workDelay", 100)
7332 option("flattenSpans", true, resetModeState, true)
7333 option("addModeClass", false, resetModeState, true)
7334 option("pollInterval", 100)
7335 option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; })
7336 option("historyEventDelay", 1250)
7337 option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true)
7338 option("maxHighlightLength", 10000, resetModeState, true)
7339 option("moveInputWithCursor", true, function (cm, val) {
7340 if (!val) { cm.display.input.resetPosition() }
7341 })
7342
7343 option("tabindex", null, function (cm, val) { return cm.display.input.getField ().tabIndex = val || ""; })
7344 option("autofocus", null)
7345 option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val ); }, true)
7346 }
7347
7348 function guttersChanged(cm) {
7349 updateGutters(cm)
7350 regChange(cm)
7351 alignHorizontally(cm)
7352 }
7353
7354 function dragDropChanged(cm, value, old) {
7355 var wasOn = old && old != Init
7356 if (!value != !wasOn) {
7357 var funcs = cm.display.dragFunctions
7358 var toggle = value ? on : off
7359 toggle(cm.display.scroller, "dragstart", funcs.start)
7360 toggle(cm.display.scroller, "dragenter", funcs.enter)
7361 toggle(cm.display.scroller, "dragover", funcs.over)
7362 toggle(cm.display.scroller, "dragleave", funcs.leave)
7363 toggle(cm.display.scroller, "drop", funcs.drop)
7364 }
7365 }
7366
7367 function wrappingChanged(cm) {
7368 if (cm.options.lineWrapping) {
7369 addClass(cm.display.wrapper, "CodeMirror-wrap")
7370 cm.display.sizer.style.minWidth = ""
7371 cm.display.sizerWidth = null
7372 } else {
7373 rmClass(cm.display.wrapper, "CodeMirror-wrap")
7374 findMaxLine(cm)
7375 }
7376 estimateLineHeights(cm)
7377 regChange(cm)
7378 clearCaches(cm)
7379 setTimeout(function () { return updateScrollbars(cm); }, 100)
7380 }
7381
7382 // A CodeMirror instance represents an editor. This is the object
7383 // that user code is usually dealing with.
7384
7385 function CodeMirror(place, options) {
7386 var this$1 = this;
7387
7388 if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) }
7389
7390 this.options = options = options ? copyObj(options) : {}
7391 // Determine effective options based on given values and defaults.
7392 copyObj(defaults, options, false)
7393 setGuttersForLineNumbers(options)
7394
7395 var doc = options.value
7396 if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.l ineSeparator, options.direction) }
7397 this.doc = doc
7398
7399 var input = new CodeMirror.inputStyles[options.inputStyle](this)
7400 var display = this.display = new Display(place, doc, input)
7401 display.wrapper.CodeMirror = this
7402 updateGutters(this)
7403 themeChanged(this)
7404 if (options.lineWrapping)
7405 { this.display.wrapper.className += " CodeMirror-wrap" }
7406 initScrollbars(this)
7407
7408 this.state = {
7409 keyMaps: [], // stores maps added by addKeyMap
7410 overlays: [], // highlighting overlays, as added by addOverlay
7411 modeGen: 0, // bumped when mode/overlay changes, used to invalidate highli ghting info
7412 overwrite: false,
7413 delayingBlurEvent: false,
7414 focused: false,
7415 suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
7416 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll
7417 selectingText: false,
7418 draggingText: false,
7419 highlight: new Delayed(), // stores highlight worker timeout
7420 keySeq: null, // Unfinished key sequence
7421 specialChars: null
7422 }
7423
7424 if (options.autofocus && !mobile) { display.input.focus() }
7425
7426 // Override magic textarea content restore that IE sometimes does
7427 // on our hidden textarea on reload
7428 if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.in put.reset(true); }, 20) }
7429
7430 registerEventHandlers(this)
7431 ensureGlobalHandlers()
7432
7433 startOperation(this)
7434 this.curOp.forceUpdate = true
7435 attachDoc(this, doc)
7436
7437 if ((options.autofocus && !mobile) || this.hasFocus())
7438 { setTimeout(bind(onFocus, this), 20) }
7439 else
7440 { onBlur(this) }
7441
7442 for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt))
7443 { optionHandlers[opt](this$1, options[opt], Init) } }
7444 maybeUpdateLineNumberWidth(this)
7445 if (options.finishInit) { options.finishInit(this) }
7446 for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1) }
7447 endOperation(this)
7448 // Suppress optimizelegibility in Webkit, since it breaks text
7449 // measuring on line wrapping boundaries.
7450 if (webkit && options.lineWrapping &&
7451 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility")
7452 { display.lineDiv.style.textRendering = "auto" }
7453 }
7454
7455 // The default configuration options.
7456 CodeMirror.defaults = defaults
7457 // Functions to run when options are changed.
7458 CodeMirror.optionHandlers = optionHandlers
7459
7460 // Attach the necessary event handlers when initializing the editor
7461 function registerEventHandlers(cm) {
7462 var d = cm.display
7463 on(d.scroller, "mousedown", operation(cm, onMouseDown))
7464 // Older IE's will not fire a second mousedown for a double click
7465 if (ie && ie_version < 11)
7466 { on(d.scroller, "dblclick", operation(cm, function (e) {
7467 if (signalDOMEvent(cm, e)) { return }
7468 var pos = posFromMouse(cm, e)
7469 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return }
7470 e_preventDefault(e)
7471 var word = cm.findWordAt(pos)
7472 extendSelection(cm.doc, word.anchor, word.head)
7473 })) }
7474 else
7475 { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }) }
7476 // Some browsers fire contextmenu *after* opening the menu, at
7477 // which point we can't mess with it anymore. Context menu is
7478 // handled in onMouseDown for these browsers.
7479 if (!captureRightClick) { on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }) }
7480
7481 // Used to suppress mouse event handling when a touch happens
7482 var touchFinished, prevTouch = {end: 0}
7483 function finishTouch() {
7484 if (d.activeTouch) {
7485 touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1 000)
7486 prevTouch = d.activeTouch
7487 prevTouch.end = +new Date
7488 }
7489 }
7490 function isMouseLikeTouchEvent(e) {
7491 if (e.touches.length != 1) { return false }
7492 var touch = e.touches[0]
7493 return touch.radiusX <= 1 && touch.radiusY <= 1
7494 }
7495 function farAway(touch, other) {
7496 if (other.left == null) { return true }
7497 var dx = other.left - touch.left, dy = other.top - touch.top
7498 return dx * dx + dy * dy > 20 * 20
7499 }
7500 on(d.scroller, "touchstart", function (e) {
7501 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) {
7502 d.input.ensurePolled()
7503 clearTimeout(touchFinished)
7504 var now = +new Date
7505 d.activeTouch = {start: now, moved: false,
7506 prev: now - prevTouch.end <= 300 ? prevTouch : null}
7507 if (e.touches.length == 1) {
7508 d.activeTouch.left = e.touches[0].pageX
7509 d.activeTouch.top = e.touches[0].pageY
7510 }
7511 }
7512 })
7513 on(d.scroller, "touchmove", function () {
7514 if (d.activeTouch) { d.activeTouch.moved = true }
7515 })
7516 on(d.scroller, "touchend", function (e) {
7517 var touch = d.activeTouch
7518 if (touch && !eventInWidget(d, e) && touch.left != null &&
7519 !touch.moved && new Date - touch.start < 300) {
7520 var pos = cm.coordsChar(d.activeTouch, "page"), range
7521 if (!touch.prev || farAway(touch, touch.prev)) // Single tap
7522 { range = new Range(pos, pos) }
7523 else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double ta p
7524 { range = cm.findWordAt(pos) }
7525 else // Triple tap
7526 { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) }
7527 cm.setSelection(range.anchor, range.head)
7528 cm.focus()
7529 e_preventDefault(e)
7530 }
7531 finishTouch()
7532 })
7533 on(d.scroller, "touchcancel", finishTouch)
7534
7535 // Sync scrolling between fake scrollbars and real scrollable
7536 // area, ensure viewport is updated when scrolling.
7537 on(d.scroller, "scroll", function () {
7538 if (d.scroller.clientHeight) {
7539 setScrollTop(cm, d.scroller.scrollTop)
7540 setScrollLeft(cm, d.scroller.scrollLeft, true)
7541 signal(cm, "scroll", cm)
7542 }
7543 })
7544
7545 // Listen to wheel events in order to try and update the viewport on time.
7546 on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); })
7547 on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); } )
7548
7549 // Prevent wrapper from ever scrolling
7550 on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.s crollLeft = 0; })
7551
7552 d.dragFunctions = {
7553 enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e) }},
7554 over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop( e) }},
7555 start: function (e) { return onDragStart(cm, e); },
7556 drop: operation(cm, onDrop),
7557 leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }}
7558 }
7559
7560 var inp = d.input.getField()
7561 on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); })
7562 on(inp, "keydown", operation(cm, onKeyDown))
7563 on(inp, "keypress", operation(cm, onKeyPress))
7564 on(inp, "focus", function (e) { return onFocus(cm, e); })
7565 on(inp, "blur", function (e) { return onBlur(cm, e); })
7566 }
7567
7568 var initHooks = []
7569 CodeMirror.defineInitHook = function (f) { return initHooks.push(f); }
7570
7571 // Indent the given line. The how parameter can be "smart",
7572 // "add"/null, "subtract", or "prev". When aggressive is false
7573 // (typically set to true for forced single-line indents), empty
7574 // lines are not indented, and places where the mode returns Pass
7575 // are left alone.
7576 function indentLine(cm, n, how, aggressive) {
7577 var doc = cm.doc, state
7578 if (how == null) { how = "add" }
7579 if (how == "smart") {
7580 // Fall back to "prev" when the mode doesn't have an indentation
7581 // method.
7582 if (!doc.mode.indent) { how = "prev" }
7583 else { state = getStateBefore(cm, n) }
7584 }
7585
7586 var tabSize = cm.options.tabSize
7587 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize)
7588 if (line.stateAfter) { line.stateAfter = null }
7589 var curSpaceString = line.text.match(/^\s*/)[0], indentation
7590 if (!aggressive && !/\S/.test(line.text)) {
7591 indentation = 0
7592 how = "not"
7593 } else if (how == "smart") {
7594 indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text)
7595 if (indentation == Pass || indentation > 150) {
7596 if (!aggressive) { return }
7597 how = "prev"
7598 }
7599 }
7600 if (how == "prev") {
7601 if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize) }
7602 else { indentation = 0 }
7603 } else if (how == "add") {
7604 indentation = curSpace + cm.options.indentUnit
7605 } else if (how == "subtract") {
7606 indentation = curSpace - cm.options.indentUnit
7607 } else if (typeof how == "number") {
7608 indentation = curSpace + how
7609 }
7610 indentation = Math.max(0, indentation)
7611
7612 var indentString = "", pos = 0
7613 if (cm.options.indentWithTabs)
7614 { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; i ndentString += "\t"} }
7615 if (pos < indentation) { indentString += spaceStr(indentation - pos) }
7616
7617 if (indentString != curSpaceString) {
7618 replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+ input")
7619 line.stateAfter = null
7620 return true
7621 } else {
7622 // Ensure that, if the cursor was in the whitespace at the start
7623 // of the line, it is moved to the end of that space.
7624 for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) {
7625 var range = doc.sel.ranges[i$1]
7626 if (range.head.line == n && range.head.ch < curSpaceString.length) {
7627 var pos$1 = Pos(n, curSpaceString.length)
7628 replaceOneSelection(doc, i$1, new Range(pos$1, pos$1))
7629 break
7630 }
7631 }
7632 }
7633 }
7634
7635 // This will be set to a {lineWise: bool, text: [string]} object, so
7636 // that, when pasting, we know what kind of selections the copied
7637 // text was made out of.
7638 var lastCopied = null
7639
7640 function setLastCopied(newLastCopied) {
7641 lastCopied = newLastCopied
7642 }
7643
7644 function applyTextInput(cm, inserted, deleted, sel, origin) {
7645 var doc = cm.doc
7646 cm.display.shift = false
7647 if (!sel) { sel = doc.sel }
7648
7649 var paste = cm.state.pasteIncoming || origin == "paste"
7650 var textLines = splitLinesAuto(inserted), multiPaste = null
7651 // When pasing N lines into N selections, insert one line per selection
7652 if (paste && sel.ranges.length > 1) {
7653 if (lastCopied && lastCopied.text.join("\n") == inserted) {
7654 if (sel.ranges.length % lastCopied.text.length == 0) {
7655 multiPaste = []
7656 for (var i = 0; i < lastCopied.text.length; i++)
7657 { multiPaste.push(doc.splitLines(lastCopied.text[i])) }
7658 }
7659 } else if (textLines.length == sel.ranges.length) {
7660 multiPaste = map(textLines, function (l) { return [l]; })
7661 }
7662 }
7663
7664 var updateInput
7665 // Normal behavior is to insert the new text into every selection
7666 for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) {
7667 var range = sel.ranges[i$1]
7668 var from = range.from(), to = range.to()
7669 if (range.empty()) {
7670 if (deleted && deleted > 0) // Handle deletion
7671 { from = Pos(from.line, from.ch - deleted) }
7672 else if (cm.state.overwrite && !paste) // Handle overwrite
7673 { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)) }
7674 else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") = = inserted)
7675 { from = to = Pos(from.line, 0) }
7676 }
7677 updateInput = cm.curOp.updateInput
7678 var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % m ultiPaste.length] : textLines,
7679 origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")}
7680 makeChange(cm.doc, changeEvent)
7681 signalLater(cm, "inputRead", cm, changeEvent)
7682 }
7683 if (inserted && !paste)
7684 { triggerElectric(cm, inserted) }
7685
7686 ensureCursorVisible(cm)
7687 cm.curOp.updateInput = updateInput
7688 cm.curOp.typing = true
7689 cm.state.pasteIncoming = cm.state.cutIncoming = false
7690 }
7691
7692 function handlePaste(e, cm) {
7693 var pasted = e.clipboardData && e.clipboardData.getData("Text")
7694 if (pasted) {
7695 e.preventDefault()
7696 if (!cm.isReadOnly() && !cm.options.disableInput)
7697 { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "pa ste"); }) }
7698 return true
7699 }
7700 }
7701
7702 function triggerElectric(cm, inserted) {
7703 // When an 'electric' character is inserted, immediately trigger a reindent
7704 if (!cm.options.electricChars || !cm.options.smartIndent) { return }
7705 var sel = cm.doc.sel
7706
7707 for (var i = sel.ranges.length - 1; i >= 0; i--) {
7708 var range = sel.ranges[i]
7709 if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.l ine)) { continue }
7710 var mode = cm.getModeAt(range.head)
7711 var indented = false
7712 if (mode.electricChars) {
7713 for (var j = 0; j < mode.electricChars.length; j++)
7714 { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
7715 indented = indentLine(cm, range.head.line, "smart")
7716 break
7717 } }
7718 } else if (mode.electricInput) {
7719 if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch)))
7720 { indented = indentLine(cm, range.head.line, "smart") }
7721 }
7722 if (indented) { signalLater(cm, "electricInput", cm, range.head.line) }
7723 }
7724 }
7725
7726 function copyableRanges(cm) {
7727 var text = [], ranges = []
7728 for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
7729 var line = cm.doc.sel.ranges[i].head.line
7730 var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}
7731 ranges.push(lineRange)
7732 text.push(cm.getRange(lineRange.anchor, lineRange.head))
7733 }
7734 return {text: text, ranges: ranges}
7735 }
7736
7737 function disableBrowserMagic(field, spellcheck) {
7738 field.setAttribute("autocorrect", "off")
7739 field.setAttribute("autocapitalize", "off")
7740 field.setAttribute("spellcheck", !!spellcheck)
7741 }
7742
7743 function hiddenTextarea() {
7744 var te = elt("textarea", null, null, "position: absolute; bottom: -1em; paddin g: 0; width: 1px; height: 1em; outline: none")
7745 var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;")
7746 // The textarea is kept positioned near the cursor to prevent the
7747 // fact that it'll be scrolled into view on input from scrolling
7748 // our fake cursor out of view. On webkit, when wrap=off, paste is
7749 // very slow. So make the area wide instead.
7750 if (webkit) { te.style.width = "1000px" }
7751 else { te.setAttribute("wrap", "off") }
7752 // If border: 0; -- iOS fails to open keyboard (issue #1287)
7753 if (ios) { te.style.border = "1px solid black" }
7754 disableBrowserMagic(te)
7755 return div
7756 }
7757
7758 // The publicly visible API. Note that methodOp(f) means
7759 // 'wrap f in an operation, performed on its `this` parameter'.
7760
7761 // This is not the complete set of editor methods. Most of the
7762 // methods defined on the Doc type are also injected into
7763 // CodeMirror.prototype, for backwards compatibility and
7764 // convenience.
7765
7766 function addEditorMethods(CodeMirror) {
7767 var optionHandlers = CodeMirror.optionHandlers
7768
7769 var helpers = CodeMirror.helpers = {}
7770 4924
7771 CodeMirror.prototype = { 4925 CodeMirror.prototype = {
7772 constructor: CodeMirror, 4926 constructor: CodeMirror,
7773 focus: function(){window.focus(); this.display.input.focus()}, 4927 focus: function(){window.focus(); this.display.input.focus();},
7774 4928
7775 setOption: function(option, value) { 4929 setOption: function(option, value) {
7776 var options = this.options, old = options[option] 4930 var options = this.options, old = options[option];
7777 if (options[option] == value && option != "mode") { return } 4931 if (options[option] == value && option != "mode") return;
7778 options[option] = value 4932 options[option] = value;
7779 if (optionHandlers.hasOwnProperty(option)) 4933 if (optionHandlers.hasOwnProperty(option))
7780 { operation(this, optionHandlers[option])(this, value, old) } 4934 operation(this, optionHandlers[option])(this, value, old);
7781 signal(this, "optionChange", this, option)
7782 }, 4935 },
7783 4936
7784 getOption: function(option) {return this.options[option]}, 4937 getOption: function(option) {return this.options[option];},
7785 getDoc: function() {return this.doc}, 4938 getDoc: function() {return this.doc;},
7786 4939
7787 addKeyMap: function(map, bottom) { 4940 addKeyMap: function(map, bottom) {
7788 this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)) 4941 this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map));
7789 }, 4942 },
7790 removeKeyMap: function(map) { 4943 removeKeyMap: function(map) {
7791 var maps = this.state.keyMaps 4944 var maps = this.state.keyMaps;
7792 for (var i = 0; i < maps.length; ++i) 4945 for (var i = 0; i < maps.length; ++i)
7793 { if (maps[i] == map || maps[i].name == map) { 4946 if (maps[i] == map || maps[i].name == map) {
7794 maps.splice(i, 1) 4947 maps.splice(i, 1);
7795 return true 4948 return true;
7796 } } 4949 }
7797 }, 4950 },
7798 4951
7799 addOverlay: methodOp(function(spec, options) { 4952 addOverlay: methodOp(function(spec, options) {
7800 var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec) 4953 var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
7801 if (mode.startState) { throw new Error("Overlays may not be stateful.") } 4954 if (mode.startState) throw new Error("Overlays may not be stateful.");
7802 insertSorted(this.state.overlays, 4955 this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && o ptions.opaque});
7803 {mode: mode, modeSpec: spec, opaque: options && options.opaqu e, 4956 this.state.modeGen++;
7804 priority: (options && options.priority) || 0}, 4957 regChange(this);
7805 function (overlay) { return overlay.priority; })
7806 this.state.modeGen++
7807 regChange(this)
7808 }), 4958 }),
7809 removeOverlay: methodOp(function(spec) { 4959 removeOverlay: methodOp(function(spec) {
7810 var this$1 = this; 4960 var overlays = this.state.overlays;
7811
7812 var overlays = this.state.overlays
7813 for (var i = 0; i < overlays.length; ++i) { 4961 for (var i = 0; i < overlays.length; ++i) {
7814 var cur = overlays[i].modeSpec 4962 var cur = overlays[i].modeSpec;
7815 if (cur == spec || typeof spec == "string" && cur.name == spec) { 4963 if (cur == spec || typeof spec == "string" && cur.name == spec) {
7816 overlays.splice(i, 1) 4964 overlays.splice(i, 1);
7817 this$1.state.modeGen++ 4965 this.state.modeGen++;
7818 regChange(this$1) 4966 regChange(this);
7819 return 4967 return;
7820 } 4968 }
7821 } 4969 }
7822 }), 4970 }),
7823 4971
7824 indentLine: methodOp(function(n, dir, aggressive) { 4972 indentLine: methodOp(function(n, dir, aggressive) {
7825 if (typeof dir != "string" && typeof dir != "number") { 4973 if (typeof dir != "string" && typeof dir != "number") {
7826 if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev" } 4974 if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
7827 else { dir = dir ? "add" : "subtract" } 4975 else dir = dir ? "add" : "subtract";
7828 } 4976 }
7829 if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive) } 4977 if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
7830 }), 4978 }),
7831 indentSelection: methodOp(function(how) { 4979 indentSelection: methodOp(function(how) {
7832 var this$1 = this; 4980 var ranges = this.doc.sel.ranges, end = -1;
7833
7834 var ranges = this.doc.sel.ranges, end = -1
7835 for (var i = 0; i < ranges.length; i++) { 4981 for (var i = 0; i < ranges.length; i++) {
7836 var range = ranges[i] 4982 var range = ranges[i];
7837 if (!range.empty()) { 4983 if (!range.empty()) {
7838 var from = range.from(), to = range.to() 4984 var from = range.from(), to = range.to();
7839 var start = Math.max(end, from.line) 4985 var start = Math.max(end, from.line);
7840 end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1 4986 end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;
7841 for (var j = start; j < end; ++j) 4987 for (var j = start; j < end; ++j)
7842 { indentLine(this$1, j, how) } 4988 indentLine(this, j, how);
7843 var newRanges = this$1.doc.sel.ranges 4989 var newRanges = this.doc.sel.ranges;
7844 if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i]. from().ch > 0) 4990 if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i]. from().ch > 0)
7845 { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to ()), sel_dontScroll) } 4991 replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll);
7846 } else if (range.head.line > end) { 4992 } else if (range.head.line > end) {
7847 indentLine(this$1, range.head.line, how, true) 4993 indentLine(this, range.head.line, how, true);
7848 end = range.head.line 4994 end = range.head.line;
7849 if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1) } 4995 if (i == this.doc.sel.primIndex) ensureCursorVisible(this);
7850 } 4996 }
7851 } 4997 }
7852 }), 4998 }),
7853 4999
7854 // Fetch the parser token for a given character. Useful for hacks 5000 // Fetch the parser token for a given character. Useful for hacks
7855 // that want to inspect the mode state (say, for completion). 5001 // that want to inspect the mode state (say, for completion).
7856 getTokenAt: function(pos, precise) { 5002 getTokenAt: function(pos, precise) {
7857 return takeToken(this, pos, precise) 5003 return takeToken(this, pos, precise);
7858 }, 5004 },
7859 5005
7860 getLineTokens: function(line, precise) { 5006 getLineTokens: function(line, precise) {
7861 return takeToken(this, Pos(line), precise, true) 5007 return takeToken(this, Pos(line), precise, true);
7862 }, 5008 },
7863 5009
7864 getTokenTypeAt: function(pos) { 5010 getTokenTypeAt: function(pos) {
7865 pos = clipPos(this.doc, pos) 5011 pos = clipPos(this.doc, pos);
7866 var styles = getLineStyles(this, getLine(this.doc, pos.line)) 5012 var styles = getLineStyles(this, getLine(this.doc, pos.line));
7867 var before = 0, after = (styles.length - 1) / 2, ch = pos.ch 5013 var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
7868 var type 5014 var type;
7869 if (ch == 0) { type = styles[2] } 5015 if (ch == 0) type = styles[2];
7870 else { for (;;) { 5016 else for (;;) {
7871 var mid = (before + after) >> 1 5017 var mid = (before + after) >> 1;
7872 if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid } 5018 if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
7873 else if (styles[mid * 2 + 1] < ch) { before = mid + 1 } 5019 else if (styles[mid * 2 + 1] < ch) before = mid + 1;
7874 else { type = styles[mid * 2 + 2]; break } 5020 else { type = styles[mid * 2 + 2]; break; }
7875 } } 5021 }
7876 var cut = type ? type.indexOf("overlay ") : -1 5022 var cut = type ? type.indexOf("cm-overlay ") : -1;
7877 return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) 5023 return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1);
7878 }, 5024 },
7879 5025
7880 getModeAt: function(pos) { 5026 getModeAt: function(pos) {
7881 var mode = this.doc.mode 5027 var mode = this.doc.mode;
7882 if (!mode.innerMode) { return mode } 5028 if (!mode.innerMode) return mode;
7883 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode 5029 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
7884 }, 5030 },
7885 5031
7886 getHelper: function(pos, type) { 5032 getHelper: function(pos, type) {
7887 return this.getHelpers(pos, type)[0] 5033 return this.getHelpers(pos, type)[0];
7888 }, 5034 },
7889 5035
7890 getHelpers: function(pos, type) { 5036 getHelpers: function(pos, type) {
7891 var this$1 = this; 5037 var found = [];
7892 5038 if (!helpers.hasOwnProperty(type)) return found;
7893 var found = [] 5039 var help = helpers[type], mode = this.getModeAt(pos);
7894 if (!helpers.hasOwnProperty(type)) { return found }
7895 var help = helpers[type], mode = this.getModeAt(pos)
7896 if (typeof mode[type] == "string") { 5040 if (typeof mode[type] == "string") {
7897 if (help[mode[type]]) { found.push(help[mode[type]]) } 5041 if (help[mode[type]]) found.push(help[mode[type]]);
7898 } else if (mode[type]) { 5042 } else if (mode[type]) {
7899 for (var i = 0; i < mode[type].length; i++) { 5043 for (var i = 0; i < mode[type].length; i++) {
7900 var val = help[mode[type][i]] 5044 var val = help[mode[type][i]];
7901 if (val) { found.push(val) } 5045 if (val) found.push(val);
7902 } 5046 }
7903 } else if (mode.helperType && help[mode.helperType]) { 5047 } else if (mode.helperType && help[mode.helperType]) {
7904 found.push(help[mode.helperType]) 5048 found.push(help[mode.helperType]);
7905 } else if (help[mode.name]) { 5049 } else if (help[mode.name]) {
7906 found.push(help[mode.name]) 5050 found.push(help[mode.name]);
7907 } 5051 }
7908 for (var i$1 = 0; i$1 < help._global.length; i$1++) { 5052 for (var i = 0; i < help._global.length; i++) {
7909 var cur = help._global[i$1] 5053 var cur = help._global[i];
7910 if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1) 5054 if (cur.pred(mode, this) && indexOf(found, cur.val) == -1)
7911 { found.push(cur.val) } 5055 found.push(cur.val);
7912 } 5056 }
7913 return found 5057 return found;
7914 }, 5058 },
7915 5059
7916 getStateAfter: function(line, precise) { 5060 getStateAfter: function(line, precise) {
7917 var doc = this.doc 5061 var doc = this.doc;
7918 line = clipLine(doc, line == null ? doc.first + doc.size - 1: line) 5062 line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
7919 return getStateBefore(this, line + 1, precise) 5063 return getStateBefore(this, line + 1, precise);
7920 }, 5064 },
7921 5065
7922 cursorCoords: function(start, mode) { 5066 cursorCoords: function(start, mode) {
7923 var pos, range = this.doc.sel.primary() 5067 var pos, range = this.doc.sel.primary();
7924 if (start == null) { pos = range.head } 5068 if (start == null) pos = range.head;
7925 else if (typeof start == "object") { pos = clipPos(this.doc, start) } 5069 else if (typeof start == "object") pos = clipPos(this.doc, start);
7926 else { pos = start ? range.from() : range.to() } 5070 else pos = start ? range.from() : range.to();
7927 return cursorCoords(this, pos, mode || "page") 5071 return cursorCoords(this, pos, mode || "page");
7928 }, 5072 },
7929 5073
7930 charCoords: function(pos, mode) { 5074 charCoords: function(pos, mode) {
7931 return charCoords(this, clipPos(this.doc, pos), mode || "page") 5075 return charCoords(this, clipPos(this.doc, pos), mode || "page");
7932 }, 5076 },
7933 5077
7934 coordsChar: function(coords, mode) { 5078 coordsChar: function(coords, mode) {
7935 coords = fromCoordSystem(this, coords, mode || "page") 5079 coords = fromCoordSystem(this, coords, mode || "page");
7936 return coordsChar(this, coords.left, coords.top) 5080 return coordsChar(this, coords.left, coords.top);
7937 }, 5081 },
7938 5082
7939 lineAtHeight: function(height, mode) { 5083 lineAtHeight: function(height, mode) {
7940 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top 5084 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top ;
7941 return lineAtHeight(this.doc, height + this.display.viewOffset) 5085 return lineAtHeight(this.doc, height + this.display.viewOffset);
7942 }, 5086 },
7943 heightAtLine: function(line, mode, includeWidgets) { 5087 heightAtLine: function(line, mode) {
7944 var end = false, lineObj 5088 var end = false, lineObj;
7945 if (typeof line == "number") { 5089 if (typeof line == "number") {
7946 var last = this.doc.first + this.doc.size - 1 5090 var last = this.doc.first + this.doc.size - 1;
7947 if (line < this.doc.first) { line = this.doc.first } 5091 if (line < this.doc.first) line = this.doc.first;
7948 else if (line > last) { line = last; end = true } 5092 else if (line > last) { line = last; end = true; }
7949 lineObj = getLine(this.doc, line) 5093 lineObj = getLine(this.doc, line);
7950 } else { 5094 } else {
7951 lineObj = line 5095 lineObj = line;
7952 } 5096 }
7953 return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", i ncludeWidgets || end).top + 5097 return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").t op +
7954 (end ? this.doc.height - heightAtLine(lineObj) : 0) 5098 (end ? this.doc.height - heightAtLine(lineObj) : 0);
7955 }, 5099 },
7956 5100
7957 defaultTextHeight: function() { return textHeight(this.display) }, 5101 defaultTextHeight: function() { return textHeight(this.display); },
7958 defaultCharWidth: function() { return charWidth(this.display) }, 5102 defaultCharWidth: function() { return charWidth(this.display); },
7959 5103
7960 getViewport: function() { return {from: this.display.viewFrom, to: this.disp lay.viewTo}}, 5104 setGutterMarker: methodOp(function(line, gutterID, value) {
5105 return changeLine(this.doc, line, "gutter", function(line) {
5106 var markers = line.gutterMarkers || (line.gutterMarkers = {});
5107 markers[gutterID] = value;
5108 if (!value && isEmpty(markers)) line.gutterMarkers = null;
5109 return true;
5110 });
5111 }),
5112
5113 clearGutter: methodOp(function(gutterID) {
5114 var cm = this, doc = cm.doc, i = doc.first;
5115 doc.iter(function(line) {
5116 if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
5117 line.gutterMarkers[gutterID] = null;
5118 regLineChange(cm, i, "gutter");
5119 if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
5120 }
5121 ++i;
5122 });
5123 }),
5124
5125 lineInfo: function(line) {
5126 if (typeof line == "number") {
5127 if (!isLine(this.doc, line)) return null;
5128 var n = line;
5129 line = getLine(this.doc, line);
5130 if (!line) return null;
5131 } else {
5132 var n = lineNo(line);
5133 if (n == null) return null;
5134 }
5135 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutter Markers,
5136 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line. wrapClass,
5137 widgets: line.widgets};
5138 },
5139
5140 getViewport: function() { return {from: this.display.viewFrom, to: this.disp lay.viewTo};},
7961 5141
7962 addWidget: function(pos, node, scroll, vert, horiz) { 5142 addWidget: function(pos, node, scroll, vert, horiz) {
7963 var display = this.display 5143 var display = this.display;
7964 pos = cursorCoords(this, clipPos(this.doc, pos)) 5144 pos = cursorCoords(this, clipPos(this.doc, pos));
7965 var top = pos.bottom, left = pos.left 5145 var top = pos.bottom, left = pos.left;
7966 node.style.position = "absolute" 5146 node.style.position = "absolute";
7967 node.setAttribute("cm-ignore-events", "true") 5147 node.setAttribute("cm-ignore-events", "true");
7968 this.display.input.setUneditable(node) 5148 this.display.input.setUneditable(node);
7969 display.sizer.appendChild(node) 5149 display.sizer.appendChild(node);
7970 if (vert == "over") { 5150 if (vert == "over") {
7971 top = pos.top 5151 top = pos.top;
7972 } else if (vert == "above" || vert == "near") { 5152 } else if (vert == "above" || vert == "near") {
7973 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), 5153 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
7974 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWid th) 5154 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWid th);
7975 // Default to positioning above (if specified and possible); otherwise d efault to positioning below 5155 // Default to positioning above (if specified and possible); otherwise d efault to positioning below
7976 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos. top > node.offsetHeight) 5156 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos. top > node.offsetHeight)
7977 { top = pos.top - node.offsetHeight } 5157 top = pos.top - node.offsetHeight;
7978 else if (pos.bottom + node.offsetHeight <= vspace) 5158 else if (pos.bottom + node.offsetHeight <= vspace)
7979 { top = pos.bottom } 5159 top = pos.bottom;
7980 if (left + node.offsetWidth > hspace) 5160 if (left + node.offsetWidth > hspace)
7981 { left = hspace - node.offsetWidth } 5161 left = hspace - node.offsetWidth;
7982 } 5162 }
7983 node.style.top = top + "px" 5163 node.style.top = top + "px";
7984 node.style.left = node.style.right = "" 5164 node.style.left = node.style.right = "";
7985 if (horiz == "right") { 5165 if (horiz == "right") {
7986 left = display.sizer.clientWidth - node.offsetWidth 5166 left = display.sizer.clientWidth - node.offsetWidth;
7987 node.style.right = "0px" 5167 node.style.right = "0px";
7988 } else { 5168 } else {
7989 if (horiz == "left") { left = 0 } 5169 if (horiz == "left") left = 0;
7990 else if (horiz == "middle") { left = (display.sizer.clientWidth - node.o ffsetWidth) / 2 } 5170 else if (horiz == "middle") left = (display.sizer.clientWidth - node.off setWidth) / 2;
7991 node.style.left = left + "px" 5171 node.style.left = left + "px";
7992 } 5172 }
7993 if (scroll) 5173 if (scroll)
7994 { scrollIntoView(this, {left: left, top: top, right: left + node.offsetW idth, bottom: top + node.offsetHeight}) } 5174 scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offs etHeight);
7995 }, 5175 },
7996 5176
7997 triggerOnKeyDown: methodOp(onKeyDown), 5177 triggerOnKeyDown: methodOp(onKeyDown),
7998 triggerOnKeyPress: methodOp(onKeyPress), 5178 triggerOnKeyPress: methodOp(onKeyPress),
7999 triggerOnKeyUp: onKeyUp, 5179 triggerOnKeyUp: onKeyUp,
8000 5180
8001 execCommand: function(cmd) { 5181 execCommand: function(cmd) {
8002 if (commands.hasOwnProperty(cmd)) 5182 if (commands.hasOwnProperty(cmd))
8003 { return commands[cmd].call(null, this) } 5183 return commands[cmd].call(null, this);
8004 }, 5184 },
8005 5185
8006 triggerElectric: methodOp(function(text) { triggerElectric(this, text) }), 5186 triggerElectric: methodOp(function(text) { triggerElectric(this, text); }),
8007 5187
8008 findPosH: function(from, amount, unit, visually) { 5188 findPosH: function(from, amount, unit, visually) {
8009 var this$1 = this; 5189 var dir = 1;
8010 5190 if (amount < 0) { dir = -1; amount = -amount; }
8011 var dir = 1 5191 for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
8012 if (amount < 0) { dir = -1; amount = -amount } 5192 cur = findPosH(this.doc, cur, dir, unit, visually);
8013 var cur = clipPos(this.doc, from) 5193 if (cur.hitSide) break;
8014 for (var i = 0; i < amount; ++i) { 5194 }
8015 cur = findPosH(this$1.doc, cur, dir, unit, visually) 5195 return cur;
8016 if (cur.hitSide) { break }
8017 }
8018 return cur
8019 }, 5196 },
8020 5197
8021 moveH: methodOp(function(dir, unit) { 5198 moveH: methodOp(function(dir, unit) {
8022 var this$1 = this; 5199 var cm = this;
8023 5200 cm.extendSelectionsBy(function(range) {
8024 this.extendSelectionsBy(function (range) { 5201 if (cm.display.shift || cm.doc.extend || range.empty())
8025 if (this$1.display.shift || this$1.doc.extend || range.empty()) 5202 return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisua lly);
8026 { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rt lMoveVisually) }
8027 else 5203 else
8028 { return dir < 0 ? range.from() : range.to() } 5204 return dir < 0 ? range.from() : range.to();
8029 }, sel_move) 5205 }, sel_move);
8030 }), 5206 }),
8031 5207
8032 deleteH: methodOp(function(dir, unit) { 5208 deleteH: methodOp(function(dir, unit) {
8033 var sel = this.doc.sel, doc = this.doc 5209 var sel = this.doc.sel, doc = this.doc;
8034 if (sel.somethingSelected()) 5210 if (sel.somethingSelected())
8035 { doc.replaceSelection("", null, "+delete") } 5211 doc.replaceSelection("", null, "+delete");
8036 else 5212 else
8037 { deleteNearSelection(this, function (range) { 5213 deleteNearSelection(this, function(range) {
8038 var other = findPosH(doc, range.head, dir, unit, false) 5214 var other = findPosH(doc, range.head, dir, unit, false);
8039 return dir < 0 ? {from: other, to: range.head} : {from: range.head, to : other} 5215 return dir < 0 ? {from: other, to: range.head} : {from: range.head, to : other};
8040 }) } 5216 });
8041 }), 5217 }),
8042 5218
8043 findPosV: function(from, amount, unit, goalColumn) { 5219 findPosV: function(from, amount, unit, goalColumn) {
8044 var this$1 = this; 5220 var dir = 1, x = goalColumn;
8045 5221 if (amount < 0) { dir = -1; amount = -amount; }
8046 var dir = 1, x = goalColumn 5222 for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
8047 if (amount < 0) { dir = -1; amount = -amount } 5223 var coords = cursorCoords(this, cur, "div");
8048 var cur = clipPos(this.doc, from) 5224 if (x == null) x = coords.left;
8049 for (var i = 0; i < amount; ++i) { 5225 else coords.left = x;
8050 var coords = cursorCoords(this$1, cur, "div") 5226 cur = findPosV(this, coords, dir, unit);
8051 if (x == null) { x = coords.left } 5227 if (cur.hitSide) break;
8052 else { coords.left = x } 5228 }
8053 cur = findPosV(this$1, coords, dir, unit) 5229 return cur;
8054 if (cur.hitSide) { break }
8055 }
8056 return cur
8057 }, 5230 },
8058 5231
8059 moveV: methodOp(function(dir, unit) { 5232 moveV: methodOp(function(dir, unit) {
8060 var this$1 = this; 5233 var cm = this, doc = this.doc, goals = [];
8061 5234 var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelect ed();
8062 var doc = this.doc, goals = [] 5235 doc.extendSelectionsBy(function(range) {
8063 var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSele cted()
8064 doc.extendSelectionsBy(function (range) {
8065 if (collapse) 5236 if (collapse)
8066 { return dir < 0 ? range.from() : range.to() } 5237 return dir < 0 ? range.from() : range.to();
8067 var headPos = cursorCoords(this$1, range.head, "div") 5238 var headPos = cursorCoords(cm, range.head, "div");
8068 if (range.goalColumn != null) { headPos.left = range.goalColumn } 5239 if (range.goalColumn != null) headPos.left = range.goalColumn;
8069 goals.push(headPos.left) 5240 goals.push(headPos.left);
8070 var pos = findPosV(this$1, headPos, dir, unit) 5241 var pos = findPosV(cm, headPos, dir, unit);
8071 if (unit == "page" && range == doc.sel.primary()) 5242 if (unit == "page" && range == doc.sel.primary())
8072 { addToScrollPos(this$1, null, charCoords(this$1, pos, "div").top - he adPos.top) } 5243 addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top) ;
8073 return pos 5244 return pos;
8074 }, sel_move) 5245 }, sel_move);
8075 if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) 5246 if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++)
8076 { doc.sel.ranges[i].goalColumn = goals[i] } } 5247 doc.sel.ranges[i].goalColumn = goals[i];
8077 }), 5248 }),
8078 5249
8079 // Find the word at the given position (as returned by coordsChar). 5250 // Find the word at the given position (as returned by coordsChar).
8080 findWordAt: function(pos) { 5251 findWordAt: function(pos) {
8081 var doc = this.doc, line = getLine(doc, pos.line).text 5252 var doc = this.doc, line = getLine(doc, pos.line).text;
8082 var start = pos.ch, end = pos.ch 5253 var start = pos.ch, end = pos.ch;
8083 if (line) { 5254 if (line) {
8084 var helper = this.getHelper(pos, "wordChars") 5255 var helper = this.getHelper(pos, "wordChars");
8085 if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end } 5256 if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
8086 var startChar = line.charAt(start) 5257 var startChar = line.charAt(start);
8087 var check = isWordChar(startChar, helper) 5258 var check = isWordChar(startChar, helper)
8088 ? function (ch) { return isWordChar(ch, helper); } 5259 ? function(ch) { return isWordChar(ch, helper); }
8089 : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } 5260 : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
8090 : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); } 5261 : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
8091 while (start > 0 && check(line.charAt(start - 1))) { --start } 5262 while (start > 0 && check(line.charAt(start - 1))) --start;
8092 while (end < line.length && check(line.charAt(end))) { ++end } 5263 while (end < line.length && check(line.charAt(end))) ++end;
8093 } 5264 }
8094 return new Range(Pos(pos.line, start), Pos(pos.line, end)) 5265 return new Range(Pos(pos.line, start), Pos(pos.line, end));
8095 }, 5266 },
8096 5267
8097 toggleOverwrite: function(value) { 5268 toggleOverwrite: function(value) {
8098 if (value != null && value == this.state.overwrite) { return } 5269 if (value != null && value == this.state.overwrite) return;
8099 if (this.state.overwrite = !this.state.overwrite) 5270 if (this.state.overwrite = !this.state.overwrite)
8100 { addClass(this.display.cursorDiv, "CodeMirror-overwrite") } 5271 addClass(this.display.cursorDiv, "CodeMirror-overwrite");
8101 else 5272 else
8102 { rmClass(this.display.cursorDiv, "CodeMirror-overwrite") } 5273 rmClass(this.display.cursorDiv, "CodeMirror-overwrite");
8103 5274
8104 signal(this, "overwriteToggle", this, this.state.overwrite) 5275 signal(this, "overwriteToggle", this, this.state.overwrite);
8105 }, 5276 },
8106 hasFocus: function() { return this.display.input.getField() == activeElt() } , 5277 hasFocus: function() { return this.display.input.getField() == activeElt(); },
8107 isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdi t) }, 5278 isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdi t); },
8108 5279
8109 scrollTo: methodOp(function(x, y) { 5280 scrollTo: methodOp(function(x, y) {
8110 if (x != null || y != null) { resolveScrollToPos(this) } 5281 if (x != null || y != null) resolveScrollToPos(this);
8111 if (x != null) { this.curOp.scrollLeft = x } 5282 if (x != null) this.curOp.scrollLeft = x;
8112 if (y != null) { this.curOp.scrollTop = y } 5283 if (y != null) this.curOp.scrollTop = y;
8113 }), 5284 }),
8114 getScrollInfo: function() { 5285 getScrollInfo: function() {
8115 var scroller = this.display.scroller 5286 var scroller = this.display.scroller;
8116 return {left: scroller.scrollLeft, top: scroller.scrollTop, 5287 return {left: scroller.scrollLeft, top: scroller.scrollTop,
8117 height: scroller.scrollHeight - scrollGap(this) - this.display.bar Height, 5288 height: scroller.scrollHeight - scrollGap(this) - this.display.bar Height,
8118 width: scroller.scrollWidth - scrollGap(this) - this.display.barWi dth, 5289 width: scroller.scrollWidth - scrollGap(this) - this.display.barWi dth,
8119 clientHeight: displayHeight(this), clientWidth: displayWidth(this) } 5290 clientHeight: displayHeight(this), clientWidth: displayWidth(this) };
8120 }, 5291 },
8121 5292
8122 scrollIntoView: methodOp(function(range, margin) { 5293 scrollIntoView: methodOp(function(range, margin) {
8123 if (range == null) { 5294 if (range == null) {
8124 range = {from: this.doc.sel.primary().head, to: null} 5295 range = {from: this.doc.sel.primary().head, to: null};
8125 if (margin == null) { margin = this.options.cursorScrollMargin } 5296 if (margin == null) margin = this.options.cursorScrollMargin;
8126 } else if (typeof range == "number") { 5297 } else if (typeof range == "number") {
8127 range = {from: Pos(range, 0), to: null} 5298 range = {from: Pos(range, 0), to: null};
8128 } else if (range.from == null) { 5299 } else if (range.from == null) {
8129 range = {from: range, to: null} 5300 range = {from: range, to: null};
8130 } 5301 }
8131 if (!range.to) { range.to = range.from } 5302 if (!range.to) range.to = range.from;
8132 range.margin = margin || 0 5303 range.margin = margin || 0;
8133 5304
8134 if (range.from.line != null) { 5305 if (range.from.line != null) {
8135 resolveScrollToPos(this) 5306 resolveScrollToPos(this);
8136 this.curOp.scrollToPos = range 5307 this.curOp.scrollToPos = range;
8137 } else { 5308 } else {
8138 var sPos = calculateScrollPos(this, { 5309 var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.l eft),
8139 left: Math.min(range.from.left, range.to.left), 5310 Math.min(range.from.top, range.to.top) - r ange.margin,
8140 top: Math.min(range.from.top, range.to.top) - range.margin, 5311 Math.max(range.from.right, range.to.right) ,
8141 right: Math.max(range.from.right, range.to.right), 5312 Math.max(range.from.bottom, range.to.botto m) + range.margin);
8142 bottom: Math.max(range.from.bottom, range.to.bottom) + range.margin 5313 this.scrollTo(sPos.scrollLeft, sPos.scrollTop);
8143 })
8144 this.scrollTo(sPos.scrollLeft, sPos.scrollTop)
8145 } 5314 }
8146 }), 5315 }),
8147 5316
8148 setSize: methodOp(function(width, height) { 5317 setSize: methodOp(function(width, height) {
8149 var this$1 = this; 5318 var cm = this;
8150 5319 function interpret(val) {
8151 var interpret = function (val) { return typeof val == "number" || /^\d+$/. test(String(val)) ? val + "px" : val; } 5320 return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
8152 if (width != null) { this.display.wrapper.style.width = interpret(width) } 5321 }
8153 if (height != null) { this.display.wrapper.style.height = interpret(height ) } 5322 if (width != null) cm.display.wrapper.style.width = interpret(width);
8154 if (this.options.lineWrapping) { clearLineMeasurementCache(this) } 5323 if (height != null) cm.display.wrapper.style.height = interpret(height);
8155 var lineNo = this.display.viewFrom 5324 if (cm.options.lineWrapping) clearLineMeasurementCache(this);
8156 this.doc.iter(lineNo, this.display.viewTo, function (line) { 5325 var lineNo = cm.display.viewFrom;
8157 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) 5326 cm.doc.iter(lineNo, cm.display.viewTo, function(line) {
8158 { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widg et"); break } } } 5327 if (line.widgets) for (var i = 0; i < line.widgets.length; i++)
8159 ++lineNo 5328 if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; }
8160 }) 5329 ++lineNo;
8161 this.curOp.forceUpdate = true 5330 });
8162 signal(this, "refresh", this) 5331 cm.curOp.forceUpdate = true;
5332 signal(cm, "refresh", this);
8163 }), 5333 }),
8164 5334
8165 operation: function(f){return runInOp(this, f)}, 5335 operation: function(f){return runInOp(this, f);},
8166 5336
8167 refresh: methodOp(function() { 5337 refresh: methodOp(function() {
8168 var oldHeight = this.display.cachedTextHeight 5338 var oldHeight = this.display.cachedTextHeight;
8169 regChange(this) 5339 regChange(this);
8170 this.curOp.forceUpdate = true 5340 this.curOp.forceUpdate = true;
8171 clearCaches(this) 5341 clearCaches(this);
8172 this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop) 5342 this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop);
8173 updateGutterSpace(this) 5343 updateGutterSpace(this);
8174 if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) 5344 if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)
8175 { estimateLineHeights(this) } 5345 estimateLineHeights(this);
8176 signal(this, "refresh", this) 5346 signal(this, "refresh", this);
8177 }), 5347 }),
8178 5348
8179 swapDoc: methodOp(function(doc) { 5349 swapDoc: methodOp(function(doc) {
8180 var old = this.doc 5350 var old = this.doc;
8181 old.cm = null 5351 old.cm = null;
8182 attachDoc(this, doc) 5352 attachDoc(this, doc);
8183 clearCaches(this) 5353 clearCaches(this);
8184 this.display.input.reset() 5354 this.display.input.reset();
8185 this.scrollTo(doc.scrollLeft, doc.scrollTop) 5355 this.scrollTo(doc.scrollLeft, doc.scrollTop);
8186 this.curOp.forceScroll = true 5356 this.curOp.forceScroll = true;
8187 signalLater(this, "swapDoc", this, old) 5357 signalLater(this, "swapDoc", this, old);
8188 return old 5358 return old;
8189 }), 5359 }),
8190 5360
8191 getInputField: function(){return this.display.input.getField()}, 5361 getInputField: function(){return this.display.input.getField();},
8192 getWrapperElement: function(){return this.display.wrapper}, 5362 getWrapperElement: function(){return this.display.wrapper;},
8193 getScrollerElement: function(){return this.display.scroller}, 5363 getScrollerElement: function(){return this.display.scroller;},
8194 getGutterElement: function(){return this.display.gutters} 5364 getGutterElement: function(){return this.display.gutters;}
8195 } 5365 };
8196 eventMixin(CodeMirror) 5366 eventMixin(CodeMirror);
8197 5367
5368 // OPTION DEFAULTS
5369
5370 // The default configuration options.
5371 var defaults = CodeMirror.defaults = {};
5372 // Functions to run when options are changed.
5373 var optionHandlers = CodeMirror.optionHandlers = {};
5374
5375 function option(name, deflt, handle, notOnInit) {
5376 CodeMirror.defaults[name] = deflt;
5377 if (handle) optionHandlers[name] =
5378 notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old); } : handle;
5379 }
5380
5381 // Passed to option handlers when there is no old value.
5382 var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}} ;
5383
5384 // These two are, on init, called from the constructor because they
5385 // have to be initialized before the editor can start at all.
5386 option("value", "", function(cm, val) {
5387 cm.setValue(val);
5388 }, true);
5389 option("mode", null, function(cm, val) {
5390 cm.doc.modeOption = val;
5391 loadMode(cm);
5392 }, true);
5393
5394 option("indentUnit", 2, loadMode, true);
5395 option("indentWithTabs", false);
5396 option("smartIndent", true);
5397 option("tabSize", 4, function(cm) {
5398 resetModeState(cm);
5399 clearCaches(cm);
5400 regChange(cm);
5401 }, true);
5402 option("lineSeparator", null, function(cm, val) {
5403 cm.doc.lineSep = val;
5404 if (!val) return;
5405 var newBreaks = [], lineNo = cm.doc.first;
5406 cm.doc.iter(function(line) {
5407 for (var pos = 0;;) {
5408 var found = line.text.indexOf(val, pos);
5409 if (found == -1) break;
5410 pos = found + val.length;
5411 newBreaks.push(Pos(lineNo, found));
5412 }
5413 lineNo++;
5414 });
5415 for (var i = newBreaks.length - 1; i >= 0; i--)
5416 replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i ].ch + val.length))
5417 });
5418 option("specialChars", /[\u0000-\u001f\u007f\u00ad\u200b-\u200f\u2028\u2029\uf eff]/g, function(cm, val, old) {
5419 cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t" ), "g");
5420 if (old != CodeMirror.Init) cm.refresh();
5421 });
5422 option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) { cm.refresh();}, true);
5423 option("electricChars", true);
5424 option("inputStyle", mobile ? "contenteditable" : "textarea", function() {
5425 throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME
5426 }, true);
5427 option("rtlMoveVisually", !windows);
5428 option("wholeLineUpdateBefore", true);
5429
5430 option("theme", "default", function(cm) {
5431 themeChanged(cm);
5432 guttersChanged(cm);
5433 }, true);
5434 option("keyMap", "default", function(cm, val, old) {
5435 var next = getKeyMap(val);
5436 var prev = old != CodeMirror.Init && getKeyMap(old);
5437 if (prev && prev.detach) prev.detach(cm, next);
5438 if (next.attach) next.attach(cm, prev || null);
5439 });
5440 option("extraKeys", null);
5441
5442 option("lineWrapping", false, wrappingChanged, true);
5443 option("gutters", [], function(cm) {
5444 setGuttersForLineNumbers(cm.options);
5445 guttersChanged(cm);
5446 }, true);
5447 option("fixedGutter", true, function(cm, val) {
5448 cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px " : "0";
5449 cm.refresh();
5450 }, true);
5451 option("coverGutterNextToScrollbar", false, function(cm) {updateScrollbars(cm) ;}, true);
5452 option("scrollbarStyle", "native", function(cm) {
5453 initScrollbars(cm);
5454 updateScrollbars(cm);
5455 cm.display.scrollbars.setScrollTop(cm.doc.scrollTop);
5456 cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft);
5457 }, true);
5458 option("lineNumbers", false, function(cm) {
5459 setGuttersForLineNumbers(cm.options);
5460 guttersChanged(cm);
5461 }, true);
5462 option("firstLineNumber", 1, guttersChanged, true);
5463 option("lineNumberFormatter", function(integer) {return integer;}, guttersChan ged, true);
5464 option("showCursorWhenSelecting", false, updateSelection, true);
5465
5466 option("resetSelectionOnContextMenu", true);
5467 option("lineWiseCopyCut", true);
5468
5469 option("readOnly", false, function(cm, val) {
5470 if (val == "nocursor") {
5471 onBlur(cm);
5472 cm.display.input.blur();
5473 cm.display.disabled = true;
5474 } else {
5475 cm.display.disabled = false;
5476 }
5477 cm.display.input.readOnlyChanged(val)
5478 });
5479 option("disableInput", false, function(cm, val) {if (!val) cm.display.input.re set();}, true);
5480 option("dragDrop", true, dragDropChanged);
5481 option("allowDropFileTypes", null);
5482
5483 option("cursorBlinkRate", 530);
5484 option("cursorScrollMargin", 0);
5485 option("cursorHeight", 1, updateSelection, true);
5486 option("singleCursorHeightPerLine", true, updateSelection, true);
5487 option("workTime", 100);
5488 option("workDelay", 100);
5489 option("flattenSpans", true, resetModeState, true);
5490 option("addModeClass", false, resetModeState, true);
5491 option("pollInterval", 100);
5492 option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;});
5493 option("historyEventDelay", 1250);
5494 option("viewportMargin", 10, function(cm){cm.refresh();}, true);
5495 option("maxHighlightLength", 10000, resetModeState, true);
5496 option("moveInputWithCursor", true, function(cm, val) {
5497 if (!val) cm.display.input.resetPosition();
5498 });
5499
5500 option("tabindex", null, function(cm, val) {
5501 cm.display.input.getField().tabIndex = val || "";
5502 });
5503 option("autofocus", null);
5504
5505 // MODE DEFINITION AND QUERYING
5506
5507 // Known modes, by name and by MIME
5508 var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
5509
5510 // Extra arguments are stored as the mode's dependencies, which is
5511 // used by (legacy) mechanisms like loadmode.js to automatically
5512 // load a mode. (Preferred mechanism is the require/define calls.)
5513 CodeMirror.defineMode = function(name, mode) {
5514 if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
5515 if (arguments.length > 2)
5516 mode.dependencies = Array.prototype.slice.call(arguments, 2);
5517 modes[name] = mode;
5518 };
5519
5520 CodeMirror.defineMIME = function(mime, spec) {
5521 mimeModes[mime] = spec;
5522 };
5523
5524 // Given a MIME type, a {name, ...options} config object, or a name
5525 // string, return a mode config object.
5526 CodeMirror.resolveMode = function(spec) {
5527 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
5528 spec = mimeModes[spec];
5529 } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty( spec.name)) {
5530 var found = mimeModes[spec.name];
5531 if (typeof found == "string") found = {name: found};
5532 spec = createObj(found, spec);
5533 spec.name = found.name;
5534 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
5535 return CodeMirror.resolveMode("application/xml");
5536 }
5537 if (typeof spec == "string") return {name: spec};
5538 else return spec || {name: "null"};
5539 };
5540
5541 // Given a mode spec (anything that resolveMode accepts), find and
5542 // initialize an actual mode object.
5543 CodeMirror.getMode = function(options, spec) {
5544 var spec = CodeMirror.resolveMode(spec);
5545 var mfactory = modes[spec.name];
5546 if (!mfactory) return CodeMirror.getMode(options, "text/plain");
5547 var modeObj = mfactory(options, spec);
5548 if (modeExtensions.hasOwnProperty(spec.name)) {
5549 var exts = modeExtensions[spec.name];
5550 for (var prop in exts) {
5551 if (!exts.hasOwnProperty(prop)) continue;
5552 if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
5553 modeObj[prop] = exts[prop];
5554 }
5555 }
5556 modeObj.name = spec.name;
5557 if (spec.helperType) modeObj.helperType = spec.helperType;
5558 if (spec.modeProps) for (var prop in spec.modeProps)
5559 modeObj[prop] = spec.modeProps[prop];
5560
5561 return modeObj;
5562 };
5563
5564 // Minimal default mode.
5565 CodeMirror.defineMode("null", function() {
5566 return {token: function(stream) {stream.skipToEnd();}};
5567 });
5568 CodeMirror.defineMIME("text/plain", "null");
5569
5570 // This can be used to attach properties to mode objects from
5571 // outside the actual mode definition.
5572 var modeExtensions = CodeMirror.modeExtensions = {};
5573 CodeMirror.extendMode = function(mode, properties) {
5574 var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (mod eExtensions[mode] = {});
5575 copyObj(properties, exts);
5576 };
5577
5578 // EXTENSIONS
5579
5580 CodeMirror.defineExtension = function(name, func) {
5581 CodeMirror.prototype[name] = func;
5582 };
5583 CodeMirror.defineDocExtension = function(name, func) {
5584 Doc.prototype[name] = func;
5585 };
5586 CodeMirror.defineOption = option;
5587
5588 var initHooks = [];
5589 CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
5590
5591 var helpers = CodeMirror.helpers = {};
8198 CodeMirror.registerHelper = function(type, name, value) { 5592 CodeMirror.registerHelper = function(type, name, value) {
8199 if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_gl obal: []} } 5593 if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_glob al: []};
8200 helpers[type][name] = value 5594 helpers[type][name] = value;
8201 } 5595 };
8202 CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { 5596 CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {
8203 CodeMirror.registerHelper(type, name, value) 5597 CodeMirror.registerHelper(type, name, value);
8204 helpers[type]._global.push({pred: predicate, val: value}) 5598 helpers[type]._global.push({pred: predicate, val: value});
8205 } 5599 };
8206 } 5600
8207 5601 // MODE STATE HANDLING
8208 // Used for horizontal relative motion. Dir is -1 or 1 (left or 5602
8209 // right), unit can be "char", "column" (like char, but doesn't 5603 // Utility functions for working with state. Exported because nested
8210 // cross line boundaries), "word" (across next word), or "group" (to 5604 // modes need to do this for their inner modes.
8211 // the start of next group of word or non-word-non-whitespace 5605
8212 // chars). The visually param controls whether, in right-to-left 5606 var copyState = CodeMirror.copyState = function(mode, state) {
8213 // text, direction 1 means to move towards the next index in the 5607 if (state === true) return state;
8214 // string, or towards the character to the right of the current 5608 if (mode.copyState) return mode.copyState(state);
8215 // position. The resulting position will have a hitSide=true 5609 var nstate = {};
8216 // property if it reached the end of the document. 5610 for (var n in state) {
8217 function findPosH(doc, pos, dir, unit, visually) { 5611 var val = state[n];
8218 var oldPos = pos 5612 if (val instanceof Array) val = val.concat([]);
8219 var origDir = dir 5613 nstate[n] = val;
8220 var lineObj = getLine(doc, pos.line) 5614 }
8221 function findNextLine() { 5615 return nstate;
8222 var l = pos.line + dir 5616 };
8223 if (l < doc.first || l >= doc.first + doc.size) { return false } 5617
8224 pos = new Pos(l, pos.ch, pos.sticky) 5618 var startState = CodeMirror.startState = function(mode, a1, a2) {
8225 return lineObj = getLine(doc, l) 5619 return mode.startState ? mode.startState(a1, a2) : true;
8226 } 5620 };
8227 function moveOnce(boundToLine) { 5621
8228 var next 5622 // Given a mode and a state (for that mode), find the inner mode and
8229 if (visually) { 5623 // state at the position that the state refers to.
8230 next = moveVisually(doc.cm, lineObj, pos, dir) 5624 CodeMirror.innerMode = function(mode, state) {
5625 while (mode.innerMode) {
5626 var info = mode.innerMode(state);
5627 if (!info || info.mode == mode) break;
5628 state = info.state;
5629 mode = info.mode;
5630 }
5631 return info || {mode: mode, state: state};
5632 };
5633
5634 // STANDARD COMMANDS
5635
5636 // Commands are parameter-less actions that can be performed on an
5637 // editor, mostly used for keybindings.
5638 var commands = CodeMirror.commands = {
5639 selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.last Line()), sel_dontScroll);},
5640 singleSelection: function(cm) {
5641 cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScro ll);
5642 },
5643 killLine: function(cm) {
5644 deleteNearSelection(cm, function(range) {
5645 if (range.empty()) {
5646 var len = getLine(cm.doc, range.head.line).text.length;
5647 if (range.head.ch == len && range.head.line < cm.lastLine())
5648 return {from: range.head, to: Pos(range.head.line + 1, 0)};
5649 else
5650 return {from: range.head, to: Pos(range.head.line, len)};
5651 } else {
5652 return {from: range.from(), to: range.to()};
5653 }
5654 });
5655 },
5656 deleteLine: function(cm) {
5657 deleteNearSelection(cm, function(range) {
5658 return {from: Pos(range.from().line, 0),
5659 to: clipPos(cm.doc, Pos(range.to().line + 1, 0))};
5660 });
5661 },
5662 delLineLeft: function(cm) {
5663 deleteNearSelection(cm, function(range) {
5664 return {from: Pos(range.from().line, 0), to: range.from()};
5665 });
5666 },
5667 delWrappedLineLeft: function(cm) {
5668 deleteNearSelection(cm, function(range) {
5669 var top = cm.charCoords(range.head, "div").top + 5;
5670 var leftPos = cm.coordsChar({left: 0, top: top}, "div");
5671 return {from: leftPos, to: range.from()};
5672 });
5673 },
5674 delWrappedLineRight: function(cm) {
5675 deleteNearSelection(cm, function(range) {
5676 var top = cm.charCoords(range.head, "div").top + 5;
5677 var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100 , top: top}, "div");
5678 return {from: range.from(), to: rightPos };
5679 });
5680 },
5681 undo: function(cm) {cm.undo();},
5682 redo: function(cm) {cm.redo();},
5683 undoSelection: function(cm) {cm.undoSelection();},
5684 redoSelection: function(cm) {cm.redoSelection();},
5685 goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
5686 goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
5687 goLineStart: function(cm) {
5688 cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.li ne); },
5689 {origin: "+move", bias: 1});
5690 },
5691 goLineStartSmart: function(cm) {
5692 cm.extendSelectionsBy(function(range) {
5693 return lineStartSmart(cm, range.head);
5694 }, {origin: "+move", bias: 1});
5695 },
5696 goLineEnd: function(cm) {
5697 cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line ); },
5698 {origin: "+move", bias: -1});
5699 },
5700 goLineRight: function(cm) {
5701 cm.extendSelectionsBy(function(range) {
5702 var top = cm.charCoords(range.head, "div").top + 5;
5703 return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: t op}, "div");
5704 }, sel_move);
5705 },
5706 goLineLeft: function(cm) {
5707 cm.extendSelectionsBy(function(range) {
5708 var top = cm.charCoords(range.head, "div").top + 5;
5709 return cm.coordsChar({left: 0, top: top}, "div");
5710 }, sel_move);
5711 },
5712 goLineLeftSmart: function(cm) {
5713 cm.extendSelectionsBy(function(range) {
5714 var top = cm.charCoords(range.head, "div").top + 5;
5715 var pos = cm.coordsChar({left: 0, top: top}, "div");
5716 if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm , range.head);
5717 return pos;
5718 }, sel_move);
5719 },
5720 goLineUp: function(cm) {cm.moveV(-1, "line");},
5721 goLineDown: function(cm) {cm.moveV(1, "line");},
5722 goPageUp: function(cm) {cm.moveV(-1, "page");},
5723 goPageDown: function(cm) {cm.moveV(1, "page");},
5724 goCharLeft: function(cm) {cm.moveH(-1, "char");},
5725 goCharRight: function(cm) {cm.moveH(1, "char");},
5726 goColumnLeft: function(cm) {cm.moveH(-1, "column");},
5727 goColumnRight: function(cm) {cm.moveH(1, "column");},
5728 goWordLeft: function(cm) {cm.moveH(-1, "word");},
5729 goGroupRight: function(cm) {cm.moveH(1, "group");},
5730 goGroupLeft: function(cm) {cm.moveH(-1, "group");},
5731 goWordRight: function(cm) {cm.moveH(1, "word");},
5732 delCharBefore: function(cm) {cm.deleteH(-1, "char");},
5733 delCharAfter: function(cm) {cm.deleteH(1, "char");},
5734 delWordBefore: function(cm) {cm.deleteH(-1, "word");},
5735 delWordAfter: function(cm) {cm.deleteH(1, "word");},
5736 delGroupBefore: function(cm) {cm.deleteH(-1, "group");},
5737 delGroupAfter: function(cm) {cm.deleteH(1, "group");},
5738 indentAuto: function(cm) {cm.indentSelection("smart");},
5739 indentMore: function(cm) {cm.indentSelection("add");},
5740 indentLess: function(cm) {cm.indentSelection("subtract");},
5741 insertTab: function(cm) {cm.replaceSelection("\t");},
5742 insertSoftTab: function(cm) {
5743 var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSiz e;
5744 for (var i = 0; i < ranges.length; i++) {
5745 var pos = ranges[i].from();
5746 var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize);
5747 spaces.push(spaceStr(tabSize - col % tabSize));
5748 }
5749 cm.replaceSelections(spaces);
5750 },
5751 defaultTab: function(cm) {
5752 if (cm.somethingSelected()) cm.indentSelection("add");
5753 else cm.execCommand("insertTab");
5754 },
5755 transposeChars: function(cm) {
5756 runInOp(cm, function() {
5757 var ranges = cm.listSelections(), newSel = [];
5758 for (var i = 0; i < ranges.length; i++) {
5759 var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text;
5760 if (line) {
5761 if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1);
5762 if (cur.ch > 0) {
5763 cur = new Pos(cur.line, cur.ch + 1);
5764 cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),
5765 Pos(cur.line, cur.ch - 2), cur, "+transpose");
5766 } else if (cur.line > cm.doc.first) {
5767 var prev = getLine(cm.doc, cur.line - 1).text;
5768 if (prev)
5769 cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +
5770 prev.charAt(prev.length - 1),
5771 Pos(cur.line - 1, prev.length - 1), Pos(cur.line , 1), "+transpose");
5772 }
5773 }
5774 newSel.push(new Range(cur, cur));
5775 }
5776 cm.setSelections(newSel);
5777 });
5778 },
5779 newlineAndIndent: function(cm) {
5780 runInOp(cm, function() {
5781 var len = cm.listSelections().length;
5782 for (var i = 0; i < len; i++) {
5783 var range = cm.listSelections()[i];
5784 cm.replaceRange(cm.doc.lineSeparator(), range.anchor, range.head, "+in put");
5785 cm.indentLine(range.from().line + 1, null, true);
5786 }
5787 ensureCursorVisible(cm);
5788 });
5789 },
5790 openLine: function(cm) {cm.replaceSelection("\n", "start")},
5791 toggleOverwrite: function(cm) {cm.toggleOverwrite();}
5792 };
5793
5794
5795 // STANDARD KEYMAPS
5796
5797 var keyMap = CodeMirror.keyMap = {};
5798
5799 keyMap.basic = {
5800 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goL ineDown",
5801 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageD own": "goPageDown",
5802 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": " delCharBefore",
5803 "Tab": "defaultTab", "Shift-Tab": "indentAuto",
5804 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
5805 "Esc": "singleSelection"
5806 };
5807 // Note that the save and find-related commands aren't defined by
5808 // default. User code or addons can define them. Unknown commands
5809 // are simply ignored.
5810 keyMap.pcDefault = {
5811 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl -Z": "redo", "Ctrl-Y": "redo",
5812 "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "C trl-Down": "goLineDown",
5813 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLin eStart", "Alt-Right": "goLineEnd",
5814 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S" : "save", "Ctrl-F": "find",
5815 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
5816 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
5817 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSe lection",
5818 fallthrough: "basic"
5819 };
5820 // Very basic readline/emacs-style bindings, which are standard on Mac.
5821 keyMap.emacsy = {
5822 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl -N": "goLineDown",
5823 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctr l-E": "goLineEnd",
5824 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter" , "Ctrl-H": "delCharBefore",
5825 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLi ne", "Ctrl-T": "transposeChars",
5826 "Ctrl-O": "openLine"
5827 };
5828 keyMap.macDefault = {
5829 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
5830 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cm d-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
5831 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineR ight", "Alt-Backspace": "delGroupBefore",
5832 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S ": "save", "Cmd-F": "find",
5833 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shi ft-Cmd-Alt-F": "replaceAll",
5834 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLi neLeft", "Cmd-Delete": "delWrappedLineRight",
5835 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocS tart", "Ctrl-Down": "goDocEnd",
5836 fallthrough: ["basic", "emacsy"]
5837 };
5838 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
5839
5840 // KEYMAP DISPATCH
5841
5842 function normalizeKeyName(name) {
5843 var parts = name.split(/-(?!$)/), name = parts[parts.length - 1];
5844 var alt, ctrl, shift, cmd;
5845 for (var i = 0; i < parts.length - 1; i++) {
5846 var mod = parts[i];
5847 if (/^(cmd|meta|m)$/i.test(mod)) cmd = true;
5848 else if (/^a(lt)?$/i.test(mod)) alt = true;
5849 else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true;
5850 else if (/^s(hift)$/i.test(mod)) shift = true;
5851 else throw new Error("Unrecognized modifier name: " + mod);
5852 }
5853 if (alt) name = "Alt-" + name;
5854 if (ctrl) name = "Ctrl-" + name;
5855 if (cmd) name = "Cmd-" + name;
5856 if (shift) name = "Shift-" + name;
5857 return name;
5858 }
5859
5860 // This is a kludge to keep keymaps mostly working as raw objects
5861 // (backwards compatibility) while at the same time support features
5862 // like normalization and multi-stroke key bindings. It compiles a
5863 // new normalized keymap, and then updates the old object to reflect
5864 // this.
5865 CodeMirror.normalizeKeyMap = function(keymap) {
5866 var copy = {};
5867 for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) {
5868 var value = keymap[keyname];
5869 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue;
5870 if (value == "...") { delete keymap[keyname]; continue; }
5871
5872 var keys = map(keyname.split(" "), normalizeKeyName);
5873 for (var i = 0; i < keys.length; i++) {
5874 var val, name;
5875 if (i == keys.length - 1) {
5876 name = keys.join(" ");
5877 val = value;
5878 } else {
5879 name = keys.slice(0, i + 1).join(" ");
5880 val = "...";
5881 }
5882 var prev = copy[name];
5883 if (!prev) copy[name] = val;
5884 else if (prev != val) throw new Error("Inconsistent bindings for " + nam e);
5885 }
5886 delete keymap[keyname];
5887 }
5888 for (var prop in copy) keymap[prop] = copy[prop];
5889 return keymap;
5890 };
5891
5892 var lookupKey = CodeMirror.lookupKey = function(key, map, handle, context) {
5893 map = getKeyMap(map);
5894 var found = map.call ? map.call(key, context) : map[key];
5895 if (found === false) return "nothing";
5896 if (found === "...") return "multi";
5897 if (found != null && handle(found)) return "handled";
5898
5899 if (map.fallthrough) {
5900 if (Object.prototype.toString.call(map.fallthrough) != "[object Array]")
5901 return lookupKey(key, map.fallthrough, handle, context);
5902 for (var i = 0; i < map.fallthrough.length; i++) {
5903 var result = lookupKey(key, map.fallthrough[i], handle, context);
5904 if (result) return result;
5905 }
5906 }
5907 };
5908
5909 // Modifier key presses don't count as 'real' key presses for the
5910 // purpose of keymap fallthrough.
5911 var isModifierKey = CodeMirror.isModifierKey = function(value) {
5912 var name = typeof value == "string" ? value : keyNames[value.keyCode];
5913 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
5914 };
5915
5916 // Look up the name of a key as indicated by an event object.
5917 var keyName = CodeMirror.keyName = function(event, noShift) {
5918 if (presto && event.keyCode == 34 && event["char"]) return false;
5919 var base = keyNames[event.keyCode], name = base;
5920 if (name == null || event.altGraphKey) return false;
5921 if (event.altKey && base != "Alt") name = "Alt-" + name;
5922 if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name;
5923 if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = " Cmd-" + name;
5924 if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name;
5925 return name;
5926 };
5927
5928 function getKeyMap(val) {
5929 return typeof val == "string" ? keyMap[val] : val;
5930 }
5931
5932 // FROMTEXTAREA
5933
5934 CodeMirror.fromTextArea = function(textarea, options) {
5935 options = options ? copyObj(options) : {};
5936 options.value = textarea.value;
5937 if (!options.tabindex && textarea.tabIndex)
5938 options.tabindex = textarea.tabIndex;
5939 if (!options.placeholder && textarea.placeholder)
5940 options.placeholder = textarea.placeholder;
5941 // Set autofocus to true if this textarea is focused, or if it has
5942 // autofocus and no other element is focused.
5943 if (options.autofocus == null) {
5944 var hasFocus = activeElt();
5945 options.autofocus = hasFocus == textarea ||
5946 textarea.getAttribute("autofocus") != null && hasFocus == document.body;
5947 }
5948
5949 function save() {textarea.value = cm.getValue();}
5950 if (textarea.form) {
5951 on(textarea.form, "submit", save);
5952 // Deplorable hack to make the submit method do the right thing.
5953 if (!options.leaveSubmitMethodAlone) {
5954 var form = textarea.form, realSubmit = form.submit;
5955 try {
5956 var wrappedSubmit = form.submit = function() {
5957 save();
5958 form.submit = realSubmit;
5959 form.submit();
5960 form.submit = wrappedSubmit;
5961 };
5962 } catch(e) {}
5963 }
5964 }
5965
5966 options.finishInit = function(cm) {
5967 cm.save = save;
5968 cm.getTextArea = function() { return textarea; };
5969 cm.toTextArea = function() {
5970 cm.toTextArea = isNaN; // Prevent this from being ran twice
5971 save();
5972 textarea.parentNode.removeChild(cm.getWrapperElement());
5973 textarea.style.display = "";
5974 if (textarea.form) {
5975 off(textarea.form, "submit", save);
5976 if (typeof textarea.form.submit == "function")
5977 textarea.form.submit = realSubmit;
5978 }
5979 };
5980 };
5981
5982 textarea.style.display = "none";
5983 var cm = CodeMirror(function(node) {
5984 textarea.parentNode.insertBefore(node, textarea.nextSibling);
5985 }, options);
5986 return cm;
5987 };
5988
5989 // STRING STREAM
5990
5991 // Fed to the mode parsers, provides helper functions to make
5992 // parsers more succinct.
5993
5994 var StringStream = CodeMirror.StringStream = function(string, tabSize) {
5995 this.pos = this.start = 0;
5996 this.string = string;
5997 this.tabSize = tabSize || 8;
5998 this.lastColumnPos = this.lastColumnValue = 0;
5999 this.lineStart = 0;
6000 };
6001
6002 StringStream.prototype = {
6003 eol: function() {return this.pos >= this.string.length;},
6004 sol: function() {return this.pos == this.lineStart;},
6005 peek: function() {return this.string.charAt(this.pos) || undefined;},
6006 next: function() {
6007 if (this.pos < this.string.length)
6008 return this.string.charAt(this.pos++);
6009 },
6010 eat: function(match) {
6011 var ch = this.string.charAt(this.pos);
6012 if (typeof match == "string") var ok = ch == match;
6013 else var ok = ch && (match.test ? match.test(ch) : match(ch));
6014 if (ok) {++this.pos; return ch;}
6015 },
6016 eatWhile: function(match) {
6017 var start = this.pos;
6018 while (this.eat(match)){}
6019 return this.pos > start;
6020 },
6021 eatSpace: function() {
6022 var start = this.pos;
6023 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
6024 return this.pos > start;
6025 },
6026 skipToEnd: function() {this.pos = this.string.length;},
6027 skipTo: function(ch) {
6028 var found = this.string.indexOf(ch, this.pos);
6029 if (found > -1) {this.pos = found; return true;}
6030 },
6031 backUp: function(n) {this.pos -= n;},
6032 column: function() {
6033 if (this.lastColumnPos < this.start) {
6034 this.lastColumnValue = countColumn(this.string, this.start, this.tabSize , this.lastColumnPos, this.lastColumnValue);
6035 this.lastColumnPos = this.start;
6036 }
6037 return this.lastColumnValue - (this.lineStart ? countColumn(this.string, t his.lineStart, this.tabSize) : 0);
6038 },
6039 indentation: function() {
6040 return countColumn(this.string, null, this.tabSize) -
6041 (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
6042 },
6043 match: function(pattern, consume, caseInsensitive) {
6044 if (typeof pattern == "string") {
6045 var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
6046 var substr = this.string.substr(this.pos, pattern.length);
6047 if (cased(substr) == cased(pattern)) {
6048 if (consume !== false) this.pos += pattern.length;
6049 return true;
6050 }
6051 } else {
6052 var match = this.string.slice(this.pos).match(pattern);
6053 if (match && match.index > 0) return null;
6054 if (match && consume !== false) this.pos += match[0].length;
6055 return match;
6056 }
6057 },
6058 current: function(){return this.string.slice(this.start, this.pos);},
6059 hideFirstChars: function(n, inner) {
6060 this.lineStart += n;
6061 try { return inner(); }
6062 finally { this.lineStart -= n; }
6063 }
6064 };
6065
6066 // TEXTMARKERS
6067
6068 // Created with markText and setBookmark methods. A TextMarker is a
6069 // handle that can be used to clear or find a marked position in the
6070 // document. Line objects hold arrays (markedSpans) containing
6071 // {from, to, marker} object pointing to such marker objects, and
6072 // indicating that such a marker is present on that line. Multiple
6073 // lines may point to the same marker when it spans across lines.
6074 // The spans will have null for their from/to properties when the
6075 // marker continues beyond the start/end of the line. Markers have
6076 // links back to the lines they currently touch.
6077
6078 var nextMarkerId = 0;
6079
6080 var TextMarker = CodeMirror.TextMarker = function(doc, type) {
6081 this.lines = [];
6082 this.type = type;
6083 this.doc = doc;
6084 this.id = ++nextMarkerId;
6085 };
6086 eventMixin(TextMarker);
6087
6088 // Clear the marker.
6089 TextMarker.prototype.clear = function() {
6090 if (this.explicitlyCleared) return;
6091 var cm = this.doc.cm, withOp = cm && !cm.curOp;
6092 if (withOp) startOperation(cm);
6093 if (hasHandler(this, "clear")) {
6094 var found = this.find();
6095 if (found) signalLater(this, "clear", found.from, found.to);
6096 }
6097 var min = null, max = null;
6098 for (var i = 0; i < this.lines.length; ++i) {
6099 var line = this.lines[i];
6100 var span = getMarkedSpanFor(line.markedSpans, this);
6101 if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text");
6102 else if (cm) {
6103 if (span.to != null) max = lineNo(line);
6104 if (span.from != null) min = lineNo(line);
6105 }
6106 line.markedSpans = removeMarkedSpan(line.markedSpans, span);
6107 if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) & & cm)
6108 updateLineHeight(line, textHeight(cm.display));
6109 }
6110 if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < th is.lines.length; ++i) {
6111 var visual = visualLine(this.lines[i]), len = lineLength(visual);
6112 if (len > cm.display.maxLineLength) {
6113 cm.display.maxLine = visual;
6114 cm.display.maxLineLength = len;
6115 cm.display.maxLineChanged = true;
6116 }
6117 }
6118
6119 if (min != null && cm && this.collapsed) regChange(cm, min, max + 1);
6120 this.lines.length = 0;
6121 this.explicitlyCleared = true;
6122 if (this.atomic && this.doc.cantEdit) {
6123 this.doc.cantEdit = false;
6124 if (cm) reCheckSelection(cm.doc);
6125 }
6126 if (cm) signalLater(cm, "markerCleared", cm, this);
6127 if (withOp) endOperation(cm);
6128 if (this.parent) this.parent.clear();
6129 };
6130
6131 // Find the position of the marker in the document. Returns a {from,
6132 // to} object by default. Side can be passed to get a specific side
6133 // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
6134 // Pos objects returned contain a line object, rather than a line
6135 // number (used to prevent looking up the same line twice).
6136 TextMarker.prototype.find = function(side, lineObj) {
6137 if (side == null && this.type == "bookmark") side = 1;
6138 var from, to;
6139 for (var i = 0; i < this.lines.length; ++i) {
6140 var line = this.lines[i];
6141 var span = getMarkedSpanFor(line.markedSpans, this);
6142 if (span.from != null) {
6143 from = Pos(lineObj ? line : lineNo(line), span.from);
6144 if (side == -1) return from;
6145 }
6146 if (span.to != null) {
6147 to = Pos(lineObj ? line : lineNo(line), span.to);
6148 if (side == 1) return to;
6149 }
6150 }
6151 return from && {from: from, to: to};
6152 };
6153
6154 // Signals that the marker's widget changed, and surrounding layout
6155 // should be recomputed.
6156 TextMarker.prototype.changed = function() {
6157 var pos = this.find(-1, true), widget = this, cm = this.doc.cm;
6158 if (!pos || !cm) return;
6159 runInOp(cm, function() {
6160 var line = pos.line, lineN = lineNo(pos.line);
6161 var view = findViewForLine(cm, lineN);
6162 if (view) {
6163 clearLineMeasurementCacheFor(view);
6164 cm.curOp.selectionChanged = cm.curOp.forceUpdate = true;
6165 }
6166 cm.curOp.updateMaxLine = true;
6167 if (!lineIsHidden(widget.doc, line) && widget.height != null) {
6168 var oldHeight = widget.height;
6169 widget.height = null;
6170 var dHeight = widgetHeight(widget) - oldHeight;
6171 if (dHeight)
6172 updateLineHeight(line, line.height + dHeight);
6173 }
6174 });
6175 };
6176
6177 TextMarker.prototype.attachLine = function(line) {
6178 if (!this.lines.length && this.doc.cm) {
6179 var op = this.doc.cm.curOp;
6180 if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
6181 (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this);
6182 }
6183 this.lines.push(line);
6184 };
6185 TextMarker.prototype.detachLine = function(line) {
6186 this.lines.splice(indexOf(this.lines, line), 1);
6187 if (!this.lines.length && this.doc.cm) {
6188 var op = this.doc.cm.curOp;
6189 (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);
6190 }
6191 };
6192
6193 // Collapsed markers have unique ids, in order to be able to order
6194 // them, which is needed for uniquely determining an outer marker
6195 // when they overlap (they may nest, but not partially overlap).
6196 var nextMarkerId = 0;
6197
6198 // Create a marker, wire it up to the right lines, and
6199 function markText(doc, from, to, options, type) {
6200 // Shared markers (across linked documents) are handled separately
6201 // (markTextShared will call out to this again, once per
6202 // document).
6203 if (options && options.shared) return markTextShared(doc, from, to, options, type);
6204 // Ensure we are in an operation.
6205 if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, t o, options, type);
6206
6207 var marker = new TextMarker(doc, type), diff = cmp(from, to);
6208 if (options) copyObj(options, marker, false);
6209 // Don't connect empty markers unless clearWhenEmpty is false
6210 if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
6211 return marker;
6212 if (marker.replacedWith) {
6213 // Showing up as a widget implies collapsed (widget replaces text)
6214 marker.collapsed = true;
6215 marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget" );
6216 if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore- events", "true");
6217 if (options.insertLeft) marker.widgetNode.insertLeft = true;
6218 }
6219 if (marker.collapsed) {
6220 if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
6221 from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))
6222 throw new Error("Inserting collapsed marker partially overlapping an exi sting one");
6223 sawCollapsedSpans = true;
6224 }
6225
6226 if (marker.addToHistory)
6227 addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN);
6228
6229 var curLine = from.line, cm = doc.cm, updateMaxLine;
6230 doc.iter(curLine, to.line + 1, function(line) {
6231 if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)
6232 updateMaxLine = true;
6233 if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0);
6234 addMarkedSpan(line, new MarkedSpan(marker,
6235 curLine == from.line ? from.ch : null,
6236 curLine == to.line ? to.ch : null));
6237 ++curLine;
6238 });
6239 // lineIsHidden depends on the presence of the spans, so needs a second pass
6240 if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
6241 if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
6242 });
6243
6244 if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker .clear(); });
6245
6246 if (marker.readOnly) {
6247 sawReadOnlySpans = true;
6248 if (doc.history.done.length || doc.history.undone.length)
6249 doc.clearHistory();
6250 }
6251 if (marker.collapsed) {
6252 marker.id = ++nextMarkerId;
6253 marker.atomic = true;
6254 }
6255 if (cm) {
6256 // Sync editor state
6257 if (updateMaxLine) cm.curOp.updateMaxLine = true;
6258 if (marker.collapsed)
6259 regChange(cm, from.line, to.line + 1);
6260 else if (marker.className || marker.title || marker.startStyle || marker.e ndStyle || marker.css)
6261 for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text");
6262 if (marker.atomic) reCheckSelection(cm.doc);
6263 signalLater(cm, "markerAdded", cm, marker);
6264 }
6265 return marker;
6266 }
6267
6268 // SHARED TEXTMARKERS
6269
6270 // A shared marker spans multiple linked documents. It is
6271 // implemented as a meta-marker-object controlling multiple normal
6272 // markers.
6273 var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary ) {
6274 this.markers = markers;
6275 this.primary = primary;
6276 for (var i = 0; i < markers.length; ++i)
6277 markers[i].parent = this;
6278 };
6279 eventMixin(SharedTextMarker);
6280
6281 SharedTextMarker.prototype.clear = function() {
6282 if (this.explicitlyCleared) return;
6283 this.explicitlyCleared = true;
6284 for (var i = 0; i < this.markers.length; ++i)
6285 this.markers[i].clear();
6286 signalLater(this, "clear");
6287 };
6288 SharedTextMarker.prototype.find = function(side, lineObj) {
6289 return this.primary.find(side, lineObj);
6290 };
6291
6292 function markTextShared(doc, from, to, options, type) {
6293 options = copyObj(options);
6294 options.shared = false;
6295 var markers = [markText(doc, from, to, options, type)], primary = markers[0] ;
6296 var widget = options.widgetNode;
6297 linkedDocs(doc, function(doc) {
6298 if (widget) options.widgetNode = widget.cloneNode(true);
6299 markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
6300 for (var i = 0; i < doc.linked.length; ++i)
6301 if (doc.linked[i].isParent) return;
6302 primary = lst(markers);
6303 });
6304 return new SharedTextMarker(markers, primary);
6305 }
6306
6307 function findSharedMarkers(doc) {
6308 return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())),
6309 function(m) { return m.parent; });
6310 }
6311
6312 function copySharedMarkers(doc, markers) {
6313 for (var i = 0; i < markers.length; i++) {
6314 var marker = markers[i], pos = marker.find();
6315 var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to);
6316 if (cmp(mFrom, mTo)) {
6317 var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.t ype);
6318 marker.markers.push(subMark);
6319 subMark.parent = marker;
6320 }
6321 }
6322 }
6323
6324 function detachSharedMarkers(markers) {
6325 for (var i = 0; i < markers.length; i++) {
6326 var marker = markers[i], linked = [marker.primary.doc];;
6327 linkedDocs(marker.primary.doc, function(d) { linked.push(d); });
6328 for (var j = 0; j < marker.markers.length; j++) {
6329 var subMarker = marker.markers[j];
6330 if (indexOf(linked, subMarker.doc) == -1) {
6331 subMarker.parent = null;
6332 marker.markers.splice(j--, 1);
6333 }
6334 }
6335 }
6336 }
6337
6338 // TEXTMARKER SPANS
6339
6340 function MarkedSpan(marker, from, to) {
6341 this.marker = marker;
6342 this.from = from; this.to = to;
6343 }
6344
6345 // Search an array of spans for a span matching the given marker.
6346 function getMarkedSpanFor(spans, marker) {
6347 if (spans) for (var i = 0; i < spans.length; ++i) {
6348 var span = spans[i];
6349 if (span.marker == marker) return span;
6350 }
6351 }
6352 // Remove a span from an array, returning undefined if no spans are
6353 // left (we don't store arrays for lines without spans).
6354 function removeMarkedSpan(spans, span) {
6355 for (var r, i = 0; i < spans.length; ++i)
6356 if (spans[i] != span) (r || (r = [])).push(spans[i]);
6357 return r;
6358 }
6359 // Add a span to a line.
6360 function addMarkedSpan(line, span) {
6361 line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [spa n];
6362 span.marker.attachLine(line);
6363 }
6364
6365 // Used for the algorithm that adjusts markers for a change in the
6366 // document. These functions cut an array of spans at a given
6367 // character position, returning an array of remaining chunks (or
6368 // undefined if nothing remains).
6369 function markedSpansBefore(old, startCh, isInsert) {
6370 if (old) for (var i = 0, nw; i < old.length; ++i) {
6371 var span = old[i], marker = span.marker;
6372 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
6373 if (startsBefore || span.from == startCh && marker.type == "bookmark" && ( !isInsert || !span.marker.insertLeft)) {
6374 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= s tartCh : span.to > startCh);
6375 (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? nul l : span.to));
6376 }
6377 }
6378 return nw;
6379 }
6380 function markedSpansAfter(old, endCh, isInsert) {
6381 if (old) for (var i = 0, nw; i < old.length; ++i) {
6382 var span = old[i], marker = span.marker;
6383 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= end Ch : span.to > endCh);
6384 if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isIn sert || span.marker.insertLeft)) {
6385 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.fro m <= endCh : span.from < endCh);
6386 (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span .from - endCh,
6387 span.to == null ? null : span.to - endCh));
6388 }
6389 }
6390 return nw;
6391 }
6392
6393 // Given a change object, compute the new set of marker spans that
6394 // cover the line in which the change took place. Removes spans
6395 // entirely within the change, reconnects spans belonging to the
6396 // same marker that appear on both sides of the change, and cuts off
6397 // spans partially within the change. Returns an array of span
6398 // arrays with one element for each line in (after) the change.
6399 function stretchSpansOverChange(doc, change) {
6400 if (change.full) return null;
6401 var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.lin e).markedSpans;
6402 var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).ma rkedSpans;
6403 if (!oldFirst && !oldLast) return null;
6404
6405 var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.fr om, change.to) == 0;
6406 // Get the spans that 'stick out' on both sides
6407 var first = markedSpansBefore(oldFirst, startCh, isInsert);
6408 var last = markedSpansAfter(oldLast, endCh, isInsert);
6409
6410 // Next, merge those two ends
6411 var sameLine = change.text.length == 1, offset = lst(change.text).length + ( sameLine ? startCh : 0);
6412 if (first) {
6413 // Fix up .to properties of first
6414 for (var i = 0; i < first.length; ++i) {
6415 var span = first[i];
6416 if (span.to == null) {
6417 var found = getMarkedSpanFor(last, span.marker);
6418 if (!found) span.to = startCh;
6419 else if (sameLine) span.to = found.to == null ? null : found.to + offs et;
6420 }
6421 }
6422 }
6423 if (last) {
6424 // Fix up .from in last (or move them into first in case of sameLine)
6425 for (var i = 0; i < last.length; ++i) {
6426 var span = last[i];
6427 if (span.to != null) span.to += offset;
6428 if (span.from == null) {
6429 var found = getMarkedSpanFor(first, span.marker);
6430 if (!found) {
6431 span.from = offset;
6432 if (sameLine) (first || (first = [])).push(span);
6433 }
6434 } else {
6435 span.from += offset;
6436 if (sameLine) (first || (first = [])).push(span);
6437 }
6438 }
6439 }
6440 // Make sure we didn't create any zero-length spans
6441 if (first) first = clearEmptySpans(first);
6442 if (last && last != first) last = clearEmptySpans(last);
6443
6444 var newMarkers = [first];
6445 if (!sameLine) {
6446 // Fill gap with whole-line-spans
6447 var gap = change.text.length - 2, gapMarkers;
6448 if (gap > 0 && first)
6449 for (var i = 0; i < first.length; ++i)
6450 if (first[i].to == null)
6451 (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marke r, null, null));
6452 for (var i = 0; i < gap; ++i)
6453 newMarkers.push(gapMarkers);
6454 newMarkers.push(last);
6455 }
6456 return newMarkers;
6457 }
6458
6459 // Remove spans that are empty and don't have a clearWhenEmpty
6460 // option of false.
6461 function clearEmptySpans(spans) {
6462 for (var i = 0; i < spans.length; ++i) {
6463 var span = spans[i];
6464 if (span.from != null && span.from == span.to && span.marker.clearWhenEmpt y !== false)
6465 spans.splice(i--, 1);
6466 }
6467 if (!spans.length) return null;
6468 return spans;
6469 }
6470
6471 // Used for un/re-doing changes from the history. Combines the
6472 // result of computing the existing spans with the set of spans that
6473 // existed in the history (so that deleting around a span and then
6474 // undoing brings back the span).
6475 function mergeOldSpans(doc, change) {
6476 var old = getOldSpans(doc, change);
6477 var stretched = stretchSpansOverChange(doc, change);
6478 if (!old) return stretched;
6479 if (!stretched) return old;
6480
6481 for (var i = 0; i < old.length; ++i) {
6482 var oldCur = old[i], stretchCur = stretched[i];
6483 if (oldCur && stretchCur) {
6484 spans: for (var j = 0; j < stretchCur.length; ++j) {
6485 var span = stretchCur[j];
6486 for (var k = 0; k < oldCur.length; ++k)
6487 if (oldCur[k].marker == span.marker) continue spans;
6488 oldCur.push(span);
6489 }
6490 } else if (stretchCur) {
6491 old[i] = stretchCur;
6492 }
6493 }
6494 return old;
6495 }
6496
6497 // Used to 'clip' out readOnly ranges when making a change.
6498 function removeReadOnlyRanges(doc, from, to) {
6499 var markers = null;
6500 doc.iter(from.line, to.line + 1, function(line) {
6501 if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
6502 var mark = line.markedSpans[i].marker;
6503 if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
6504 (markers || (markers = [])).push(mark);
6505 }
6506 });
6507 if (!markers) return null;
6508 var parts = [{from: from, to: to}];
6509 for (var i = 0; i < markers.length; ++i) {
6510 var mk = markers[i], m = mk.find(0);
6511 for (var j = 0; j < parts.length; ++j) {
6512 var p = parts[j];
6513 if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue;
6514 var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to );
6515 if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)
6516 newParts.push({from: p.from, to: m.from});
6517 if (dto > 0 || !mk.inclusiveRight && !dto)
6518 newParts.push({from: m.to, to: p.to});
6519 parts.splice.apply(parts, newParts);
6520 j += newParts.length - 1;
6521 }
6522 }
6523 return parts;
6524 }
6525
6526 // Connect or disconnect spans from a line.
6527 function detachMarkedSpans(line) {
6528 var spans = line.markedSpans;
6529 if (!spans) return;
6530 for (var i = 0; i < spans.length; ++i)
6531 spans[i].marker.detachLine(line);
6532 line.markedSpans = null;
6533 }
6534 function attachMarkedSpans(line, spans) {
6535 if (!spans) return;
6536 for (var i = 0; i < spans.length; ++i)
6537 spans[i].marker.attachLine(line);
6538 line.markedSpans = spans;
6539 }
6540
6541 // Helpers used when computing which overlapping collapsed span
6542 // counts as the larger one.
6543 function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; }
6544 function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; }
6545
6546 // Returns a number indicating which of two overlapping collapsed
6547 // spans is larger (and thus includes the other). Falls back to
6548 // comparing ids when the spans cover exactly the same range.
6549 function compareCollapsedMarkers(a, b) {
6550 var lenDiff = a.lines.length - b.lines.length;
6551 if (lenDiff != 0) return lenDiff;
6552 var aPos = a.find(), bPos = b.find();
6553 var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b);
6554 if (fromCmp) return -fromCmp;
6555 var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b);
6556 if (toCmp) return toCmp;
6557 return b.id - a.id;
6558 }
6559
6560 // Find out whether a line ends or starts in a collapsed span. If
6561 // so, return the marker for that span.
6562 function collapsedSpanAtSide(line, start) {
6563 var sps = sawCollapsedSpans && line.markedSpans, found;
6564 if (sps) for (var sp, i = 0; i < sps.length; ++i) {
6565 sp = sps[i];
6566 if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&
6567 (!found || compareCollapsedMarkers(found, sp.marker) < 0))
6568 found = sp.marker;
6569 }
6570 return found;
6571 }
6572 function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); }
6573 function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); }
6574
6575 // Test whether there exists a collapsed span that partially
6576 // overlaps (covers the start or end, but not both) of a new span.
6577 // Such overlap is not allowed.
6578 function conflictingCollapsedRange(doc, lineNo, from, to, marker) {
6579 var line = getLine(doc, lineNo);
6580 var sps = sawCollapsedSpans && line.markedSpans;
6581 if (sps) for (var i = 0; i < sps.length; ++i) {
6582 var sp = sps[i];
6583 if (!sp.marker.collapsed) continue;
6584 var found = sp.marker.find(0);
6585 var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(ma rker);
6586 var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker );
6587 if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue;
6588 if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cm p(found.to, from) >= 0 : cmp(found.to, from) > 0) ||
6589 fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cm p(found.from, to) <= 0 : cmp(found.from, to) < 0))
6590 return true;
6591 }
6592 }
6593
6594 // A visual line is a line as drawn on the screen. Folding, for
6595 // example, can cause multiple logical lines to appear on the same
6596 // visual line. This finds the start of the visual line that the
6597 // given line is part of (usually that is the line itself).
6598 function visualLine(line) {
6599 var merged;
6600 while (merged = collapsedSpanAtStart(line))
6601 line = merged.find(-1, true).line;
6602 return line;
6603 }
6604
6605 // Returns an array of logical lines that continue the visual line
6606 // started by the argument, or undefined if there are no such lines.
6607 function visualLineContinued(line) {
6608 var merged, lines;
6609 while (merged = collapsedSpanAtEnd(line)) {
6610 line = merged.find(1, true).line;
6611 (lines || (lines = [])).push(line);
6612 }
6613 return lines;
6614 }
6615
6616 // Get the line number of the start of the visual line that the
6617 // given line number is part of.
6618 function visualLineNo(doc, lineN) {
6619 var line = getLine(doc, lineN), vis = visualLine(line);
6620 if (line == vis) return lineN;
6621 return lineNo(vis);
6622 }
6623 // Get the line number of the start of the next visual line after
6624 // the given line.
6625 function visualLineEndNo(doc, lineN) {
6626 if (lineN > doc.lastLine()) return lineN;
6627 var line = getLine(doc, lineN), merged;
6628 if (!lineIsHidden(doc, line)) return lineN;
6629 while (merged = collapsedSpanAtEnd(line))
6630 line = merged.find(1, true).line;
6631 return lineNo(line) + 1;
6632 }
6633
6634 // Compute whether a line is hidden. Lines count as hidden when they
6635 // are part of a visual line that starts with another line, or when
6636 // they are entirely covered by collapsed, non-widget span.
6637 function lineIsHidden(doc, line) {
6638 var sps = sawCollapsedSpans && line.markedSpans;
6639 if (sps) for (var sp, i = 0; i < sps.length; ++i) {
6640 sp = sps[i];
6641 if (!sp.marker.collapsed) continue;
6642 if (sp.from == null) return true;
6643 if (sp.marker.widgetNode) continue;
6644 if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line , sp))
6645 return true;
6646 }
6647 }
6648 function lineIsHiddenInner(doc, line, span) {
6649 if (span.to == null) {
6650 var end = span.marker.find(1, true);
6651 return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSp ans, span.marker));
6652 }
6653 if (span.marker.inclusiveRight && span.to == line.text.length)
6654 return true;
6655 for (var sp, i = 0; i < line.markedSpans.length; ++i) {
6656 sp = line.markedSpans[i];
6657 if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&
6658 (sp.to == null || sp.to != span.from) &&
6659 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
6660 lineIsHiddenInner(doc, line, sp)) return true;
6661 }
6662 }
6663
6664 // LINE WIDGETS
6665
6666 // Line widgets are block elements displayed above or below a line.
6667
6668 var LineWidget = CodeMirror.LineWidget = function(doc, node, options) {
6669 if (options) for (var opt in options) if (options.hasOwnProperty(opt))
6670 this[opt] = options[opt];
6671 this.doc = doc;
6672 this.node = node;
6673 };
6674 eventMixin(LineWidget);
6675
6676 function adjustScrollWhenAboveVisible(cm, line, diff) {
6677 if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollT op))
6678 addToScrollPos(cm, null, diff);
6679 }
6680
6681 LineWidget.prototype.clear = function() {
6682 var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo( line);
6683 if (no == null || !ws) return;
6684 for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
6685 if (!ws.length) line.widgets = null;
6686 var height = widgetHeight(this);
6687 updateLineHeight(line, Math.max(0, line.height - height));
6688 if (cm) runInOp(cm, function() {
6689 adjustScrollWhenAboveVisible(cm, line, -height);
6690 regLineChange(cm, no, "widget");
6691 });
6692 };
6693 LineWidget.prototype.changed = function() {
6694 var oldH = this.height, cm = this.doc.cm, line = this.line;
6695 this.height = null;
6696 var diff = widgetHeight(this) - oldH;
6697 if (!diff) return;
6698 updateLineHeight(line, line.height + diff);
6699 if (cm) runInOp(cm, function() {
6700 cm.curOp.forceUpdate = true;
6701 adjustScrollWhenAboveVisible(cm, line, diff);
6702 });
6703 };
6704
6705 function widgetHeight(widget) {
6706 if (widget.height != null) return widget.height;
6707 var cm = widget.doc.cm;
6708 if (!cm) return 0;
6709 if (!contains(document.body, widget.node)) {
6710 var parentStyle = "position: relative;";
6711 if (widget.coverGutter)
6712 parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;" ;
6713 if (widget.noHScroll)
6714 parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;";
6715 removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, p arentStyle));
6716 }
6717 return widget.height = widget.node.parentNode.offsetHeight;
6718 }
6719
6720 function addLineWidget(doc, handle, node, options) {
6721 var widget = new LineWidget(doc, node, options);
6722 var cm = doc.cm;
6723 if (cm && widget.noHScroll) cm.display.alignWidgets = true;
6724 changeLine(doc, handle, "widget", function(line) {
6725 var widgets = line.widgets || (line.widgets = []);
6726 if (widget.insertAt == null) widgets.push(widget);
6727 else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insert At)), 0, widget);
6728 widget.line = line;
6729 if (cm && !lineIsHidden(doc, line)) {
6730 var aboveVisible = heightAtLine(line) < doc.scrollTop;
6731 updateLineHeight(line, line.height + widgetHeight(widget));
6732 if (aboveVisible) addToScrollPos(cm, null, widget.height);
6733 cm.curOp.forceUpdate = true;
6734 }
6735 return true;
6736 });
6737 return widget;
6738 }
6739
6740 // LINE DATA STRUCTURE
6741
6742 // Line objects. These hold state related to a line, including
6743 // highlighting info (the styles array).
6744 var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) {
6745 this.text = text;
6746 attachMarkedSpans(this, markedSpans);
6747 this.height = estimateHeight ? estimateHeight(this) : 1;
6748 };
6749 eventMixin(Line);
6750 Line.prototype.lineNo = function() { return lineNo(this); };
6751
6752 // Change the content (text, markers) of a line. Automatically
6753 // invalidates cached information and tries to re-estimate the
6754 // line's height.
6755 function updateLine(line, text, markedSpans, estimateHeight) {
6756 line.text = text;
6757 if (line.stateAfter) line.stateAfter = null;
6758 if (line.styles) line.styles = null;
6759 if (line.order != null) line.order = null;
6760 detachMarkedSpans(line);
6761 attachMarkedSpans(line, markedSpans);
6762 var estHeight = estimateHeight ? estimateHeight(line) : 1;
6763 if (estHeight != line.height) updateLineHeight(line, estHeight);
6764 }
6765
6766 // Detach a line from the document tree and its markers.
6767 function cleanUpLine(line) {
6768 line.parent = null;
6769 detachMarkedSpans(line);
6770 }
6771
6772 function extractLineClasses(type, output) {
6773 if (type) for (;;) {
6774 var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/);
6775 if (!lineClass) break;
6776 type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineC lass[0].length);
6777 var prop = lineClass[1] ? "bgClass" : "textClass";
6778 if (output[prop] == null)
6779 output[prop] = lineClass[2];
6780 else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output [prop]))
6781 output[prop] += " " + lineClass[2];
6782 }
6783 return type;
6784 }
6785
6786 function callBlankLine(mode, state) {
6787 if (mode.blankLine) return mode.blankLine(state);
6788 if (!mode.innerMode) return;
6789 var inner = CodeMirror.innerMode(mode, state);
6790 if (inner.mode.blankLine) return inner.mode.blankLine(inner.state);
6791 }
6792
6793 function readToken(mode, stream, state, inner) {
6794 for (var i = 0; i < 10; i++) {
6795 if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode;
6796 var style = mode.token(stream, state);
6797 if (stream.pos > stream.start) return style;
6798 }
6799 throw new Error("Mode " + mode.name + " failed to advance stream.");
6800 }
6801
6802 // Utility for getTokenAt and getLineTokens
6803 function takeToken(cm, pos, precise, asArray) {
6804 function getObj(copy) {
6805 return {start: stream.start, end: stream.pos,
6806 string: stream.current(),
6807 type: style || null,
6808 state: copy ? copyState(doc.mode, state) : state};
6809 }
6810
6811 var doc = cm.doc, mode = doc.mode, style;
6812 pos = clipPos(doc, pos);
6813 var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, prec ise);
6814 var stream = new StringStream(line.text, cm.options.tabSize), tokens;
6815 if (asArray) tokens = [];
6816 while ((asArray || stream.pos < pos.ch) && !stream.eol()) {
6817 stream.start = stream.pos;
6818 style = readToken(mode, stream, state);
6819 if (asArray) tokens.push(getObj(true));
6820 }
6821 return asArray ? tokens : getObj();
6822 }
6823
6824 // Run the given mode's parser over a line, calling f for each token.
6825 function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) {
6826 var flattenSpans = mode.flattenSpans;
6827 if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
6828 var curStart = 0, curStyle = null;
6829 var stream = new StringStream(text, cm.options.tabSize), style;
6830 var inner = cm.options.addModeClass && [null];
6831 if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses);
6832 while (!stream.eol()) {
6833 if (stream.pos > cm.options.maxHighlightLength) {
6834 flattenSpans = false;
6835 if (forceToEnd) processLine(cm, text, state, stream.pos);
6836 stream.pos = text.length;
6837 style = null;
6838 } else {
6839 style = extractLineClasses(readToken(mode, stream, state, inner), lineCl asses);
6840 }
6841 if (inner) {
6842 var mName = inner[0].name;
6843 if (mName) style = "m-" + (style ? mName + " " + style : mName);
6844 }
6845 if (!flattenSpans || curStyle != style) {
6846 while (curStart < stream.start) {
6847 curStart = Math.min(stream.start, curStart + 50000);
6848 f(curStart, curStyle);
6849 }
6850 curStyle = style;
6851 }
6852 stream.start = stream.pos;
6853 }
6854 while (curStart < stream.pos) {
6855 // Webkit seems to refuse to render text nodes longer than 57444 character s
6856 var pos = Math.min(stream.pos, curStart + 50000);
6857 f(pos, curStyle);
6858 curStart = pos;
6859 }
6860 }
6861
6862 // Compute a style array (an array starting with a mode generation
6863 // -- for invalidation -- followed by pairs of end positions and
6864 // style strings), which is used to highlight the tokens on the
6865 // line.
6866 function highlightLine(cm, line, state, forceToEnd) {
6867 // A styles array always starts with a number identifying the
6868 // mode/overlays that it is based on (for easy invalidation).
6869 var st = [cm.state.modeGen], lineClasses = {};
6870 // Compute the base array of styles
6871 runMode(cm, line.text, cm.doc.mode, state, function(end, style) {
6872 st.push(end, style);
6873 }, lineClasses, forceToEnd);
6874
6875 // Run overlays, adjust style array.
6876 for (var o = 0; o < cm.state.overlays.length; ++o) {
6877 var overlay = cm.state.overlays[o], i = 1, at = 0;
6878 runMode(cm, line.text, overlay.mode, true, function(end, style) {
6879 var start = i;
6880 // Ensure there's a token end at the current position, and that i points at it
6881 while (at < end) {
6882 var i_end = st[i];
6883 if (i_end > end)
6884 st.splice(i, 1, end, st[i+1], i_end);
6885 i += 2;
6886 at = Math.min(end, i_end);
6887 }
6888 if (!style) return;
6889 if (overlay.opaque) {
6890 st.splice(start, i - start, end, "cm-overlay " + style);
6891 i = start + 2;
6892 } else {
6893 for (; start < i; start += 2) {
6894 var cur = st[start+1];
6895 st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style;
6896 }
6897 }
6898 }, lineClasses);
6899 }
6900
6901 return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null};
6902 }
6903
6904 function getLineStyles(cm, line, updateFrontier) {
6905 if (!line.styles || line.styles[0] != cm.state.modeGen) {
6906 var state = getStateBefore(cm, lineNo(line));
6907 var result = highlightLine(cm, line, line.text.length > cm.options.maxHigh lightLength ? copyState(cm.doc.mode, state) : state);
6908 line.stateAfter = state;
6909 line.styles = result.styles;
6910 if (result.classes) line.styleClasses = result.classes;
6911 else if (line.styleClasses) line.styleClasses = null;
6912 if (updateFrontier === cm.doc.frontier) cm.doc.frontier++;
6913 }
6914 return line.styles;
6915 }
6916
6917 // Lightweight form of highlight -- proceed over this line and
6918 // update state, but don't save a style array. Used for lines that
6919 // aren't currently visible.
6920 function processLine(cm, text, state, startAt) {
6921 var mode = cm.doc.mode;
6922 var stream = new StringStream(text, cm.options.tabSize);
6923 stream.start = stream.pos = startAt || 0;
6924 if (text == "") callBlankLine(mode, state);
6925 while (!stream.eol()) {
6926 readToken(mode, stream, state);
6927 stream.start = stream.pos;
6928 }
6929 }
6930
6931 // Convert a style as returned by a mode (either null, or a string
6932 // containing one or more styles) to a CSS style. This is cached,
6933 // and also looks for line-wide styles.
6934 var styleToClassCache = {}, styleToClassCacheWithMode = {};
6935 function interpretTokenStyle(style, options) {
6936 if (!style || /^\s*$/.test(style)) return null;
6937 var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassC ache;
6938 return cache[style] ||
6939 (cache[style] = style.replace(/\S+/g, "cm-$&"));
6940 }
6941
6942 // Render the DOM representation of the text of a line. Also builds
6943 // up a 'line map', which points at the DOM nodes that represent
6944 // specific stretches of text, and is used by the measuring code.
6945 // The returned object contains the DOM node, this map, and
6946 // information about line-wide styles that were set by the mode.
6947 function buildLineContent(cm, lineView) {
6948 // The padding-right forces the element to have a 'border', which
6949 // is needed on Webkit to be able to get line-level bounding
6950 // rectangles for it (in measureChar).
6951 var content = elt("span", null, null, webkit ? "padding-right: .1px" : null) ;
6952 var builder = {pre: elt("pre", [content], "CodeMirror-line"), content: conte nt,
6953 col: 0, pos: 0, cm: cm,
6954 trailingSpace: false,
6955 splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")};
6956 lineView.measure = {};
6957
6958 // Iterate over the logical lines that make up this visual line.
6959 for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {
6960 var line = i ? lineView.rest[i - 1] : lineView.line, order;
6961 builder.pos = 0;
6962 builder.addToken = buildToken;
6963 // Optionally wire in some hacks into the token-rendering
6964 // algorithm, to deal with browser quirks.
6965 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line)))
6966 builder.addToken = buildTokenBadBidi(builder.addToken, order);
6967 builder.map = [];
6968 var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineN o(line);
6969 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpda te));
6970 if (line.styleClasses) {
6971 if (line.styleClasses.bgClass)
6972 builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgCla ss || "");
6973 if (line.styleClasses.textClass)
6974 builder.textClass = joinClasses(line.styleClasses.textClass, builder.t extClass || "");
6975 }
6976
6977 // Ensure at least a single node is present, for measuring.
6978 if (builder.map.length == 0)
6979 builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.d isplay.measure)));
6980
6981 // Store the map and a cache object for the current logical line
6982 if (i == 0) {
6983 lineView.measure.map = builder.map;
6984 lineView.measure.cache = {};
6985 } else {
6986 (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map );
6987 (lineView.measure.caches || (lineView.measure.caches = [])).push({});
6988 }
6989 }
6990
6991 // See issue #2901
6992 if (webkit) {
6993 var last = builder.content.lastChild
6994 if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.query Selector(".cm-tab")))
6995 builder.content.className = "cm-tab-wrap-hack";
6996 }
6997
6998 signal(cm, "renderLine", cm, lineView.line, builder.pre);
6999 if (builder.pre.className)
7000 builder.textClass = joinClasses(builder.pre.className, builder.textClass | | "");
7001
7002 return builder;
7003 }
7004
7005 function defaultSpecialCharPlaceholder(ch) {
7006 var token = elt("span", "\u2022", "cm-invalidchar");
7007 token.title = "\\u" + ch.charCodeAt(0).toString(16);
7008 token.setAttribute("aria-label", token.title);
7009 return token;
7010 }
7011
7012 // Build up the DOM representation for a single token, and add it to
7013 // the line map. Takes care to render special characters separately.
7014 function buildToken(builder, text, style, startStyle, endStyle, title, css) {
7015 if (!text) return;
7016 var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSp ace) : text
7017 var special = builder.cm.state.specialChars, mustWrap = false;
7018 if (!special.test(text)) {
7019 builder.col += text.length;
7020 var content = document.createTextNode(displayText);
7021 builder.map.push(builder.pos, builder.pos + text.length, content);
7022 if (ie && ie_version < 9) mustWrap = true;
7023 builder.pos += text.length;
8231 } else { 7024 } else {
8232 next = moveLogically(lineObj, pos, dir) 7025 var content = document.createDocumentFragment(), pos = 0;
8233 } 7026 while (true) {
8234 if (next == null) { 7027 special.lastIndex = pos;
8235 if (!boundToLine && findNextLine()) 7028 var m = special.exec(text);
8236 { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir) } 7029 var skipped = m ? m.index - pos : text.length - pos;
8237 else 7030 if (skipped) {
8238 { return false } 7031 var txt = document.createTextNode(displayText.slice(pos, pos + skipped ));
7032 if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
7033 else content.appendChild(txt);
7034 builder.map.push(builder.pos, builder.pos + skipped, txt);
7035 builder.col += skipped;
7036 builder.pos += skipped;
7037 }
7038 if (!m) break;
7039 pos += skipped + 1;
7040 if (m[0] == "\t") {
7041 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder .col % tabSize;
7042 var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab" ));
7043 txt.setAttribute("role", "presentation");
7044 txt.setAttribute("cm-text", "\t");
7045 builder.col += tabWidth;
7046 } else if (m[0] == "\r" || m[0] == "\n") {
7047 var txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\ u2424", "cm-invalidchar"));
7048 txt.setAttribute("cm-text", m[0]);
7049 builder.col += 1;
7050 } else {
7051 var txt = builder.cm.options.specialCharPlaceholder(m[0]);
7052 txt.setAttribute("cm-text", m[0]);
7053 if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
7054 else content.appendChild(txt);
7055 builder.col += 1;
7056 }
7057 builder.map.push(builder.pos, builder.pos + 1, txt);
7058 builder.pos++;
7059 }
7060 }
7061 builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32
7062 if (style || startStyle || endStyle || mustWrap || css) {
7063 var fullStyle = style || "";
7064 if (startStyle) fullStyle += startStyle;
7065 if (endStyle) fullStyle += endStyle;
7066 var token = elt("span", [content], fullStyle, css);
7067 if (title) token.title = title;
7068 return builder.content.appendChild(token);
7069 }
7070 builder.content.appendChild(content);
7071 }
7072
7073 function splitSpaces(text, trailingBefore) {
7074 if (text.length > 1 && !/ /.test(text)) return text
7075 var spaceBefore = trailingBefore, result = ""
7076 for (var i = 0; i < text.length; i++) {
7077 var ch = text.charAt(i)
7078 if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32))
7079 ch = "\u00a0"
7080 result += ch
7081 spaceBefore = ch == " "
7082 }
7083 return result
7084 }
7085
7086 // Work around nonsense dimensions being reported for stretches of
7087 // right-to-left text.
7088 function buildTokenBadBidi(inner, order) {
7089 return function(builder, text, style, startStyle, endStyle, title, css) {
7090 style = style ? style + " cm-force-border" : "cm-force-border";
7091 var start = builder.pos, end = start + text.length;
7092 for (;;) {
7093 // Find the part that overlaps with the start of this text
7094 for (var i = 0; i < order.length; i++) {
7095 var part = order[i];
7096 if (part.to > start && part.from <= start) break;
7097 }
7098 if (part.to >= end) return inner(builder, text, style, startStyle, endSt yle, title, css);
7099 inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css);
7100 startStyle = null;
7101 text = text.slice(part.to - start);
7102 start = part.to;
7103 }
7104 };
7105 }
7106
7107 function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
7108 var widget = !ignoreWidget && marker.widgetNode;
7109 if (widget) builder.map.push(builder.pos, builder.pos + size, widget);
7110 if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) {
7111 if (!widget)
7112 widget = builder.content.appendChild(document.createElement("span"));
7113 widget.setAttribute("cm-marker", marker.id);
7114 }
7115 if (widget) {
7116 builder.cm.display.input.setUneditable(widget);
7117 builder.content.appendChild(widget);
7118 }
7119 builder.pos += size;
7120 builder.trailingSpace = false
7121 }
7122
7123 // Outputs a number of spans to make up a line, taking highlighting
7124 // and marked text into account.
7125 function insertLineContent(line, builder, styles) {
7126 var spans = line.markedSpans, allText = line.text, at = 0;
7127 if (!spans) {
7128 for (var i = 1; i < styles.length; i+=2)
7129 builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTo kenStyle(styles[i+1], builder.cm.options));
7130 return;
7131 }
7132
7133 var len = allText.length, pos = 0, i = 1, text = "", style, css;
7134 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapse d;
7135 for (;;) {
7136 if (nextChange == pos) { // Update current marker set
7137 spanStyle = spanEndStyle = spanStartStyle = title = css = "";
7138 collapsed = null; nextChange = Infinity;
7139 var foundBookmarks = [], endStyles
7140 for (var j = 0; j < spans.length; ++j) {
7141 var sp = spans[j], m = sp.marker;
7142 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) {
7143 foundBookmarks.push(m);
7144 } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collap sed && sp.to == pos && sp.from == pos)) {
7145 if (sp.to != null && sp.to != pos && nextChange > sp.to) {
7146 nextChange = sp.to;
7147 spanEndStyle = "";
7148 }
7149 if (m.className) spanStyle += " " + m.className;
7150 if (m.css) css = (css ? css + ";" : "") + m.css;
7151 if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startS tyle;
7152 if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [] )).push(m.endStyle, sp.to)
7153 if (m.title && !title) title = m.title;
7154 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed. marker, m) < 0))
7155 collapsed = sp;
7156 } else if (sp.from > pos && nextChange > sp.from) {
7157 nextChange = sp.from;
7158 }
7159 }
7160 if (endStyles) for (var j = 0; j < endStyles.length; j += 2)
7161 if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j]
7162
7163 if (!collapsed || collapsed.from == pos) for (var j = 0; j < foundBookma rks.length; ++j)
7164 buildCollapsedSpan(builder, 0, foundBookmarks[j]);
7165 if (collapsed && (collapsed.from || 0) == pos) {
7166 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapse d.to) - pos,
7167 collapsed.marker, collapsed.from == null);
7168 if (collapsed.to == null) return;
7169 if (collapsed.to == pos) collapsed = false;
7170 }
7171 }
7172 if (pos >= len) break;
7173
7174 var upto = Math.min(len, nextChange);
7175 while (true) {
7176 if (text) {
7177 var end = pos + text.length;
7178 if (!collapsed) {
7179 var tokenText = end > upto ? text.slice(0, upto - pos) : text;
7180 builder.addToken(builder, tokenText, style ? style + spanStyle : spa nStyle,
7181 spanStartStyle, pos + tokenText.length == nextChang e ? spanEndStyle : "", title, css);
7182 }
7183 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
7184 pos = end;
7185 spanStartStyle = "";
7186 }
7187 text = allText.slice(at, at = styles[i++]);
7188 style = interpretTokenStyle(styles[i++], builder.cm.options);
7189 }
7190 }
7191 }
7192
7193 // DOCUMENT DATA STRUCTURE
7194
7195 // By default, updates that start and end at the beginning of a line
7196 // are treated specially, in order to make the association of line
7197 // widgets and marker elements with the text behave more intuitive.
7198 function isWholeLineUpdate(doc, change) {
7199 return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" &&
7200 (!doc.cm || doc.cm.options.wholeLineUpdateBefore);
7201 }
7202
7203 // Perform a change on the document data structure.
7204 function updateDoc(doc, change, markedSpans, estimateHeight) {
7205 function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
7206 function update(line, text, spans) {
7207 updateLine(line, text, spans, estimateHeight);
7208 signalLater(line, "change", line, change);
7209 }
7210 function linesFor(start, end) {
7211 for (var i = start, result = []; i < end; ++i)
7212 result.push(new Line(text[i], spansFor(i), estimateHeight));
7213 return result;
7214 }
7215
7216 var from = change.from, to = change.to, text = change.text;
7217 var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
7218 var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to .line - from.line;
7219
7220 // Adjust the line structure
7221 if (change.full) {
7222 doc.insert(0, linesFor(0, text.length));
7223 doc.remove(text.length, doc.size - text.length);
7224 } else if (isWholeLineUpdate(doc, change)) {
7225 // This is a whole-line replace. Treated specially to make
7226 // sure line objects move the way they are supposed to.
7227 var added = linesFor(0, text.length - 1);
7228 update(lastLine, lastLine.text, lastSpans);
7229 if (nlines) doc.remove(from.line, nlines);
7230 if (added.length) doc.insert(from.line, added);
7231 } else if (firstLine == lastLine) {
7232 if (text.length == 1) {
7233 update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLin e.text.slice(to.ch), lastSpans);
7234 } else {
7235 var added = linesFor(1, text.length - 1);
7236 added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, e stimateHeight));
7237 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0 ));
7238 doc.insert(from.line + 1, added);
7239 }
7240 } else if (text.length == 1) {
7241 update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.te xt.slice(to.ch), spansFor(0));
7242 doc.remove(from.line + 1, nlines);
8239 } else { 7243 } else {
8240 pos = next 7244 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) ;
8241 } 7245 update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
8242 return true 7246 var added = linesFor(1, text.length - 1);
8243 } 7247 if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
8244 7248 doc.insert(from.line + 1, added);
8245 if (unit == "char") { 7249 }
8246 moveOnce() 7250
8247 } else if (unit == "column") { 7251 signalLater(doc, "change", doc, change);
8248 moveOnce(true) 7252 }
8249 } else if (unit == "word" || unit == "group") { 7253
8250 var sawType = null, group = unit == "group" 7254 // The document is represented as a BTree consisting of leaves, with
8251 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars") 7255 // chunk of lines in them, and branches, with up to ten leaves or
8252 for (var first = true;; first = false) { 7256 // other branch nodes below them. The top node is always a branch
8253 if (dir < 0 && !moveOnce(!first)) { break } 7257 // node, and is the document object itself (meaning it has
8254 var cur = lineObj.text.charAt(pos.ch) || "\n" 7258 // additional methods and properties).
8255 var type = isWordChar(cur, helper) ? "w" 7259 //
8256 : group && cur == "\n" ? "n" 7260 // All nodes have parent links. The tree is used both to go from
8257 : !group || /\s/.test(cur) ? null 7261 // line numbers to line objects, and to go from objects to numbers.
8258 : "p" 7262 // It also indexes by height, and is used to convert between height
8259 if (group && !first && !type) { type = "s" } 7263 // and line object, and to find the total height of the document.
8260 if (sawType && sawType != type) { 7264 //
8261 if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after"} 7265 // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
8262 break 7266
8263 } 7267 function LeafChunk(lines) {
8264 7268 this.lines = lines;
8265 if (type) { sawType = type } 7269 this.parent = null;
8266 if (dir > 0 && !moveOnce(!first)) { break } 7270 for (var i = 0, height = 0; i < lines.length; ++i) {
8267 } 7271 lines[i].parent = this;
8268 } 7272 height += lines[i].height;
8269 var result = skipAtomic(doc, pos, oldPos, origDir, true) 7273 }
8270 if (equalCursorPos(oldPos, result)) { result.hitSide = true } 7274 this.height = height;
8271 return result 7275 }
8272 } 7276
8273 7277 LeafChunk.prototype = {
8274 // For relative vertical movement. Dir may be -1 or 1. Unit can be 7278 chunkSize: function() { return this.lines.length; },
8275 // "page" or "line". The resulting position will have a hitSide=true 7279 // Remove the n lines at offset 'at'.
8276 // property if it reached the end of the document. 7280 removeInner: function(at, n) {
8277 function findPosV(cm, pos, dir, unit) { 7281 for (var i = at, e = at + n; i < e; ++i) {
8278 var doc = cm.doc, x = pos.left, y 7282 var line = this.lines[i];
8279 if (unit == "page") { 7283 this.height -= line.height;
8280 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight) 7284 cleanUpLine(line);
8281 var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3) 7285 signalLater(line, "delete");
8282 y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount 7286 }
8283 7287 this.lines.splice(at, n);
8284 } else if (unit == "line") { 7288 },
8285 y = dir > 0 ? pos.bottom + 3 : pos.top - 3 7289 // Helper used to collapse a small branch into a single leaf.
8286 } 7290 collapse: function(lines) {
8287 var target 7291 lines.push.apply(lines, this.lines);
8288 for (;;) { 7292 },
8289 target = coordsChar(cm, x, y) 7293 // Insert the given array of lines at offset 'at', count them as
8290 if (!target.outside) { break } 7294 // having the given height.
8291 if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } 7295 insertInner: function(at, lines, height) {
8292 y += dir * 5 7296 this.height += height;
8293 } 7297 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice (at));
8294 return target 7298 for (var i = 0; i < lines.length; ++i) lines[i].parent = this;
8295 } 7299 },
8296 7300 // Used to iterate over a part of the tree.
8297 // CONTENTEDITABLE INPUT STYLE 7301 iterN: function(at, n, op) {
8298 7302 for (var e = at + n; at < e; ++at)
8299 var ContentEditableInput = function ContentEditableInput(cm) { 7303 if (op(this.lines[at])) return true;
8300 this.cm = cm 7304 }
8301 this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFo cusOffset = null 7305 };
8302 this.polling = new Delayed() 7306
8303 this.composing = null 7307 function BranchChunk(children) {
8304 this.gracePeriod = false 7308 this.children = children;
8305 this.readDOMTimeout = null 7309 var size = 0, height = 0;
8306 }; 7310 for (var i = 0; i < children.length; ++i) {
8307 7311 var ch = children[i];
8308 ContentEditableInput.prototype.init = function init (display) { 7312 size += ch.chunkSize(); height += ch.height;
8309 var this$1 = this; 7313 ch.parent = this;
8310 7314 }
8311 var input = this, cm = input.cm 7315 this.size = size;
8312 var div = input.div = display.lineDiv 7316 this.height = height;
8313 disableBrowserMagic(div, cm.options.spellcheck) 7317 this.parent = null;
8314 7318 }
8315 on(div, "paste", function (e) { 7319
8316 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } 7320 BranchChunk.prototype = {
8317 // IE doesn't fire input events, so we schedule a read for the pasted conten t in this way 7321 chunkSize: function() { return this.size; },
8318 if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1 .updateFromDOM(); }), 20) } 7322 removeInner: function(at, n) {
8319 }) 7323 this.size -= n;
8320 7324 for (var i = 0; i < this.children.length; ++i) {
8321 on(div, "compositionstart", function (e) { 7325 var child = this.children[i], sz = child.chunkSize();
8322 this$1.composing = {data: e.data, done: false} 7326 if (at < sz) {
8323 }) 7327 var rm = Math.min(n, sz - at), oldHeight = child.height;
8324 on(div, "compositionupdate", function (e) { 7328 child.removeInner(at, rm);
8325 if (!this$1.composing) { this$1.composing = {data: e.data, done: false} } 7329 this.height -= oldHeight - child.height;
8326 }) 7330 if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
8327 on(div, "compositionend", function (e) { 7331 if ((n -= rm) == 0) break;
8328 if (this$1.composing) { 7332 at = 0;
8329 if (e.data != this$1.composing.data) { this$1.readFromDOMSoon() } 7333 } else at -= sz;
8330 this$1.composing.done = true 7334 }
8331 } 7335 // If the result is smaller than 25 lines, ensure that it is a
8332 }) 7336 // single leaf node.
8333 7337 if (this.size - n < 25 &&
8334 on(div, "touchstart", function () { return input.forceCompositionEnd(); }) 7338 (this.children.length > 1 || !(this.children[0] instanceof LeafChunk)) ) {
8335 7339 var lines = [];
8336 on(div, "input", function () { 7340 this.collapse(lines);
8337 if (!this$1.composing) { this$1.readFromDOMSoon() } 7341 this.children = [new LeafChunk(lines)];
8338 }) 7342 this.children[0].parent = this;
8339 7343 }
8340 function onCopyCut(e) { 7344 },
8341 if (signalDOMEvent(cm, e)) { return } 7345 collapse: function(lines) {
8342 if (cm.somethingSelected()) { 7346 for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(l ines);
8343 setLastCopied({lineWise: false, text: cm.getSelections()}) 7347 },
8344 if (e.type == "cut") { cm.replaceSelection("", null, "cut") } 7348 insertInner: function(at, lines, height) {
8345 } else if (!cm.options.lineWiseCopyCut) { 7349 this.size += lines.length;
8346 return 7350 this.height += height;
7351 for (var i = 0; i < this.children.length; ++i) {
7352 var child = this.children[i], sz = child.chunkSize();
7353 if (at <= sz) {
7354 child.insertInner(at, lines, height);
7355 if (child.lines && child.lines.length > 50) {
7356 // To avoid memory thrashing when child.lines is huge (e.g. first vi ew of a large file), it's never spliced.
7357 // Instead, small slices are taken. They're taken in order because s equential memory accesses are fastest.
7358 var remaining = child.lines.length % 25 + 25
7359 for (var pos = remaining; pos < child.lines.length;) {
7360 var leaf = new LeafChunk(child.lines.slice(pos, pos += 25));
7361 child.height -= leaf.height;
7362 this.children.splice(++i, 0, leaf);
7363 leaf.parent = this;
7364 }
7365 child.lines = child.lines.slice(0, remaining);
7366 this.maybeSpill();
7367 }
7368 break;
7369 }
7370 at -= sz;
7371 }
7372 },
7373 // When a node has grown, check whether it should be split.
7374 maybeSpill: function() {
7375 if (this.children.length <= 10) return;
7376 var me = this;
7377 do {
7378 var spilled = me.children.splice(me.children.length - 5, 5);
7379 var sibling = new BranchChunk(spilled);
7380 if (!me.parent) { // Become the parent node
7381 var copy = new BranchChunk(me.children);
7382 copy.parent = me;
7383 me.children = [copy, sibling];
7384 me = copy;
7385 } else {
7386 me.size -= sibling.size;
7387 me.height -= sibling.height;
7388 var myIndex = indexOf(me.parent.children, me);
7389 me.parent.children.splice(myIndex + 1, 0, sibling);
7390 }
7391 sibling.parent = me.parent;
7392 } while (me.children.length > 10);
7393 me.parent.maybeSpill();
7394 },
7395 iterN: function(at, n, op) {
7396 for (var i = 0; i < this.children.length; ++i) {
7397 var child = this.children[i], sz = child.chunkSize();
7398 if (at < sz) {
7399 var used = Math.min(n, sz - at);
7400 if (child.iterN(at, used, op)) return true;
7401 if ((n -= used) == 0) break;
7402 at = 0;
7403 } else at -= sz;
7404 }
7405 }
7406 };
7407
7408 var nextDocId = 0;
7409 var Doc = CodeMirror.Doc = function(text, mode, firstLine, lineSep) {
7410 if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep);
7411 if (firstLine == null) firstLine = 0;
7412
7413 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
7414 this.first = firstLine;
7415 this.scrollTop = this.scrollLeft = 0;
7416 this.cantEdit = false;
7417 this.cleanGeneration = 1;
7418 this.frontier = firstLine;
7419 var start = Pos(firstLine, 0);
7420 this.sel = simpleSelection(start);
7421 this.history = new History(null);
7422 this.id = ++nextDocId;
7423 this.modeOption = mode;
7424 this.lineSep = lineSep;
7425 this.extend = false;
7426
7427 if (typeof text == "string") text = this.splitLines(text);
7428 updateDoc(this, {from: start, to: start, text: text});
7429 setSelection(this, simpleSelection(start), sel_dontScroll);
7430 };
7431
7432 Doc.prototype = createObj(BranchChunk.prototype, {
7433 constructor: Doc,
7434 // Iterate over the document. Supports two forms -- with only one
7435 // argument, it calls that for each line in the document. With
7436 // three, it iterates over the range given by the first two (with
7437 // the second being non-inclusive).
7438 iter: function(from, to, op) {
7439 if (op) this.iterN(from - this.first, to - from, op);
7440 else this.iterN(this.first, this.first + this.size, from);
7441 },
7442
7443 // Non-public interface for adding and removing lines.
7444 insert: function(at, lines) {
7445 var height = 0;
7446 for (var i = 0; i < lines.length; ++i) height += lines[i].height;
7447 this.insertInner(at - this.first, lines, height);
7448 },
7449 remove: function(at, n) { this.removeInner(at - this.first, n); },
7450
7451 // From here, the methods are part of the public interface. Most
7452 // are also available from CodeMirror (editor) instances.
7453
7454 getValue: function(lineSep) {
7455 var lines = getLines(this, this.first, this.first + this.size);
7456 if (lineSep === false) return lines;
7457 return lines.join(lineSep || this.lineSeparator());
7458 },
7459 setValue: docMethodOp(function(code) {
7460 var top = Pos(this.first, 0), last = this.first + this.size - 1;
7461 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length ),
7462 text: this.splitLines(code), origin: "setValue", full: t rue}, true);
7463 setSelection(this, simpleSelection(top));
7464 }),
7465 replaceRange: function(code, from, to, origin) {
7466 from = clipPos(this, from);
7467 to = to ? clipPos(this, to) : from;
7468 replaceRange(this, code, from, to, origin);
7469 },
7470 getRange: function(from, to, lineSep) {
7471 var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
7472 if (lineSep === false) return lines;
7473 return lines.join(lineSep || this.lineSeparator());
7474 },
7475
7476 getLine: function(line) {var l = this.getLineHandle(line); return l && l.tex t;},
7477
7478 getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
7479 getLineNumber: function(line) {return lineNo(line);},
7480
7481 getLineHandleVisualStart: function(line) {
7482 if (typeof line == "number") line = getLine(this, line);
7483 return visualLine(line);
7484 },
7485
7486 lineCount: function() {return this.size;},
7487 firstLine: function() {return this.first;},
7488 lastLine: function() {return this.first + this.size - 1;},
7489
7490 clipPos: function(pos) {return clipPos(this, pos);},
7491
7492 getCursor: function(start) {
7493 var range = this.sel.primary(), pos;
7494 if (start == null || start == "head") pos = range.head;
7495 else if (start == "anchor") pos = range.anchor;
7496 else if (start == "end" || start == "to" || start === false) pos = range.t o();
7497 else pos = range.from();
7498 return pos;
7499 },
7500 listSelections: function() { return this.sel.ranges; },
7501 somethingSelected: function() {return this.sel.somethingSelected();},
7502
7503 setCursor: docMethodOp(function(line, ch, options) {
7504 setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options);
7505 }),
7506 setSelection: docMethodOp(function(anchor, head, options) {
7507 setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anch or), options);
7508 }),
7509 extendSelection: docMethodOp(function(head, other, options) {
7510 extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);
7511 }),
7512 extendSelections: docMethodOp(function(heads, options) {
7513 extendSelections(this, clipPosArray(this, heads), options);
7514 }),
7515 extendSelectionsBy: docMethodOp(function(f, options) {
7516 var heads = map(this.sel.ranges, f);
7517 extendSelections(this, clipPosArray(this, heads), options);
7518 }),
7519 setSelections: docMethodOp(function(ranges, primary, options) {
7520 if (!ranges.length) return;
7521 for (var i = 0, out = []; i < ranges.length; i++)
7522 out[i] = new Range(clipPos(this, ranges[i].anchor),
7523 clipPos(this, ranges[i].head));
7524 if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIn dex);
7525 setSelection(this, normalizeSelection(out, primary), options);
7526 }),
7527 addSelection: docMethodOp(function(anchor, head, options) {
7528 var ranges = this.sel.ranges.slice(0);
7529 ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor) ));
7530 setSelection(this, normalizeSelection(ranges, ranges.length - 1), options) ;
7531 }),
7532
7533 getSelection: function(lineSep) {
7534 var ranges = this.sel.ranges, lines;
7535 for (var i = 0; i < ranges.length; i++) {
7536 var sel = getBetween(this, ranges[i].from(), ranges[i].to());
7537 lines = lines ? lines.concat(sel) : sel;
7538 }
7539 if (lineSep === false) return lines;
7540 else return lines.join(lineSep || this.lineSeparator());
7541 },
7542 getSelections: function(lineSep) {
7543 var parts = [], ranges = this.sel.ranges;
7544 for (var i = 0; i < ranges.length; i++) {
7545 var sel = getBetween(this, ranges[i].from(), ranges[i].to());
7546 if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator());
7547 parts[i] = sel;
7548 }
7549 return parts;
7550 },
7551 replaceSelection: function(code, collapse, origin) {
7552 var dup = [];
7553 for (var i = 0; i < this.sel.ranges.length; i++)
7554 dup[i] = code;
7555 this.replaceSelections(dup, collapse, origin || "+input");
7556 },
7557 replaceSelections: docMethodOp(function(code, collapse, origin) {
7558 var changes = [], sel = this.sel;
7559 for (var i = 0; i < sel.ranges.length; i++) {
7560 var range = sel.ranges[i];
7561 changes[i] = {from: range.from(), to: range.to(), text: this.splitLines( code[i]), origin: origin};
7562 }
7563 var newSel = collapse && collapse != "end" && computeReplacedSel(this, cha nges, collapse);
7564 for (var i = changes.length - 1; i >= 0; i--)
7565 makeChange(this, changes[i]);
7566 if (newSel) setSelectionReplaceHistory(this, newSel);
7567 else if (this.cm) ensureCursorVisible(this.cm);
7568 }),
7569 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
7570 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
7571 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", t rue);}),
7572 redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", t rue);}),
7573
7574 setExtending: function(val) {this.extend = val;},
7575 getExtending: function() {return this.extend;},
7576
7577 historySize: function() {
7578 var hist = this.history, done = 0, undone = 0;
7579 for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++don e;
7580 for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) + +undone;
7581 return {undo: done, redo: undone};
7582 },
7583 clearHistory: function() {this.history = new History(this.history.maxGenerat ion);},
7584
7585 markClean: function() {
7586 this.cleanGeneration = this.changeGeneration(true);
7587 },
7588 changeGeneration: function(forceSplit) {
7589 if (forceSplit)
7590 this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null;
7591 return this.history.generation;
7592 },
7593 isClean: function (gen) {
7594 return this.history.generation == (gen || this.cleanGeneration);
7595 },
7596
7597 getHistory: function() {
7598 return {done: copyHistoryArray(this.history.done),
7599 undone: copyHistoryArray(this.history.undone)};
7600 },
7601 setHistory: function(histData) {
7602 var hist = this.history = new History(this.history.maxGeneration);
7603 hist.done = copyHistoryArray(histData.done.slice(0), null, true);
7604 hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);
7605 },
7606
7607 addLineClass: docMethodOp(function(handle, where, cls) {
7608 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", fu nction(line) {
7609 var prop = where == "text" ? "textClass"
7610 : where == "background" ? "bgClass"
7611 : where == "gutter" ? "gutterClass" : "wrapClass";
7612 if (!line[prop]) line[prop] = cls;
7613 else if (classTest(cls).test(line[prop])) return false;
7614 else line[prop] += " " + cls;
7615 return true;
7616 });
7617 }),
7618 removeLineClass: docMethodOp(function(handle, where, cls) {
7619 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", fu nction(line) {
7620 var prop = where == "text" ? "textClass"
7621 : where == "background" ? "bgClass"
7622 : where == "gutter" ? "gutterClass" : "wrapClass";
7623 var cur = line[prop];
7624 if (!cur) return false;
7625 else if (cls == null) line[prop] = null;
7626 else {
7627 var found = cur.match(classTest(cls));
7628 if (!found) return false;
7629 var end = found.index + found[0].length;
7630 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.l ength ? "" : " ") + cur.slice(end) || null;
7631 }
7632 return true;
7633 });
7634 }),
7635
7636 addLineWidget: docMethodOp(function(handle, node, options) {
7637 return addLineWidget(this, handle, node, options);
7638 }),
7639 removeLineWidget: function(widget) { widget.clear(); },
7640
7641 markText: function(from, to, options) {
7642 return markText(this, clipPos(this, from), clipPos(this, to), options, opt ions && options.type || "range");
7643 },
7644 setBookmark: function(pos, options) {
7645 var realOpts = {replacedWith: options && (options.nodeType == null ? optio ns.widget : options),
7646 insertLeft: options && options.insertLeft,
7647 clearWhenEmpty: false, shared: options && options.shared,
7648 handleMouseEvents: options && options.handleMouseEvents};
7649 pos = clipPos(this, pos);
7650 return markText(this, pos, pos, realOpts, "bookmark");
7651 },
7652 findMarksAt: function(pos) {
7653 pos = clipPos(this, pos);
7654 var markers = [], spans = getLine(this, pos.line).markedSpans;
7655 if (spans) for (var i = 0; i < spans.length; ++i) {
7656 var span = spans[i];
7657 if ((span.from == null || span.from <= pos.ch) &&
7658 (span.to == null || span.to >= pos.ch))
7659 markers.push(span.marker.parent || span.marker);
7660 }
7661 return markers;
7662 },
7663 findMarks: function(from, to, filter) {
7664 from = clipPos(this, from); to = clipPos(this, to);
7665 var found = [], lineNo = from.line;
7666 this.iter(from.line, to.line + 1, function(line) {
7667 var spans = line.markedSpans;
7668 if (spans) for (var i = 0; i < spans.length; i++) {
7669 var span = spans[i];
7670 if (!(span.to != null && lineNo == from.line && from.ch >= span.to ||
7671 span.from == null && lineNo != from.line ||
7672 span.from != null && lineNo == to.line && span.from >= to.ch) &&
7673 (!filter || filter(span.marker)))
7674 found.push(span.marker.parent || span.marker);
7675 }
7676 ++lineNo;
7677 });
7678 return found;
7679 },
7680 getAllMarks: function() {
7681 var markers = [];
7682 this.iter(function(line) {
7683 var sps = line.markedSpans;
7684 if (sps) for (var i = 0; i < sps.length; ++i)
7685 if (sps[i].from != null) markers.push(sps[i].marker);
7686 });
7687 return markers;
7688 },
7689
7690 posFromIndex: function(off) {
7691 var ch, lineNo = this.first, sepSize = this.lineSeparator().length;
7692 this.iter(function(line) {
7693 var sz = line.text.length + sepSize;
7694 if (sz > off) { ch = off; return true; }
7695 off -= sz;
7696 ++lineNo;
7697 });
7698 return clipPos(this, Pos(lineNo, ch));
7699 },
7700 indexFromPos: function (coords) {
7701 coords = clipPos(this, coords);
7702 var index = coords.ch;
7703 if (coords.line < this.first || coords.ch < 0) return 0;
7704 var sepSize = this.lineSeparator().length;
7705 this.iter(this.first, coords.line, function (line) {
7706 index += line.text.length + sepSize;
7707 });
7708 return index;
7709 },
7710
7711 copy: function(copyHistory) {
7712 var doc = new Doc(getLines(this, this.first, this.first + this.size),
7713 this.modeOption, this.first, this.lineSep);
7714 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
7715 doc.sel = this.sel;
7716 doc.extend = false;
7717 if (copyHistory) {
7718 doc.history.undoDepth = this.history.undoDepth;
7719 doc.setHistory(this.getHistory());
7720 }
7721 return doc;
7722 },
7723
7724 linkedDoc: function(options) {
7725 if (!options) options = {};
7726 var from = this.first, to = this.first + this.size;
7727 if (options.from != null && options.from > from) from = options.from;
7728 if (options.to != null && options.to < to) to = options.to;
7729 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOpti on, from, this.lineSep);
7730 if (options.sharedHist) copy.history = this.history;
7731 (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.s haredHist});
7732 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist} ];
7733 copySharedMarkers(copy, findSharedMarkers(this));
7734 return copy;
7735 },
7736 unlinkDoc: function(other) {
7737 if (other instanceof CodeMirror) other = other.doc;
7738 if (this.linked) for (var i = 0; i < this.linked.length; ++i) {
7739 var link = this.linked[i];
7740 if (link.doc != other) continue;
7741 this.linked.splice(i, 1);
7742 other.unlinkDoc(this);
7743 detachSharedMarkers(findSharedMarkers(this));
7744 break;
7745 }
7746 // If the histories were shared, split them again
7747 if (other.history == this.history) {
7748 var splitIds = [other.id];
7749 linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);
7750 other.history = new History(null);
7751 other.history.done = copyHistoryArray(this.history.done, splitIds);
7752 other.history.undone = copyHistoryArray(this.history.undone, splitIds);
7753 }
7754 },
7755 iterLinkedDocs: function(f) {linkedDocs(this, f);},
7756
7757 getMode: function() {return this.mode;},
7758 getEditor: function() {return this.cm;},
7759
7760 splitLines: function(str) {
7761 if (this.lineSep) return str.split(this.lineSep);
7762 return splitLinesAuto(str);
7763 },
7764 lineSeparator: function() { return this.lineSep || "\n"; }
7765 });
7766
7767 // Public alias.
7768 Doc.prototype.eachLine = Doc.prototype.iter;
7769
7770 // Set up methods on CodeMirror's prototype to redirect to the editor's docume nt.
7771 var dontDelegate = "iter insert remove copy getEditor constructor".split(" ");
7772 for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && inde xOf(dontDelegate, prop) < 0)
7773 CodeMirror.prototype[prop] = (function(method) {
7774 return function() {return method.apply(this.doc, arguments);};
7775 })(Doc.prototype[prop]);
7776
7777 eventMixin(Doc);
7778
7779 // Call f for all linked documents.
7780 function linkedDocs(doc, f, sharedHistOnly) {
7781 function propagate(doc, skip, sharedHist) {
7782 if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {
7783 var rel = doc.linked[i];
7784 if (rel.doc == skip) continue;
7785 var shared = sharedHist && rel.sharedHist;
7786 if (sharedHistOnly && !shared) continue;
7787 f(rel.doc, shared);
7788 propagate(rel.doc, doc, shared);
7789 }
7790 }
7791 propagate(doc, null, true);
7792 }
7793
7794 // Attach a document to an editor.
7795 function attachDoc(cm, doc) {
7796 if (doc.cm) throw new Error("This document is already in use.");
7797 cm.doc = doc;
7798 doc.cm = cm;
7799 estimateLineHeights(cm);
7800 loadMode(cm);
7801 if (!cm.options.lineWrapping) findMaxLine(cm);
7802 cm.options.mode = doc.modeOption;
7803 regChange(cm);
7804 }
7805
7806 // LINE UTILITIES
7807
7808 // Find the line object corresponding to the given line number.
7809 function getLine(doc, n) {
7810 n -= doc.first;
7811 if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.f irst) + " in the document.");
7812 for (var chunk = doc; !chunk.lines;) {
7813 for (var i = 0;; ++i) {
7814 var child = chunk.children[i], sz = child.chunkSize();
7815 if (n < sz) { chunk = child; break; }
7816 n -= sz;
7817 }
7818 }
7819 return chunk.lines[n];
7820 }
7821
7822 // Get the part of a document between two positions, as an array of
7823 // strings.
7824 function getBetween(doc, start, end) {
7825 var out = [], n = start.line;
7826 doc.iter(start.line, end.line + 1, function(line) {
7827 var text = line.text;
7828 if (n == end.line) text = text.slice(0, end.ch);
7829 if (n == start.line) text = text.slice(start.ch);
7830 out.push(text);
7831 ++n;
7832 });
7833 return out;
7834 }
7835 // Get the lines between from and to, as array of strings.
7836 function getLines(doc, from, to) {
7837 var out = [];
7838 doc.iter(from, to, function(line) { out.push(line.text); });
7839 return out;
7840 }
7841
7842 // Update the height of a line, propagating the height change
7843 // upwards to parent nodes.
7844 function updateLineHeight(line, height) {
7845 var diff = height - line.height;
7846 if (diff) for (var n = line; n; n = n.parent) n.height += diff;
7847 }
7848
7849 // Given a line object, find its line number by walking up through
7850 // its parent links.
7851 function lineNo(line) {
7852 if (line.parent == null) return null;
7853 var cur = line.parent, no = indexOf(cur.lines, line);
7854 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
7855 for (var i = 0;; ++i) {
7856 if (chunk.children[i] == cur) break;
7857 no += chunk.children[i].chunkSize();
7858 }
7859 }
7860 return no + cur.first;
7861 }
7862
7863 // Find the line at the given vertical position, using the height
7864 // information in the document tree.
7865 function lineAtHeight(chunk, h) {
7866 var n = chunk.first;
7867 outer: do {
7868 for (var i = 0; i < chunk.children.length; ++i) {
7869 var child = chunk.children[i], ch = child.height;
7870 if (h < ch) { chunk = child; continue outer; }
7871 h -= ch;
7872 n += child.chunkSize();
7873 }
7874 return n;
7875 } while (!chunk.lines);
7876 for (var i = 0; i < chunk.lines.length; ++i) {
7877 var line = chunk.lines[i], lh = line.height;
7878 if (h < lh) break;
7879 h -= lh;
7880 }
7881 return n + i;
7882 }
7883
7884
7885 // Find the height above the given line.
7886 function heightAtLine(lineObj) {
7887 lineObj = visualLine(lineObj);
7888
7889 var h = 0, chunk = lineObj.parent;
7890 for (var i = 0; i < chunk.lines.length; ++i) {
7891 var line = chunk.lines[i];
7892 if (line == lineObj) break;
7893 else h += line.height;
7894 }
7895 for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
7896 for (var i = 0; i < p.children.length; ++i) {
7897 var cur = p.children[i];
7898 if (cur == chunk) break;
7899 else h += cur.height;
7900 }
7901 }
7902 return h;
7903 }
7904
7905 // Get the bidi ordering for the given line (and cache it). Returns
7906 // false for lines that are fully left-to-right, and an array of
7907 // BidiSpan objects otherwise.
7908 function getOrder(line) {
7909 var order = line.order;
7910 if (order == null) order = line.order = bidiOrdering(line.text);
7911 return order;
7912 }
7913
7914 // HISTORY
7915
7916 function History(startGen) {
7917 // Arrays of change events and selections. Doing something adds an
7918 // event to done and clears undo. Undoing moves events from done
7919 // to undone, redoing moves them in the other direction.
7920 this.done = []; this.undone = [];
7921 this.undoDepth = Infinity;
7922 // Used to track when changes can be merged into a single undo
7923 // event
7924 this.lastModTime = this.lastSelTime = 0;
7925 this.lastOp = this.lastSelOp = null;
7926 this.lastOrigin = this.lastSelOrigin = null;
7927 // Used by the isClean() method
7928 this.generation = this.maxGeneration = startGen || 1;
7929 }
7930
7931 // Create a history change event from an updateDoc-style change
7932 // object.
7933 function historyChangeFromChange(doc, change) {
7934 var histChange = {from: copyPos(change.from), to: changeEnd(change), text: g etBetween(doc, change.from, change.to)};
7935 attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
7936 linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from .line, change.to.line + 1);}, true);
7937 return histChange;
7938 }
7939
7940 // Pop all selection events off the end of a history array. Stop at
7941 // a change event.
7942 function clearSelectionEvents(array) {
7943 while (array.length) {
7944 var last = lst(array);
7945 if (last.ranges) array.pop();
7946 else break;
7947 }
7948 }
7949
7950 // Find the top change event in the history. Pop off selection
7951 // events that are in the way.
7952 function lastChangeEvent(hist, force) {
7953 if (force) {
7954 clearSelectionEvents(hist.done);
7955 return lst(hist.done);
7956 } else if (hist.done.length && !lst(hist.done).ranges) {
7957 return lst(hist.done);
7958 } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {
7959 hist.done.pop();
7960 return lst(hist.done);
7961 }
7962 }
7963
7964 // Register a change in the history. Merges changes that are within
7965 // a single operation, ore are close together with an origin that
7966 // allows merging (starting with "+") into a single event.
7967 function addChangeToHistory(doc, change, selAfter, opId) {
7968 var hist = doc.history;
7969 hist.undone.length = 0;
7970 var time = +new Date, cur;
7971
7972 if ((hist.lastOp == opId ||
7973 hist.lastOrigin == change.origin && change.origin &&
7974 ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) ||
7975 change.origin.charAt(0) == "*")) &&
7976 (cur = lastChangeEvent(hist, hist.lastOp == opId))) {
7977 // Merge this change into the last event
7978 var last = lst(cur.changes);
7979 if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {
7980 // Optimized case for simple insertion -- don't want to add
7981 // new changesets for every character typed
7982 last.to = changeEnd(change);
7983 } else {
7984 // Add new sub-event
7985 cur.changes.push(historyChangeFromChange(doc, change));
7986 }
8347 } else { 7987 } else {
8348 var ranges = copyableRanges(cm) 7988 // Can not be merged, start a new event.
8349 setLastCopied({lineWise: true, text: ranges.text}) 7989 var before = lst(hist.done);
8350 if (e.type == "cut") { 7990 if (!before || !before.ranges)
8351 cm.operation(function () { 7991 pushSelectionToHistory(doc.sel, hist.done);
8352 cm.setSelections(ranges.ranges, 0, sel_dontScroll) 7992 cur = {changes: [historyChangeFromChange(doc, change)],
8353 cm.replaceSelection("", null, "cut") 7993 generation: hist.generation};
8354 }) 7994 hist.done.push(cur);
8355 } 7995 while (hist.done.length > hist.undoDepth) {
8356 } 7996 hist.done.shift();
8357 if (e.clipboardData) { 7997 if (!hist.done[0].ranges) hist.done.shift();
8358 e.clipboardData.clearData() 7998 }
8359 var content = lastCopied.text.join("\n") 7999 }
8360 // iOS exposes the clipboard API, but seems to discard content inserted in to it 8000 hist.done.push(selAfter);
8361 e.clipboardData.setData("Text", content) 8001 hist.generation = ++hist.maxGeneration;
8362 if (e.clipboardData.getData("Text") == content) { 8002 hist.lastModTime = hist.lastSelTime = time;
8363 e.preventDefault() 8003 hist.lastOp = hist.lastSelOp = opId;
8364 return 8004 hist.lastOrigin = hist.lastSelOrigin = change.origin;
8365 } 8005
8366 } 8006 if (!last) signal(doc, "historyAdded");
8367 // Old-fashioned briefly-focus-a-textarea hack 8007 }
8368 var kludge = hiddenTextarea(), te = kludge.firstChild 8008
8369 cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild) 8009 function selectionEventCanBeMerged(doc, origin, prev, sel) {
8370 te.value = lastCopied.text.join("\n") 8010 var ch = origin.charAt(0);
8371 var hadFocus = document.activeElement 8011 return ch == "*" ||
8372 selectInput(te) 8012 ch == "+" &&
8373 setTimeout(function () { 8013 prev.ranges.length == sel.ranges.length &&
8374 cm.display.lineSpace.removeChild(kludge) 8014 prev.somethingSelected() == sel.somethingSelected() &&
8375 hadFocus.focus() 8015 new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEven tDelay : 500);
8376 if (hadFocus == div) { input.showPrimarySelection() } 8016 }
8377 }, 50) 8017
8378 } 8018 // Called whenever the selection changes, sets the new selection as
8379 on(div, "copy", onCopyCut) 8019 // the pending selection in the history, and pushes the old pending
8380 on(div, "cut", onCopyCut) 8020 // selection into the 'done' array when it was significantly
8381 }; 8021 // different (in number of selected ranges, emptiness, or time).
8382 8022 function addSelectionToHistory(doc, sel, opId, options) {
8383 ContentEditableInput.prototype.prepareSelection = function prepareSelection$1 () { 8023 var hist = doc.history, origin = options && options.origin;
8384 var result = prepareSelection(this.cm, false) 8024
8385 result.focus = this.cm.state.focused 8025 // A new event is started when the previous origin does not match
8386 return result 8026 // the current, or the origins don't allow matching. Origins
8387 }; 8027 // starting with * are always merged, those starting with + are
8388 8028 // merged when similar and close together in time.
8389 ContentEditableInput.prototype.showSelection = function showSelection (info, tak eFocus) { 8029 if (opId == hist.lastSelOp ||
8390 if (!info || !this.cm.display.view.length) { return } 8030 (origin && hist.lastSelOrigin == origin &&
8391 if (info.focus || takeFocus) { this.showPrimarySelection() } 8031 (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
8392 this.showMultipleSelections(info) 8032 selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
8393 }; 8033 hist.done[hist.done.length - 1] = sel;
8394 8034 else
8395 ContentEditableInput.prototype.showPrimarySelection = function showPrimarySelect ion () { 8035 pushSelectionToHistory(sel, hist.done);
8396 var sel = window.getSelection(), prim = this.cm.doc.sel.primary() 8036
8397 var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset) 8037 hist.lastSelTime = +new Date;
8398 var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset) 8038 hist.lastSelOrigin = origin;
8399 if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && 8039 hist.lastSelOp = opId;
8400 cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && 8040 if (options && options.clearRedo !== false)
8401 cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) 8041 clearSelectionEvents(hist.undone);
8402 { return } 8042 }
8403 8043
8404 var start = posToDOM(this.cm, prim.from()) 8044 function pushSelectionToHistory(sel, dest) {
8405 var end = posToDOM(this.cm, prim.to()) 8045 var top = lst(dest);
8406 if (!start && !end) { 8046 if (!(top && top.ranges && top.equals(sel)))
8407 sel.removeAllRanges() 8047 dest.push(sel);
8408 return 8048 }
8409 } 8049
8410 8050 // Used to store marked span information in the history.
8411 var view = this.cm.display.view 8051 function attachLocalSpans(doc, change, from, to) {
8412 var old = sel.rangeCount && sel.getRangeAt(0) 8052 var existing = change["spans_" + doc.id], n = 0;
8413 if (!start) { 8053 doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), func tion(line) {
8414 start = {node: view[0].measure.map[2], offset: 0} 8054 if (line.markedSpans)
8415 } else if (!end) { // FIXME dangerously hacky 8055 (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.mark edSpans;
8416 var measure = view[view.length - 1].measure 8056 ++n;
8417 var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map 8057 });
8418 end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.leng th - 3]} 8058 }
8419 } 8059
8420 8060 // When un/re-doing restores text containing marked spans, those
8421 var rng 8061 // that have been explicitly cleared should not be restored.
8422 try { rng = range(start.node, start.offset, end.offset, end.node) } 8062 function removeClearedSpans(spans) {
8423 catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible 8063 if (!spans) return null;
8424 if (rng) { 8064 for (var i = 0, out; i < spans.length; ++i) {
8425 if (!gecko && this.cm.state.focused) { 8065 if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i) ; }
8426 sel.collapse(start.node, start.offset) 8066 else if (out) out.push(spans[i]);
8427 if (!rng.collapsed) { 8067 }
8428 sel.removeAllRanges() 8068 return !out ? spans : out.length ? out : null;
8429 sel.addRange(rng) 8069 }
8430 } 8070
8071 // Retrieve and filter the old marked spans stored in a change event.
8072 function getOldSpans(doc, change) {
8073 var found = change["spans_" + doc.id];
8074 if (!found) return null;
8075 for (var i = 0, nw = []; i < change.text.length; ++i)
8076 nw.push(removeClearedSpans(found[i]));
8077 return nw;
8078 }
8079
8080 // Used both to provide a JSON-safe object in .getHistory, and, when
8081 // detaching a document, to split the history in two
8082 function copyHistoryArray(events, newGroup, instantiateSel) {
8083 for (var i = 0, copy = []; i < events.length; ++i) {
8084 var event = events[i];
8085 if (event.ranges) {
8086 copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : ev ent);
8087 continue;
8088 }
8089 var changes = event.changes, newChanges = [];
8090 copy.push({changes: newChanges});
8091 for (var j = 0; j < changes.length; ++j) {
8092 var change = changes[j], m;
8093 newChanges.push({from: change.from, to: change.to, text: change.text});
8094 if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$ /)) {
8095 if (indexOf(newGroup, Number(m[1])) > -1) {
8096 lst(newChanges)[prop] = change[prop];
8097 delete change[prop];
8098 }
8099 }
8100 }
8101 }
8102 return copy;
8103 }
8104
8105 // Rebasing/resetting history to deal with externally-sourced changes
8106
8107 function rebaseHistSelSingle(pos, from, to, diff) {
8108 if (to < pos.line) {
8109 pos.line += diff;
8110 } else if (from < pos.line) {
8111 pos.line = from;
8112 pos.ch = 0;
8113 }
8114 }
8115
8116 // Tries to rebase an array of history events given a change in the
8117 // document. If the change touches the same lines as the event, the
8118 // event, and everything 'behind' it, is discarded. If the change is
8119 // before the event, the event's positions are updated. Uses a
8120 // copy-on-write scheme for the positions, to avoid having to
8121 // reallocate them all on every rebase, but also avoid problems with
8122 // shared position objects being unsafely updated.
8123 function rebaseHistArray(array, from, to, diff) {
8124 for (var i = 0; i < array.length; ++i) {
8125 var sub = array[i], ok = true;
8126 if (sub.ranges) {
8127 if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; }
8128 for (var j = 0; j < sub.ranges.length; j++) {
8129 rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff);
8130 rebaseHistSelSingle(sub.ranges[j].head, from, to, diff);
8131 }
8132 continue;
8133 }
8134 for (var j = 0; j < sub.changes.length; ++j) {
8135 var cur = sub.changes[j];
8136 if (to < cur.from.line) {
8137 cur.from = Pos(cur.from.line + diff, cur.from.ch);
8138 cur.to = Pos(cur.to.line + diff, cur.to.ch);
8139 } else if (from <= cur.to.line) {
8140 ok = false;
8141 break;
8142 }
8143 }
8144 if (!ok) {
8145 array.splice(0, i + 1);
8146 i = 0;
8147 }
8148 }
8149 }
8150
8151 function rebaseHist(hist, change) {
8152 var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;
8153 rebaseHistArray(hist.done, from, to, diff);
8154 rebaseHistArray(hist.undone, from, to, diff);
8155 }
8156
8157 // EVENT UTILITIES
8158
8159 // Due to the fact that we still support jurassic IE versions, some
8160 // compatibility wrappers are needed.
8161
8162 var e_preventDefault = CodeMirror.e_preventDefault = function(e) {
8163 if (e.preventDefault) e.preventDefault();
8164 else e.returnValue = false;
8165 };
8166 var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) {
8167 if (e.stopPropagation) e.stopPropagation();
8168 else e.cancelBubble = true;
8169 };
8170 function e_defaultPrevented(e) {
8171 return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == fa lse;
8172 }
8173 var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropa gation(e);};
8174
8175 function e_target(e) {return e.target || e.srcElement;}
8176 function e_button(e) {
8177 var b = e.which;
8178 if (b == null) {
8179 if (e.button & 1) b = 1;
8180 else if (e.button & 2) b = 3;
8181 else if (e.button & 4) b = 2;
8182 }
8183 if (mac && e.ctrlKey && b == 1) b = 3;
8184 return b;
8185 }
8186
8187 // EVENT HANDLING
8188
8189 // Lightweight event framework. on/off also work on DOM nodes,
8190 // registering native DOM handlers.
8191
8192 var on = CodeMirror.on = function(emitter, type, f) {
8193 if (emitter.addEventListener)
8194 emitter.addEventListener(type, f, false);
8195 else if (emitter.attachEvent)
8196 emitter.attachEvent("on" + type, f);
8197 else {
8198 var map = emitter._handlers || (emitter._handlers = {});
8199 var arr = map[type] || (map[type] = []);
8200 arr.push(f);
8201 }
8202 };
8203
8204 var noHandlers = []
8205 function getHandlers(emitter, type, copy) {
8206 var arr = emitter._handlers && emitter._handlers[type]
8207 if (copy) return arr && arr.length > 0 ? arr.slice() : noHandlers
8208 else return arr || noHandlers
8209 }
8210
8211 var off = CodeMirror.off = function(emitter, type, f) {
8212 if (emitter.removeEventListener)
8213 emitter.removeEventListener(type, f, false);
8214 else if (emitter.detachEvent)
8215 emitter.detachEvent("on" + type, f);
8216 else {
8217 var handlers = getHandlers(emitter, type, false)
8218 for (var i = 0; i < handlers.length; ++i)
8219 if (handlers[i] == f) { handlers.splice(i, 1); break; }
8220 }
8221 };
8222
8223 var signal = CodeMirror.signal = function(emitter, type /*, values...*/) {
8224 var handlers = getHandlers(emitter, type, true)
8225 if (!handlers.length) return;
8226 var args = Array.prototype.slice.call(arguments, 2);
8227 for (var i = 0; i < handlers.length; ++i) handlers[i].apply(null, args);
8228 };
8229
8230 var orphanDelayedCallbacks = null;
8231
8232 // Often, we want to signal events at a point where we are in the
8233 // middle of some work, but don't want the handler to start calling
8234 // other methods on the editor, which might be in an inconsistent
8235 // state or simply not expect any other events to happen.
8236 // signalLater looks whether there are any handlers, and schedules
8237 // them to be executed when the last operation ends, or, if no
8238 // operation is active, when a timeout fires.
8239 function signalLater(emitter, type /*, values...*/) {
8240 var arr = getHandlers(emitter, type, false)
8241 if (!arr.length) return;
8242 var args = Array.prototype.slice.call(arguments, 2), list;
8243 if (operationGroup) {
8244 list = operationGroup.delayedCallbacks;
8245 } else if (orphanDelayedCallbacks) {
8246 list = orphanDelayedCallbacks;
8431 } else { 8247 } else {
8432 sel.removeAllRanges() 8248 list = orphanDelayedCallbacks = [];
8433 sel.addRange(rng) 8249 setTimeout(fireOrphanDelayed, 0);
8434 } 8250 }
8435 if (old && sel.anchorNode == null) { sel.addRange(old) } 8251 function bnd(f) {return function(){f.apply(null, args);};};
8436 else if (gecko) { this.startGracePeriod() } 8252 for (var i = 0; i < arr.length; ++i)
8437 } 8253 list.push(bnd(arr[i]));
8438 this.rememberSelection() 8254 }
8439 }; 8255
8440 8256 function fireOrphanDelayed() {
8441 ContentEditableInput.prototype.startGracePeriod = function startGracePeriod () { 8257 var delayed = orphanDelayedCallbacks;
8442 var this$1 = this; 8258 orphanDelayedCallbacks = null;
8443 8259 for (var i = 0; i < delayed.length; ++i) delayed[i]();
8444 clearTimeout(this.gracePeriod) 8260 }
8445 this.gracePeriod = setTimeout(function () { 8261
8446 this$1.gracePeriod = false 8262 // The DOM events that CodeMirror handles can be overridden by
8447 if (this$1.selectionChanged()) 8263 // registering a (non-DOM) handler on the editor for the event name,
8448 { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChange d = true; }) } 8264 // and preventDefault-ing the event in that handler.
8449 }, 20) 8265 function signalDOMEvent(cm, e, override) {
8450 }; 8266 if (typeof e == "string")
8451 8267 e = {type: e, preventDefault: function() { this.defaultPrevented = true; } };
8452 ContentEditableInput.prototype.showMultipleSelections = function showMultipleSel ections (info) { 8268 signal(cm, override || e.type, cm, e);
8453 removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors) 8269 return e_defaultPrevented(e) || e.codemirrorIgnore;
8454 removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection) 8270 }
8455 }; 8271
8456 8272 function signalCursorActivity(cm) {
8457 ContentEditableInput.prototype.rememberSelection = function rememberSelection () { 8273 var arr = cm._handlers && cm._handlers.cursorActivity;
8458 var sel = window.getSelection() 8274 if (!arr) return;
8459 this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset 8275 var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandler s = []);
8460 this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset 8276 for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1)
8461 }; 8277 set.push(arr[i]);
8462 8278 }
8463 ContentEditableInput.prototype.selectionInEditor = function selectionInEditor () { 8279
8464 var sel = window.getSelection() 8280 function hasHandler(emitter, type) {
8465 if (!sel.rangeCount) { return false } 8281 return getHandlers(emitter, type).length > 0
8466 var node = sel.getRangeAt(0).commonAncestorContainer 8282 }
8467 return contains(this.div, node) 8283
8468 }; 8284 // Add on and off methods to a constructor's prototype, to make
8469 8285 // registering events on such objects more convenient.
8470 ContentEditableInput.prototype.focus = function focus () { 8286 function eventMixin(ctor) {
8471 if (this.cm.options.readOnly != "nocursor") { 8287 ctor.prototype.on = function(type, f) {on(this, type, f);};
8472 if (!this.selectionInEditor()) 8288 ctor.prototype.off = function(type, f) {off(this, type, f);};
8473 { this.showSelection(this.prepareSelection(), true) } 8289 }
8474 this.div.focus() 8290
8475 } 8291 // MISC UTILITIES
8476 }; 8292
8477 ContentEditableInput.prototype.blur = function blur () { this.div.blur() }; 8293 // Number of pixels added to scroller and sizer to hide scrollbar
8478 ContentEditableInput.prototype.getField = function getField () { return this.div }; 8294 var scrollerGap = 30;
8479 8295
8480 ContentEditableInput.prototype.supportsTouch = function supportsTouch () { retur n true }; 8296 // Returned or thrown by various protocols to signal 'I'm not
8481 8297 // handling this'.
8482 ContentEditableInput.prototype.receivedFocus = function receivedFocus () { 8298 var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}} ;
8483 var input = this 8299
8484 if (this.selectionInEditor()) 8300 // Reused option objects for setSelection & friends
8485 { this.pollSelection() } 8301 var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"};
8486 else 8302
8487 { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = tr ue; }) } 8303 function Delayed() {this.id = null;}
8488 8304 Delayed.prototype.set = function(ms, f) {
8489 function poll() { 8305 clearTimeout(this.id);
8490 if (input.cm.state.focused) { 8306 this.id = setTimeout(f, ms);
8491 input.pollSelection() 8307 };
8492 input.polling.set(input.cm.options.pollInterval, poll) 8308
8493 } 8309 // Counts the column offset in a string, taking tabs into account.
8494 } 8310 // Used mostly to find indentation.
8495 this.polling.set(this.cm.options.pollInterval, poll) 8311 var countColumn = CodeMirror.countColumn = function(string, end, tabSize, star tIndex, startValue) {
8496 }; 8312 if (end == null) {
8497 8313 end = string.search(/[^\s\u00a0]/);
8498 ContentEditableInput.prototype.selectionChanged = function selectionChanged () { 8314 if (end == -1) end = string.length;
8499 var sel = window.getSelection() 8315 }
8500 return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastA nchorOffset || 8316 for (var i = startIndex || 0, n = startValue || 0;;) {
8501 sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffs et 8317 var nextTab = string.indexOf("\t", i);
8502 }; 8318 if (nextTab < 0 || nextTab >= end)
8503 8319 return n + (end - i);
8504 ContentEditableInput.prototype.pollSelection = function pollSelection () { 8320 n += nextTab - i;
8505 if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged( )) { return } 8321 n += tabSize - (n % tabSize);
8506 var sel = window.getSelection(), cm = this.cm 8322 i = nextTab + 1;
8507 // On Android Chrome (version 56, at least), backspacing into an 8323 }
8508 // uneditable block element will put the cursor in that element, 8324 };
8509 // and then, because it's not editable, hide the virtual keyboard. 8325
8510 // Because Android doesn't allow us to actually detect backspace 8326 // The inverse of countColumn -- find the offset that corresponds to
8511 // presses in a sane way, this code checks for when that happens 8327 // a particular column.
8512 // and simulates a backspace press in this case. 8328 var findColumn = CodeMirror.findColumn = function(string, goal, tabSize) {
8513 if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anch orNode)) { 8329 for (var pos = 0, col = 0;;) {
8514 this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math. abs}) 8330 var nextTab = string.indexOf("\t", pos);
8515 this.blur() 8331 if (nextTab == -1) nextTab = string.length;
8516 this.focus() 8332 var skipped = nextTab - pos;
8517 return 8333 if (nextTab == string.length || col + skipped >= goal)
8518 } 8334 return pos + Math.min(skipped, goal - col);
8519 if (this.composing) { return } 8335 col += nextTab - pos;
8520 this.rememberSelection() 8336 col += tabSize - (col % tabSize);
8521 var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) 8337 pos = nextTab + 1;
8522 var head = domToPos(cm, sel.focusNode, sel.focusOffset) 8338 if (col >= goal) return pos;
8523 if (anchor && head) { runInOp(cm, function () { 8339 }
8524 setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll) 8340 }
8525 if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true } 8341
8526 }) } 8342 var spaceStrs = [""];
8527 }; 8343 function spaceStr(n) {
8528 8344 while (spaceStrs.length <= n)
8529 ContentEditableInput.prototype.pollContent = function pollContent () { 8345 spaceStrs.push(lst(spaceStrs) + " ");
8530 if (this.readDOMTimeout != null) { 8346 return spaceStrs[n];
8531 clearTimeout(this.readDOMTimeout) 8347 }
8532 this.readDOMTimeout = null 8348
8533 } 8349 function lst(arr) { return arr[arr.length-1]; }
8534 8350
8535 var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary() 8351 var selectInput = function(node) { node.select(); };
8536 var from = sel.from(), to = sel.to() 8352 if (ios) // Mobile Safari apparently has a bug where select() is broken.
8537 if (from.ch == 0 && from.line > cm.firstLine()) 8353 selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; };
8538 { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) } 8354 else if (ie) // Suppress mysterious IE10 errors
8539 if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) 8355 selectInput = function(node) { try { node.select(); } catch(_e) {} };
8540 { to = Pos(to.line + 1, 0) } 8356
8541 if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return fal se } 8357 function indexOf(array, elt) {
8542 8358 for (var i = 0; i < array.length; ++i)
8543 var fromIndex, fromLine, fromNode 8359 if (array[i] == elt) return i;
8544 if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line) ) == 0) { 8360 return -1;
8545 fromLine = lineNo(display.view[0].line) 8361 }
8546 fromNode = display.view[0].node 8362 function map(array, f) {
8547 } else { 8363 var out = [];
8548 fromLine = lineNo(display.view[fromIndex].line) 8364 for (var i = 0; i < array.length; i++) out[i] = f(array[i], i);
8549 fromNode = display.view[fromIndex - 1].node.nextSibling 8365 return out;
8550 } 8366 }
8551 var toIndex = findViewIndex(cm, to.line) 8367
8552 var toLine, toNode 8368 function nothing() {}
8553 if (toIndex == display.view.length - 1) { 8369
8554 toLine = display.viewTo - 1 8370 function createObj(base, props) {
8555 toNode = display.lineDiv.lastChild 8371 var inst;
8556 } else { 8372 if (Object.create) {
8557 toLine = lineNo(display.view[toIndex + 1].line) - 1 8373 inst = Object.create(base);
8558 toNode = display.view[toIndex + 1].node.previousSibling 8374 } else {
8559 } 8375 nothing.prototype = base;
8560 8376 inst = new nothing();
8561 if (!fromNode) { return false } 8377 }
8562 var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)) 8378 if (props) copyObj(props, inst);
8563 var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)) 8379 return inst;
8564 while (newText.length > 1 && oldText.length > 1) { 8380 };
8565 if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- } 8381
8566 else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromL ine++ } 8382 function copyObj(obj, target, overwrite) {
8567 else { break } 8383 if (!target) target = {};
8568 } 8384 for (var prop in obj)
8569 8385 if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProp erty(prop)))
8570 var cutFront = 0, cutEnd = 0 8386 target[prop] = obj[prop];
8571 var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.le ngth, oldTop.length) 8387 return target;
8572 while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCod eAt(cutFront)) 8388 }
8573 { ++cutFront } 8389
8574 var newBot = lst(newText), oldBot = lst(oldText) 8390 function bind(f) {
8575 var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), 8391 var args = Array.prototype.slice.call(arguments, 1);
8576 oldBot.length - (oldText.length == 1 ? cutFront : 0)) 8392 return function(){return f.apply(null, args);};
8577 while (cutEnd < maxCutEnd && 8393 }
8578 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldB ot.length - cutEnd - 1)) 8394
8579 { ++cutEnd } 8395 var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u304 0-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
8580 // Try to move start of change to start of selection if ambiguous 8396 var isWordCharBasic = CodeMirror.isWordChar = function(ch) {
8581 if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { 8397 return /\w/.test(ch) || ch > "\x80" &&
8582 while (cutFront && cutFront > from.ch && 8398 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(c h));
8583 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(ol dBot.length - cutEnd - 1)) { 8399 };
8584 cutFront-- 8400 function isWordChar(ch, helper) {
8585 cutEnd++ 8401 if (!helper) return isWordCharBasic(ch);
8586 } 8402 if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true;
8587 } 8403 return helper.test(ch);
8588 8404 }
8589 newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace( /^\u200b+/, "") 8405
8590 newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "") 8406 function isEmpty(obj) {
8591 8407 for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
8592 var chFrom = Pos(fromLine, cutFront) 8408 return true;
8593 var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0) 8409 }
8594 if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { 8410
8595 replaceRange(cm.doc, newText, chFrom, chTo, "+input") 8411 // Extending unicode characters. A series of a non-extending char +
8596 return true 8412 // any number of extending chars is treated as a single unit as far
8597 } 8413 // as editing and measuring is concerned. This is not fully correct,
8598 }; 8414 // since some scripts/fonts/browsers also treat other configurations
8599 8415 // of code points as a group.
8600 ContentEditableInput.prototype.ensurePolled = function ensurePolled () { 8416 var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05 c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u 06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u081 9\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u 0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u 0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0 a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b 3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3 e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0c c6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d6 3\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0e b4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0 f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037 \u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086 \u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17b d\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u19 39-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u 1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2- \u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1c e8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\ u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b 3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab 2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe0 0-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;
8601 this.forceCompositionEnd() 8417 function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChar s.test(ch); }
8602 }; 8418
8603 ContentEditableInput.prototype.reset = function reset () { 8419 // DOM UTILITIES
8604 this.forceCompositionEnd() 8420
8605 }; 8421 function elt(tag, content, className, style) {
8606 ContentEditableInput.prototype.forceCompositionEnd = function forceCompositionEn d () { 8422 var e = document.createElement(tag);
8607 if (!this.composing) { return } 8423 if (className) e.className = className;
8608 clearTimeout(this.readDOMTimeout) 8424 if (style) e.style.cssText = style;
8609 this.composing = null 8425 if (typeof content == "string") e.appendChild(document.createTextNode(conten t));
8610 this.updateFromDOM() 8426 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(con tent[i]);
8611 this.div.blur() 8427 return e;
8612 this.div.focus() 8428 }
8613 }; 8429
8614 ContentEditableInput.prototype.readFromDOMSoon = function readFromDOMSoon () { 8430 var range;
8615 var this$1 = this; 8431 if (document.createRange) range = function(node, start, end, endNode) {
8616 8432 var r = document.createRange();
8617 if (this.readDOMTimeout != null) { return } 8433 r.setEnd(endNode || node, end);
8618 this.readDOMTimeout = setTimeout(function () { 8434 r.setStart(node, start);
8619 this$1.readDOMTimeout = null 8435 return r;
8620 if (this$1.composing) { 8436 };
8621 if (this$1.composing.done) { this$1.composing = null } 8437 else range = function(node, start, end) {
8622 else { return } 8438 var r = document.body.createTextRange();
8623 } 8439 try { r.moveToElementText(node.parentNode); }
8624 this$1.updateFromDOM() 8440 catch(e) { return r; }
8625 }, 80) 8441 r.collapse(true);
8626 }; 8442 r.moveEnd("character", end);
8627 8443 r.moveStart("character", start);
8628 ContentEditableInput.prototype.updateFromDOM = function updateFromDOM () { 8444 return r;
8629 var this$1 = this; 8445 };
8630 8446
8631 if (this.cm.isReadOnly() || !this.pollContent()) 8447 function removeChildren(e) {
8632 { runInOp(this.cm, function () { return regChange(this$1.cm); }) } 8448 for (var count = e.childNodes.length; count > 0; --count)
8633 }; 8449 e.removeChild(e.firstChild);
8634 8450 return e;
8635 ContentEditableInput.prototype.setUneditable = function setUneditable (node) { 8451 }
8636 node.contentEditable = "false" 8452
8637 }; 8453 function removeChildrenAndAdd(parent, e) {
8638 8454 return removeChildren(parent).appendChild(e);
8639 ContentEditableInput.prototype.onKeyPress = function onKeyPress (e) { 8455 }
8640 if (e.charCode == 0) { return } 8456
8641 e.preventDefault() 8457 var contains = CodeMirror.contains = function(parent, child) {
8642 if (!this.cm.isReadOnly()) 8458 if (child.nodeType == 3) // Android browser always returns false when child is a textnode
8643 { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0) } 8459 child = child.parentNode;
8644 }; 8460 if (parent.contains)
8645 8461 return parent.contains(child);
8646 ContentEditableInput.prototype.readOnlyChanged = function readOnlyChanged (val) { 8462 do {
8647 this.div.contentEditable = String(val != "nocursor") 8463 if (child.nodeType == 11) child = child.host;
8648 }; 8464 if (child == parent) return true;
8649 8465 } while (child = child.parentNode);
8650 ContentEditableInput.prototype.onContextMenu = function onContextMenu () {}; 8466 };
8651 ContentEditableInput.prototype.resetPosition = function resetPosition () {}; 8467
8652 8468 function activeElt() {
8653 ContentEditableInput.prototype.needsContentAttribute = true 8469 var activeElement = document.activeElement;
8654 8470 while (activeElement && activeElement.root && activeElement.root.activeEleme nt)
8655 function posToDOM(cm, pos) { 8471 activeElement = activeElement.root.activeElement;
8656 var view = findViewForLine(cm, pos.line) 8472 return activeElement;
8657 if (!view || view.hidden) { return null } 8473 }
8658 var line = getLine(cm.doc, pos.line) 8474 // Older versions of IE throws unspecified error when touching
8659 var info = mapFromLineView(view, line, pos.line) 8475 // document.activeElement in some cases (during loading, in iframe)
8660 8476 if (ie && ie_version < 11) activeElt = function() {
8661 var order = getOrder(line, cm.doc.direction), side = "left" 8477 try { return document.activeElement; }
8662 if (order) { 8478 catch(e) { return document.body; }
8663 var partPos = getBidiPartAt(order, pos.ch) 8479 };
8664 side = partPos % 2 ? "right" : "left" 8480
8665 } 8481 function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") ; }
8666 var result = nodeAndOffsetInLineMap(info.map, pos.ch, side) 8482 var rmClass = CodeMirror.rmClass = function(node, cls) {
8667 result.offset = result.collapse == "right" ? result.end : result.start 8483 var current = node.className;
8668 return result 8484 var match = classTest(cls).exec(current);
8669 } 8485 if (match) {
8670 8486 var after = current.slice(match.index + match[0].length);
8671 function isInGutter(node) { 8487 node.className = current.slice(0, match.index) + (after ? match[1] + after : "");
8672 for (var scan = node; scan; scan = scan.parentNode) 8488 }
8673 { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } } 8489 };
8674 return false 8490 var addClass = CodeMirror.addClass = function(node, cls) {
8675 } 8491 var current = node.className;
8676 8492 if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls;
8677 function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } 8493 };
8678 8494 function joinClasses(a, b) {
8679 function domTextBetween(cm, from, to, fromLine, toLine) { 8495 var as = a.split(" ");
8680 var text = "", closing = false, lineSep = cm.doc.lineSeparator() 8496 for (var i = 0; i < as.length; i++)
8681 function recognizeMarker(id) { return function (marker) { return marker.id == id; } } 8497 if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i];
8682 function close() { 8498 return b;
8683 if (closing) { 8499 }
8684 text += lineSep 8500
8685 closing = false 8501 // WINDOW-WIDE EVENTS
8686 } 8502
8687 } 8503 // These must be handled carefully, because naively registering a
8688 function addText(str) { 8504 // handler for each editor will cause the editors to never be
8689 if (str) { 8505 // garbage collected.
8690 close() 8506
8691 text += str 8507 function forEachCodeMirror(f) {
8692 } 8508 if (!document.body.getElementsByClassName) return;
8693 } 8509 var byClass = document.body.getElementsByClassName("CodeMirror");
8694 function walk(node) { 8510 for (var i = 0; i < byClass.length; i++) {
8695 if (node.nodeType == 1) { 8511 var cm = byClass[i].CodeMirror;
8696 var cmText = node.getAttribute("cm-text") 8512 if (cm) f(cm);
8697 if (cmText != null) { 8513 }
8698 addText(cmText || node.textContent.replace(/\u200b/g, "")) 8514 }
8699 return 8515
8700 } 8516 var globalsRegistered = false;
8701 var markerID = node.getAttribute("cm-marker"), range 8517 function ensureGlobalHandlers() {
8702 if (markerID) { 8518 if (globalsRegistered) return;
8703 var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognize Marker(+markerID)) 8519 registerGlobalHandlers();
8704 if (found.length && (range = found[0].find())) 8520 globalsRegistered = true;
8705 { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)) } 8521 }
8706 return 8522 function registerGlobalHandlers() {
8707 } 8523 // When the window resizes, we need to refresh active editors.
8708 if (node.getAttribute("contenteditable") == "false") { return } 8524 var resizeTimer;
8709 var isBlock = /^(pre|div|p)$/i.test(node.nodeName) 8525 on(window, "resize", function() {
8710 if (isBlock) { close() } 8526 if (resizeTimer == null) resizeTimer = setTimeout(function() {
8711 for (var i = 0; i < node.childNodes.length; i++) 8527 resizeTimer = null;
8712 { walk(node.childNodes[i]) } 8528 forEachCodeMirror(onResize);
8713 if (isBlock) { closing = true } 8529 }, 100);
8714 } else if (node.nodeType == 3) { 8530 });
8715 addText(node.nodeValue) 8531 // When the window loses focus, we want to show the editor as blurred
8716 } 8532 on(window, "blur", function() {
8717 } 8533 forEachCodeMirror(onBlur);
8718 for (;;) { 8534 });
8719 walk(from) 8535 }
8720 if (from == to) { break } 8536
8721 from = from.nextSibling 8537 // FEATURE DETECTION
8722 } 8538
8723 return text 8539 // Detect drag-and-drop
8724 } 8540 var dragAndDrop = function() {
8725 8541 // There is *some* kind of drag-and-drop support in IE6-8, but I
8726 function domToPos(cm, node, offset) { 8542 // couldn't get it to work yet.
8727 var lineNode 8543 if (ie && ie_version < 9) return false;
8728 if (node == cm.display.lineDiv) { 8544 var div = elt('div');
8729 lineNode = cm.display.lineDiv.childNodes[offset] 8545 return "draggable" in div || "dragDrop" in div;
8730 if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) } 8546 }();
8731 node = null; offset = 0 8547
8732 } else { 8548 var zwspSupported;
8733 for (lineNode = node;; lineNode = lineNode.parentNode) { 8549 function zeroWidthElement(measure) {
8734 if (!lineNode || lineNode == cm.display.lineDiv) { return null } 8550 if (zwspSupported == null) {
8735 if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { br eak } 8551 var test = elt("span", "\u200b");
8736 } 8552 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode(" x")]));
8737 } 8553 if (measure.firstChild.offsetHeight != 0)
8738 for (var i = 0; i < cm.display.view.length; i++) { 8554 zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie & & ie_version < 8);
8739 var lineView = cm.display.view[i] 8555 }
8740 if (lineView.node == lineNode) 8556 var node = zwspSupported ? elt("span", "\u200b") :
8741 { return locateNodeInLineView(lineView, node, offset) } 8557 elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-rig ht: -1px");
8742 } 8558 node.setAttribute("cm-text", "");
8743 } 8559 return node;
8744 8560 }
8745 function locateNodeInLineView(lineView, node, offset) { 8561
8746 var wrapper = lineView.text.firstChild, bad = false 8562 // Feature-detect IE's crummy client rect reporting for bidi text
8747 if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.lin e), 0), true) } 8563 var badBidiRects;
8748 if (node == wrapper) { 8564 function hasBadBidiRects(measure) {
8749 bad = true 8565 if (badBidiRects != null) return badBidiRects;
8750 node = wrapper.childNodes[offset] 8566 var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")) ;
8751 offset = 0 8567 var r0 = range(txt, 0, 1).getBoundingClientRect();
8752 if (!node) { 8568 var r1 = range(txt, 1, 2).getBoundingClientRect();
8753 var line = lineView.rest ? lst(lineView.rest) : lineView.line 8569 removeChildren(measure);
8754 return badPos(Pos(lineNo(line), line.text.length), bad) 8570 if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780)
8755 } 8571 return badBidiRects = (r1.right - r0.right < 3);
8756 } 8572 }
8757 8573
8758 var textNode = node.nodeType == 3 ? node : null, topNode = node 8574 // See if "".split is the broken IE version, if so, provide an
8759 if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { 8575 // alternative way to split lines.
8760 textNode = node.firstChild 8576 var splitLinesAuto = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
8761 if (offset) { offset = textNode.nodeValue.length } 8577 var pos = 0, result = [], l = string.length;
8762 } 8578 while (pos <= l) {
8763 while (topNode.parentNode != wrapper) { topNode = topNode.parentNode } 8579 var nl = string.indexOf("\n", pos);
8764 var measure = lineView.measure, maps = measure.maps 8580 if (nl == -1) nl = string.length;
8765 8581 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
8766 function find(textNode, topNode, offset) { 8582 var rt = line.indexOf("\r");
8767 for (var i = -1; i < (maps ? maps.length : 0); i++) { 8583 if (rt != -1) {
8768 var map = i < 0 ? measure.map : maps[i] 8584 result.push(line.slice(0, rt));
8769 for (var j = 0; j < map.length; j += 3) { 8585 pos += rt + 1;
8770 var curNode = map[j + 2] 8586 } else {
8771 if (curNode == textNode || curNode == topNode) { 8587 result.push(line);
8772 var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]) 8588 pos = nl + 1;
8773 var ch = map[j] + offset 8589 }
8774 if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0) ] } 8590 }
8775 return Pos(line, ch) 8591 return result;
8592 } : function(string){return string.split(/\r\n?|\n/);};
8593
8594 var hasSelection = window.getSelection ? function(te) {
8595 try { return te.selectionStart != te.selectionEnd; }
8596 catch(e) { return false; }
8597 } : function(te) {
8598 try {var range = te.ownerDocument.selection.createRange();}
8599 catch(e) {}
8600 if (!range || range.parentElement() != te) return false;
8601 return range.compareEndPoints("StartToEnd", range) != 0;
8602 };
8603
8604 var hasCopyEvent = (function() {
8605 var e = elt("div");
8606 if ("oncopy" in e) return true;
8607 e.setAttribute("oncopy", "return;");
8608 return typeof e.oncopy == "function";
8609 })();
8610
8611 var badZoomedRects = null;
8612 function hasBadZoomedRects(measure) {
8613 if (badZoomedRects != null) return badZoomedRects;
8614 var node = removeChildrenAndAdd(measure, elt("span", "x"));
8615 var normal = node.getBoundingClientRect();
8616 var fromRange = range(node, 0, 1).getBoundingClientRect();
8617 return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1;
8618 }
8619
8620 // KEY NAMES
8621
8622 var keyNames = CodeMirror.keyNames = {
8623 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
8624 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "Page Down", 35: "End",
8625 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
8626 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod",
8627 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete",
8628 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: " `", 219: "[", 220: "\\",
8629 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right ", 63272: "Delete",
8630 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Ins ert"
8631 };
8632 (function() {
8633 // Number keys
8634 for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i) ;
8635 // Alphabetic keys
8636 for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
8637 // Function keys
8638 for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
8639 })();
8640
8641 // BIDI HELPERS
8642
8643 function iterateBidiSections(order, from, to, f) {
8644 if (!order) return f(from, to, "ltr");
8645 var found = false;
8646 for (var i = 0; i < order.length; ++i) {
8647 var part = order[i];
8648 if (part.from < to && part.to > from || from == to && part.to == from) {
8649 f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "r tl" : "ltr");
8650 found = true;
8651 }
8652 }
8653 if (!found) f(from, to, "ltr");
8654 }
8655
8656 function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
8657 function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
8658
8659 function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft( order[0]) : 0; }
8660 function lineRight(line) {
8661 var order = getOrder(line);
8662 if (!order) return line.text.length;
8663 return bidiRight(lst(order));
8664 }
8665
8666 function lineStart(cm, lineN) {
8667 var line = getLine(cm.doc, lineN);
8668 var visual = visualLine(line);
8669 if (visual != line) lineN = lineNo(visual);
8670 var order = getOrder(visual);
8671 var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visu al);
8672 return Pos(lineN, ch);
8673 }
8674 function lineEnd(cm, lineN) {
8675 var merged, line = getLine(cm.doc, lineN);
8676 while (merged = collapsedSpanAtEnd(line)) {
8677 line = merged.find(1, true).line;
8678 lineN = null;
8679 }
8680 var order = getOrder(line);
8681 var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : l ineRight(line);
8682 return Pos(lineN == null ? lineNo(line) : lineN, ch);
8683 }
8684 function lineStartSmart(cm, pos) {
8685 var start = lineStart(cm, pos.line);
8686 var line = getLine(cm.doc, start.line);
8687 var order = getOrder(line);
8688 if (!order || order[0].level == 0) {
8689 var firstNonWS = Math.max(0, line.text.search(/\S/));
8690 var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;
8691 return Pos(start.line, inWS ? 0 : firstNonWS);
8692 }
8693 return start;
8694 }
8695
8696 function compareBidiLevel(order, a, b) {
8697 var linedir = order[0].level;
8698 if (a == linedir) return true;
8699 if (b == linedir) return false;
8700 return a < b;
8701 }
8702 var bidiOther;
8703 function getBidiPartAt(order, pos) {
8704 bidiOther = null;
8705 for (var i = 0, found; i < order.length; ++i) {
8706 var cur = order[i];
8707 if (cur.from < pos && cur.to > pos) return i;
8708 if ((cur.from == pos || cur.to == pos)) {
8709 if (found == null) {
8710 found = i;
8711 } else if (compareBidiLevel(order, cur.level, order[found].level)) {
8712 if (cur.from != cur.to) bidiOther = found;
8713 return i;
8714 } else {
8715 if (cur.from != cur.to) bidiOther = i;
8716 return found;
8776 } 8717 }
8777 } 8718 }
8778 } 8719 }
8779 } 8720 return found;
8780 var found = find(textNode, topNode, offset) 8721 }
8781 if (found) { return badPos(found, bad) } 8722
8782 8723 function moveInLine(line, pos, dir, byUnit) {
8783 // FIXME this is all really shaky. might handle the few cases it needs to hand le, but likely to cause problems 8724 if (!byUnit) return pos + dir;
8784 for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.len gth - offset : 0; after; after = after.nextSibling) { 8725 do pos += dir;
8785 found = find(after, after.firstChild, 0) 8726 while (pos > 0 && isExtendingChar(line.text.charAt(pos)));
8786 if (found) 8727 return pos;
8787 { return badPos(Pos(found.line, found.ch - dist), bad) } 8728 }
8788 else 8729
8789 { dist += after.textContent.length } 8730 // This is needed in order to move 'visually' through bi-directional
8790 } 8731 // text -- i.e., pressing left should make the cursor go left, even
8791 for (var before = topNode.previousSibling, dist$1 = offset; before; before = b efore.previousSibling) { 8732 // when in RTL text. The tricky part is the 'jumps', where RTL and
8792 found = find(before, before.firstChild, -1) 8733 // LTR text touch each other. This often requires the cursor offset
8793 if (found) 8734 // to move more than one unit, in order to visually move one unit.
8794 { return badPos(Pos(found.line, found.ch + dist$1), bad) } 8735 function moveVisually(line, start, dir, byUnit) {
8795 else 8736 var bidi = getOrder(line);
8796 { dist$1 += before.textContent.length } 8737 if (!bidi) return moveLogically(line, start, dir, byUnit);
8797 } 8738 var pos = getBidiPartAt(bidi, start), part = bidi[pos];
8798 } 8739 var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit);
8799 8740
8800 // TEXTAREA INPUT STYLE 8741 for (;;) {
8801 8742 if (target > part.from && target < part.to) return target;
8802 var TextareaInput = function TextareaInput(cm) { 8743 if (target == part.from || target == part.to) {
8803 this.cm = cm 8744 if (getBidiPartAt(bidi, target) == pos) return target;
8804 // See input.poll and input.reset 8745 part = bidi[pos += dir];
8805 this.prevInput = "" 8746 return (dir > 0) == part.level % 2 ? part.to : part.from;
8806
8807 // Flag that indicates whether we expect input to appear real soon
8808 // now (after some event like 'keypress' or 'input') and are
8809 // polling intensively.
8810 this.pollingFast = false
8811 // Self-resetting timeout for the poller
8812 this.polling = new Delayed()
8813 // Tracks when input.reset has punted to just putting a short
8814 // string into the textarea instead of the full selection.
8815 this.inaccurateSelection = false
8816 // Used to work around IE issue with selection being forgotten when focus move s away from textarea
8817 this.hasSelection = false
8818 this.composing = null
8819 };
8820
8821 TextareaInput.prototype.init = function init (display) {
8822 var this$1 = this;
8823
8824 var input = this, cm = this.cm
8825
8826 // Wraps and hides input textarea
8827 var div = this.wrapper = hiddenTextarea()
8828 // The semihidden textarea that is focused when the editor is
8829 // focused, and receives input.
8830 var te = this.textarea = div.firstChild
8831 display.wrapper.insertBefore(div, display.wrapper.firstChild)
8832
8833 // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to w ork in iOS 8 anymore)
8834 if (ios) { te.style.width = "0px" }
8835
8836 on(te, "input", function () {
8837 if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = nu ll }
8838 input.poll()
8839 })
8840
8841 on(te, "paste", function (e) {
8842 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }
8843
8844 cm.state.pasteIncoming = true
8845 input.fastPoll()
8846 })
8847
8848 function prepareCopyCut(e) {
8849 if (signalDOMEvent(cm, e)) { return }
8850 if (cm.somethingSelected()) {
8851 setLastCopied({lineWise: false, text: cm.getSelections()})
8852 if (input.inaccurateSelection) {
8853 input.prevInput = ""
8854 input.inaccurateSelection = false
8855 te.value = lastCopied.text.join("\n")
8856 selectInput(te)
8857 }
8858 } else if (!cm.options.lineWiseCopyCut) {
8859 return
8860 } else {
8861 var ranges = copyableRanges(cm)
8862 setLastCopied({lineWise: true, text: ranges.text})
8863 if (e.type == "cut") {
8864 cm.setSelections(ranges.ranges, null, sel_dontScroll)
8865 } else { 8747 } else {
8866 input.prevInput = "" 8748 part = bidi[pos += dir];
8867 te.value = ranges.text.join("\n") 8749 if (!part) return null;
8868 selectInput(te) 8750 if ((dir > 0) == part.level % 2)
8869 } 8751 target = moveInLine(line, part.to, -1, byUnit);
8870 } 8752 else
8871 if (e.type == "cut") { cm.state.cutIncoming = true } 8753 target = moveInLine(line, part.from, 1, byUnit);
8872 } 8754 }
8873 on(te, "cut", prepareCopyCut) 8755 }
8874 on(te, "copy", prepareCopyCut) 8756 }
8875 8757
8876 on(display.scroller, "paste", function (e) { 8758 function moveLogically(line, start, dir, byUnit) {
8877 if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } 8759 var target = start + dir;
8878 cm.state.pasteIncoming = true 8760 if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir;
8879 input.focus() 8761 return target < 0 || target > line.text.length ? null : target;
8880 }) 8762 }
8881 8763
8882 // Prevent normal selection in the editor (we handle our own) 8764 // Bidirectional ordering algorithm
8883 on(display.lineSpace, "selectstart", function (e) { 8765 // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
8884 if (!eventInWidget(display, e)) { e_preventDefault(e) } 8766 // that this (partially) implements.
8885 }) 8767
8886 8768 // One-char codes used for character types:
8887 on(te, "compositionstart", function () { 8769 // L (L): Left-to-Right
8888 var start = cm.getCursor("from") 8770 // R (R): Right-to-Left
8889 if (input.composing) { input.composing.range.clear() } 8771 // r (AL): Right-to-Left Arabic
8890 input.composing = { 8772 // 1 (EN): European Number
8891 start: start, 8773 // + (ES): European Number Separator
8892 range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-comp osing"}) 8774 // % (ET): European Number Terminator
8893 } 8775 // n (AN): Arabic Number
8894 }) 8776 // , (CS): Common Number Separator
8895 on(te, "compositionend", function () { 8777 // m (NSM): Non-Spacing Mark
8896 if (input.composing) { 8778 // b (BN): Boundary Neutral
8897 input.poll() 8779 // s (B): Paragraph Separator
8898 input.composing.range.clear() 8780 // t (S): Segment Separator
8899 input.composing = null 8781 // w (WS): Whitespace
8900 } 8782 // N (ON): Other Neutrals
8901 }) 8783
8902 }; 8784 // Returns null if characters are ordered as they appear
8903 8785 // (left-to-right), or an array of sections ({from, to, level}
8904 TextareaInput.prototype.prepareSelection = function prepareSelection$1 () { 8786 // objects) in the order in which they occur visually.
8905 // Redraw the selection and/or cursor 8787 var bidiOrdering = (function() {
8906 var cm = this.cm, display = cm.display, doc = cm.doc 8788 // Character types for codepoints 0 to 0xff
8907 var result = prepareSelection(cm) 8789 var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NN NNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbb bbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLL LLLLLLLLLLLLLLLLLLLLLLLLLLLN";
8908 8790 // Character types for codepoints 0x600 to 0x6ff
8909 // Move the hidden textarea near the cursor to prevent scrolling artifacts 8791 var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmm mmmmmmmmmmmmmmmmNmmmm";
8910 if (cm.options.moveInputWithCursor) { 8792 function charType(code) {
8911 var headPos = cursorCoords(cm, doc.sel.primary().head, "div") 8793 if (code <= 0xf7) return lowTypes.charAt(code);
8912 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lin eDiv.getBoundingClientRect() 8794 else if (0x590 <= code && code <= 0x5f4) return "R";
8913 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, 8795 else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600);
8914 headPos.top + lineOff.top - wrapOff.top) ) 8796 else if (0x6ee <= code && code <= 0x8ac) return "r";
8915 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, 8797 else if (0x2000 <= code && code <= 0x200b) return "w";
8916 headPos.left + lineOff.left - wrapOff.l eft)) 8798 else if (code == 0x200c) return "b";
8917 } 8799 else return "L";
8918 8800 }
8919 return result 8801
8920 }; 8802 var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
8921 8803 var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, count sAsNum = /[1n]/;
8922 TextareaInput.prototype.showSelection = function showSelection (drawn) { 8804 // Browsers seem to always treat the boundaries of block elements as being L .
8923 var cm = this.cm, display = cm.display 8805 var outerType = "L";
8924 removeChildrenAndAdd(display.cursorDiv, drawn.cursors) 8806
8925 removeChildrenAndAdd(display.selectionDiv, drawn.selection) 8807 function BidiSpan(level, from, to) {
8926 if (drawn.teTop != null) { 8808 this.level = level;
8927 this.wrapper.style.top = drawn.teTop + "px" 8809 this.from = from; this.to = to;
8928 this.wrapper.style.left = drawn.teLeft + "px" 8810 }
8929 } 8811
8930 }; 8812 return function(str) {
8931 8813 if (!bidiRE.test(str)) return false;
8932 // Reset the input to correspond to the selection (or to be empty, 8814 var len = str.length, types = [];
8933 // when not typing and nothing is selected) 8815 for (var i = 0, type; i < len; ++i)
8934 TextareaInput.prototype.reset = function reset (typing) { 8816 types.push(type = charType(str.charCodeAt(i)));
8935 if (this.contextMenuPending) { return } 8817
8936 var minimal, selected, cm = this.cm, doc = cm.doc 8818 // W1. Examine each non-spacing mark (NSM) in the level run, and
8937 if (cm.somethingSelected()) { 8819 // change the type of the NSM to the type of the previous
8938 this.prevInput = "" 8820 // character. If the NSM is at the start of the level run, it will
8939 var range = doc.sel.primary() 8821 // get the type of sor.
8940 minimal = hasCopyEvent && 8822 for (var i = 0, prev = outerType; i < len; ++i) {
8941 (range.to().line - range.from().line > 100 || (selected = cm.getSelection( )).length > 1000) 8823 var type = types[i];
8942 var content = minimal ? "-" : selected || cm.getSelection() 8824 if (type == "m") types[i] = prev;
8943 this.textarea.value = content 8825 else prev = type;
8944 if (cm.state.focused) { selectInput(this.textarea) } 8826 }
8945 if (ie && ie_version >= 9) { this.hasSelection = content } 8827
8946 } else if (!typing) { 8828 // W2. Search backwards from each instance of a European number
8947 this.prevInput = this.textarea.value = "" 8829 // until the first strong type (R, L, AL, or sor) is found. If an
8948 if (ie && ie_version >= 9) { this.hasSelection = null } 8830 // AL is found, change the type of the European number to Arabic
8949 } 8831 // number.
8950 this.inaccurateSelection = minimal 8832 // W3. Change all ALs to R.
8951 }; 8833 for (var i = 0, cur = outerType; i < len; ++i) {
8952 8834 var type = types[i];
8953 TextareaInput.prototype.getField = function getField () { return this.textarea } ; 8835 if (type == "1" && cur == "r") types[i] = "n";
8954 8836 else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
8955 TextareaInput.prototype.supportsTouch = function supportsTouch () { return false }; 8837 }
8956 8838
8957 TextareaInput.prototype.focus = function focus () { 8839 // W4. A single European separator between two European numbers
8958 if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this. textarea)) { 8840 // changes to a European number. A single common separator between
8959 try { this.textarea.focus() } 8841 // two numbers of the same type changes to that type.
8960 catch (e) {} // IE8 will throw if the textarea is display: none or not in DO M 8842 for (var i = 1, prev = types[0]; i < len - 1; ++i) {
8961 } 8843 var type = types[i];
8962 }; 8844 if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
8963 8845 else if (type == "," && prev == types[i+1] &&
8964 TextareaInput.prototype.blur = function blur () { this.textarea.blur() }; 8846 (prev == "1" || prev == "n")) types[i] = prev;
8965 8847 prev = type;
8966 TextareaInput.prototype.resetPosition = function resetPosition () { 8848 }
8967 this.wrapper.style.top = this.wrapper.style.left = 0 8849
8968 }; 8850 // W5. A sequence of European terminators adjacent to European
8969 8851 // numbers changes to all European numbers.
8970 TextareaInput.prototype.receivedFocus = function receivedFocus () { this.slowPol l() }; 8852 // W6. Otherwise, separators and terminators change to Other
8971 8853 // Neutral.
8972 // Poll for input changes, using the normal rate of polling. This 8854 for (var i = 0; i < len; ++i) {
8973 // runs as long as the editor is focused. 8855 var type = types[i];
8974 TextareaInput.prototype.slowPoll = function slowPoll () { 8856 if (type == ",") types[i] = "N";
8975 var this$1 = this; 8857 else if (type == "%") {
8976 8858 for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
8977 if (this.pollingFast) { return } 8859 var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N";
8978 this.polling.set(this.cm.options.pollInterval, function () { 8860 for (var j = i; j < end; ++j) types[j] = replace;
8979 this$1.poll() 8861 i = end - 1;
8980 if (this$1.cm.state.focused) { this$1.slowPoll() } 8862 }
8981 }) 8863 }
8982 }; 8864
8983 8865 // W7. Search backwards from each instance of a European number
8984 // When an event has just come in that is likely to add or change 8866 // until the first strong type (R, L, or sor) is found. If an L is
8985 // something in the input textarea, we poll faster, to ensure that 8867 // found, then change the type of the European number to L.
8986 // the change appears on the screen quickly. 8868 for (var i = 0, cur = outerType; i < len; ++i) {
8987 TextareaInput.prototype.fastPoll = function fastPoll () { 8869 var type = types[i];
8988 var missed = false, input = this 8870 if (cur == "L" && type == "1") types[i] = "L";
8989 input.pollingFast = true 8871 else if (isStrong.test(type)) cur = type;
8990 function p() { 8872 }
8991 var changed = input.poll() 8873
8992 if (!changed && !missed) {missed = true; input.polling.set(60, p)} 8874 // N1. A sequence of neutrals takes the direction of the
8993 else {input.pollingFast = false; input.slowPoll()} 8875 // surrounding strong text if the text on both sides has the same
8994 } 8876 // direction. European and Arabic numbers act as if they were R in
8995 input.polling.set(20, p) 8877 // terms of their influence on neutrals. Start-of-level-run (sor)
8996 }; 8878 // and end-of-level-run (eor) are used at level run boundaries.
8997 8879 // N2. Any remaining neutrals take the embedding direction.
8998 // Read input from the textarea, and update the document to match. 8880 for (var i = 0; i < len; ++i) {
8999 // When something is selected, it is present in the textarea, and 8881 if (isNeutral.test(types[i])) {
9000 // selected (unless it is huge, in which case a placeholder is 8882 for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
9001 // used). When nothing is selected, the cursor sits after previously 8883 var before = (i ? types[i-1] : outerType) == "L";
9002 // seen text (can be empty), which is stored in prevInput (we must 8884 var after = (end < len ? types[end] : outerType) == "L";
9003 // not reset the textarea when typing, because that breaks IME). 8885 var replace = before || after ? "L" : "R";
9004 TextareaInput.prototype.poll = function poll () { 8886 for (var j = i; j < end; ++j) types[j] = replace;
9005 var this$1 = this; 8887 i = end - 1;
9006 8888 }
9007 var cm = this.cm, input = this.textarea, prevInput = this.prevInput 8889 }
9008 // Since this is called a *lot*, try to bail out as cheaply as 8890
9009 // possible when it is clear that nothing happened. hasSelection 8891 // Here we depart from the documented algorithm, in order to avoid
9010 // will be the case when there is a lot of text in the textarea, 8892 // building up an actual levels array. Since there are only three
9011 // in which case reading its value would be expensive. 8893 // levels (0, 1, 2) in an implementation that doesn't take
9012 if (this.contextMenuPending || !cm.state.focused || 8894 // explicit embedding into account, we can build up the order on
9013 (hasSelection(input) && !prevInput && !this.composing) || 8895 // the fly, without following the level-based algorithm.
9014 cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) 8896 var order = [], m;
9015 { return false } 8897 for (var i = 0; i < len;) {
9016 8898 if (countsAsLeft.test(types[i])) {
9017 var text = input.value 8899 var start = i;
9018 // If nothing changed, bail. 8900 for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
9019 if (text == prevInput && !cm.somethingSelected()) { return false } 8901 order.push(new BidiSpan(0, start, i));
9020 // Work around nonsensical selection resetting in IE9/10, and
9021 // inexplicable appearance of private area unicode characters on
9022 // some key combos in Mac (#2689).
9023 if (ie && ie_version >= 9 && this.hasSelection === text ||
9024 mac && /[\uf700-\uf7ff]/.test(text)) {
9025 cm.display.input.reset()
9026 return false
9027 }
9028
9029 if (cm.doc.sel == cm.display.selForContextMenu) {
9030 var first = text.charCodeAt(0)
9031 if (first == 0x200b && !prevInput) { prevInput = "\u200b" }
9032 if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") }
9033 }
9034 // Find the part of the input that is actually new
9035 var same = 0, l = Math.min(prevInput.length, text.length)
9036 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++sa me }
9037
9038 runInOp(cm, function () {
9039 applyTextInput(cm, text.slice(same), prevInput.length - same,
9040 null, this$1.composing ? "*compose" : null)
9041
9042 // Don't leave long text in the textarea, since it makes further polling slo w
9043 if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.pr evInput = "" }
9044 else { this$1.prevInput = text }
9045
9046 if (this$1.composing) {
9047 this$1.composing.range.clear()
9048 this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor( "to"),
9049 {className: "CodeMirror-composing"})
9050 }
9051 })
9052 return true
9053 };
9054
9055 TextareaInput.prototype.ensurePolled = function ensurePolled () {
9056 if (this.pollingFast && this.poll()) { this.pollingFast = false }
9057 };
9058
9059 TextareaInput.prototype.onKeyPress = function onKeyPress () {
9060 if (ie && ie_version >= 9) { this.hasSelection = null }
9061 this.fastPoll()
9062 };
9063
9064 TextareaInput.prototype.onContextMenu = function onContextMenu (e) {
9065 var input = this, cm = input.cm, display = cm.display, te = input.textarea
9066 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop
9067 if (!pos || presto) { return } // Opera is difficult.
9068
9069 // Reset the current text selection only if the click is done outside of the s election
9070 // and 'resetSelectionOnContextMenu' option is true.
9071 var reset = cm.options.resetSelectionOnContextMenu
9072 if (reset && cm.doc.sel.contains(pos) == -1)
9073 { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) }
9074
9075 var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText
9076 input.wrapper.style.cssText = "position: absolute"
9077 var wrapperBox = input.wrapper.getBoundingClientRect()
9078 te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.l eft - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: non e; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"
9079 var oldScrollY
9080 if (webkit) { oldScrollY = window.scrollY } // Work around Chrome issue (#2712 )
9081 display.input.focus()
9082 if (webkit) { window.scrollTo(null, oldScrollY) }
9083 display.input.reset()
9084 // Adds "Select all" to context menu in FF
9085 if (!cm.somethingSelected()) { te.value = input.prevInput = " " }
9086 input.contextMenuPending = true
9087 display.selForContextMenu = cm.doc.sel
9088 clearTimeout(display.detectingSelectAll)
9089
9090 // Select-all will be greyed out if there's nothing to select, so
9091 // this adds a zero-width space so that we can later check whether
9092 // it got selected.
9093 function prepareSelectAllHack() {
9094 if (te.selectionStart != null) {
9095 var selected = cm.somethingSelected()
9096 var extval = "\u200b" + (selected ? te.value : "")
9097 te.value = "\u21da" // Used to catch context-menu undo
9098 te.value = extval
9099 input.prevInput = selected ? "" : "\u200b"
9100 te.selectionStart = 1; te.selectionEnd = extval.length
9101 // Re-set this, in case some other handler touched the
9102 // selection in the meantime.
9103 display.selForContextMenu = cm.doc.sel
9104 }
9105 }
9106 function rehide() {
9107 input.contextMenuPending = false
9108 input.wrapper.style.cssText = oldWrapperCSS
9109 te.style.cssText = oldCSS
9110 if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller .scrollTop = scrollPos) }
9111
9112 // Try to detect the user choosing select-all
9113 if (te.selectionStart != null) {
9114 if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack() }
9115 var i = 0, poll = function () {
9116 if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&
9117 te.selectionEnd > 0 && input.prevInput == "\u200b") {
9118 operation(cm, selectAll)(cm)
9119 } else if (i++ < 10) {
9120 display.detectingSelectAll = setTimeout(poll, 500)
9121 } else { 8902 } else {
9122 display.selForContextMenu = null 8903 var pos = i, at = order.length;
9123 display.input.reset() 8904 for (++i; i < len && types[i] != "L"; ++i) {}
8905 for (var j = pos; j < i;) {
8906 if (countsAsNum.test(types[j])) {
8907 if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j));
8908 var nstart = j;
8909 for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
8910 order.splice(at, 0, new BidiSpan(2, nstart, j));
8911 pos = j;
8912 } else ++j;
8913 }
8914 if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i));
9124 } 8915 }
9125 } 8916 }
9126 display.detectingSelectAll = setTimeout(poll, 200) 8917 if (order[0].level == 1 && (m = str.match(/^\s+/))) {
9127 } 8918 order[0].from = m[0].length;
9128 } 8919 order.unshift(new BidiSpan(0, 0, m[0].length));
9129 8920 }
9130 if (ie && ie_version >= 9) { prepareSelectAllHack() } 8921 if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
9131 if (captureRightClick) { 8922 lst(order).to -= m[0].length;
9132 e_stop(e) 8923 order.push(new BidiSpan(0, len - m[0].length, len));
9133 var mouseup = function () { 8924 }
9134 off(window, "mouseup", mouseup) 8925 if (order[0].level == 2)
9135 setTimeout(rehide, 20) 8926 order.unshift(new BidiSpan(1, order[0].to, order[0].to));
9136 } 8927 if (order[0].level != lst(order).level)
9137 on(window, "mouseup", mouseup) 8928 order.push(new BidiSpan(order[0].level, len, len));
9138 } else { 8929
9139 setTimeout(rehide, 50) 8930 return order;
9140 } 8931 };
9141 }; 8932 })();
9142 8933
9143 TextareaInput.prototype.readOnlyChanged = function readOnlyChanged (val) { 8934 // THE END
9144 if (!val) { this.reset() } 8935
9145 }; 8936 CodeMirror.version = "5.17.1";
9146 8937
9147 TextareaInput.prototype.setUneditable = function setUneditable () {}; 8938 return CodeMirror;
9148 8939 });
9149 TextareaInput.prototype.needsContentAttribute = false
9150
9151 function fromTextArea(textarea, options) {
9152 options = options ? copyObj(options) : {}
9153 options.value = textarea.value
9154 if (!options.tabindex && textarea.tabIndex)
9155 { options.tabindex = textarea.tabIndex }
9156 if (!options.placeholder && textarea.placeholder)
9157 { options.placeholder = textarea.placeholder }
9158 // Set autofocus to true if this textarea is focused, or if it has
9159 // autofocus and no other element is focused.
9160 if (options.autofocus == null) {
9161 var hasFocus = activeElt()
9162 options.autofocus = hasFocus == textarea ||
9163 textarea.getAttribute("autofocus") != null && hasFocus == document.body
9164 }
9165
9166 function save() {textarea.value = cm.getValue()}
9167
9168 var realSubmit
9169 if (textarea.form) {
9170 on(textarea.form, "submit", save)
9171 // Deplorable hack to make the submit method do the right thing.
9172 if (!options.leaveSubmitMethodAlone) {
9173 var form = textarea.form
9174 realSubmit = form.submit
9175 try {
9176 var wrappedSubmit = form.submit = function () {
9177 save()
9178 form.submit = realSubmit
9179 form.submit()
9180 form.submit = wrappedSubmit
9181 }
9182 } catch(e) {}
9183 }
9184 }
9185
9186 options.finishInit = function (cm) {
9187 cm.save = save
9188 cm.getTextArea = function () { return textarea; }
9189 cm.toTextArea = function () {
9190 cm.toTextArea = isNaN // Prevent this from being ran twice
9191 save()
9192 textarea.parentNode.removeChild(cm.getWrapperElement())
9193 textarea.style.display = ""
9194 if (textarea.form) {
9195 off(textarea.form, "submit", save)
9196 if (typeof textarea.form.submit == "function")
9197 { textarea.form.submit = realSubmit }
9198 }
9199 }
9200 }
9201
9202 textarea.style.display = "none"
9203 var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore( node, textarea.nextSibling); },
9204 options)
9205 return cm
9206 }
9207
9208 function addLegacyProps(CodeMirror) {
9209 CodeMirror.off = off
9210 CodeMirror.on = on
9211 CodeMirror.wheelEventPixels = wheelEventPixels
9212 CodeMirror.Doc = Doc
9213 CodeMirror.splitLines = splitLinesAuto
9214 CodeMirror.countColumn = countColumn
9215 CodeMirror.findColumn = findColumn
9216 CodeMirror.isWordChar = isWordCharBasic
9217 CodeMirror.Pass = Pass
9218 CodeMirror.signal = signal
9219 CodeMirror.Line = Line
9220 CodeMirror.changeEnd = changeEnd
9221 CodeMirror.scrollbarModel = scrollbarModel
9222 CodeMirror.Pos = Pos
9223 CodeMirror.cmpPos = cmp
9224 CodeMirror.modes = modes
9225 CodeMirror.mimeModes = mimeModes
9226 CodeMirror.resolveMode = resolveMode
9227 CodeMirror.getMode = getMode
9228 CodeMirror.modeExtensions = modeExtensions
9229 CodeMirror.extendMode = extendMode
9230 CodeMirror.copyState = copyState
9231 CodeMirror.startState = startState
9232 CodeMirror.innerMode = innerMode
9233 CodeMirror.commands = commands
9234 CodeMirror.keyMap = keyMap
9235 CodeMirror.keyName = keyName
9236 CodeMirror.isModifierKey = isModifierKey
9237 CodeMirror.lookupKey = lookupKey
9238 CodeMirror.normalizeKeyMap = normalizeKeyMap
9239 CodeMirror.StringStream = StringStream
9240 CodeMirror.SharedTextMarker = SharedTextMarker
9241 CodeMirror.TextMarker = TextMarker
9242 CodeMirror.LineWidget = LineWidget
9243 CodeMirror.e_preventDefault = e_preventDefault
9244 CodeMirror.e_stopPropagation = e_stopPropagation
9245 CodeMirror.e_stop = e_stop
9246 CodeMirror.addClass = addClass
9247 CodeMirror.contains = contains
9248 CodeMirror.rmClass = rmClass
9249 CodeMirror.keyNames = keyNames
9250 }
9251
9252 // EDITOR CONSTRUCTOR
9253
9254 defineOptions(CodeMirror)
9255
9256 addEditorMethods(CodeMirror)
9257
9258 // Set up methods on CodeMirror's prototype to redirect to the editor's document .
9259 var dontDelegate = "iter insert remove copy getEditor constructor".split(" ")
9260 for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && inde xOf(dontDelegate, prop) < 0)
9261 { CodeMirror.prototype[prop] = (function(method) {
9262 return function() {return method.apply(this.doc, arguments)}
9263 })(Doc.prototype[prop]) } }
9264
9265 eventMixin(Doc)
9266
9267 // INPUT HANDLING
9268
9269 CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentE ditableInput}
9270
9271 // MODE DEFINITION AND QUERYING
9272
9273 // Extra arguments are stored as the mode's dependencies, which is
9274 // used by (legacy) mechanisms like loadmode.js to automatically
9275 // load a mode. (Preferred mechanism is the require/define calls.)
9276 CodeMirror.defineMode = function(name/*, mode, …*/) {
9277 if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name }
9278 defineMode.apply(this, arguments)
9279 }
9280
9281 CodeMirror.defineMIME = defineMIME
9282
9283 // Minimal default mode.
9284 CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); })
9285 CodeMirror.defineMIME("text/plain", "null")
9286
9287 // EXTENSIONS
9288
9289 CodeMirror.defineExtension = function (name, func) {
9290 CodeMirror.prototype[name] = func
9291 }
9292 CodeMirror.defineDocExtension = function (name, func) {
9293 Doc.prototype[name] = func
9294 }
9295
9296 CodeMirror.fromTextArea = fromTextArea
9297
9298 addLegacyProps(CodeMirror)
9299
9300 CodeMirror.version = "5.25.1"
9301
9302 return CodeMirror;
9303
9304 })));
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698