PluginProbe ʕ •ᴥ•ʔ
Pods – Custom Content Types and Fields / 2.8.23.4
Pods – Custom Content Types and Fields v2.8.23.4
2.7.31.4 2.8.23.5 2.9.19.5 3.0.10.5 3.1.4.3 3.2.8.4 3.3.9.2 2.8.23.4 2.9.19.4 3.0.10.4 3.1.4.2 3.2.8.3 3.3.9.1 trunk 1.14.8 2.7.31.3 2.8.23.3 2.9.19.3 3.0.10.3 3.1.4.1 3.2.0 3.2.1 3.2.1.1 3.2.2 3.2.4 3.2.5 3.2.6 3.2.7 3.2.7.1 3.2.8 3.2.8.1 3.2.8.2 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9
pods / ui / js / codemirror / codemirror.js
pods / ui / js / codemirror Last commit date
addon 2 weeks ago mode 2 weeks ago codemirror.js 2 weeks ago
codemirror.js
7923 lines
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: http://codemirror.net/LICENSE
3
4 // This is CodeMirror (http://codemirror.net), a code editor
5 // implemented in JavaScript on top of the browser's DOM.
6 //
7 // You can find some technical background for some of the code below
8 // at http://marijnhaverbeke.nl/blog/#cm-internals .
9
10 (function(mod) {
11 if (typeof exports == "object" && typeof module == "object") // CommonJS
12 module.exports = mod();
13 else if (typeof define == "function" && define.amd) // AMD
14 return define([], mod);
15 else // Plain browser env
16 this.CodeMirror = mod();
17 })(function() {
18 "use strict";
19
20 // BROWSER SNIFFING
21
22 // Kludges for bugs and behavior differences that can't be feature
23 // detected are enabled based on userAgent etc sniffing.
24
25 var gecko = /gecko\/\d/i.test(navigator.userAgent);
26 // ie_uptoN means Internet Explorer version N or lower
27 var ie_upto10 = /MSIE \d/.test(navigator.userAgent);
28 var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
29 var ie = ie_upto10 || ie_11up;
30 var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]);
31 var webkit = /WebKit\//.test(navigator.userAgent);
32 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
33 var chrome = /Chrome\//.test(navigator.userAgent);
34 var presto = /Opera\//.test(navigator.userAgent);
35 var safari = /Apple Computer/.test(navigator.vendor);
36 var khtml = /KHTML\//.test(navigator.userAgent);
37 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
38 var phantom = /PhantomJS/.test(navigator.userAgent);
39
40 var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
41 // This is woefully incomplete. Suggestions for alternative methods welcome.
42 var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
43 var mac = ios || /Mac/.test(navigator.platform);
44 var windows = /win/i.test(navigator.platform);
45
46 var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
47 if (presto_version) presto_version = Number(presto_version[1]);
48 if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
49 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
50 var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));
51 var captureRightClick = gecko || (ie && ie_version >= 9);
52
53 // Optimize some code when these features are not used.
54 var sawReadOnlySpans = false, sawCollapsedSpans = false;
55
56 // EDITOR CONSTRUCTOR
57
58 // A CodeMirror instance represents an editor. This is the object
59 // that user code is usually dealing with.
60
61 function CodeMirror(place, options) {
62 if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
63
64 this.options = options = options ? copyObj(options) : {};
65 // Determine effective options based on given values and defaults.
66 copyObj(defaults, options, false);
67 setGuttersForLineNumbers(options);
68
69 var doc = options.value;
70 if (typeof doc == "string") doc = new Doc(doc, options.mode);
71 this.doc = doc;
72
73 var display = this.display = new Display(place, doc);
74 display.wrapper.CodeMirror = this;
75 updateGutters(this);
76 themeChanged(this);
77 if (options.lineWrapping)
78 this.display.wrapper.className += " CodeMirror-wrap";
79 if (options.autofocus && !mobile) focusInput(this);
80
81 this.state = {
82 keyMaps: [], // stores maps added by addKeyMap
83 overlays: [], // highlighting overlays, as added by addOverlay
84 modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
85 overwrite: false, focused: false,
86 suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
87 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in readInput
88 draggingText: false,
89 highlight: new Delayed(), // stores highlight worker timeout
90 keySeq: null // Unfinished key sequence
91 };
92
93 // Override magic textarea content restore that IE sometimes does
94 // on our hidden textarea on reload
95 if (ie && ie_version < 11) setTimeout(bind(resetInput, this, true), 20);
96
97 registerEventHandlers(this);
98 ensureGlobalHandlers();
99
100 startOperation(this);
101 this.curOp.forceUpdate = true;
102 attachDoc(this, doc);
103
104 if ((options.autofocus && !mobile) || activeElt() == display.input)
105 setTimeout(bind(onFocus, this), 20);
106 else
107 onBlur(this);
108
109 for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
110 optionHandlers[opt](this, options[opt], Init);
111 maybeUpdateLineNumberWidth(this);
112 for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
113 endOperation(this);
114 }
115
116 // DISPLAY CONSTRUCTOR
117
118 // The display handles the DOM integration, both for input reading
119 // and content drawing. It holds references to DOM nodes and
120 // display-related state.
121
122 function Display(place, doc) {
123 var d = this;
124
125 // The semihidden textarea that is focused when the editor is
126 // focused, and receives input.
127 var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
128 // The textarea is kept positioned near the cursor to prevent the
129 // fact that it'll be scrolled into view on input from scrolling
130 // our fake cursor out of view. On webkit, when wrap=off, paste is
131 // very slow. So make the area wide instead.
132 if (webkit) input.style.width = "1000px";
133 else input.setAttribute("wrap", "off");
134 // If border: 0; -- iOS fails to open keyboard (issue #1287)
135 if (ios) input.style.border = "1px solid black";
136 input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
137
138 // Wraps and hides input textarea
139 d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
140 // The fake scrollbar elements.
141 d.scrollbarH = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
142 d.scrollbarV = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
143 // Covers bottom-right square when both scrollbars are present.
144 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
145 // Covers bottom of gutter when coverGutterNextToScrollbar is on
146 // and h scrollbar is present.
147 d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
148 // Will contain the actual code, positioned to cover the viewport.
149 d.lineDiv = elt("div", null, "CodeMirror-code");
150 // Elements are added to these to represent selection and cursors.
151 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
152 d.cursorDiv = elt("div", null, "CodeMirror-cursors");
153 // A visibility: hidden element used to find the size of things.
154 d.measure = elt("div", null, "CodeMirror-measure");
155 // When lines outside of the viewport are measured, they are drawn in this.
156 d.lineMeasure = elt("div", null, "CodeMirror-measure");
157 // Wraps everything that needs to exist inside the vertically-padded coordinate system
158 d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],
159 null, "position: relative; outline: none");
160 // Moved around its parent to cover visible view.
161 d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
162 // Set to the height of the document, allowing scrolling.
163 d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
164 // Behavior of elts with overflow: auto and padding is
165 // inconsistent across browsers. This is used to ensure the
166 // scrollable area is big enough.
167 d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
168 // Will contain the gutters, if any.
169 d.gutters = elt("div", null, "CodeMirror-gutters");
170 d.lineGutter = null;
171 // Actual scrollable element.
172 d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
173 d.scroller.setAttribute("tabIndex", "-1");
174 // The element in which the editor lives.
175 d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
176 d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
177
178 // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
179 if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
180 // Needed to hide big blue blinking cursor on Mobile Safari
181 if (ios) input.style.width = "0px";
182 if (!webkit) d.scroller.draggable = true;
183 // Needed to handle Tab key in KHTML
184 if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
185 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
186 if (ie && ie_version < 8) d.scrollbarH.style.minHeight = d.scrollbarV.style.minWidth = "18px";
187
188 if (place) {
189 if (place.appendChild) place.appendChild(d.wrapper);
190 else place(d.wrapper);
191 }
192
193 // Current rendered range (may be bigger than the view window).
194 d.viewFrom = d.viewTo = doc.first;
195 // Information about the rendered lines.
196 d.view = [];
197 // Holds info about a single rendered line when it was rendered
198 // for measurement, while not in view.
199 d.externalMeasured = null;
200 // Empty space (in pixels) above the view
201 d.viewOffset = 0;
202 d.lastWrapHeight = d.lastWrapWidth = 0;
203 d.updateLineNumbers = null;
204
205 // Used to only resize the line number gutter when necessary (when
206 // the amount of lines crosses a boundary that makes its width change)
207 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
208 // See readInput and resetInput
209 d.prevInput = "";
210 // Set to true when a non-horizontal-scrolling line widget is
211 // added. As an optimization, line widget aligning is skipped when
212 // this is false.
213 d.alignWidgets = false;
214 // Flag that indicates whether we expect input to appear real soon
215 // now (after some event like 'keypress' or 'input') and are
216 // polling intensively.
217 d.pollingFast = false;
218 // Self-resetting timeout for the poller
219 d.poll = new Delayed();
220
221 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
222
223 // Tracks when resetInput has punted to just putting a short
224 // string into the textarea instead of the full selection.
225 d.inaccurateSelection = false;
226
227 // Tracks the maximum line length so that the horizontal scrollbar
228 // can be kept static when scrolling.
229 d.maxLine = null;
230 d.maxLineLength = 0;
231 d.maxLineChanged = false;
232
233 // Used for measuring wheel scrolling granularity
234 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
235
236 // True when shift is held down.
237 d.shift = false;
238
239 // Used to track whether anything happened since the context menu
240 // was opened.
241 d.selForContextMenu = null;
242 }
243
244 // STATE UPDATES
245
246 // Used to get the editor into a consistent state again when options change.
247
248 function loadMode(cm) {
249 cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
250 resetModeState(cm);
251 }
252
253 function resetModeState(cm) {
254 cm.doc.iter(function(line) {
255 if (line.stateAfter) line.stateAfter = null;
256 if (line.styles) line.styles = null;
257 });
258 cm.doc.frontier = cm.doc.first;
259 startWorker(cm, 100);
260 cm.state.modeGen++;
261 if (cm.curOp) regChange(cm);
262 }
263
264 function wrappingChanged(cm) {
265 if (cm.options.lineWrapping) {
266 addClass(cm.display.wrapper, "CodeMirror-wrap");
267 cm.display.sizer.style.minWidth = "";
268 } else {
269 rmClass(cm.display.wrapper, "CodeMirror-wrap");
270 findMaxLine(cm);
271 }
272 estimateLineHeights(cm);
273 regChange(cm);
274 clearCaches(cm);
275 setTimeout(function(){updateScrollbars(cm);}, 100);
276 }
277
278 // Returns a function that estimates the height of a line, to use as
279 // first approximation until the line becomes visible (and is thus
280 // properly measurable).
281 function estimateHeight(cm) {
282 var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
283 var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
284 return function(line) {
285 if (lineIsHidden(cm.doc, line)) return 0;
286
287 var widgetsHeight = 0;
288 if (line.widgets) for (var i = 0; i < line.widgets.length; i++) {
289 if (line.widgets[i].height) widgetsHeight += line.widgets[i].height;
290 }
291
292 if (wrapping)
293 return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th;
294 else
295 return widgetsHeight + th;
296 };
297 }
298
299 function estimateLineHeights(cm) {
300 var doc = cm.doc, est = estimateHeight(cm);
301 doc.iter(function(line) {
302 var estHeight = est(line);
303 if (estHeight != line.height) updateLineHeight(line, estHeight);
304 });
305 }
306
307 function themeChanged(cm) {
308 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
309 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
310 clearCaches(cm);
311 }
312
313 function guttersChanged(cm) {
314 updateGutters(cm);
315 regChange(cm);
316 setTimeout(function(){alignHorizontally(cm);}, 20);
317 }
318
319 // Rebuild the gutter elements, ensure the margin to the left of the
320 // code matches their width.
321 function updateGutters(cm) {
322 var gutters = cm.display.gutters, specs = cm.options.gutters;
323 removeChildren(gutters);
324 for (var i = 0; i < specs.length; ++i) {
325 var gutterClass = specs[i];
326 var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
327 if (gutterClass == "CodeMirror-linenumbers") {
328 cm.display.lineGutter = gElt;
329 gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
330 }
331 }
332 gutters.style.display = i ? "" : "none";
333 updateGutterSpace(cm);
334 }
335
336 function updateGutterSpace(cm) {
337 var width = cm.display.gutters.offsetWidth;
338 cm.display.sizer.style.marginLeft = width + "px";
339 cm.display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0;
340 }
341
342 // Compute the character length of a line, taking into account
343 // collapsed ranges (see markText) that might hide parts, and join
344 // other lines onto it.
345 function lineLength(line) {
346 if (line.height == 0) return 0;
347 var len = line.text.length, merged, cur = line;
348 while (merged = collapsedSpanAtStart(cur)) {
349 var found = merged.find(0, true);
350 cur = found.from.line;
351 len += found.from.ch - found.to.ch;
352 }
353 cur = line;
354 while (merged = collapsedSpanAtEnd(cur)) {
355 var found = merged.find(0, true);
356 len -= cur.text.length - found.from.ch;
357 cur = found.to.line;
358 len += cur.text.length - found.to.ch;
359 }
360 return len;
361 }
362
363 // Find the longest line in the document.
364 function findMaxLine(cm) {
365 var d = cm.display, doc = cm.doc;
366 d.maxLine = getLine(doc, doc.first);
367 d.maxLineLength = lineLength(d.maxLine);
368 d.maxLineChanged = true;
369 doc.iter(function(line) {
370 var len = lineLength(line);
371 if (len > d.maxLineLength) {
372 d.maxLineLength = len;
373 d.maxLine = line;
374 }
375 });
376 }
377
378 // Make sure the gutters options contains the element
379 // "CodeMirror-linenumbers" when the lineNumbers option is true.
380 function setGuttersForLineNumbers(options) {
381 var found = indexOf(options.gutters, "CodeMirror-linenumbers");
382 if (found == -1 && options.lineNumbers) {
383 options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]);
384 } else if (found > -1 && !options.lineNumbers) {
385 options.gutters = options.gutters.slice(0);
386 options.gutters.splice(found, 1);
387 }
388 }
389
390 // SCROLLBARS
391
392 function hScrollbarTakesSpace(cm) {
393 return cm.display.scroller.clientHeight - cm.display.wrapper.clientHeight < scrollerCutOff - 3;
394 }
395
396 // Prepare DOM reads needed to update the scrollbars. Done in one
397 // shot to minimize update/measure roundtrips.
398 function measureForScrollbars(cm) {
399 var scroll = cm.display.scroller;
400 return {
401 clientHeight: scroll.clientHeight,
402 barHeight: cm.display.scrollbarV.clientHeight,
403 scrollWidth: scroll.scrollWidth, clientWidth: scroll.clientWidth,
404 hScrollbarTakesSpace: hScrollbarTakesSpace(cm),
405 barWidth: cm.display.scrollbarH.clientWidth,
406 docHeight: Math.round(cm.doc.height + paddingVert(cm.display))
407 };
408 }
409
410 // Re-synchronize the fake scrollbars with the actual size of the
411 // content.
412 function updateScrollbars(cm, measure) {
413 if (!measure) measure = measureForScrollbars(cm);
414 var d = cm.display, sWidth = scrollbarWidth(d.measure);
415 var scrollHeight = measure.docHeight + scrollerCutOff;
416 var needsH = measure.scrollWidth > measure.clientWidth;
417 if (needsH && measure.scrollWidth <= measure.clientWidth + 1 &&
418 sWidth > 0 && !measure.hScrollbarTakesSpace)
419 needsH = false; // (Issue #2562)
420 var needsV = scrollHeight > measure.clientHeight;
421
422 if (needsV) {
423 d.scrollbarV.style.display = "block";
424 d.scrollbarV.style.bottom = needsH ? sWidth + "px" : "0";
425 // A bug in IE8 can cause this value to be negative, so guard it.
426 d.scrollbarV.firstChild.style.height =
427 Math.max(0, scrollHeight - measure.clientHeight + (measure.barHeight || d.scrollbarV.clientHeight)) + "px";
428 } else {
429 d.scrollbarV.style.display = "";
430 d.scrollbarV.firstChild.style.height = "0";
431 }
432 if (needsH) {
433 d.scrollbarH.style.display = "block";
434 d.scrollbarH.style.right = needsV ? sWidth + "px" : "0";
435 d.scrollbarH.firstChild.style.width =
436 (measure.scrollWidth - measure.clientWidth + (measure.barWidth || d.scrollbarH.clientWidth)) + "px";
437 } else {
438 d.scrollbarH.style.display = "";
439 d.scrollbarH.firstChild.style.width = "0";
440 }
441 if (needsH && needsV) {
442 d.scrollbarFiller.style.display = "block";
443 d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = sWidth + "px";
444 } else d.scrollbarFiller.style.display = "";
445 if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
446 d.gutterFiller.style.display = "block";
447 d.gutterFiller.style.height = sWidth + "px";
448 d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
449 } else d.gutterFiller.style.display = "";
450
451 if (!cm.state.checkedOverlayScrollbar && measure.clientHeight > 0) {
452 if (sWidth === 0) {
453 var w = mac && !mac_geMountainLion ? "12px" : "18px";
454 d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = w;
455 var barMouseDown = function(e) {
456 if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
457 operation(cm, onMouseDown)(e);
458 };
459 on(d.scrollbarV, "mousedown", barMouseDown);
460 on(d.scrollbarH, "mousedown", barMouseDown);
461 }
462 cm.state.checkedOverlayScrollbar = true;
463 }
464 }
465
466 // Compute the lines that are visible in a given viewport (defaults
467 // the the current scroll position). viewport may contain top,
468 // height, and ensure (see op.scrollToPos) properties.
469 function visibleLines(display, doc, viewport) {
470 var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop;
471 top = Math.floor(top - paddingTop(display));
472 var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight;
473
474 var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);
475 // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
476 // forces those lines into the viewport (if possible).
477 if (viewport && viewport.ensure) {
478 var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line;
479 if (ensureFrom < from)
480 return {from: ensureFrom,
481 to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)};
482 if (Math.min(ensureTo, doc.lastLine()) >= to)
483 return {from: lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight),
484 to: ensureTo};
485 }
486 return {from: from, to: Math.max(to, from + 1)};
487 }
488
489 // LINE NUMBERS
490
491 // Re-align line numbers and gutter marks to compensate for
492 // horizontal scrolling.
493 function alignHorizontally(cm) {
494 var display = cm.display, view = display.view;
495 if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
496 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
497 var gutterW = display.gutters.offsetWidth, left = comp + "px";
498 for (var i = 0; i < view.length; i++) if (!view[i].hidden) {
499 if (cm.options.fixedGutter && view[i].gutter)
500 view[i].gutter.style.left = left;
501 var align = view[i].alignable;
502 if (align) for (var j = 0; j < align.length; j++)
503 align[j].style.left = left;
504 }
505 if (cm.options.fixedGutter)
506 display.gutters.style.left = (comp + gutterW) + "px";
507 }
508
509 // Used to ensure that the line number gutter is still the right
510 // size for the current document size. Returns true when an update
511 // is needed.
512 function maybeUpdateLineNumberWidth(cm) {
513 if (!cm.options.lineNumbers) return false;
514 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
515 if (last.length != display.lineNumChars) {
516 var test = display.measure.appendChild(elt("div", [elt("div", last)],
517 "CodeMirror-linenumber CodeMirror-gutter-elt"));
518 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
519 display.lineGutter.style.width = "";
520 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
521 display.lineNumWidth = display.lineNumInnerWidth + padding;
522 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
523 display.lineGutter.style.width = display.lineNumWidth + "px";
524 updateGutterSpace(cm);
525 return true;
526 }
527 return false;
528 }
529
530 function lineNumberFor(options, i) {
531 return String(options.lineNumberFormatter(i + options.firstLineNumber));
532 }
533
534 // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
535 // but using getBoundingClientRect to get a sub-pixel-accurate
536 // result.
537 function compensateForHScroll(display) {
538 return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left;
539 }
540
541 // DISPLAY DRAWING
542
543 function DisplayUpdate(cm, viewport, force) {
544 var display = cm.display;
545
546 this.viewport = viewport;
547 // Store some values that we'll need later (but don't want to force a relayout for)
548 this.visible = visibleLines(display, cm.doc, viewport);
549 this.editorIsHidden = !display.wrapper.offsetWidth;
550 this.wrapperHeight = display.wrapper.clientHeight;
551 this.wrapperWidth = display.wrapper.clientWidth;
552 this.oldViewFrom = display.viewFrom; this.oldViewTo = display.viewTo;
553 this.oldScrollerWidth = display.scroller.clientWidth;
554 this.force = force;
555 this.dims = getDimensions(cm);
556 }
557
558 // Does the actual updating of the line display. Bails out
559 // (returning false) when there is nothing to be done and forced is
560 // false.
561 function updateDisplayIfNeeded(cm, update) {
562 var display = cm.display, doc = cm.doc;
563 if (update.editorIsHidden) {
564 resetView(cm);
565 return false;
566 }
567
568 // Bail out if the visible area is already rendered and nothing changed.
569 if (!update.force &&
570 update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
571 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
572 countDirtyView(cm) == 0)
573 return false;
574
575 if (maybeUpdateLineNumberWidth(cm)) {
576 resetView(cm);
577 update.dims = getDimensions(cm);
578 }
579
580 // Compute a suitable new viewport (from & to)
581 var end = doc.first + doc.size;
582 var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);
583 var to = Math.min(end, update.visible.to + cm.options.viewportMargin);
584 if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom);
585 if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo);
586 if (sawCollapsedSpans) {
587 from = visualLineNo(cm.doc, from);
588 to = visualLineEndNo(cm.doc, to);
589 }
590
591 var different = from != display.viewFrom || to != display.viewTo ||
592 display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;
593 adjustView(cm, from, to);
594
595 display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));
596 // Position the mover div to align with the current scroll position
597 cm.display.mover.style.top = display.viewOffset + "px";
598
599 var toUpdate = countDirtyView(cm);
600 if (!different && toUpdate == 0 && !update.force &&
601 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
602 return false;
603
604 // For big changes, we hide the enclosing element during the
605 // update, since that speeds up the operations on most browsers.
606 var focused = activeElt();
607 if (toUpdate > 4) display.lineDiv.style.display = "none";
608 patchDisplay(cm, display.updateLineNumbers, update.dims);
609 if (toUpdate > 4) display.lineDiv.style.display = "";
610 // There might have been a widget with a focused element that got
611 // hidden or updated, if so re-focus it.
612 if (focused && activeElt() != focused && focused.offsetHeight) focused.focus();
613
614 // Prevent selection and cursors from interfering with the scroll
615 // width.
616 removeChildren(display.cursorDiv);
617 removeChildren(display.selectionDiv);
618
619 if (different) {
620 display.lastWrapHeight = update.wrapperHeight;
621 display.lastWrapWidth = update.wrapperWidth;
622 startWorker(cm, 400);
623 }
624
625 display.updateLineNumbers = null;
626
627 return true;
628 }
629
630 function postUpdateDisplay(cm, update) {
631 var force = update.force, viewport = update.viewport;
632 for (var first = true;; first = false) {
633 if (first && cm.options.lineWrapping && update.oldScrollerWidth != cm.display.scroller.clientWidth) {
634 force = true;
635 } else {
636 force = false;
637 // Clip forced viewport to actual scrollable area.
638 if (viewport && viewport.top != null)
639 viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - scrollerCutOff -
640 cm.display.scroller.clientHeight, viewport.top)};
641 // Updated line heights might result in the drawn area not
642 // actually covering the viewport. Keep looping until it does.
643 update.visible = visibleLines(cm.display, cm.doc, viewport);
644 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)
645 break;
646 }
647 if (!updateDisplayIfNeeded(cm, update)) break;
648 updateHeightsInViewport(cm);
649 var barMeasure = measureForScrollbars(cm);
650 updateSelection(cm);
651 setDocumentHeight(cm, barMeasure);
652 updateScrollbars(cm, barMeasure);
653 }
654
655 signalLater(cm, "update", cm);
656 if (cm.display.viewFrom != update.oldViewFrom || cm.display.viewTo != update.oldViewTo)
657 signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
658 }
659
660 function updateDisplaySimple(cm, viewport) {
661 var update = new DisplayUpdate(cm, viewport);
662 if (updateDisplayIfNeeded(cm, update)) {
663 updateHeightsInViewport(cm);
664 postUpdateDisplay(cm, update);
665 var barMeasure = measureForScrollbars(cm);
666 updateSelection(cm);
667 setDocumentHeight(cm, barMeasure);
668 updateScrollbars(cm, barMeasure);
669 }
670 }
671
672 function setDocumentHeight(cm, measure) {
673 cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = measure.docHeight + "px";
674 cm.display.gutters.style.height = Math.max(measure.docHeight, measure.clientHeight - scrollerCutOff) + "px";
675 }
676
677 function checkForWebkitWidthBug(cm, measure) {
678 // Work around Webkit bug where it sometimes reserves space for a
679 // non-existing phantom scrollbar in the scroller (Issue #2420)
680 if (cm.display.sizer.offsetWidth + cm.display.gutters.offsetWidth < cm.display.scroller.clientWidth - 1) {
681 cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = "0px";
682 cm.display.gutters.style.height = measure.docHeight + "px";
683 }
684 }
685
686 // Read the actual heights of the rendered lines, and update their
687 // stored heights to match.
688 function updateHeightsInViewport(cm) {
689 var display = cm.display;
690 var prevBottom = display.lineDiv.offsetTop;
691 for (var i = 0; i < display.view.length; i++) {
692 var cur = display.view[i], height;
693 if (cur.hidden) continue;
694 if (ie && ie_version < 8) {
695 var bot = cur.node.offsetTop + cur.node.offsetHeight;
696 height = bot - prevBottom;
697 prevBottom = bot;
698 } else {
699 var box = cur.node.getBoundingClientRect();
700 height = box.bottom - box.top;
701 }
702 var diff = cur.line.height - height;
703 if (height < 2) height = textHeight(display);
704 if (diff > .001 || diff < -.001) {
705 updateLineHeight(cur.line, height);
706 updateWidgetHeight(cur.line);
707 if (cur.rest) for (var j = 0; j < cur.rest.length; j++)
708 updateWidgetHeight(cur.rest[j]);
709 }
710 }
711 }
712
713 // Read and store the height of line widgets associated with the
714 // given line.
715 function updateWidgetHeight(line) {
716 if (line.widgets) for (var i = 0; i < line.widgets.length; ++i)
717 line.widgets[i].height = line.widgets[i].node.offsetHeight;
718 }
719
720 // Do a bulk-read of the DOM positions and sizes needed to draw the
721 // view, so that we don't interleave reading and writing to the DOM.
722 function getDimensions(cm) {
723 var d = cm.display, left = {}, width = {};
724 var gutterLeft = d.gutters.clientLeft;
725 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
726 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft;
727 width[cm.options.gutters[i]] = n.clientWidth;
728 }
729 return {fixedPos: compensateForHScroll(d),
730 gutterTotalWidth: d.gutters.offsetWidth,
731 gutterLeft: left,
732 gutterWidth: width,
733 wrapperWidth: d.wrapper.clientWidth};
734 }
735
736 // Sync the actual display DOM structure with display.view, removing
737 // nodes for lines that are no longer in view, and creating the ones
738 // that are not there yet, and updating the ones that are out of
739 // date.
740 function patchDisplay(cm, updateNumbersFrom, dims) {
741 var display = cm.display, lineNumbers = cm.options.lineNumbers;
742 var container = display.lineDiv, cur = container.firstChild;
743
744 function rm(node) {
745 var next = node.nextSibling;
746 // Works around a throw-scroll bug in OS X Webkit
747 if (webkit && mac && cm.display.currentWheelTarget == node)
748 node.style.display = "none";
749 else
750 node.parentNode.removeChild(node);
751 return next;
752 }
753
754 var view = display.view, lineN = display.viewFrom;
755 // Loop over the elements in the view, syncing cur (the DOM nodes
756 // in display.lineDiv) with the view as we go.
757 for (var i = 0; i < view.length; i++) {
758 var lineView = view[i];
759 if (lineView.hidden) {
760 } else if (!lineView.node) { // Not drawn yet
761 var node = buildLineElement(cm, lineView, lineN, dims);
762 container.insertBefore(node, cur);
763 } else { // Already drawn
764 while (cur != lineView.node) cur = rm(cur);
765 var updateNumber = lineNumbers && updateNumbersFrom != null &&
766 updateNumbersFrom <= lineN && lineView.lineNumber;
767 if (lineView.changes) {
768 if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false;
769 updateLineForChanges(cm, lineView, lineN, dims);
770 }
771 if (updateNumber) {
772 removeChildren(lineView.lineNumber);
773 lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)));
774 }
775 cur = lineView.node.nextSibling;
776 }
777 lineN += lineView.size;
778 }
779 while (cur) cur = rm(cur);
780 }
781
782 // When an aspect of a line changes, a string is added to
783 // lineView.changes. This updates the relevant part of the line's
784 // DOM structure.
785 function updateLineForChanges(cm, lineView, lineN, dims) {
786 for (var j = 0; j < lineView.changes.length; j++) {
787 var type = lineView.changes[j];
788 if (type == "text") updateLineText(cm, lineView);
789 else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims);
790 else if (type == "class") updateLineClasses(lineView);
791 else if (type == "widget") updateLineWidgets(lineView, dims);
792 }
793 lineView.changes = null;
794 }
795
796 // Lines with gutter elements, widgets or a background class need to
797 // be wrapped, and have the extra elements added to the wrapper div
798 function ensureLineWrapped(lineView) {
799 if (lineView.node == lineView.text) {
800 lineView.node = elt("div", null, null, "position: relative");
801 if (lineView.text.parentNode)
802 lineView.text.parentNode.replaceChild(lineView.node, lineView.text);
803 lineView.node.appendChild(lineView.text);
804 if (ie && ie_version < 8) lineView.node.style.zIndex = 2;
805 }
806 return lineView.node;
807 }
808
809 function updateLineBackground(lineView) {
810 var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass;
811 if (cls) cls += " CodeMirror-linebackground";
812 if (lineView.background) {
813 if (cls) lineView.background.className = cls;
814 else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; }
815 } else if (cls) {
816 var wrap = ensureLineWrapped(lineView);
817 lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild);
818 }
819 }
820
821 // Wrapper around buildLineContent which will reuse the structure
822 // in display.externalMeasured when possible.
823 function getLineContent(cm, lineView) {
824 var ext = cm.display.externalMeasured;
825 if (ext && ext.line == lineView.line) {
826 cm.display.externalMeasured = null;
827 lineView.measure = ext.measure;
828 return ext.built;
829 }
830 return buildLineContent(cm, lineView);
831 }
832
833 // Redraw the line's text. Interacts with the background and text
834 // classes because the mode may output tokens that influence these
835 // classes.
836 function updateLineText(cm, lineView) {
837 var cls = lineView.text.className;
838 var built = getLineContent(cm, lineView);
839 if (lineView.text == lineView.node) lineView.node = built.pre;
840 lineView.text.parentNode.replaceChild(built.pre, lineView.text);
841 lineView.text = built.pre;
842 if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {
843 lineView.bgClass = built.bgClass;
844 lineView.textClass = built.textClass;
845 updateLineClasses(lineView);
846 } else if (cls) {
847 lineView.text.className = cls;
848 }
849 }
850
851 function updateLineClasses(lineView) {
852 updateLineBackground(lineView);
853 if (lineView.line.wrapClass)
854 ensureLineWrapped(lineView).className = lineView.line.wrapClass;
855 else if (lineView.node != lineView.text)
856 lineView.node.className = "";
857 var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass;
858 lineView.text.className = textClass || "";
859 }
860
861 function updateLineGutter(cm, lineView, lineN, dims) {
862 if (lineView.gutter) {
863 lineView.node.removeChild(lineView.gutter);
864 lineView.gutter = null;
865 }
866 var markers = lineView.line.gutterMarkers;
867 if (cm.options.lineNumbers || markers) {
868 var wrap = ensureLineWrapped(lineView);
869 var gutterWrap = lineView.gutter =
870 wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "left: " +
871 (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) +
872 "px; width: " + dims.gutterTotalWidth + "px"),
873 lineView.text);
874 if (lineView.line.gutterClass)
875 gutterWrap.className += " " + lineView.line.gutterClass;
876 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
877 lineView.lineNumber = gutterWrap.appendChild(
878 elt("div", lineNumberFor(cm.options, lineN),
879 "CodeMirror-linenumber CodeMirror-gutter-elt",
880 "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
881 + cm.display.lineNumInnerWidth + "px"));
882 if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) {
883 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
884 if (found)
885 gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
886 dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
887 }
888 }
889 }
890
891 function updateLineWidgets(lineView, dims) {
892 if (lineView.alignable) lineView.alignable = null;
893 for (var node = lineView.node.firstChild, next; node; node = next) {
894 var next = node.nextSibling;
895 if (node.className == "CodeMirror-linewidget")
896 lineView.node.removeChild(node);
897 }
898 insertLineWidgets(lineView, dims);
899 }
900
901 // Build a line's DOM representation from scratch
902 function buildLineElement(cm, lineView, lineN, dims) {
903 var built = getLineContent(cm, lineView);
904 lineView.text = lineView.node = built.pre;
905 if (built.bgClass) lineView.bgClass = built.bgClass;
906 if (built.textClass) lineView.textClass = built.textClass;
907
908 updateLineClasses(lineView);
909 updateLineGutter(cm, lineView, lineN, dims);
910 insertLineWidgets(lineView, dims);
911 return lineView.node;
912 }
913
914 // A lineView may contain multiple logical lines (when merged by
915 // collapsed spans). The widgets for all of them need to be drawn.
916 function insertLineWidgets(lineView, dims) {
917 insertLineWidgetsFor(lineView.line, lineView, dims, true);
918 if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
919 insertLineWidgetsFor(lineView.rest[i], lineView, dims, false);
920 }
921
922 function insertLineWidgetsFor(line, lineView, dims, allowAbove) {
923 if (!line.widgets) return;
924 var wrap = ensureLineWrapped(lineView);
925 for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
926 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
927 if (!widget.handleMouseEvents) node.ignoreEvents = true;
928 positionLineWidget(widget, node, lineView, dims);
929 if (allowAbove && widget.above)
930 wrap.insertBefore(node, lineView.gutter || lineView.text);
931 else
932 wrap.appendChild(node);
933 signalLater(widget, "redraw");
934 }
935 }
936
937 function positionLineWidget(widget, node, lineView, dims) {
938 if (widget.noHScroll) {
939 (lineView.alignable || (lineView.alignable = [])).push(node);
940 var width = dims.wrapperWidth;
941 node.style.left = dims.fixedPos + "px";
942 if (!widget.coverGutter) {
943 width -= dims.gutterTotalWidth;
944 node.style.paddingLeft = dims.gutterTotalWidth + "px";
945 }
946 node.style.width = width + "px";
947 }
948 if (widget.coverGutter) {
949 node.style.zIndex = 5;
950 node.style.position = "relative";
951 if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
952 }
953 }
954
955 // POSITION OBJECT
956
957 // A Pos instance represents a position within the text.
958 var Pos = CodeMirror.Pos = function(line, ch) {
959 if (!(this instanceof Pos)) return new Pos(line, ch);
960 this.line = line; this.ch = ch;
961 };
962
963 // Compare two positions, return 0 if they are the same, a negative
964 // number when a is less, and a positive number otherwise.
965 var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; };
966
967 function copyPos(x) {return Pos(x.line, x.ch);}
968 function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
969 function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }
970
971 // SELECTION / CURSOR
972
973 // Selection objects are immutable. A new one is created every time
974 // the selection changes. A selection is one or more non-overlapping
975 // (and non-touching) ranges, sorted, and an integer that indicates
976 // which one is the primary selection (the one that's scrolled into
977 // view, that getCursor returns, etc).
978 function Selection(ranges, primIndex) {
979 this.ranges = ranges;
980 this.primIndex = primIndex;
981 }
982
983 Selection.prototype = {
984 primary: function() { return this.ranges[this.primIndex]; },
985 equals: function(other) {
986 if (other == this) return true;
987 if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false;
988 for (var i = 0; i < this.ranges.length; i++) {
989 var here = this.ranges[i], there = other.ranges[i];
990 if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false;
991 }
992 return true;
993 },
994 deepCopy: function() {
995 for (var out = [], i = 0; i < this.ranges.length; i++)
996 out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head));
997 return new Selection(out, this.primIndex);
998 },
999 somethingSelected: function() {
1000 for (var i = 0; i < this.ranges.length; i++)
1001 if (!this.ranges[i].empty()) return true;
1002 return false;
1003 },
1004 contains: function(pos, end) {
1005 if (!end) end = pos;
1006 for (var i = 0; i < this.ranges.length; i++) {
1007 var range = this.ranges[i];
1008 if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
1009 return i;
1010 }
1011 return -1;
1012 }
1013 };
1014
1015 function Range(anchor, head) {
1016 this.anchor = anchor; this.head = head;
1017 }
1018
1019 Range.prototype = {
1020 from: function() { return minPos(this.anchor, this.head); },
1021 to: function() { return maxPos(this.anchor, this.head); },
1022 empty: function() {
1023 return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch;
1024 }
1025 };
1026
1027 // Take an unsorted, potentially overlapping set of ranges, and
1028 // build a selection out of it. 'Consumes' ranges array (modifying
1029 // it).
1030 function normalizeSelection(ranges, primIndex) {
1031 var prim = ranges[primIndex];
1032 ranges.sort(function(a, b) { return cmp(a.from(), b.from()); });
1033 primIndex = indexOf(ranges, prim);
1034 for (var i = 1; i < ranges.length; i++) {
1035 var cur = ranges[i], prev = ranges[i - 1];
1036 if (cmp(prev.to(), cur.from()) >= 0) {
1037 var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to());
1038 var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head;
1039 if (i <= primIndex) --primIndex;
1040 ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));
1041 }
1042 }
1043 return new Selection(ranges, primIndex);
1044 }
1045
1046 function simpleSelection(anchor, head) {
1047 return new Selection([new Range(anchor, head || anchor)], 0);
1048 }
1049
1050 // Most of the external API clips given positions to make sure they
1051 // actually exist within the document.
1052 function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
1053 function clipPos(doc, pos) {
1054 if (pos.line < doc.first) return Pos(doc.first, 0);
1055 var last = doc.first + doc.size - 1;
1056 if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
1057 return clipToLen(pos, getLine(doc, pos.line).text.length);
1058 }
1059 function clipToLen(pos, linelen) {
1060 var ch = pos.ch;
1061 if (ch == null || ch > linelen) return Pos(pos.line, linelen);
1062 else if (ch < 0) return Pos(pos.line, 0);
1063 else return pos;
1064 }
1065 function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
1066 function clipPosArray(doc, array) {
1067 for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]);
1068 return out;
1069 }
1070
1071 // SELECTION UPDATES
1072
1073 // The 'scroll' parameter given to many of these indicated whether
1074 // the new cursor position should be scrolled into view after
1075 // modifying the selection.
1076
1077 // If shift is held or the extend flag is set, extends a range to
1078 // include a given position (and optionally a second position).
1079 // Otherwise, simply returns the range between the given positions.
1080 // Used for cursor motion and such.
1081 function extendRange(doc, range, head, other) {
1082 if (doc.cm && doc.cm.display.shift || doc.extend) {
1083 var anchor = range.anchor;
1084 if (other) {
1085 var posBefore = cmp(head, anchor) < 0;
1086 if (posBefore != (cmp(other, anchor) < 0)) {
1087 anchor = head;
1088 head = other;
1089 } else if (posBefore != (cmp(head, other) < 0)) {
1090 head = other;
1091 }
1092 }
1093 return new Range(anchor, head);
1094 } else {
1095 return new Range(other || head, head);
1096 }
1097 }
1098
1099 // Extend the primary selection range, discard the rest.
1100 function extendSelection(doc, head, other, options) {
1101 setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options);
1102 }
1103
1104 // Extend all selections (pos is an array of selections with length
1105 // equal the number of selections)
1106 function extendSelections(doc, heads, options) {
1107 for (var out = [], i = 0; i < doc.sel.ranges.length; i++)
1108 out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null);
1109 var newSel = normalizeSelection(out, doc.sel.primIndex);
1110 setSelection(doc, newSel, options);
1111 }
1112
1113 // Updates a single range in the selection.
1114 function replaceOneSelection(doc, i, range, options) {
1115 var ranges = doc.sel.ranges.slice(0);
1116 ranges[i] = range;
1117 setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options);
1118 }
1119
1120 // Reset the selection to a single range.
1121 function setSimpleSelection(doc, anchor, head, options) {
1122 setSelection(doc, simpleSelection(anchor, head), options);
1123 }
1124
1125 // Give beforeSelectionChange handlers a change to influence a
1126 // selection update.
1127 function filterSelectionChange(doc, sel) {
1128 var obj = {
1129 ranges: sel.ranges,
1130 update: function(ranges) {
1131 this.ranges = [];
1132 for (var i = 0; i < ranges.length; i++)
1133 this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
1134 clipPos(doc, ranges[i].head));
1135 }
1136 };
1137 signal(doc, "beforeSelectionChange", doc, obj);
1138 if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
1139 if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1);
1140 else return sel;
1141 }
1142
1143 function setSelectionReplaceHistory(doc, sel, options) {
1144 var done = doc.history.done, last = lst(done);
1145 if (last && last.ranges) {
1146 done[done.length - 1] = sel;
1147 setSelectionNoUndo(doc, sel, options);
1148 } else {
1149 setSelection(doc, sel, options);
1150 }
1151 }
1152
1153 // Set a new selection.
1154 function setSelection(doc, sel, options) {
1155 setSelectionNoUndo(doc, sel, options);
1156 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);
1157 }
1158
1159 function setSelectionNoUndo(doc, sel, options) {
1160 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
1161 sel = filterSelectionChange(doc, sel);
1162
1163 var bias = options && options.bias ||
1164 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
1165 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
1166
1167 if (!(options && options.scroll === false) && doc.cm)
1168 ensureCursorVisible(doc.cm);
1169 }
1170
1171 function setSelectionInner(doc, sel) {
1172 if (sel.equals(doc.sel)) return;
1173
1174 doc.sel = sel;
1175
1176 if (doc.cm) {
1177 doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
1178 signalCursorActivity(doc.cm);
1179 }
1180 signalLater(doc, "cursorActivity", doc);
1181 }
1182
1183 // Verify that the selection does not partially select any atomic
1184 // marked ranges.
1185 function reCheckSelection(doc) {
1186 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll);
1187 }
1188
1189 // Return a selection that does not partially select any atomic
1190 // ranges.
1191 function skipAtomicInSelection(doc, sel, bias, mayClear) {
1192 var out;
1193 for (var i = 0; i < sel.ranges.length; i++) {
1194 var range = sel.ranges[i];
1195 var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear);
1196 var newHead = skipAtomic(doc, range.head, bias, mayClear);
1197 if (out || newAnchor != range.anchor || newHead != range.head) {
1198 if (!out) out = sel.ranges.slice(0, i);
1199 out[i] = new Range(newAnchor, newHead);
1200 }
1201 }
1202 return out ? normalizeSelection(out, sel.primIndex) : sel;
1203 }
1204
1205 // Ensure a given position is not inside an atomic range.
1206 function skipAtomic(doc, pos, bias, mayClear) {
1207 var flipped = false, curPos = pos;
1208 var dir = bias || 1;
1209 doc.cantEdit = false;
1210 search: for (;;) {
1211 var line = getLine(doc, curPos.line);
1212 if (line.markedSpans) {
1213 for (var i = 0; i < line.markedSpans.length; ++i) {
1214 var sp = line.markedSpans[i], m = sp.marker;
1215 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
1216 (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
1217 if (mayClear) {
1218 signal(m, "beforeCursorEnter");
1219 if (m.explicitlyCleared) {
1220 if (!line.markedSpans) break;
1221 else {--i; continue;}
1222 }
1223 }
1224 if (!m.atomic) continue;
1225 var newPos = m.find(dir < 0 ? -1 : 1);
1226 if (cmp(newPos, curPos) == 0) {
1227 newPos.ch += dir;
1228 if (newPos.ch < 0) {
1229 if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
1230 else newPos = null;
1231 } else if (newPos.ch > line.text.length) {
1232 if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
1233 else newPos = null;
1234 }
1235 if (!newPos) {
1236 if (flipped) {
1237 // Driven in a corner -- no valid cursor position found at all
1238 // -- try again *with* clearing, if we didn't already
1239 if (!mayClear) return skipAtomic(doc, pos, bias, true);
1240 // Otherwise, turn off editing until further notice, and return the start of the doc
1241 doc.cantEdit = true;
1242 return Pos(doc.first, 0);
1243 }
1244 flipped = true; newPos = pos; dir = -dir;
1245 }
1246 }
1247 curPos = newPos;
1248 continue search;
1249 }
1250 }
1251 }
1252 return curPos;
1253 }
1254 }
1255
1256 // SELECTION DRAWING
1257
1258 // Redraw the selection and/or cursor
1259 function drawSelection(cm) {
1260 var display = cm.display, doc = cm.doc, result = {};
1261 var curFragment = result.cursors = document.createDocumentFragment();
1262 var selFragment = result.selection = document.createDocumentFragment();
1263
1264 for (var i = 0; i < doc.sel.ranges.length; i++) {
1265 var range = doc.sel.ranges[i];
1266 var collapsed = range.empty();
1267 if (collapsed || cm.options.showCursorWhenSelecting)
1268 drawSelectionCursor(cm, range, curFragment);
1269 if (!collapsed)
1270 drawSelectionRange(cm, range, selFragment);
1271 }
1272
1273 // Move the hidden textarea near the cursor to prevent scrolling artifacts
1274 if (cm.options.moveInputWithCursor) {
1275 var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
1276 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
1277 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
1278 headPos.top + lineOff.top - wrapOff.top));
1279 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
1280 headPos.left + lineOff.left - wrapOff.left));
1281 }
1282
1283 return result;
1284 }
1285
1286 function showSelection(cm, drawn) {
1287 removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors);
1288 removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection);
1289 if (drawn.teTop != null) {
1290 cm.display.inputDiv.style.top = drawn.teTop + "px";
1291 cm.display.inputDiv.style.left = drawn.teLeft + "px";
1292 }
1293 }
1294
1295 function updateSelection(cm) {
1296 showSelection(cm, drawSelection(cm));
1297 }
1298
1299 // Draws a cursor for the given range
1300 function drawSelectionCursor(cm, range, output) {
1301 var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine);
1302
1303 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
1304 cursor.style.left = pos.left + "px";
1305 cursor.style.top = pos.top + "px";
1306 cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
1307
1308 if (pos.other) {
1309 // Secondary cursor, shown when on a 'jump' in bi-directional text
1310 var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"));
1311 otherCursor.style.display = "";
1312 otherCursor.style.left = pos.other.left + "px";
1313 otherCursor.style.top = pos.other.top + "px";
1314 otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
1315 }
1316 }
1317
1318 // Draws the given range as a highlighted selection
1319 function drawSelectionRange(cm, range, output) {
1320 var display = cm.display, doc = cm.doc;
1321 var fragment = document.createDocumentFragment();
1322 var padding = paddingH(cm.display), leftSide = padding.left, rightSide = display.lineSpace.offsetWidth - padding.right;
1323
1324 function add(left, top, width, bottom) {
1325 if (top < 0) top = 0;
1326 top = Math.round(top);
1327 bottom = Math.round(bottom);
1328 fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
1329 "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) +
1330 "px; height: " + (bottom - top) + "px"));
1331 }
1332
1333 function drawForLine(line, fromArg, toArg) {
1334 var lineObj = getLine(doc, line);
1335 var lineLen = lineObj.text.length;
1336 var start, end;
1337 function coords(ch, bias) {
1338 return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
1339 }
1340
1341 iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
1342 var leftPos = coords(from, "left"), rightPos, left, right;
1343 if (from == to) {
1344 rightPos = leftPos;
1345 left = right = leftPos.left;
1346 } else {
1347 rightPos = coords(to - 1, "right");
1348 if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
1349 left = leftPos.left;
1350 right = rightPos.right;
1351 }
1352 if (fromArg == null && from == 0) left = leftSide;
1353 if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
1354 add(left, leftPos.top, null, leftPos.bottom);
1355 left = leftSide;
1356 if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
1357 }
1358 if (toArg == null && to == lineLen) right = rightSide;
1359 if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
1360 start = leftPos;
1361 if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
1362 end = rightPos;
1363 if (left < leftSide + 1) left = leftSide;
1364 add(left, rightPos.top, right - left, rightPos.bottom);
1365 });
1366 return {start: start, end: end};
1367 }
1368
1369 var sFrom = range.from(), sTo = range.to();
1370 if (sFrom.line == sTo.line) {
1371 drawForLine(sFrom.line, sFrom.ch, sTo.ch);
1372 } else {
1373 var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);
1374 var singleVLine = visualLine(fromLine) == visualLine(toLine);
1375 var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end;
1376 var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start;
1377 if (singleVLine) {
1378 if (leftEnd.top < rightStart.top - 2) {
1379 add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
1380 add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);
1381 } else {
1382 add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
1383 }
1384 }
1385 if (leftEnd.bottom < rightStart.top)
1386 add(leftSide, leftEnd.bottom, null, rightStart.top);
1387 }
1388
1389 output.appendChild(fragment);
1390 }
1391
1392 // Cursor-blinking
1393 function restartBlink(cm) {
1394 if (!cm.state.focused) return;
1395 var display = cm.display;
1396 clearInterval(display.blinker);
1397 var on = true;
1398 display.cursorDiv.style.visibility = "";
1399 if (cm.options.cursorBlinkRate > 0)
1400 display.blinker = setInterval(function() {
1401 display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden";
1402 }, cm.options.cursorBlinkRate);
1403 else if (cm.options.cursorBlinkRate < 0)
1404 display.cursorDiv.style.visibility = "hidden";
1405 }
1406
1407 // HIGHLIGHT WORKER
1408
1409 function startWorker(cm, time) {
1410 if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo)
1411 cm.state.highlight.set(time, bind(highlightWorker, cm));
1412 }
1413
1414 function highlightWorker(cm) {
1415 var doc = cm.doc;
1416 if (doc.frontier < doc.first) doc.frontier = doc.first;
1417 if (doc.frontier >= cm.display.viewTo) return;
1418 var end = +new Date + cm.options.workTime;
1419 var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
1420 var changedLines = [];
1421
1422 doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) {
1423 if (doc.frontier >= cm.display.viewFrom) { // Visible
1424 var oldStyles = line.styles;
1425 var highlighted = highlightLine(cm, line, state, true);
1426 line.styles = highlighted.styles;
1427 var oldCls = line.styleClasses, newCls = highlighted.classes;
1428 if (newCls) line.styleClasses = newCls;
1429 else if (oldCls) line.styleClasses = null;
1430 var ischange = !oldStyles || oldStyles.length != line.styles.length ||
1431 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass);
1432 for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
1433 if (ischange) changedLines.push(doc.frontier);
1434 line.stateAfter = copyState(doc.mode, state);
1435 } else {
1436 processLine(cm, line.text, state);
1437 line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
1438 }
1439 ++doc.frontier;
1440 if (+new Date > end) {
1441 startWorker(cm, cm.options.workDelay);
1442 return true;
1443 }
1444 });
1445 if (changedLines.length) runInOp(cm, function() {
1446 for (var i = 0; i < changedLines.length; i++)
1447 regLineChange(cm, changedLines[i], "text");
1448 });
1449 }
1450
1451 // Finds the line to start with when starting a parse. Tries to
1452 // find a line with a stateAfter, so that it can start with a
1453 // valid state. If that fails, it returns the line with the
1454 // smallest indentation, which tends to need the least context to
1455 // parse correctly.
1456 function findStartLine(cm, n, precise) {
1457 var minindent, minline, doc = cm.doc;
1458 var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);
1459 for (var search = n; search > lim; --search) {
1460 if (search <= doc.first) return doc.first;
1461 var line = getLine(doc, search - 1);
1462 if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
1463 var indented = countColumn(line.text, null, cm.options.tabSize);
1464 if (minline == null || minindent > indented) {
1465 minline = search - 1;
1466 minindent = indented;
1467 }
1468 }
1469 return minline;
1470 }
1471
1472 function getStateBefore(cm, n, precise) {
1473 var doc = cm.doc, display = cm.display;
1474 if (!doc.mode.startState) return true;
1475 var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
1476 if (!state) state = startState(doc.mode);
1477 else state = copyState(doc.mode, state);
1478 doc.iter(pos, n, function(line) {
1479 processLine(cm, line.text, state);
1480 var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo;
1481 line.stateAfter = save ? copyState(doc.mode, state) : null;
1482 ++pos;
1483 });
1484 if (precise) doc.frontier = pos;
1485 return state;
1486 }
1487
1488 // POSITION MEASUREMENT
1489
1490 function paddingTop(display) {return display.lineSpace.offsetTop;}
1491 function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
1492 function paddingH(display) {
1493 if (display.cachedPaddingH) return display.cachedPaddingH;
1494 var e = removeChildrenAndAdd(display.measure, elt("pre", "x"));
1495 var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;
1496 var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};
1497 if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data;
1498 return data;
1499 }
1500
1501 // Ensure the lineView.wrapping.heights array is populated. This is
1502 // an array of bottom offsets for the lines that make up a drawn
1503 // line. When lineWrapping is on, there might be more than one
1504 // height.
1505 function ensureLineHeights(cm, lineView, rect) {
1506 var wrapping = cm.options.lineWrapping;
1507 var curWidth = wrapping && cm.display.scroller.clientWidth;
1508 if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
1509 var heights = lineView.measure.heights = [];
1510 if (wrapping) {
1511 lineView.measure.width = curWidth;
1512 var rects = lineView.text.firstChild.getClientRects();
1513 for (var i = 0; i < rects.length - 1; i++) {
1514 var cur = rects[i], next = rects[i + 1];
1515 if (Math.abs(cur.bottom - next.bottom) > 2)
1516 heights.push((cur.bottom + next.top) / 2 - rect.top);
1517 }
1518 }
1519 heights.push(rect.bottom - rect.top);
1520 }
1521 }
1522
1523 // Find a line map (mapping character offsets to text nodes) and a
1524 // measurement cache for the given line number. (A line view might
1525 // contain multiple lines when collapsed ranges are present.)
1526 function mapFromLineView(lineView, line, lineN) {
1527 if (lineView.line == line)
1528 return {map: lineView.measure.map, cache: lineView.measure.cache};
1529 for (var i = 0; i < lineView.rest.length; i++)
1530 if (lineView.rest[i] == line)
1531 return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]};
1532 for (var i = 0; i < lineView.rest.length; i++)
1533 if (lineNo(lineView.rest[i]) > lineN)
1534 return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true};
1535 }
1536
1537 // Render a line into the hidden node display.externalMeasured. Used
1538 // when measurement is needed for a line that's not in the viewport.
1539 function updateExternalMeasurement(cm, line) {
1540 line = visualLine(line);
1541 var lineN = lineNo(line);
1542 var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);
1543 view.lineN = lineN;
1544 var built = view.built = buildLineContent(cm, view);
1545 view.text = built.pre;
1546 removeChildrenAndAdd(cm.display.lineMeasure, built.pre);
1547 return view;
1548 }
1549
1550 // Get a {top, bottom, left, right} box (in line-local coordinates)
1551 // for a given character.
1552 function measureChar(cm, line, ch, bias) {
1553 return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias);
1554 }
1555
1556 // Find a line view that corresponds to the given line number.
1557 function findViewForLine(cm, lineN) {
1558 if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
1559 return cm.display.view[findViewIndex(cm, lineN)];
1560 var ext = cm.display.externalMeasured;
1561 if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
1562 return ext;
1563 }
1564
1565 // Measurement can be split in two steps, the set-up work that
1566 // applies to the whole line, and the measurement of the actual
1567 // character. Functions like coordsChar, that need to do a lot of
1568 // measurements in a row, can thus ensure that the set-up work is
1569 // only done once.
1570 function prepareMeasureForLine(cm, line) {
1571 var lineN = lineNo(line);
1572 var view = findViewForLine(cm, lineN);
1573 if (view && !view.text)
1574 view = null;
1575 else if (view && view.changes)
1576 updateLineForChanges(cm, view, lineN, getDimensions(cm));
1577 if (!view)
1578 view = updateExternalMeasurement(cm, line);
1579
1580 var info = mapFromLineView(view, line, lineN);
1581 return {
1582 line: line, view: view, rect: null,
1583 map: info.map, cache: info.cache, before: info.before,
1584 hasHeights: false
1585 };
1586 }
1587
1588 // Given a prepared measurement object, measures the position of an
1589 // actual character (or fetches it from the cache).
1590 function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
1591 if (prepared.before) ch = -1;
1592 var key = ch + (bias || ""), found;
1593 if (prepared.cache.hasOwnProperty(key)) {
1594 found = prepared.cache[key];
1595 } else {
1596 if (!prepared.rect)
1597 prepared.rect = prepared.view.text.getBoundingClientRect();
1598 if (!prepared.hasHeights) {
1599 ensureLineHeights(cm, prepared.view, prepared.rect);
1600 prepared.hasHeights = true;
1601 }
1602 found = measureCharInner(cm, prepared, ch, bias);
1603 if (!found.bogus) prepared.cache[key] = found;
1604 }
1605 return {left: found.left, right: found.right,
1606 top: varHeight ? found.rtop : found.top,
1607 bottom: varHeight ? found.rbottom : found.bottom};
1608 }
1609
1610 var nullRect = {left: 0, right: 0, top: 0, bottom: 0};
1611
1612 function measureCharInner(cm, prepared, ch, bias) {
1613 var map = prepared.map;
1614
1615 var node, start, end, collapse;
1616 // First, search the line map for the text node corresponding to,
1617 // or closest to, the target character.
1618 for (var i = 0; i < map.length; i += 3) {
1619 var mStart = map[i], mEnd = map[i + 1];
1620 if (ch < mStart) {
1621 start = 0; end = 1;
1622 collapse = "left";
1623 } else if (ch < mEnd) {
1624 start = ch - mStart;
1625 end = start + 1;
1626 } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {
1627 end = mEnd - mStart;
1628 start = end - 1;
1629 if (ch >= mEnd) collapse = "right";
1630 }
1631 if (start != null) {
1632 node = map[i + 2];
1633 if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
1634 collapse = bias;
1635 if (bias == "left" && start == 0)
1636 while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {
1637 node = map[(i -= 3) + 2];
1638 collapse = "left";
1639 }
1640 if (bias == "right" && start == mEnd - mStart)
1641 while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) {
1642 node = map[(i += 3) + 2];
1643 collapse = "right";
1644 }
1645 break;
1646 }
1647 }
1648
1649 var rect;
1650 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
1651 for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned
1652 while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start;
1653 while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end;
1654 if (ie && ie_version < 9 && start == 0 && end == mEnd - mStart) {
1655 rect = node.parentNode.getBoundingClientRect();
1656 } else if (ie && cm.options.lineWrapping) {
1657 var rects = range(node, start, end).getClientRects();
1658 if (rects.length)
1659 rect = rects[bias == "right" ? rects.length - 1 : 0];
1660 else
1661 rect = nullRect;
1662 } else {
1663 rect = range(node, start, end).getBoundingClientRect() || nullRect;
1664 }
1665 if (rect.left || rect.right || start == 0) break;
1666 end = start;
1667 start = start - 1;
1668 collapse = "right";
1669 }
1670 if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect);
1671 } else { // If it is a widget, simply get the box for the whole widget.
1672 if (start > 0) collapse = bias = "right";
1673 var rects;
1674 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
1675 rect = rects[bias == "right" ? rects.length - 1 : 0];
1676 else
1677 rect = node.getBoundingClientRect();
1678 }
1679 if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {
1680 var rSpan = node.parentNode.getClientRects()[0];
1681 if (rSpan)
1682 rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom};
1683 else
1684 rect = nullRect;
1685 }
1686
1687 var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top;
1688 var mid = (rtop + rbot) / 2;
1689 var heights = prepared.view.measure.heights;
1690 for (var i = 0; i < heights.length - 1; i++)
1691 if (mid < heights[i]) break;
1692 var top = i ? heights[i - 1] : 0, bot = heights[i];
1693 var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left,
1694 right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left,
1695 top: top, bottom: bot};
1696 if (!rect.left && !rect.right) result.bogus = true;
1697 if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; }
1698
1699 return result;
1700 }
1701
1702 // Work around problem with bounding client rects on ranges being
1703 // returned incorrectly when zoomed on IE10 and below.
1704 function maybeUpdateRectForZooming(measure, rect) {
1705 if (!window.screen || screen.logicalXDPI == null ||
1706 screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
1707 return rect;
1708 var scaleX = screen.logicalXDPI / screen.deviceXDPI;
1709 var scaleY = screen.logicalYDPI / screen.deviceYDPI;
1710 return {left: rect.left * scaleX, right: rect.right * scaleX,
1711 top: rect.top * scaleY, bottom: rect.bottom * scaleY};
1712 }
1713
1714 function clearLineMeasurementCacheFor(lineView) {
1715 if (lineView.measure) {
1716 lineView.measure.cache = {};
1717 lineView.measure.heights = null;
1718 if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
1719 lineView.measure.caches[i] = {};
1720 }
1721 }
1722
1723 function clearLineMeasurementCache(cm) {
1724 cm.display.externalMeasure = null;
1725 removeChildren(cm.display.lineMeasure);
1726 for (var i = 0; i < cm.display.view.length; i++)
1727 clearLineMeasurementCacheFor(cm.display.view[i]);
1728 }
1729
1730 function clearCaches(cm) {
1731 clearLineMeasurementCache(cm);
1732 cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null;
1733 if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
1734 cm.display.lineNumChars = null;
1735 }
1736
1737 function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
1738 function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
1739
1740 // Converts a {top, bottom, left, right} box from line-local
1741 // coordinates into another coordinate system. Context may be one of
1742 // "line", "div" (display.lineDiv), "local"/null (editor), or "page".
1743 function intoCoordSystem(cm, lineObj, rect, context) {
1744 if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
1745 var size = widgetHeight(lineObj.widgets[i]);
1746 rect.top += size; rect.bottom += size;
1747 }
1748 if (context == "line") return rect;
1749 if (!context) context = "local";
1750 var yOff = heightAtLine(lineObj);
1751 if (context == "local") yOff += paddingTop(cm.display);
1752 else yOff -= cm.display.viewOffset;
1753 if (context == "page" || context == "window") {
1754 var lOff = cm.display.lineSpace.getBoundingClientRect();
1755 yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
1756 var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
1757 rect.left += xOff; rect.right += xOff;
1758 }
1759 rect.top += yOff; rect.bottom += yOff;
1760 return rect;
1761 }
1762
1763 // Coverts a box from "div" coords to another coordinate system.
1764 // Context may be "window", "page", "div", or "local"/null.
1765 function fromCoordSystem(cm, coords, context) {
1766 if (context == "div") return coords;
1767 var left = coords.left, top = coords.top;
1768 // First move into "page" coordinate system
1769 if (context == "page") {
1770 left -= pageScrollX();
1771 top -= pageScrollY();
1772 } else if (context == "local" || !context) {
1773 var localBox = cm.display.sizer.getBoundingClientRect();
1774 left += localBox.left;
1775 top += localBox.top;
1776 }
1777
1778 var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();
1779 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
1780 }
1781
1782 function charCoords(cm, pos, context, lineObj, bias) {
1783 if (!lineObj) lineObj = getLine(cm.doc, pos.line);
1784 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context);
1785 }
1786
1787 // Returns a box for a given cursor position, which may have an
1788 // 'other' property containing the position of the secondary cursor
1789 // on a bidi boundary.
1790 function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {
1791 lineObj = lineObj || getLine(cm.doc, pos.line);
1792 if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj);
1793 function get(ch, right) {
1794 var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight);
1795 if (right) m.left = m.right; else m.right = m.left;
1796 return intoCoordSystem(cm, lineObj, m, context);
1797 }
1798 function getBidi(ch, partPos) {
1799 var part = order[partPos], right = part.level % 2;
1800 if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
1801 part = order[--partPos];
1802 ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
1803 right = true;
1804 } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
1805 part = order[++partPos];
1806 ch = bidiLeft(part) - part.level % 2;
1807 right = false;
1808 }
1809 if (right && ch == part.to && ch > part.from) return get(ch - 1);
1810 return get(ch, right);
1811 }
1812 var order = getOrder(lineObj), ch = pos.ch;
1813 if (!order) return get(ch);
1814 var partPos = getBidiPartAt(order, ch);
1815 var val = getBidi(ch, partPos);
1816 if (bidiOther != null) val.other = getBidi(ch, bidiOther);
1817 return val;
1818 }
1819
1820 // Used to cheaply estimate the coordinates for a position. Used for
1821 // intermediate scroll updates.
1822 function estimateCoords(cm, pos) {
1823 var left = 0, pos = clipPos(cm.doc, pos);
1824 if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch;
1825 var lineObj = getLine(cm.doc, pos.line);
1826 var top = heightAtLine(lineObj) + paddingTop(cm.display);
1827 return {left: left, right: left, top: top, bottom: top + lineObj.height};
1828 }
1829
1830 // Positions returned by coordsChar contain some extra information.
1831 // xRel is the relative x position of the input coordinates compared
1832 // to the found position (so xRel > 0 means the coordinates are to
1833 // the right of the character position, for example). When outside
1834 // is true, that means the coordinates lie outside the line's
1835 // vertical range.
1836 function PosWithInfo(line, ch, outside, xRel) {
1837 var pos = Pos(line, ch);
1838 pos.xRel = xRel;
1839 if (outside) pos.outside = true;
1840 return pos;
1841 }
1842
1843 // Compute the character position closest to the given coordinates.
1844 // Input must be lineSpace-local ("div" coordinate system).
1845 function coordsChar(cm, x, y) {
1846 var doc = cm.doc;
1847 y += cm.display.viewOffset;
1848 if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
1849 var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
1850 if (lineN > last)
1851 return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
1852 if (x < 0) x = 0;
1853
1854 var lineObj = getLine(doc, lineN);
1855 for (;;) {
1856 var found = coordsCharInner(cm, lineObj, lineN, x, y);
1857 var merged = collapsedSpanAtEnd(lineObj);
1858 var mergedPos = merged && merged.find(0, true);
1859 if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
1860 lineN = lineNo(lineObj = mergedPos.to.line);
1861 else
1862 return found;
1863 }
1864 }
1865
1866 function coordsCharInner(cm, lineObj, lineNo, x, y) {
1867 var innerOff = y - heightAtLine(lineObj);
1868 var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
1869 var preparedMeasure = prepareMeasureForLine(cm, lineObj);
1870
1871 function getX(ch) {
1872 var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure);
1873 wrongLine = true;
1874 if (innerOff > sp.bottom) return sp.left - adjust;
1875 else if (innerOff < sp.top) return sp.left + adjust;
1876 else wrongLine = false;
1877 return sp.left;
1878 }
1879
1880 var bidi = getOrder(lineObj), dist = lineObj.text.length;
1881 var from = lineLeft(lineObj), to = lineRight(lineObj);
1882 var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
1883
1884 if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
1885 // Do a binary search between these bounds.
1886 for (;;) {
1887 if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1888 var ch = x < fromX || x - fromX <= toX - x ? from : to;
1889 var xDiff = x - (ch == from ? fromX : toX);
1890 while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
1891 var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
1892 xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
1893 return pos;
1894 }
1895 var step = Math.ceil(dist / 2), middle = from + step;
1896 if (bidi) {
1897 middle = from;
1898 for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1899 }
1900 var middleX = getX(middle);
1901 if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
1902 else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
1903 }
1904 }
1905
1906 var measureText;
1907 // Compute the default text height.
1908 function textHeight(display) {
1909 if (display.cachedTextHeight != null) return display.cachedTextHeight;
1910 if (measureText == null) {
1911 measureText = elt("pre");
1912 // Measure a bunch of lines, for browsers that compute
1913 // fractional heights.
1914 for (var i = 0; i < 49; ++i) {
1915 measureText.appendChild(document.createTextNode("x"));
1916 measureText.appendChild(elt("br"));
1917 }
1918 measureText.appendChild(document.createTextNode("x"));
1919 }
1920 removeChildrenAndAdd(display.measure, measureText);
1921 var height = measureText.offsetHeight / 50;
1922 if (height > 3) display.cachedTextHeight = height;
1923 removeChildren(display.measure);
1924 return height || 1;
1925 }
1926
1927 // Compute the default character width.
1928 function charWidth(display) {
1929 if (display.cachedCharWidth != null) return display.cachedCharWidth;
1930 var anchor = elt("span", "xxxxxxxxxx");
1931 var pre = elt("pre", [anchor]);
1932 removeChildrenAndAdd(display.measure, pre);
1933 var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;
1934 if (width > 2) display.cachedCharWidth = width;
1935 return width || 10;
1936 }
1937
1938 // OPERATIONS
1939
1940 // Operations are used to wrap a series of changes to the editor
1941 // state in such a way that each change won't have to update the
1942 // cursor and display (which would be awkward, slow, and
1943 // error-prone). Instead, display updates are batched and then all
1944 // combined and executed at once.
1945
1946 var operationGroup = null;
1947
1948 var nextOpId = 0;
1949 // Start a new operation.
1950 function startOperation(cm) {
1951 cm.curOp = {
1952 cm: cm,
1953 viewChanged: false, // Flag that indicates that lines might need to be redrawn
1954 startHeight: cm.doc.height, // Used to detect need to update scrollbar
1955 forceUpdate: false, // Used to force a redraw
1956 updateInput: null, // Whether to reset the input textarea
1957 typing: false, // Whether this reset should be careful to leave existing text (for compositing)
1958 changeObjs: null, // Accumulated changes, for firing change events
1959 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
1960 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already
1961 selectionChanged: false, // Whether the selection needs to be redrawn
1962 updateMaxLine: false, // Set when the widest line needs to be determined anew
1963 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
1964 scrollToPos: null, // Used to scroll to a specific position
1965 id: ++nextOpId // Unique ID
1966 };
1967 if (operationGroup) {
1968 operationGroup.ops.push(cm.curOp);
1969 } else {
1970 cm.curOp.ownsGroup = operationGroup = {
1971 ops: [cm.curOp],
1972 delayedCallbacks: []
1973 };
1974 }
1975 }
1976
1977 function fireCallbacksForOps(group) {
1978 // Calls delayed callbacks and cursorActivity handlers until no
1979 // new ones appear
1980 var callbacks = group.delayedCallbacks, i = 0;
1981 do {
1982 for (; i < callbacks.length; i++)
1983 callbacks[i]();
1984 for (var j = 0; j < group.ops.length; j++) {
1985 var op = group.ops[j];
1986 if (op.cursorActivityHandlers)
1987 while (op.cursorActivityCalled < op.cursorActivityHandlers.length)
1988 op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm);
1989 }
1990 } while (i < callbacks.length);
1991 }
1992
1993 // Finish an operation, updating the display and signalling delayed events
1994 function endOperation(cm) {
1995 var op = cm.curOp, group = op.ownsGroup;
1996 if (!group) return;
1997
1998 try { fireCallbacksForOps(group); }
1999 finally {
2000 operationGroup = null;
2001 for (var i = 0; i < group.ops.length; i++)
2002 group.ops[i].cm.curOp = null;
2003 endOperations(group);
2004 }
2005 }
2006
2007 // The DOM updates done when an operation finishes are batched so
2008 // that the minimum number of relayouts are required.
2009 function endOperations(group) {
2010 var ops = group.ops;
2011 for (var i = 0; i < ops.length; i++) // Read DOM
2012 endOperation_R1(ops[i]);
2013 for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
2014 endOperation_W1(ops[i]);
2015 for (var i = 0; i < ops.length; i++) // Read DOM
2016 endOperation_R2(ops[i]);
2017 for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
2018 endOperation_W2(ops[i]);
2019 for (var i = 0; i < ops.length; i++) // Read DOM
2020 endOperation_finish(ops[i]);
2021 }
2022
2023 function endOperation_R1(op) {
2024 var cm = op.cm, display = cm.display;
2025 if (op.updateMaxLine) findMaxLine(cm);
2026
2027 op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||
2028 op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
2029 op.scrollToPos.to.line >= display.viewTo) ||
2030 display.maxLineChanged && cm.options.lineWrapping;
2031 op.update = op.mustUpdate &&
2032 new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);
2033 }
2034
2035 function endOperation_W1(op) {
2036 op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update);
2037 }
2038
2039 function endOperation_R2(op) {
2040 var cm = op.cm, display = cm.display;
2041 if (op.updatedDisplay) updateHeightsInViewport(cm);
2042
2043 op.barMeasure = measureForScrollbars(cm);
2044
2045 // If the max line changed since it was last measured, measure it,
2046 // and ensure the document's width matches it.
2047 // updateDisplay_W2 will use these properties to do the actual resizing
2048 if (display.maxLineChanged && !cm.options.lineWrapping) {
2049 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3;
2050 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo +
2051 scrollerCutOff - display.scroller.clientWidth);
2052 }
2053
2054 if (op.updatedDisplay || op.selectionChanged)
2055 op.newSelectionNodes = drawSelection(cm);
2056 }
2057
2058 function endOperation_W2(op) {
2059 var cm = op.cm;
2060
2061 if (op.adjustWidthTo != null) {
2062 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px";
2063 if (op.maxScrollLeft < cm.doc.scrollLeft)
2064 setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true);
2065 cm.display.maxLineChanged = false;
2066 }
2067
2068 if (op.newSelectionNodes)
2069 showSelection(cm, op.newSelectionNodes);
2070 if (op.updatedDisplay)
2071 setDocumentHeight(cm, op.barMeasure);
2072 if (op.updatedDisplay || op.startHeight != cm.doc.height)
2073 updateScrollbars(cm, op.barMeasure);
2074
2075 if (op.selectionChanged) restartBlink(cm);
2076
2077 if (cm.state.focused && op.updateInput)
2078 resetInput(cm, op.typing);
2079 }
2080
2081 function endOperation_finish(op) {
2082 var cm = op.cm, display = cm.display, doc = cm.doc;
2083
2084 if (op.adjustWidthTo != null && Math.abs(op.barMeasure.scrollWidth - cm.display.scroller.scrollWidth) > 1)
2085 updateScrollbars(cm);
2086
2087 if (op.updatedDisplay) postUpdateDisplay(cm, op.update);
2088
2089 // Abort mouse wheel delta measurement, when scrolling explicitly
2090 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))
2091 display.wheelStartX = display.wheelStartY = null;
2092
2093 // Propagate the scroll position to the actual DOM scroller
2094 if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) {
2095 var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop));
2096 display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top;
2097 }
2098 if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) {
2099 var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft));
2100 display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left;
2101 alignHorizontally(cm);
2102 }
2103 // If we need to scroll a specific position into view, do so.
2104 if (op.scrollToPos) {
2105 var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
2106 clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin);
2107 if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords);
2108 }
2109
2110 // Fire events for markers that are hidden/unidden by editing or
2111 // undoing
2112 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
2113 if (hidden) for (var i = 0; i < hidden.length; ++i)
2114 if (!hidden[i].lines.length) signal(hidden[i], "hide");
2115 if (unhidden) for (var i = 0; i < unhidden.length; ++i)
2116 if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
2117
2118 if (display.wrapper.offsetHeight)
2119 doc.scrollTop = cm.display.scroller.scrollTop;
2120
2121 // Apply workaround for two webkit bugs
2122 if (op.updatedDisplay && webkit) {
2123 if (cm.options.lineWrapping)
2124 checkForWebkitWidthBug(cm, op.barMeasure); // (Issue #2420)
2125 if (op.barMeasure.scrollWidth > op.barMeasure.clientWidth &&
2126 op.barMeasure.scrollWidth < op.barMeasure.clientWidth + 1 &&
2127 !hScrollbarTakesSpace(cm))
2128 updateScrollbars(cm); // (Issue #2562)
2129 }
2130
2131 // Fire change events, and delayed event handlers
2132 if (op.changeObjs)
2133 signal(cm, "changes", cm, op.changeObjs);
2134 }
2135
2136 // Run the given function in an operation
2137 function runInOp(cm, f) {
2138 if (cm.curOp) return f();
2139 startOperation(cm);
2140 try { return f(); }
2141 finally { endOperation(cm); }
2142 }
2143 // Wraps a function in an operation. Returns the wrapped function.
2144 function operation(cm, f) {
2145 return function() {
2146 if (cm.curOp) return f.apply(cm, arguments);
2147 startOperation(cm);
2148 try { return f.apply(cm, arguments); }
2149 finally { endOperation(cm); }
2150 };
2151 }
2152 // Used to add methods to editor and doc instances, wrapping them in
2153 // operations.
2154 function methodOp(f) {
2155 return function() {
2156 if (this.curOp) return f.apply(this, arguments);
2157 startOperation(this);
2158 try { return f.apply(this, arguments); }
2159 finally { endOperation(this); }
2160 };
2161 }
2162 function docMethodOp(f) {
2163 return function() {
2164 var cm = this.cm;
2165 if (!cm || cm.curOp) return f.apply(this, arguments);
2166 startOperation(cm);
2167 try { return f.apply(this, arguments); }
2168 finally { endOperation(cm); }
2169 };
2170 }
2171
2172 // VIEW TRACKING
2173
2174 // These objects are used to represent the visible (currently drawn)
2175 // part of the document. A LineView may correspond to multiple
2176 // logical lines, if those are connected by collapsed ranges.
2177 function LineView(doc, line, lineN) {
2178 // The starting line
2179 this.line = line;
2180 // Continuing lines, if any
2181 this.rest = visualLineContinued(line);
2182 // Number of logical lines in this visual line
2183 this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;
2184 this.node = this.text = null;
2185 this.hidden = lineIsHidden(doc, line);
2186 }
2187
2188 // Create a range of LineView objects for the given lines.
2189 function buildViewArray(cm, from, to) {
2190 var array = [], nextPos;
2191 for (var pos = from; pos < to; pos = nextPos) {
2192 var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);
2193 nextPos = pos + view.size;
2194 array.push(view);
2195 }
2196 return array;
2197 }
2198
2199 // Updates the display.view data structure for a given change to the
2200 // document. From and to are in pre-change coordinates. Lendiff is
2201 // the amount of lines added or subtracted by the change. This is
2202 // used for changes that span multiple lines, or change the way
2203 // lines are divided into visual lines. regLineChange (below)
2204 // registers single-line changes.
2205 function regChange(cm, from, to, lendiff) {
2206 if (from == null) from = cm.doc.first;
2207 if (to == null) to = cm.doc.first + cm.doc.size;
2208 if (!lendiff) lendiff = 0;
2209
2210 var display = cm.display;
2211 if (lendiff && to < display.viewTo &&
2212 (display.updateLineNumbers == null || display.updateLineNumbers > from))
2213 display.updateLineNumbers = from;
2214
2215 cm.curOp.viewChanged = true;
2216
2217 if (from >= display.viewTo) { // Change after
2218 if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
2219 resetView(cm);
2220 } else if (to <= display.viewFrom) { // Change before
2221 if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
2222 resetView(cm);
2223 } else {
2224 display.viewFrom += lendiff;
2225 display.viewTo += lendiff;
2226 }
2227 } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
2228 resetView(cm);
2229 } else if (from <= display.viewFrom) { // Top overlap
2230 var cut = viewCuttingPoint(cm, to, to + lendiff, 1);
2231 if (cut) {
2232 display.view = display.view.slice(cut.index);
2233 display.viewFrom = cut.lineN;
2234 display.viewTo += lendiff;
2235 } else {
2236 resetView(cm);
2237 }
2238 } else if (to >= display.viewTo) { // Bottom overlap
2239 var cut = viewCuttingPoint(cm, from, from, -1);
2240 if (cut) {
2241 display.view = display.view.slice(0, cut.index);
2242 display.viewTo = cut.lineN;
2243 } else {
2244 resetView(cm);
2245 }
2246 } else { // Gap in the middle
2247 var cutTop = viewCuttingPoint(cm, from, from, -1);
2248 var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);
2249 if (cutTop && cutBot) {
2250 display.view = display.view.slice(0, cutTop.index)
2251 .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
2252 .concat(display.view.slice(cutBot.index));
2253 display.viewTo += lendiff;
2254 } else {
2255 resetView(cm);
2256 }
2257 }
2258
2259 var ext = display.externalMeasured;
2260 if (ext) {
2261 if (to < ext.lineN)
2262 ext.lineN += lendiff;
2263 else if (from < ext.lineN + ext.size)
2264 display.externalMeasured = null;
2265 }
2266 }
2267
2268 // Register a change to a single line. Type must be one of "text",
2269 // "gutter", "class", "widget"
2270 function regLineChange(cm, line, type) {
2271 cm.curOp.viewChanged = true;
2272 var display = cm.display, ext = cm.display.externalMeasured;
2273 if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
2274 display.externalMeasured = null;
2275
2276 if (line < display.viewFrom || line >= display.viewTo) return;
2277 var lineView = display.view[findViewIndex(cm, line)];
2278 if (lineView.node == null) return;
2279 var arr = lineView.changes || (lineView.changes = []);
2280 if (indexOf(arr, type) == -1) arr.push(type);
2281 }
2282
2283 // Clear the view.
2284 function resetView(cm) {
2285 cm.display.viewFrom = cm.display.viewTo = cm.doc.first;
2286 cm.display.view = [];
2287 cm.display.viewOffset = 0;
2288 }
2289
2290 // Find the view element corresponding to a given line. Return null
2291 // when the line isn't visible.
2292 function findViewIndex(cm, n) {
2293 if (n >= cm.display.viewTo) return null;
2294 n -= cm.display.viewFrom;
2295 if (n < 0) return null;
2296 var view = cm.display.view;
2297 for (var i = 0; i < view.length; i++) {
2298 n -= view[i].size;
2299 if (n < 0) return i;
2300 }
2301 }
2302
2303 function viewCuttingPoint(cm, oldN, newN, dir) {
2304 var index = findViewIndex(cm, oldN), diff, view = cm.display.view;
2305 if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
2306 return {index: index, lineN: newN};
2307 for (var i = 0, n = cm.display.viewFrom; i < index; i++)
2308 n += view[i].size;
2309 if (n != oldN) {
2310 if (dir > 0) {
2311 if (index == view.length - 1) return null;
2312 diff = (n + view[index].size) - oldN;
2313 index++;
2314 } else {
2315 diff = n - oldN;
2316 }
2317 oldN += diff; newN += diff;
2318 }
2319 while (visualLineNo(cm.doc, newN) != newN) {
2320 if (index == (dir < 0 ? 0 : view.length - 1)) return null;
2321 newN += dir * view[index - (dir < 0 ? 1 : 0)].size;
2322 index += dir;
2323 }
2324 return {index: index, lineN: newN};
2325 }
2326
2327 // Force the view to cover a given range, adding empty view element
2328 // or clipping off existing ones as needed.
2329 function adjustView(cm, from, to) {
2330 var display = cm.display, view = display.view;
2331 if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
2332 display.view = buildViewArray(cm, from, to);
2333 display.viewFrom = from;
2334 } else {
2335 if (display.viewFrom > from)
2336 display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view);
2337 else if (display.viewFrom < from)
2338 display.view = display.view.slice(findViewIndex(cm, from));
2339 display.viewFrom = from;
2340 if (display.viewTo < to)
2341 display.view = display.view.concat(buildViewArray(cm, display.viewTo, to));
2342 else if (display.viewTo > to)
2343 display.view = display.view.slice(0, findViewIndex(cm, to));
2344 }
2345 display.viewTo = to;
2346 }
2347
2348 // Count the number of lines in the view whose DOM representation is
2349 // out of date (or nonexistent).
2350 function countDirtyView(cm) {
2351 var view = cm.display.view, dirty = 0;
2352 for (var i = 0; i < view.length; i++) {
2353 var lineView = view[i];
2354 if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty;
2355 }
2356 return dirty;
2357 }
2358
2359 // INPUT HANDLING
2360
2361 // Poll for input changes, using the normal rate of polling. This
2362 // runs as long as the editor is focused.
2363 function slowPoll(cm) {
2364 if (cm.display.pollingFast) return;
2365 cm.display.poll.set(cm.options.pollInterval, function() {
2366 readInput(cm);
2367 if (cm.state.focused) slowPoll(cm);
2368 });
2369 }
2370
2371 // When an event has just come in that is likely to add or change
2372 // something in the input textarea, we poll faster, to ensure that
2373 // the change appears on the screen quickly.
2374 function fastPoll(cm) {
2375 var missed = false;
2376 cm.display.pollingFast = true;
2377 function p() {
2378 var changed = readInput(cm);
2379 if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
2380 else {cm.display.pollingFast = false; slowPoll(cm);}
2381 }
2382 cm.display.poll.set(20, p);
2383 }
2384
2385 // This will be set to an array of strings when copying, so that,
2386 // when pasting, we know what kind of selections the copied text
2387 // was made out of.
2388 var lastCopied = null;
2389
2390 // Read input from the textarea, and update the document to match.
2391 // When something is selected, it is present in the textarea, and
2392 // selected (unless it is huge, in which case a placeholder is
2393 // used). When nothing is selected, the cursor sits after previously
2394 // seen text (can be empty), which is stored in prevInput (we must
2395 // not reset the textarea when typing, because that breaks IME).
2396 function readInput(cm) {
2397 var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc;
2398 // Since this is called a *lot*, try to bail out as cheaply as
2399 // possible when it is clear that nothing happened. hasSelection
2400 // will be the case when there is a lot of text in the textarea,
2401 // in which case reading its value would be expensive.
2402 if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq)
2403 return false;
2404 // See paste handler for more on the fakedLastChar kludge
2405 if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
2406 input.value = input.value.substring(0, input.value.length - 1);
2407 cm.state.fakedLastChar = false;
2408 }
2409 var text = input.value;
2410 // If nothing changed, bail.
2411 if (text == prevInput && !cm.somethingSelected()) return false;
2412 // Work around nonsensical selection resetting in IE9/10, and
2413 // inexplicable appearance of private area unicode characters on
2414 // some key combos in Mac (#2689).
2415 if (ie && ie_version >= 9 && cm.display.inputHasSelection === text ||
2416 mac && /[\uf700-\uf7ff]/.test(text)) {
2417 resetInput(cm);
2418 return false;
2419 }
2420
2421 var withOp = !cm.curOp;
2422 if (withOp) startOperation(cm);
2423 cm.display.shift = false;
2424
2425 if (text.charCodeAt(0) == 0x200b && doc.sel == cm.display.selForContextMenu && !prevInput)
2426 prevInput = "\u200b";
2427 // Find the part of the input that is actually new
2428 var same = 0, l = Math.min(prevInput.length, text.length);
2429 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
2430 var inserted = text.slice(same), textLines = splitLines(inserted);
2431
2432 // When pasing N lines into N selections, insert one line per selection
2433 var multiPaste = null;
2434 if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) {
2435 if (lastCopied && lastCopied.join("\n") == inserted)
2436 multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines);
2437 else if (textLines.length == doc.sel.ranges.length)
2438 multiPaste = map(textLines, function(l) { return [l]; });
2439 }
2440
2441 // Normal behavior is to insert the new text into every selection
2442 for (var i = doc.sel.ranges.length - 1; i >= 0; i--) {
2443 var range = doc.sel.ranges[i];
2444 var from = range.from(), to = range.to();
2445 // Handle deletion
2446 if (same < prevInput.length)
2447 from = Pos(from.line, from.ch - (prevInput.length - same));
2448 // Handle overwrite
2449 else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming)
2450 to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
2451 var updateInput = cm.curOp.updateInput;
2452 var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines,
2453 origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
2454 makeChange(cm.doc, changeEvent);
2455 signalLater(cm, "inputRead", cm, changeEvent);
2456 // When an 'electric' character is inserted, immediately trigger a reindent
2457 if (inserted && !cm.state.pasteIncoming && cm.options.electricChars &&
2458 cm.options.smartIndent && range.head.ch < 100 &&
2459 (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) {
2460 var mode = cm.getModeAt(range.head);
2461 var end = changeEnd(changeEvent);
2462 if (mode.electricChars) {
2463 for (var j = 0; j < mode.electricChars.length; j++)
2464 if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
2465 indentLine(cm, end.line, "smart");
2466 break;
2467 }
2468 } else if (mode.electricInput) {
2469 if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch)))
2470 indentLine(cm, end.line, "smart");
2471 }
2472 }
2473 }
2474 ensureCursorVisible(cm);
2475 cm.curOp.updateInput = updateInput;
2476 cm.curOp.typing = true;
2477
2478 // Don't leave long text in the textarea, since it makes further polling slow
2479 if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
2480 else cm.display.prevInput = text;
2481 if (withOp) endOperation(cm);
2482 cm.state.pasteIncoming = cm.state.cutIncoming = false;
2483 return true;
2484 }
2485
2486 // Reset the input to correspond to the selection (or to be empty,
2487 // when not typing and nothing is selected)
2488 function resetInput(cm, typing) {
2489 var minimal, selected, doc = cm.doc;
2490 if (cm.somethingSelected()) {
2491 cm.display.prevInput = "";
2492 var range = doc.sel.primary();
2493 minimal = hasCopyEvent &&
2494 (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
2495 var content = minimal ? "-" : selected || cm.getSelection();
2496 cm.display.input.value = content;
2497 if (cm.state.focused) selectInput(cm.display.input);
2498 if (ie && ie_version >= 9) cm.display.inputHasSelection = content;
2499 } else if (!typing) {
2500 cm.display.prevInput = cm.display.input.value = "";
2501 if (ie && ie_version >= 9) cm.display.inputHasSelection = null;
2502 }
2503 cm.display.inaccurateSelection = minimal;
2504 }
2505
2506 function focusInput(cm) {
2507 if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input))
2508 cm.display.input.focus();
2509 }
2510
2511 function ensureFocus(cm) {
2512 if (!cm.state.focused) { focusInput(cm); onFocus(cm); }
2513 }
2514
2515 function isReadOnly(cm) {
2516 return cm.options.readOnly || cm.doc.cantEdit;
2517 }
2518
2519 // EVENT HANDLERS
2520
2521 // Attach the necessary event handlers when initializing the editor
2522 function registerEventHandlers(cm) {
2523 var d = cm.display;
2524 on(d.scroller, "mousedown", operation(cm, onMouseDown));
2525 // Older IE's will not fire a second mousedown for a double click
2526 if (ie && ie_version < 11)
2527 on(d.scroller, "dblclick", operation(cm, function(e) {
2528 if (signalDOMEvent(cm, e)) return;
2529 var pos = posFromMouse(cm, e);
2530 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
2531 e_preventDefault(e);
2532 var word = cm.findWordAt(pos);
2533 extendSelection(cm.doc, word.anchor, word.head);
2534 }));
2535 else
2536 on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
2537 // Prevent normal selection in the editor (we handle our own)
2538 on(d.lineSpace, "selectstart", function(e) {
2539 if (!eventInWidget(d, e)) e_preventDefault(e);
2540 });
2541 // Some browsers fire contextmenu *after* opening the menu, at
2542 // which point we can't mess with it anymore. Context menu is
2543 // handled in onMouseDown for these browsers.
2544 if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
2545
2546 // Sync scrolling between fake scrollbars and real scrollable
2547 // area, ensure viewport is updated when scrolling.
2548 on(d.scroller, "scroll", function() {
2549 if (d.scroller.clientHeight) {
2550 setScrollTop(cm, d.scroller.scrollTop);
2551 setScrollLeft(cm, d.scroller.scrollLeft, true);
2552 signal(cm, "scroll", cm);
2553 }
2554 });
2555 on(d.scrollbarV, "scroll", function() {
2556 if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
2557 });
2558 on(d.scrollbarH, "scroll", function() {
2559 if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
2560 });
2561
2562 // Listen to wheel events in order to try and update the viewport on time.
2563 on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
2564 on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
2565
2566 // Prevent clicks in the scrollbars from killing focus
2567 function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
2568 on(d.scrollbarH, "mousedown", reFocus);
2569 on(d.scrollbarV, "mousedown", reFocus);
2570 // Prevent wrapper from ever scrolling
2571 on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
2572
2573 on(d.input, "keyup", function(e) { onKeyUp.call(cm, e); });
2574 on(d.input, "input", function() {
2575 if (ie && ie_version >= 9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
2576 fastPoll(cm);
2577 });
2578 on(d.input, "keydown", operation(cm, onKeyDown));
2579 on(d.input, "keypress", operation(cm, onKeyPress));
2580 on(d.input, "focus", bind(onFocus, cm));
2581 on(d.input, "blur", bind(onBlur, cm));
2582
2583 function drag_(e) {
2584 if (!signalDOMEvent(cm, e)) e_stop(e);
2585 }
2586 if (cm.options.dragDrop) {
2587 on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
2588 on(d.scroller, "dragenter", drag_);
2589 on(d.scroller, "dragover", drag_);
2590 on(d.scroller, "drop", operation(cm, onDrop));
2591 }
2592 on(d.scroller, "paste", function(e) {
2593 if (eventInWidget(d, e)) return;
2594 cm.state.pasteIncoming = true;
2595 focusInput(cm);
2596 fastPoll(cm);
2597 });
2598 on(d.input, "paste", function() {
2599 // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
2600 // Add a char to the end of textarea before paste occur so that
2601 // selection doesn't span to the end of textarea.
2602 if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
2603 var start = d.input.selectionStart, end = d.input.selectionEnd;
2604 d.input.value += "$";
2605 // The selection end needs to be set before the start, otherwise there
2606 // can be an intermediate non-empty selection between the two, which
2607 // can override the middle-click paste buffer on linux and cause the
2608 // wrong thing to get pasted.
2609 d.input.selectionEnd = end;
2610 d.input.selectionStart = start;
2611 cm.state.fakedLastChar = true;
2612 }
2613 cm.state.pasteIncoming = true;
2614 fastPoll(cm);
2615 });
2616
2617 function prepareCopyCut(e) {
2618 if (cm.somethingSelected()) {
2619 lastCopied = cm.getSelections();
2620 if (d.inaccurateSelection) {
2621 d.prevInput = "";
2622 d.inaccurateSelection = false;
2623 d.input.value = lastCopied.join("\n");
2624 selectInput(d.input);
2625 }
2626 } else {
2627 var text = [], ranges = [];
2628 for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
2629 var line = cm.doc.sel.ranges[i].head.line;
2630 var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
2631 ranges.push(lineRange);
2632 text.push(cm.getRange(lineRange.anchor, lineRange.head));
2633 }
2634 if (e.type == "cut") {
2635 cm.setSelections(ranges, null, sel_dontScroll);
2636 } else {
2637 d.prevInput = "";
2638 d.input.value = text.join("\n");
2639 selectInput(d.input);
2640 }
2641 lastCopied = text;
2642 }
2643 if (e.type == "cut") cm.state.cutIncoming = true;
2644 }
2645 on(d.input, "cut", prepareCopyCut);
2646 on(d.input, "copy", prepareCopyCut);
2647
2648 // Needed to handle Tab key in KHTML
2649 if (khtml) on(d.sizer, "mouseup", function() {
2650 if (activeElt() == d.input) d.input.blur();
2651 focusInput(cm);
2652 });
2653 }
2654
2655 // Called when the window resizes
2656 function onResize(cm) {
2657 var d = cm.display;
2658 if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth)
2659 return;
2660 // Might be a text scaling operation, clear size caches.
2661 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
2662 cm.setSize();
2663 }
2664
2665 // MOUSE EVENTS
2666
2667 // Return true when the given mouse event happened in a widget
2668 function eventInWidget(display, e) {
2669 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
2670 if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
2671 }
2672 }
2673
2674 // Given a mouse event, find the corresponding position. If liberal
2675 // is false, it checks whether a gutter or scrollbar was clicked,
2676 // and returns null if it was. forRect is used by rectangular
2677 // selections, and tries to estimate a character position even for
2678 // coordinates beyond the right of the text.
2679 function posFromMouse(cm, e, liberal, forRect) {
2680 var display = cm.display;
2681 if (!liberal) {
2682 var target = e_target(e);
2683 if (target == display.scrollbarH || target == display.scrollbarV ||
2684 target == display.scrollbarFiller || target == display.gutterFiller) return null;
2685 }
2686 var x, y, space = display.lineSpace.getBoundingClientRect();
2687 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
2688 try { x = e.clientX - space.left; y = e.clientY - space.top; }
2689 catch (e) { return null; }
2690 var coords = coordsChar(cm, x, y), line;
2691 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
2692 var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;
2693 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));
2694 }
2695 return coords;
2696 }
2697
2698 // A mouse down can be a single click, double click, triple click,
2699 // start of selection drag, start of text drag, new cursor
2700 // (ctrl-click), rectangle drag (alt-drag), or xwin
2701 // middle-click-paste. Or it might be a click on something we should
2702 // not interfere with, such as a scrollbar or widget.
2703 function onMouseDown(e) {
2704 if (signalDOMEvent(this, e)) return;
2705 var cm = this, display = cm.display;
2706 display.shift = e.shiftKey;
2707
2708 if (eventInWidget(display, e)) {
2709 if (!webkit) {
2710 // Briefly turn off draggability, to allow widgets to do
2711 // normal dragging things.
2712 display.scroller.draggable = false;
2713 setTimeout(function(){display.scroller.draggable = true;}, 100);
2714 }
2715 return;
2716 }
2717 if (clickInGutter(cm, e)) return;
2718 var start = posFromMouse(cm, e);
2719 window.focus();
2720
2721 switch (e_button(e)) {
2722 case 1:
2723 if (start)
2724 leftButtonDown(cm, e, start);
2725 else if (e_target(e) == display.scroller)
2726 e_preventDefault(e);
2727 break;
2728 case 2:
2729 if (webkit) cm.state.lastMiddleDown = +new Date;
2730 if (start) extendSelection(cm.doc, start);
2731 setTimeout(bind(focusInput, cm), 20);
2732 e_preventDefault(e);
2733 break;
2734 case 3:
2735 if (captureRightClick) onContextMenu(cm, e);
2736 break;
2737 }
2738 }
2739
2740 var lastClick, lastDoubleClick;
2741 function leftButtonDown(cm, e, start) {
2742 setTimeout(bind(ensureFocus, cm), 0);
2743
2744 var now = +new Date, type;
2745 if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) {
2746 type = "triple";
2747 } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) {
2748 type = "double";
2749 lastDoubleClick = {time: now, pos: start};
2750 } else {
2751 type = "single";
2752 lastClick = {time: now, pos: start};
2753 }
2754
2755 var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey;
2756 if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) &&
2757 type == "single" && sel.contains(start) > -1 && sel.somethingSelected())
2758 leftButtonStartDrag(cm, e, start, modifier);
2759 else
2760 leftButtonSelect(cm, e, start, type, modifier);
2761 }
2762
2763 // Start a text drag. When it ends, see if any dragging actually
2764 // happen, and treat as a click if it didn't.
2765 function leftButtonStartDrag(cm, e, start, modifier) {
2766 var display = cm.display;
2767 var dragEnd = operation(cm, function(e2) {
2768 if (webkit) display.scroller.draggable = false;
2769 cm.state.draggingText = false;
2770 off(document, "mouseup", dragEnd);
2771 off(display.scroller, "drop", dragEnd);
2772 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
2773 e_preventDefault(e2);
2774 if (!modifier)
2775 extendSelection(cm.doc, start);
2776 focusInput(cm);
2777 // Work around unexplainable focus problem in IE9 (#2127)
2778 if (ie && ie_version == 9)
2779 setTimeout(function() {document.body.focus(); focusInput(cm);}, 20);
2780 }
2781 });
2782 // Let the drag handler handle this.
2783 if (webkit) display.scroller.draggable = true;
2784 cm.state.draggingText = dragEnd;
2785 // IE's approach to draggable
2786 if (display.scroller.dragDrop) display.scroller.dragDrop();
2787 on(document, "mouseup", dragEnd);
2788 on(display.scroller, "drop", dragEnd);
2789 }
2790
2791 // Normal selection, as opposed to text dragging.
2792 function leftButtonSelect(cm, e, start, type, addNew) {
2793 var display = cm.display, doc = cm.doc;
2794 e_preventDefault(e);
2795
2796 var ourRange, ourIndex, startSel = doc.sel;
2797 if (addNew && !e.shiftKey) {
2798 ourIndex = doc.sel.contains(start);
2799 if (ourIndex > -1)
2800 ourRange = doc.sel.ranges[ourIndex];
2801 else
2802 ourRange = new Range(start, start);
2803 } else {
2804 ourRange = doc.sel.primary();
2805 }
2806
2807 if (e.altKey) {
2808 type = "rect";
2809 if (!addNew) ourRange = new Range(start, start);
2810 start = posFromMouse(cm, e, true, true);
2811 ourIndex = -1;
2812 } else if (type == "double") {
2813 var word = cm.findWordAt(start);
2814 if (cm.display.shift || doc.extend)
2815 ourRange = extendRange(doc, ourRange, word.anchor, word.head);
2816 else
2817 ourRange = word;
2818 } else if (type == "triple") {
2819 var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0)));
2820 if (cm.display.shift || doc.extend)
2821 ourRange = extendRange(doc, ourRange, line.anchor, line.head);
2822 else
2823 ourRange = line;
2824 } else {
2825 ourRange = extendRange(doc, ourRange, start);
2826 }
2827
2828 if (!addNew) {
2829 ourIndex = 0;
2830 setSelection(doc, new Selection([ourRange], 0), sel_mouse);
2831 startSel = doc.sel;
2832 } else if (ourIndex > -1) {
2833 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
2834 } else {
2835 ourIndex = doc.sel.ranges.length;
2836 setSelection(doc, normalizeSelection(doc.sel.ranges.concat([ourRange]), ourIndex),
2837 {scroll: false, origin: "*mouse"});
2838 }
2839
2840 var lastPos = start;
2841 function extendTo(pos) {
2842 if (cmp(lastPos, pos) == 0) return;
2843 lastPos = pos;
2844
2845 if (type == "rect") {
2846 var ranges = [], tabSize = cm.options.tabSize;
2847 var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize);
2848 var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);
2849 var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol);
2850 for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));
2851 line <= end; line++) {
2852 var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize);
2853 if (left == right)
2854 ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos)));
2855 else if (text.length > leftPos)
2856 ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize))));
2857 }
2858 if (!ranges.length) ranges.push(new Range(start, start));
2859 setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex),
2860 {origin: "*mouse", scroll: false});
2861 cm.scrollIntoView(pos);
2862 } else {
2863 var oldRange = ourRange;
2864 var anchor = oldRange.anchor, head = pos;
2865 if (type != "single") {
2866 if (type == "double")
2867 var range = cm.findWordAt(pos);
2868 else
2869 var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0)));
2870 if (cmp(range.anchor, anchor) > 0) {
2871 head = range.head;
2872 anchor = minPos(oldRange.from(), range.anchor);
2873 } else {
2874 head = range.anchor;
2875 anchor = maxPos(oldRange.to(), range.head);
2876 }
2877 }
2878 var ranges = startSel.ranges.slice(0);
2879 ranges[ourIndex] = new Range(clipPos(doc, anchor), head);
2880 setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse);
2881 }
2882 }
2883
2884 var editorSize = display.wrapper.getBoundingClientRect();
2885 // Used to ensure timeout re-tries don't fire when another extend
2886 // happened in the meantime (clearTimeout isn't reliable -- at
2887 // least on Chrome, the timeouts still happen even when cleared,
2888 // if the clear happens after their scheduled firing time).
2889 var counter = 0;
2890
2891 function extend(e) {
2892 var curCount = ++counter;
2893 var cur = posFromMouse(cm, e, true, type == "rect");
2894 if (!cur) return;
2895 if (cmp(cur, lastPos) != 0) {
2896 ensureFocus(cm);
2897 extendTo(cur);
2898 var visible = visibleLines(display, doc);
2899 if (cur.line >= visible.to || cur.line < visible.from)
2900 setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
2901 } else {
2902 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
2903 if (outside) setTimeout(operation(cm, function() {
2904 if (counter != curCount) return;
2905 display.scroller.scrollTop += outside;
2906 extend(e);
2907 }), 50);
2908 }
2909 }
2910
2911 function done(e) {
2912 counter = Infinity;
2913 e_preventDefault(e);
2914 focusInput(cm);
2915 off(document, "mousemove", move);
2916 off(document, "mouseup", up);
2917 doc.history.lastSelOrigin = null;
2918 }
2919
2920 var move = operation(cm, function(e) {
2921 if (!e_button(e)) done(e);
2922 else extend(e);
2923 });
2924 var up = operation(cm, done);
2925 on(document, "mousemove", move);
2926 on(document, "mouseup", up);
2927 }
2928
2929 // Determines whether an event happened in the gutter, and fires the
2930 // handlers for the corresponding event.
2931 function gutterEvent(cm, e, type, prevent, signalfn) {
2932 try { var mX = e.clientX, mY = e.clientY; }
2933 catch(e) { return false; }
2934 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false;
2935 if (prevent) e_preventDefault(e);
2936
2937 var display = cm.display;
2938 var lineBox = display.lineDiv.getBoundingClientRect();
2939
2940 if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e);
2941 mY -= lineBox.top - display.viewOffset;
2942
2943 for (var i = 0; i < cm.options.gutters.length; ++i) {
2944 var g = display.gutters.childNodes[i];
2945 if (g && g.getBoundingClientRect().right >= mX) {
2946 var line = lineAtHeight(cm.doc, mY);
2947 var gutter = cm.options.gutters[i];
2948 signalfn(cm, type, cm, line, gutter, e);
2949 return e_defaultPrevented(e);
2950 }
2951 }
2952 }
2953
2954 function clickInGutter(cm, e) {
2955 return gutterEvent(cm, e, "gutterClick", true, signalLater);
2956 }
2957
2958 // Kludge to work around strange IE behavior where it'll sometimes
2959 // re-fire a series of drag-related events right after the drop (#1551)
2960 var lastDrop = 0;
2961
2962 function onDrop(e) {
2963 var cm = this;
2964 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
2965 return;
2966 e_preventDefault(e);
2967 if (ie) lastDrop = +new Date;
2968 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
2969 if (!pos || isReadOnly(cm)) return;
2970 // Might be a file drop, in which case we simply extract the text
2971 // and insert it.
2972 if (files && files.length && window.FileReader && window.File) {
2973 var n = files.length, text = Array(n), read = 0;
2974 var loadFile = function(file, i) {
2975 var reader = new FileReader;
2976 reader.onload = operation(cm, function() {
2977 text[i] = reader.result;
2978 if (++read == n) {
2979 pos = clipPos(cm.doc, pos);
2980 var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"};
2981 makeChange(cm.doc, change);
2982 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));
2983 }
2984 });
2985 reader.readAsText(file);
2986 };
2987 for (var i = 0; i < n; ++i) loadFile(files[i], i);
2988 } else { // Normal drop
2989 // Don't do a replace if the drop happened inside of the selected text.
2990 if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
2991 cm.state.draggingText(e);
2992 // Ensure the editor is re-focused
2993 setTimeout(bind(focusInput, cm), 20);
2994 return;
2995 }
2996 try {
2997 var text = e.dataTransfer.getData("Text");
2998 if (text) {
2999 if (cm.state.draggingText && !(mac ? e.metaKey : e.ctrlKey))
3000 var selected = cm.listSelections();
3001 setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
3002 if (selected) for (var i = 0; i < selected.length; ++i)
3003 replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag");
3004 cm.replaceSelection(text, "around", "paste");
3005 focusInput(cm);
3006 }
3007 }
3008 catch(e){}
3009 }
3010 }
3011
3012 function onDragStart(cm, e) {
3013 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
3014 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
3015
3016 e.dataTransfer.setData("Text", cm.getSelection());
3017
3018 // Use dummy image instead of default browsers image.
3019 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
3020 if (e.dataTransfer.setDragImage && !safari) {
3021 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
3022 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
3023 if (presto) {
3024 img.width = img.height = 1;
3025 cm.display.wrapper.appendChild(img);
3026 // Force a relayout, or Opera won't use our image for some obscure reason
3027 img._top = img.offsetTop;
3028 }
3029 e.dataTransfer.setDragImage(img, 0, 0);
3030 if (presto) img.parentNode.removeChild(img);
3031 }
3032 }
3033
3034 // SCROLL EVENTS
3035
3036 // Sync the scrollable area and scrollbars, ensure the viewport
3037 // covers the visible area.
3038 function setScrollTop(cm, val) {
3039 if (Math.abs(cm.doc.scrollTop - val) < 2) return;
3040 cm.doc.scrollTop = val;
3041 if (!gecko) updateDisplaySimple(cm, {top: val});
3042 if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
3043 if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
3044 if (gecko) updateDisplaySimple(cm);
3045 startWorker(cm, 100);
3046 }
3047 // Sync scroller and scrollbar, ensure the gutter elements are
3048 // aligned.
3049 function setScrollLeft(cm, val, isScroller) {
3050 if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
3051 val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
3052 cm.doc.scrollLeft = val;
3053 alignHorizontally(cm);
3054 if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
3055 if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
3056 }
3057
3058 // Since the delta values reported on mouse wheel events are
3059 // unstandardized between browsers and even browser versions, and
3060 // generally horribly unpredictable, this code starts by measuring
3061 // the scroll effect that the first few mouse wheel events have,
3062 // and, from that, detects the way it can convert deltas to pixel
3063 // offsets afterwards.
3064 //
3065 // The reason we want to know the amount a wheel event will scroll
3066 // is that it gives us a chance to update the display before the
3067 // actual scrolling happens, reducing flickering.
3068
3069 var wheelSamples = 0, wheelPixelsPerUnit = null;
3070 // Fill in a browser-detected starting value on browsers where we
3071 // know one. These don't have to be accurate -- the result of them
3072 // being wrong would just be a slight flicker on the first wheel
3073 // scroll (if it is large enough).
3074 if (ie) wheelPixelsPerUnit = -.53;
3075 else if (gecko) wheelPixelsPerUnit = 15;
3076 else if (chrome) wheelPixelsPerUnit = -.7;
3077 else if (safari) wheelPixelsPerUnit = -1/3;
3078
3079 function onScrollWheel(cm, e) {
3080 var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
3081 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
3082 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
3083 else if (dy == null) dy = e.wheelDelta;
3084
3085 var display = cm.display, scroll = display.scroller;
3086 // Quit if there's nothing to scroll here
3087 if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
3088 dy && scroll.scrollHeight > scroll.clientHeight)) return;
3089
3090 // Webkit browsers on OS X abort momentum scrolls when the target
3091 // of the scroll event is removed from the scrollable element.
3092 // This hack (see related code in patchDisplay) makes sure the
3093 // element is kept around.
3094 if (dy && mac && webkit) {
3095 outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {
3096 for (var i = 0; i < view.length; i++) {
3097 if (view[i].node == cur) {
3098 cm.display.currentWheelTarget = cur;
3099 break outer;
3100 }
3101 }
3102 }
3103 }
3104
3105 // On some browsers, horizontal scrolling will cause redraws to
3106 // happen before the gutter has been realigned, causing it to
3107 // wriggle around in a most unseemly way. When we have an
3108 // estimated pixels/delta value, we just handle horizontal
3109 // scrolling entirely here. It'll be slightly off from native, but
3110 // better than glitching out.
3111 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
3112 if (dy)
3113 setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
3114 setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
3115 e_preventDefault(e);
3116 display.wheelStartX = null; // Abort measurement, if in progress
3117 return;
3118 }
3119
3120 // 'Project' the visible viewport to cover the area that is being
3121 // scrolled into view (if we know enough to estimate it).
3122 if (dy && wheelPixelsPerUnit != null) {
3123 var pixels = dy * wheelPixelsPerUnit;
3124 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
3125 if (pixels < 0) top = Math.max(0, top + pixels - 50);
3126 else bot = Math.min(cm.doc.height, bot + pixels + 50);
3127 updateDisplaySimple(cm, {top: top, bottom: bot});
3128 }
3129
3130 if (wheelSamples < 20) {
3131 if (display.wheelStartX == null) {
3132 display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
3133 display.wheelDX = dx; display.wheelDY = dy;
3134 setTimeout(function() {
3135 if (display.wheelStartX == null) return;
3136 var movedX = scroll.scrollLeft - display.wheelStartX;
3137 var movedY = scroll.scrollTop - display.wheelStartY;
3138 var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
3139 (movedX && display.wheelDX && movedX / display.wheelDX);
3140 display.wheelStartX = display.wheelStartY = null;
3141 if (!sample) return;
3142 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
3143 ++wheelSamples;
3144 }, 200);
3145 } else {
3146 display.wheelDX += dx; display.wheelDY += dy;
3147 }
3148 }
3149 }
3150
3151 // KEY EVENTS
3152
3153 // Run a handler that was bound to a key.
3154 function doHandleBinding(cm, bound, dropShift) {
3155 if (typeof bound == "string") {
3156 bound = commands[bound];
3157 if (!bound) return false;
3158 }
3159 // Ensure previous input has been read, so that the handler sees a
3160 // consistent view of the document
3161 if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
3162 var prevShift = cm.display.shift, done = false;
3163 try {
3164 if (isReadOnly(cm)) cm.state.suppressEdits = true;
3165 if (dropShift) cm.display.shift = false;
3166 done = bound(cm) != Pass;
3167 } finally {
3168 cm.display.shift = prevShift;
3169 cm.state.suppressEdits = false;
3170 }
3171 return done;
3172 }
3173
3174 function lookupKeyForEditor(cm, name, handle) {
3175 for (var i = 0; i < cm.state.keyMaps.length; i++) {
3176 var result = lookupKey(name, cm.state.keyMaps[i], handle);
3177 if (result) return result;
3178 }
3179 return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle))
3180 || lookupKey(name, cm.options.keyMap, handle);
3181 }
3182
3183 var stopSeq = new Delayed;
3184 function dispatchKey(cm, name, e, handle) {
3185 var seq = cm.state.keySeq;
3186 if (seq) {
3187 if (isModifierKey(name)) return "handled";
3188 stopSeq.set(50, function() {
3189 if (cm.state.keySeq == seq) {
3190 cm.state.keySeq = null;
3191 resetInput(cm);
3192 }
3193 });
3194 name = seq + " " + name;
3195 }
3196 var result = lookupKeyForEditor(cm, name, handle);
3197
3198 if (result == "multi")
3199 cm.state.keySeq = name;
3200 if (result == "handled")
3201 signalLater(cm, "keyHandled", cm, name, e);
3202
3203 if (result == "handled" || result == "multi") {
3204 e_preventDefault(e);
3205 restartBlink(cm);
3206 }
3207
3208 if (seq && !result && /\'$/.test(name)) {
3209 e_preventDefault(e);
3210 return true;
3211 }
3212 return !!result;
3213 }
3214
3215 // Handle a key from the keydown event.
3216 function handleKeyBinding(cm, e) {
3217 var name = keyName(e, true);
3218 if (!name) return false;
3219
3220 if (e.shiftKey && !cm.state.keySeq) {
3221 // First try to resolve full name (including 'Shift-'). Failing
3222 // that, see if there is a cursor-motion command (starting with
3223 // 'go') bound to the keyname without 'Shift-'.
3224 return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBinding(cm, b, true);})
3225 || dispatchKey(cm, name, e, function(b) {
3226 if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
3227 return doHandleBinding(cm, b);
3228 });
3229 } else {
3230 return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b); });
3231 }
3232 }
3233
3234 // Handle a key from the keypress event
3235 function handleCharBinding(cm, e, ch) {
3236 return dispatchKey(cm, "'" + ch + "'", e,
3237 function(b) { return doHandleBinding(cm, b, true); });
3238 }
3239
3240 var lastStoppedKey = null;
3241 function onKeyDown(e) {
3242 var cm = this;
3243 ensureFocus(cm);
3244 if (signalDOMEvent(cm, e)) return;
3245 // IE does strange things with escape.
3246 if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false;
3247 var code = e.keyCode;
3248 cm.display.shift = code == 16 || e.shiftKey;
3249 var handled = handleKeyBinding(cm, e);
3250 if (presto) {
3251 lastStoppedKey = handled ? code : null;
3252 // Opera has no cut event... we try to at least catch the key combo
3253 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
3254 cm.replaceSelection("", null, "cut");
3255 }
3256
3257 // Turn mouse into crosshair when Alt is held on Mac.
3258 if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className))
3259 showCrossHair(cm);
3260 }
3261
3262 function showCrossHair(cm) {
3263 var lineDiv = cm.display.lineDiv;
3264 addClass(lineDiv, "CodeMirror-crosshair");
3265
3266 function up(e) {
3267 if (e.keyCode == 18 || !e.altKey) {
3268 rmClass(lineDiv, "CodeMirror-crosshair");
3269 off(document, "keyup", up);
3270 off(document, "mouseover", up);
3271 }
3272 }
3273 on(document, "keyup", up);
3274 on(document, "mouseover", up);
3275 }
3276
3277 function onKeyUp(e) {
3278 if (e.keyCode == 16) this.doc.sel.shift = false;
3279 signalDOMEvent(this, e);
3280 }
3281
3282 function onKeyPress(e) {
3283 var cm = this;
3284 if (signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return;
3285 var keyCode = e.keyCode, charCode = e.charCode;
3286 if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
3287 if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
3288 var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
3289 if (handleCharBinding(cm, e, ch)) return;
3290 if (ie && ie_version >= 9) cm.display.inputHasSelection = null;
3291 fastPoll(cm);
3292 }
3293
3294 // FOCUS/BLUR EVENTS
3295
3296 function onFocus(cm) {
3297 if (cm.options.readOnly == "nocursor") return;
3298 if (!cm.state.focused) {
3299 signal(cm, "focus", cm);
3300 cm.state.focused = true;
3301 addClass(cm.display.wrapper, "CodeMirror-focused");
3302 // The prevInput test prevents this from firing when a context
3303 // menu is closed (since the resetInput would kill the
3304 // select-all detection hack)
3305 if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
3306 resetInput(cm);
3307 if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
3308 }
3309 }
3310 slowPoll(cm);
3311 restartBlink(cm);
3312 }
3313 function onBlur(cm) {
3314 if (cm.state.focused) {
3315 signal(cm, "blur", cm);
3316 cm.state.focused = false;
3317 rmClass(cm.display.wrapper, "CodeMirror-focused");
3318 }
3319 clearInterval(cm.display.blinker);
3320 setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150);
3321 }
3322
3323 // CONTEXT MENU HANDLING
3324
3325 // To make the context menu work, we need to briefly unhide the
3326 // textarea (making it as unobtrusive as possible) to let the
3327 // right-click take effect on it.
3328 function onContextMenu(cm, e) {
3329 if (signalDOMEvent(cm, e, "contextmenu")) return;
3330 var display = cm.display;
3331 if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return;
3332
3333 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
3334 if (!pos || presto) return; // Opera is difficult.
3335
3336 // Reset the current text selection only if the click is done outside of the selection
3337 // and 'resetSelectionOnContextMenu' option is true.
3338 var reset = cm.options.resetSelectionOnContextMenu;
3339 if (reset && cm.doc.sel.contains(pos) == -1)
3340 operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll);
3341
3342 var oldCSS = display.input.style.cssText;
3343 display.inputDiv.style.position = "absolute";
3344 display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
3345 "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " +
3346 (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
3347 "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
3348 if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712)
3349 focusInput(cm);
3350 if (webkit) window.scrollTo(null, oldScrollY);
3351 resetInput(cm);
3352 // Adds "Select all" to context menu in FF
3353 if (!cm.somethingSelected()) display.input.value = display.prevInput = " ";
3354 display.selForContextMenu = cm.doc.sel;
3355 clearTimeout(display.detectingSelectAll);
3356
3357 // Select-all will be greyed out if there's nothing to select, so
3358 // this adds a zero-width space so that we can later check whether
3359 // it got selected.
3360 function prepareSelectAllHack() {
3361 if (display.input.selectionStart != null) {
3362 var selected = cm.somethingSelected();
3363 var extval = display.input.value = "\u200b" + (selected ? display.input.value : "");
3364 display.prevInput = selected ? "" : "\u200b";
3365 display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
3366 // Re-set this, in case some other handler touched the
3367 // selection in the meantime.
3368 display.selForContextMenu = cm.doc.sel;
3369 }
3370 }
3371 function rehide() {
3372 display.inputDiv.style.position = "relative";
3373 display.input.style.cssText = oldCSS;
3374 if (ie && ie_version < 9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
3375 slowPoll(cm);
3376
3377 // Try to detect the user choosing select-all
3378 if (display.input.selectionStart != null) {
3379 if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
3380 var i = 0, poll = function() {
3381 if (display.selForContextMenu == cm.doc.sel && display.input.selectionStart == 0)
3382 operation(cm, commands.selectAll)(cm);
3383 else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
3384 else resetInput(cm);
3385 };
3386 display.detectingSelectAll = setTimeout(poll, 200);
3387 }
3388 }
3389
3390 if (ie && ie_version >= 9) prepareSelectAllHack();
3391 if (captureRightClick) {
3392 e_stop(e);
3393 var mouseup = function() {
3394 off(window, "mouseup", mouseup);
3395 setTimeout(rehide, 20);
3396 };
3397 on(window, "mouseup", mouseup);
3398 } else {
3399 setTimeout(rehide, 50);
3400 }
3401 }
3402
3403 function contextMenuInGutter(cm, e) {
3404 if (!hasHandler(cm, "gutterContextMenu")) return false;
3405 return gutterEvent(cm, e, "gutterContextMenu", false, signal);
3406 }
3407
3408 // UPDATING
3409
3410 // Compute the position of the end of a change (its 'to' property
3411 // refers to the pre-change end).
3412 var changeEnd = CodeMirror.changeEnd = function(change) {
3413 if (!change.text) return change.to;
3414 return Pos(change.from.line + change.text.length - 1,
3415 lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
3416 };
3417
3418 // Adjust a position to refer to the post-change position of the
3419 // same text, or the end of the change if the change covers it.
3420 function adjustForChange(pos, change) {
3421 if (cmp(pos, change.from) < 0) return pos;
3422 if (cmp(pos, change.to) <= 0) return changeEnd(change);
3423
3424 var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
3425 if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch;
3426 return Pos(line, ch);
3427 }
3428
3429 function computeSelAfterChange(doc, change) {
3430 var out = [];
3431 for (var i = 0; i < doc.sel.ranges.length; i++) {
3432 var range = doc.sel.ranges[i];
3433 out.push(new Range(adjustForChange(range.anchor, change),
3434 adjustForChange(range.head, change)));
3435 }
3436 return normalizeSelection(out, doc.sel.primIndex);
3437 }
3438
3439 function offsetPos(pos, old, nw) {
3440 if (pos.line == old.line)
3441 return Pos(nw.line, pos.ch - old.ch + nw.ch);
3442 else
3443 return Pos(nw.line + (pos.line - old.line), pos.ch);
3444 }
3445
3446 // Used by replaceSelections to allow moving the selection to the
3447 // start or around the replaced test. Hint may be "start" or "around".
3448 function computeReplacedSel(doc, changes, hint) {
3449 var out = [];
3450 var oldPrev = Pos(doc.first, 0), newPrev = oldPrev;
3451 for (var i = 0; i < changes.length; i++) {
3452 var change = changes[i];
3453 var from = offsetPos(change.from, oldPrev, newPrev);
3454 var to = offsetPos(changeEnd(change), oldPrev, newPrev);
3455 oldPrev = change.to;
3456 newPrev = to;
3457 if (hint == "around") {
3458 var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0;
3459 out[i] = new Range(inv ? to : from, inv ? from : to);
3460 } else {
3461 out[i] = new Range(from, from);
3462 }
3463 }
3464 return new Selection(out, doc.sel.primIndex);
3465 }
3466
3467 // Allow "beforeChange" event handlers to influence a change
3468 function filterChange(doc, change, update) {
3469 var obj = {
3470 canceled: false,
3471 from: change.from,
3472 to: change.to,
3473 text: change.text,
3474 origin: change.origin,
3475 cancel: function() { this.canceled = true; }
3476 };
3477 if (update) obj.update = function(from, to, text, origin) {
3478 if (from) this.from = clipPos(doc, from);
3479 if (to) this.to = clipPos(doc, to);
3480 if (text) this.text = text;
3481 if (origin !== undefined) this.origin = origin;
3482 };
3483 signal(doc, "beforeChange", doc, obj);
3484 if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
3485
3486 if (obj.canceled) return null;
3487 return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
3488 }
3489
3490 // Apply a change to a document, and add it to the document's
3491 // history, and propagating it to all linked documents.
3492 function makeChange(doc, change, ignoreReadOnly) {
3493 if (doc.cm) {
3494 if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly);
3495 if (doc.cm.state.suppressEdits) return;
3496 }
3497
3498 if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
3499 change = filterChange(doc, change, true);
3500 if (!change) return;
3501 }
3502
3503 // Possibly split or suppress the update based on the presence
3504 // of read-only spans in its range.
3505 var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
3506 if (split) {
3507 for (var i = split.length - 1; i >= 0; --i)
3508 makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text});
3509 } else {
3510 makeChangeInner(doc, change);
3511 }
3512 }
3513
3514 function makeChangeInner(doc, change) {
3515 if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return;
3516 var selAfter = computeSelAfterChange(doc, change);
3517 addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
3518
3519 makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
3520 var rebased = [];
3521
3522 linkedDocs(doc, function(doc, sharedHist) {
3523 if (!sharedHist && indexOf(rebased, doc.history) == -1) {
3524 rebaseHist(doc.history, change);
3525 rebased.push(doc.history);
3526 }
3527 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
3528 });
3529 }
3530
3531 // Revert a change stored in a document's history.
3532 function makeChangeFromHistory(doc, type, allowSelectionOnly) {
3533 if (doc.cm && doc.cm.state.suppressEdits) return;
3534
3535 var hist = doc.history, event, selAfter = doc.sel;
3536 var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done;
3537
3538 // Verify that there is a useable event (so that ctrl-z won't
3539 // needlessly clear selection events)
3540 for (var i = 0; i < source.length; i++) {
3541 event = source[i];
3542 if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)
3543 break;
3544 }
3545 if (i == source.length) return;
3546 hist.lastOrigin = hist.lastSelOrigin = null;
3547
3548 for (;;) {
3549 event = source.pop();
3550 if (event.ranges) {
3551 pushSelectionToHistory(event, dest);
3552 if (allowSelectionOnly && !event.equals(doc.sel)) {
3553 setSelection(doc, event, {clearRedo: false});
3554 return;
3555 }
3556 selAfter = event;
3557 }
3558 else break;
3559 }
3560
3561 // Build up a reverse change object to add to the opposite history
3562 // stack (redo when undoing, and vice versa).
3563 var antiChanges = [];
3564 pushSelectionToHistory(selAfter, dest);
3565 dest.push({changes: antiChanges, generation: hist.generation});
3566 hist.generation = event.generation || ++hist.maxGeneration;
3567
3568 var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
3569
3570 for (var i = event.changes.length - 1; i >= 0; --i) {
3571 var change = event.changes[i];
3572 change.origin = type;
3573 if (filter && !filterChange(doc, change, false)) {
3574 source.length = 0;
3575 return;
3576 }
3577
3578 antiChanges.push(historyChangeFromChange(doc, change));
3579
3580 var after = i ? computeSelAfterChange(doc, change) : lst(source);
3581 makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
3582 if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)});
3583 var rebased = [];
3584
3585 // Propagate to the linked documents
3586 linkedDocs(doc, function(doc, sharedHist) {
3587 if (!sharedHist && indexOf(rebased, doc.history) == -1) {
3588 rebaseHist(doc.history, change);
3589 rebased.push(doc.history);
3590 }
3591 makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
3592 });
3593 }
3594 }
3595
3596 // Sub-views need their line numbers shifted when text is added
3597 // above or below them in the parent document.
3598 function shiftDoc(doc, distance) {
3599 if (distance == 0) return;
3600 doc.first += distance;
3601 doc.sel = new Selection(map(doc.sel.ranges, function(range) {
3602 return new Range(Pos(range.anchor.line + distance, range.anchor.ch),
3603 Pos(range.head.line + distance, range.head.ch));
3604 }), doc.sel.primIndex);
3605 if (doc.cm) {
3606 regChange(doc.cm, doc.first, doc.first - distance, distance);
3607 for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)
3608 regLineChange(doc.cm, l, "gutter");
3609 }
3610 }
3611
3612 // More lower-level change function, handling only a single document
3613 // (not linked ones).
3614 function makeChangeSingleDoc(doc, change, selAfter, spans) {
3615 if (doc.cm && !doc.cm.curOp)
3616 return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);
3617
3618 if (change.to.line < doc.first) {
3619 shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
3620 return;
3621 }
3622 if (change.from.line > doc.lastLine()) return;
3623
3624 // Clip the change to the size of this doc
3625 if (change.from.line < doc.first) {
3626 var shift = change.text.length - 1 - (doc.first - change.from.line);
3627 shiftDoc(doc, shift);
3628 change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
3629 text: [lst(change.text)], origin: change.origin};
3630 }
3631 var last = doc.lastLine();
3632 if (change.to.line > last) {
3633 change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
3634 text: [change.text[0]], origin: change.origin};
3635 }
3636
3637 change.removed = getBetween(doc, change.from, change.to);
3638
3639 if (!selAfter) selAfter = computeSelAfterChange(doc, change);
3640 if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans);
3641 else updateDoc(doc, change, spans);
3642 setSelectionNoUndo(doc, selAfter, sel_dontScroll);
3643 }
3644
3645 // Handle the interaction of a change to a document with the editor
3646 // that this document is part of.
3647 function makeChangeSingleDocInEditor(cm, change, spans) {
3648 var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
3649
3650 var recomputeMaxLength = false, checkWidthStart = from.line;
3651 if (!cm.options.lineWrapping) {
3652 checkWidthStart = lineNo(visualLine(getLine(doc, from.line)));
3653 doc.iter(checkWidthStart, to.line + 1, function(line) {
3654 if (line == display.maxLine) {
3655 recomputeMaxLength = true;
3656 return true;
3657 }
3658 });
3659 }
3660
3661 if (doc.sel.contains(change.from, change.to) > -1)
3662 signalCursorActivity(cm);
3663
3664 updateDoc(doc, change, spans, estimateHeight(cm));
3665
3666 if (!cm.options.lineWrapping) {
3667 doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
3668 var len = lineLength(line);
3669 if (len > display.maxLineLength) {
3670 display.maxLine = line;
3671 display.maxLineLength = len;
3672 display.maxLineChanged = true;
3673 recomputeMaxLength = false;
3674 }
3675 });
3676 if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
3677 }
3678
3679 // Adjust frontier, schedule worker
3680 doc.frontier = Math.min(doc.frontier, from.line);
3681 startWorker(cm, 400);
3682
3683 var lendiff = change.text.length - (to.line - from.line) - 1;
3684 // Remember that these lines changed, for updating the display
3685 if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
3686 regLineChange(cm, from.line, "text");
3687 else
3688 regChange(cm, from.line, to.line + 1, lendiff);
3689
3690 var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change");
3691 if (changeHandler || changesHandler) {
3692 var obj = {
3693 from: from, to: to,
3694 text: change.text,
3695 removed: change.removed,
3696 origin: change.origin
3697 };
3698 if (changeHandler) signalLater(cm, "change", cm, obj);
3699 if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj);
3700 }
3701 cm.display.selForContextMenu = null;
3702 }
3703
3704 function replaceRange(doc, code, from, to, origin) {
3705 if (!to) to = from;
3706 if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; }
3707 if (typeof code == "string") code = splitLines(code);
3708 makeChange(doc, {from: from, to: to, text: code, origin: origin});
3709 }
3710
3711 // SCROLLING THINGS INTO VIEW
3712
3713 // If an editor sits on the top or bottom of the window, partially
3714 // scrolled out of view, this ensures that the cursor is visible.
3715 function maybeScrollWindow(cm, coords) {
3716 if (signalDOMEvent(cm, "scrollCursorIntoView")) return;
3717
3718 var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
3719 if (coords.top + box.top < 0) doScroll = true;
3720 else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
3721 if (doScroll != null && !phantom) {
3722 var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " +
3723 (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " +
3724 (coords.bottom - coords.top + scrollerCutOff) + "px; left: " +
3725 coords.left + "px; width: 2px;");
3726 cm.display.lineSpace.appendChild(scrollNode);
3727 scrollNode.scrollIntoView(doScroll);
3728 cm.display.lineSpace.removeChild(scrollNode);
3729 }
3730 }
3731
3732 // Scroll a given position into view (immediately), verifying that
3733 // it actually became visible (as line heights are accurately
3734 // measured, the position of something may 'drift' during drawing).
3735 function scrollPosIntoView(cm, pos, end, margin) {
3736 if (margin == null) margin = 0;
3737 for (var limit = 0; limit < 5; limit++) {
3738 var changed = false, coords = cursorCoords(cm, pos);
3739 var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);
3740 var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left),
3741 Math.min(coords.top, endCoords.top) - margin,
3742 Math.max(coords.left, endCoords.left),
3743 Math.max(coords.bottom, endCoords.bottom) + margin);
3744 var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
3745 if (scrollPos.scrollTop != null) {
3746 setScrollTop(cm, scrollPos.scrollTop);
3747 if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
3748 }
3749 if (scrollPos.scrollLeft != null) {
3750 setScrollLeft(cm, scrollPos.scrollLeft);
3751 if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
3752 }
3753 if (!changed) return coords;
3754 }
3755 }
3756
3757 // Scroll a given set of coordinates into view (immediately).
3758 function scrollIntoView(cm, x1, y1, x2, y2) {
3759 var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
3760 if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
3761 if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
3762 }
3763
3764 // Calculate a new scroll position needed to scroll the given
3765 // rectangle into view. Returns an object with scrollTop and
3766 // scrollLeft properties. When these are undefined, the
3767 // vertical/horizontal position does not need to be adjusted.
3768 function calculateScrollPos(cm, x1, y1, x2, y2) {
3769 var display = cm.display, snapMargin = textHeight(cm.display);
3770 if (y1 < 0) y1 = 0;
3771 var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;
3772 var screen = display.scroller.clientHeight - scrollerCutOff, result = {};
3773 if (y2 - y1 > screen) y2 = y1 + screen;
3774 var docBottom = cm.doc.height + paddingVert(display);
3775 var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
3776 if (y1 < screentop) {
3777 result.scrollTop = atTop ? 0 : y1;
3778 } else if (y2 > screentop + screen) {
3779 var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
3780 if (newTop != screentop) result.scrollTop = newTop;
3781 }
3782
3783 var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;
3784 var screenw = display.scroller.clientWidth - scrollerCutOff - display.gutters.offsetWidth;
3785 var tooWide = x2 - x1 > screenw;
3786 if (tooWide) x2 = x1 + screenw;
3787 if (x1 < 10)
3788 result.scrollLeft = 0;
3789 else if (x1 < screenleft)
3790 result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10));
3791 else if (x2 > screenw + screenleft - 3)
3792 result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw;
3793
3794 return result;
3795 }
3796
3797 // Store a relative adjustment to the scroll position in the current
3798 // operation (to be applied when the operation finishes).
3799 function addToScrollPos(cm, left, top) {
3800 if (left != null || top != null) resolveScrollToPos(cm);
3801 if (left != null)
3802 cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left;
3803 if (top != null)
3804 cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top;
3805 }
3806
3807 // Make sure that at the end of the operation the current cursor is
3808 // shown.
3809 function ensureCursorVisible(cm) {
3810 resolveScrollToPos(cm);
3811 var cur = cm.getCursor(), from = cur, to = cur;
3812 if (!cm.options.lineWrapping) {
3813 from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur;
3814 to = Pos(cur.line, cur.ch + 1);
3815 }
3816 cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true};
3817 }
3818
3819 // When an operation has its scrollToPos property set, and another
3820 // scroll action is applied before the end of the operation, this
3821 // 'simulates' scrolling that position into view in a cheap way, so
3822 // that the effect of intermediate scroll commands is not ignored.
3823 function resolveScrollToPos(cm) {
3824 var range = cm.curOp.scrollToPos;
3825 if (range) {
3826 cm.curOp.scrollToPos = null;
3827 var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to);
3828 var sPos = calculateScrollPos(cm, Math.min(from.left, to.left),
3829 Math.min(from.top, to.top) - range.margin,
3830 Math.max(from.right, to.right),
3831 Math.max(from.bottom, to.bottom) + range.margin);
3832 cm.scrollTo(sPos.scrollLeft, sPos.scrollTop);
3833 }
3834 }
3835
3836 // API UTILITIES
3837
3838 // Indent the given line. The how parameter can be "smart",
3839 // "add"/null, "subtract", or "prev". When aggressive is false
3840 // (typically set to true for forced single-line indents), empty
3841 // lines are not indented, and places where the mode returns Pass
3842 // are left alone.
3843 function indentLine(cm, n, how, aggressive) {
3844 var doc = cm.doc, state;
3845 if (how == null) how = "add";
3846 if (how == "smart") {
3847 // Fall back to "prev" when the mode doesn't have an indentation
3848 // method.
3849 if (!doc.mode.indent) how = "prev";
3850 else state = getStateBefore(cm, n);
3851 }
3852
3853 var tabSize = cm.options.tabSize;
3854 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
3855 if (line.stateAfter) line.stateAfter = null;
3856 var curSpaceString = line.text.match(/^\s*/)[0], indentation;
3857 if (!aggressive && !/\S/.test(line.text)) {
3858 indentation = 0;
3859 how = "not";
3860 } else if (how == "smart") {
3861 indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
3862 if (indentation == Pass || indentation > 150) {
3863 if (!aggressive) return;
3864 how = "prev";
3865 }
3866 }
3867 if (how == "prev") {
3868 if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
3869 else indentation = 0;
3870 } else if (how == "add") {
3871 indentation = curSpace + cm.options.indentUnit;
3872 } else if (how == "subtract") {
3873 indentation = curSpace - cm.options.indentUnit;
3874 } else if (typeof how == "number") {
3875 indentation = curSpace + how;
3876 }
3877 indentation = Math.max(0, indentation);
3878
3879 var indentString = "", pos = 0;
3880 if (cm.options.indentWithTabs)
3881 for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
3882 if (pos < indentation) indentString += spaceStr(indentation - pos);
3883
3884 if (indentString != curSpaceString) {
3885 replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
3886 } else {
3887 // Ensure that, if the cursor was in the whitespace at the start
3888 // of the line, it is moved to the end of that space.
3889 for (var i = 0; i < doc.sel.ranges.length; i++) {
3890 var range = doc.sel.ranges[i];
3891 if (range.head.line == n && range.head.ch < curSpaceString.length) {
3892 var pos = Pos(n, curSpaceString.length);
3893 replaceOneSelection(doc, i, new Range(pos, pos));
3894 break;
3895 }
3896 }
3897 }
3898 line.stateAfter = null;
3899 }
3900
3901 // Utility for applying a change to a line by handle or number,
3902 // returning the number and optionally registering the line as
3903 // changed.
3904 function changeLine(doc, handle, changeType, op) {
3905 var no = handle, line = handle;
3906 if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
3907 else no = lineNo(handle);
3908 if (no == null) return null;
3909 if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType);
3910 return line;
3911 }
3912
3913 // Helper for deleting text near the selection(s), used to implement
3914 // backspace, delete, and similar functionality.
3915 function deleteNearSelection(cm, compute) {
3916 var ranges = cm.doc.sel.ranges, kill = [];
3917 // Build up a set of ranges to kill first, merging overlapping
3918 // ranges.
3919 for (var i = 0; i < ranges.length; i++) {
3920 var toKill = compute(ranges[i]);
3921 while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {
3922 var replaced = kill.pop();
3923 if (cmp(replaced.from, toKill.from) < 0) {
3924 toKill.from = replaced.from;
3925 break;
3926 }
3927 }
3928 kill.push(toKill);
3929 }
3930 // Next, remove those actual ranges.
3931 runInOp(cm, function() {
3932 for (var i = kill.length - 1; i >= 0; i--)
3933 replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete");
3934 ensureCursorVisible(cm);
3935 });
3936 }
3937
3938 // Used for horizontal relative motion. Dir is -1 or 1 (left or
3939 // right), unit can be "char", "column" (like char, but doesn't
3940 // cross line boundaries), "word" (across next word), or "group" (to
3941 // the start of next group of word or non-word-non-whitespace
3942 // chars). The visually param controls whether, in right-to-left
3943 // text, direction 1 means to move towards the next index in the
3944 // string, or towards the character to the right of the current
3945 // position. The resulting position will have a hitSide=true
3946 // property if it reached the end of the document.
3947 function findPosH(doc, pos, dir, unit, visually) {
3948 var line = pos.line, ch = pos.ch, origDir = dir;
3949 var lineObj = getLine(doc, line);
3950 var possible = true;
3951 function findNextLine() {
3952 var l = line + dir;
3953 if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
3954 line = l;
3955 return lineObj = getLine(doc, l);
3956 }
3957 function moveOnce(boundToLine) {
3958 var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
3959 if (next == null) {
3960 if (!boundToLine && findNextLine()) {
3961 if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
3962 else ch = dir < 0 ? lineObj.text.length : 0;
3963 } else return (possible = false);
3964 } else ch = next;
3965 return true;
3966 }
3967
3968 if (unit == "char") moveOnce();
3969 else if (unit == "column") moveOnce(true);
3970 else if (unit == "word" || unit == "group") {
3971 var sawType = null, group = unit == "group";
3972 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars");
3973 for (var first = true;; first = false) {
3974 if (dir < 0 && !moveOnce(!first)) break;
3975 var cur = lineObj.text.charAt(ch) || "\n";
3976 var type = isWordChar(cur, helper) ? "w"
3977 : group && cur == "\n" ? "n"
3978 : !group || /\s/.test(cur) ? null
3979 : "p";
3980 if (group && !first && !type) type = "s";
3981 if (sawType && sawType != type) {
3982 if (dir < 0) {dir = 1; moveOnce();}
3983 break;
3984 }
3985
3986 if (type) sawType = type;
3987 if (dir > 0 && !moveOnce(!first)) break;
3988 }
3989 }
3990 var result = skipAtomic(doc, Pos(line, ch), origDir, true);
3991 if (!possible) result.hitSide = true;
3992 return result;
3993 }
3994
3995 // For relative vertical movement. Dir may be -1 or 1. Unit can be
3996 // "page" or "line". The resulting position will have a hitSide=true
3997 // property if it reached the end of the document.
3998 function findPosV(cm, pos, dir, unit) {
3999 var doc = cm.doc, x = pos.left, y;
4000 if (unit == "page") {
4001 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
4002 y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
4003 } else if (unit == "line") {
4004 y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
4005 }
4006 for (;;) {
4007 var target = coordsChar(cm, x, y);
4008 if (!target.outside) break;
4009 if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
4010 y += dir * 5;
4011 }
4012 return target;
4013 }
4014
4015 // EDITOR METHODS
4016
4017 // The publicly visible API. Note that methodOp(f) means
4018 // 'wrap f in an operation, performed on its `this` parameter'.
4019
4020 // This is not the complete set of editor methods. Most of the
4021 // methods defined on the Doc type are also injected into
4022 // CodeMirror.prototype, for backwards compatibility and
4023 // convenience.
4024
4025 CodeMirror.prototype = {
4026 constructor: CodeMirror,
4027 focus: function(){window.focus(); focusInput(this); fastPoll(this);},
4028
4029 setOption: function(option, value) {
4030 var options = this.options, old = options[option];
4031 if (options[option] == value && option != "mode") return;
4032 options[option] = value;
4033 if (optionHandlers.hasOwnProperty(option))
4034 operation(this, optionHandlers[option])(this, value, old);
4035 },
4036
4037 getOption: function(option) {return this.options[option];},
4038 getDoc: function() {return this.doc;},
4039
4040 addKeyMap: function(map, bottom) {
4041 this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map));
4042 },
4043 removeKeyMap: function(map) {
4044 var maps = this.state.keyMaps;
4045 for (var i = 0; i < maps.length; ++i)
4046 if (maps[i] == map || maps[i].name == map) {
4047 maps.splice(i, 1);
4048 return true;
4049 }
4050 },
4051
4052 addOverlay: methodOp(function(spec, options) {
4053 var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
4054 if (mode.startState) throw new Error("Overlays may not be stateful.");
4055 this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
4056 this.state.modeGen++;
4057 regChange(this);
4058 }),
4059 removeOverlay: methodOp(function(spec) {
4060 var overlays = this.state.overlays;
4061 for (var i = 0; i < overlays.length; ++i) {
4062 var cur = overlays[i].modeSpec;
4063 if (cur == spec || typeof spec == "string" && cur.name == spec) {
4064 overlays.splice(i, 1);
4065 this.state.modeGen++;
4066 regChange(this);
4067 return;
4068 }
4069 }
4070 }),
4071
4072 indentLine: methodOp(function(n, dir, aggressive) {
4073 if (typeof dir != "string" && typeof dir != "number") {
4074 if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
4075 else dir = dir ? "add" : "subtract";
4076 }
4077 if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
4078 }),
4079 indentSelection: methodOp(function(how) {
4080 var ranges = this.doc.sel.ranges, end = -1;
4081 for (var i = 0; i < ranges.length; i++) {
4082 var range = ranges[i];
4083 if (!range.empty()) {
4084 var from = range.from(), to = range.to();
4085 var start = Math.max(end, from.line);
4086 end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;
4087 for (var j = start; j < end; ++j)
4088 indentLine(this, j, how);
4089 var newRanges = this.doc.sel.ranges;
4090 if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)
4091 replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll);
4092 } else if (range.head.line > end) {
4093 indentLine(this, range.head.line, how, true);
4094 end = range.head.line;
4095 if (i == this.doc.sel.primIndex) ensureCursorVisible(this);
4096 }
4097 }
4098 }),
4099
4100 // Fetch the parser token for a given character. Useful for hacks
4101 // that want to inspect the mode state (say, for completion).
4102 getTokenAt: function(pos, precise) {
4103 return takeToken(this, pos, precise);
4104 },
4105
4106 getLineTokens: function(line, precise) {
4107 return takeToken(this, Pos(line), precise, true);
4108 },
4109
4110 getTokenTypeAt: function(pos) {
4111 pos = clipPos(this.doc, pos);
4112 var styles = getLineStyles(this, getLine(this.doc, pos.line));
4113 var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
4114 var type;
4115 if (ch == 0) type = styles[2];
4116 else for (;;) {
4117 var mid = (before + after) >> 1;
4118 if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
4119 else if (styles[mid * 2 + 1] < ch) before = mid + 1;
4120 else { type = styles[mid * 2 + 2]; break; }
4121 }
4122 var cut = type ? type.indexOf("cm-overlay ") : -1;
4123 return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1);
4124 },
4125
4126 getModeAt: function(pos) {
4127 var mode = this.doc.mode;
4128 if (!mode.innerMode) return mode;
4129 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
4130 },
4131
4132 getHelper: function(pos, type) {
4133 return this.getHelpers(pos, type)[0];
4134 },
4135
4136 getHelpers: function(pos, type) {
4137 var found = [];
4138 if (!helpers.hasOwnProperty(type)) return helpers;
4139 var help = helpers[type], mode = this.getModeAt(pos);
4140 if (typeof mode[type] == "string") {
4141 if (help[mode[type]]) found.push(help[mode[type]]);
4142 } else if (mode[type]) {
4143 for (var i = 0; i < mode[type].length; i++) {
4144 var val = help[mode[type][i]];
4145 if (val) found.push(val);
4146 }
4147 } else if (mode.helperType && help[mode.helperType]) {
4148 found.push(help[mode.helperType]);
4149 } else if (help[mode.name]) {
4150 found.push(help[mode.name]);
4151 }
4152 for (var i = 0; i < help._global.length; i++) {
4153 var cur = help._global[i];
4154 if (cur.pred(mode, this) && indexOf(found, cur.val) == -1)
4155 found.push(cur.val);
4156 }
4157 return found;
4158 },
4159
4160 getStateAfter: function(line, precise) {
4161 var doc = this.doc;
4162 line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
4163 return getStateBefore(this, line + 1, precise);
4164 },
4165
4166 cursorCoords: function(start, mode) {
4167 var pos, range = this.doc.sel.primary();
4168 if (start == null) pos = range.head;
4169 else if (typeof start == "object") pos = clipPos(this.doc, start);
4170 else pos = start ? range.from() : range.to();
4171 return cursorCoords(this, pos, mode || "page");
4172 },
4173
4174 charCoords: function(pos, mode) {
4175 return charCoords(this, clipPos(this.doc, pos), mode || "page");
4176 },
4177
4178 coordsChar: function(coords, mode) {
4179 coords = fromCoordSystem(this, coords, mode || "page");
4180 return coordsChar(this, coords.left, coords.top);
4181 },
4182
4183 lineAtHeight: function(height, mode) {
4184 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top;
4185 return lineAtHeight(this.doc, height + this.display.viewOffset);
4186 },
4187 heightAtLine: function(line, mode) {
4188 var end = false, last = this.doc.first + this.doc.size - 1;
4189 if (line < this.doc.first) line = this.doc.first;
4190 else if (line > last) { line = last; end = true; }
4191 var lineObj = getLine(this.doc, line);
4192 return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top +
4193 (end ? this.doc.height - heightAtLine(lineObj) : 0);
4194 },
4195
4196 defaultTextHeight: function() { return textHeight(this.display); },
4197 defaultCharWidth: function() { return charWidth(this.display); },
4198
4199 setGutterMarker: methodOp(function(line, gutterID, value) {
4200 return changeLine(this.doc, line, "gutter", function(line) {
4201 var markers = line.gutterMarkers || (line.gutterMarkers = {});
4202 markers[gutterID] = value;
4203 if (!value && isEmpty(markers)) line.gutterMarkers = null;
4204 return true;
4205 });
4206 }),
4207
4208 clearGutter: methodOp(function(gutterID) {
4209 var cm = this, doc = cm.doc, i = doc.first;
4210 doc.iter(function(line) {
4211 if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
4212 line.gutterMarkers[gutterID] = null;
4213 regLineChange(cm, i, "gutter");
4214 if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
4215 }
4216 ++i;
4217 });
4218 }),
4219
4220 addLineWidget: methodOp(function(handle, node, options) {
4221 return addLineWidget(this, handle, node, options);
4222 }),
4223
4224 removeLineWidget: function(widget) { widget.clear(); },
4225
4226 lineInfo: function(line) {
4227 if (typeof line == "number") {
4228 if (!isLine(this.doc, line)) return null;
4229 var n = line;
4230 line = getLine(this.doc, line);
4231 if (!line) return null;
4232 } else {
4233 var n = lineNo(line);
4234 if (n == null) return null;
4235 }
4236 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
4237 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
4238 widgets: line.widgets};
4239 },
4240
4241 getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};},
4242
4243 addWidget: function(pos, node, scroll, vert, horiz) {
4244 var display = this.display;
4245 pos = cursorCoords(this, clipPos(this.doc, pos));
4246 var top = pos.bottom, left = pos.left;
4247 node.style.position = "absolute";
4248 display.sizer.appendChild(node);
4249 if (vert == "over") {
4250 top = pos.top;
4251 } else if (vert == "above" || vert == "near") {
4252 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
4253 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
4254 // Default to positioning above (if specified and possible); otherwise default to positioning below
4255 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
4256 top = pos.top - node.offsetHeight;
4257 else if (pos.bottom + node.offsetHeight <= vspace)
4258 top = pos.bottom;
4259 if (left + node.offsetWidth > hspace)
4260 left = hspace - node.offsetWidth;
4261 }
4262 node.style.top = top + "px";
4263 node.style.left = node.style.right = "";
4264 if (horiz == "right") {
4265 left = display.sizer.clientWidth - node.offsetWidth;
4266 node.style.right = "0px";
4267 } else {
4268 if (horiz == "left") left = 0;
4269 else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
4270 node.style.left = left + "px";
4271 }
4272 if (scroll)
4273 scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
4274 },
4275
4276 triggerOnKeyDown: methodOp(onKeyDown),
4277 triggerOnKeyPress: methodOp(onKeyPress),
4278 triggerOnKeyUp: onKeyUp,
4279
4280 execCommand: function(cmd) {
4281 if (commands.hasOwnProperty(cmd))
4282 return commands[cmd](this);
4283 },
4284
4285 findPosH: function(from, amount, unit, visually) {
4286 var dir = 1;
4287 if (amount < 0) { dir = -1; amount = -amount; }
4288 for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
4289 cur = findPosH(this.doc, cur, dir, unit, visually);
4290 if (cur.hitSide) break;
4291 }
4292 return cur;
4293 },
4294
4295 moveH: methodOp(function(dir, unit) {
4296 var cm = this;
4297 cm.extendSelectionsBy(function(range) {
4298 if (cm.display.shift || cm.doc.extend || range.empty())
4299 return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually);
4300 else
4301 return dir < 0 ? range.from() : range.to();
4302 }, sel_move);
4303 }),
4304
4305 deleteH: methodOp(function(dir, unit) {
4306 var sel = this.doc.sel, doc = this.doc;
4307 if (sel.somethingSelected())
4308 doc.replaceSelection("", null, "+delete");
4309 else
4310 deleteNearSelection(this, function(range) {
4311 var other = findPosH(doc, range.head, dir, unit, false);
4312 return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other};
4313 });
4314 }),
4315
4316 findPosV: function(from, amount, unit, goalColumn) {
4317 var dir = 1, x = goalColumn;
4318 if (amount < 0) { dir = -1; amount = -amount; }
4319 for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
4320 var coords = cursorCoords(this, cur, "div");
4321 if (x == null) x = coords.left;
4322 else coords.left = x;
4323 cur = findPosV(this, coords, dir, unit);
4324 if (cur.hitSide) break;
4325 }
4326 return cur;
4327 },
4328
4329 moveV: methodOp(function(dir, unit) {
4330 var cm = this, doc = this.doc, goals = [];
4331 var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected();
4332 doc.extendSelectionsBy(function(range) {
4333 if (collapse)
4334 return dir < 0 ? range.from() : range.to();
4335 var headPos = cursorCoords(cm, range.head, "div");
4336 if (range.goalColumn != null) headPos.left = range.goalColumn;
4337 goals.push(headPos.left);
4338 var pos = findPosV(cm, headPos, dir, unit);
4339 if (unit == "page" && range == doc.sel.primary())
4340 addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top);
4341 return pos;
4342 }, sel_move);
4343 if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++)
4344 doc.sel.ranges[i].goalColumn = goals[i];
4345 }),
4346
4347 // Find the word at the given position (as returned by coordsChar).
4348 findWordAt: function(pos) {
4349 var doc = this.doc, line = getLine(doc, pos.line).text;
4350 var start = pos.ch, end = pos.ch;
4351 if (line) {
4352 var helper = this.getHelper(pos, "wordChars");
4353 if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
4354 var startChar = line.charAt(start);
4355 var check = isWordChar(startChar, helper)
4356 ? function(ch) { return isWordChar(ch, helper); }
4357 : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
4358 : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
4359 while (start > 0 && check(line.charAt(start - 1))) --start;
4360 while (end < line.length && check(line.charAt(end))) ++end;
4361 }
4362 return new Range(Pos(pos.line, start), Pos(pos.line, end));
4363 },
4364
4365 toggleOverwrite: function(value) {
4366 if (value != null && value == this.state.overwrite) return;
4367 if (this.state.overwrite = !this.state.overwrite)
4368 addClass(this.display.cursorDiv, "CodeMirror-overwrite");
4369 else
4370 rmClass(this.display.cursorDiv, "CodeMirror-overwrite");
4371
4372 signal(this, "overwriteToggle", this, this.state.overwrite);
4373 },
4374 hasFocus: function() { return activeElt() == this.display.input; },
4375
4376 scrollTo: methodOp(function(x, y) {
4377 if (x != null || y != null) resolveScrollToPos(this);
4378 if (x != null) this.curOp.scrollLeft = x;
4379 if (y != null) this.curOp.scrollTop = y;
4380 }),
4381 getScrollInfo: function() {
4382 var scroller = this.display.scroller, co = scrollerCutOff;
4383 return {left: scroller.scrollLeft, top: scroller.scrollTop,
4384 height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
4385 clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
4386 },
4387
4388 scrollIntoView: methodOp(function(range, margin) {
4389 if (range == null) {
4390 range = {from: this.doc.sel.primary().head, to: null};
4391 if (margin == null) margin = this.options.cursorScrollMargin;
4392 } else if (typeof range == "number") {
4393 range = {from: Pos(range, 0), to: null};
4394 } else if (range.from == null) {
4395 range = {from: range, to: null};
4396 }
4397 if (!range.to) range.to = range.from;
4398 range.margin = margin || 0;
4399
4400 if (range.from.line != null) {
4401 resolveScrollToPos(this);
4402 this.curOp.scrollToPos = range;
4403 } else {
4404 var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left),
4405 Math.min(range.from.top, range.to.top) - range.margin,
4406 Math.max(range.from.right, range.to.right),
4407 Math.max(range.from.bottom, range.to.bottom) + range.margin);
4408 this.scrollTo(sPos.scrollLeft, sPos.scrollTop);
4409 }
4410 }),
4411
4412 setSize: methodOp(function(width, height) {
4413 var cm = this;
4414 function interpret(val) {
4415 return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
4416 }
4417 if (width != null) cm.display.wrapper.style.width = interpret(width);
4418 if (height != null) cm.display.wrapper.style.height = interpret(height);
4419 if (cm.options.lineWrapping) clearLineMeasurementCache(this);
4420 var lineNo = cm.display.viewFrom;
4421 cm.doc.iter(lineNo, cm.display.viewTo, function(line) {
4422 if (line.widgets) for (var i = 0; i < line.widgets.length; i++)
4423 if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; }
4424 ++lineNo;
4425 });
4426 cm.curOp.forceUpdate = true;
4427 signal(cm, "refresh", this);
4428 }),
4429
4430 operation: function(f){return runInOp(this, f);},
4431
4432 refresh: methodOp(function() {
4433 var oldHeight = this.display.cachedTextHeight;
4434 regChange(this);
4435 this.curOp.forceUpdate = true;
4436 clearCaches(this);
4437 this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop);
4438 updateGutterSpace(this);
4439 if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)
4440 estimateLineHeights(this);
4441 signal(this, "refresh", this);
4442 }),
4443
4444 swapDoc: methodOp(function(doc) {
4445 var old = this.doc;
4446 old.cm = null;
4447 attachDoc(this, doc);
4448 clearCaches(this);
4449 resetInput(this);
4450 this.scrollTo(doc.scrollLeft, doc.scrollTop);
4451 this.curOp.forceScroll = true;
4452 signalLater(this, "swapDoc", this, old);
4453 return old;
4454 }),
4455
4456 getInputField: function(){return this.display.input;},
4457 getWrapperElement: function(){return this.display.wrapper;},
4458 getScrollerElement: function(){return this.display.scroller;},
4459 getGutterElement: function(){return this.display.gutters;}
4460 };
4461 eventMixin(CodeMirror);
4462
4463 // OPTION DEFAULTS
4464
4465 // The default configuration options.
4466 var defaults = CodeMirror.defaults = {};
4467 // Functions to run when options are changed.
4468 var optionHandlers = CodeMirror.optionHandlers = {};
4469
4470 function option(name, deflt, handle, notOnInit) {
4471 CodeMirror.defaults[name] = deflt;
4472 if (handle) optionHandlers[name] =
4473 notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
4474 }
4475
4476 // Passed to option handlers when there is no old value.
4477 var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
4478
4479 // These two are, on init, called from the constructor because they
4480 // have to be initialized before the editor can start at all.
4481 option("value", "", function(cm, val) {
4482 cm.setValue(val);
4483 }, true);
4484 option("mode", null, function(cm, val) {
4485 cm.doc.modeOption = val;
4486 loadMode(cm);
4487 }, true);
4488
4489 option("indentUnit", 2, loadMode, true);
4490 option("indentWithTabs", false);
4491 option("smartIndent", true);
4492 option("tabSize", 4, function(cm) {
4493 resetModeState(cm);
4494 clearCaches(cm);
4495 regChange(cm);
4496 }, true);
4497 option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val) {
4498 cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
4499 cm.refresh();
4500 }, true);
4501 option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true);
4502 option("electricChars", true);
4503 option("rtlMoveVisually", !windows);
4504 option("wholeLineUpdateBefore", true);
4505
4506 option("theme", "default", function(cm) {
4507 themeChanged(cm);
4508 guttersChanged(cm);
4509 }, true);
4510 option("keyMap", "default", function(cm, val, old) {
4511 var next = getKeyMap(val);
4512 var prev = old != CodeMirror.Init && getKeyMap(old);
4513 if (prev && prev.detach) prev.detach(cm, next);
4514 if (next.attach) next.attach(cm, prev || null);
4515 });
4516 option("extraKeys", null);
4517
4518 option("lineWrapping", false, wrappingChanged, true);
4519 option("gutters", [], function(cm) {
4520 setGuttersForLineNumbers(cm.options);
4521 guttersChanged(cm);
4522 }, true);
4523 option("fixedGutter", true, function(cm, val) {
4524 cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
4525 cm.refresh();
4526 }, true);
4527 option("coverGutterNextToScrollbar", false, updateScrollbars, true);
4528 option("lineNumbers", false, function(cm) {
4529 setGuttersForLineNumbers(cm.options);
4530 guttersChanged(cm);
4531 }, true);
4532 option("firstLineNumber", 1, guttersChanged, true);
4533 option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
4534 option("showCursorWhenSelecting", false, updateSelection, true);
4535
4536 option("resetSelectionOnContextMenu", true);
4537
4538 option("readOnly", false, function(cm, val) {
4539 if (val == "nocursor") {
4540 onBlur(cm);
4541 cm.display.input.blur();
4542 cm.display.disabled = true;
4543 } else {
4544 cm.display.disabled = false;
4545 if (!val) resetInput(cm);
4546 }
4547 });
4548 option("disableInput", false, function(cm, val) {if (!val) resetInput(cm);}, true);
4549 option("dragDrop", true);
4550
4551 option("cursorBlinkRate", 530);
4552 option("cursorScrollMargin", 0);
4553 option("cursorHeight", 1, updateSelection, true);
4554 option("singleCursorHeightPerLine", true, updateSelection, true);
4555 option("workTime", 100);
4556 option("workDelay", 100);
4557 option("flattenSpans", true, resetModeState, true);
4558 option("addModeClass", false, resetModeState, true);
4559 option("pollInterval", 100);
4560 option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;});
4561 option("historyEventDelay", 1250);
4562 option("viewportMargin", 10, function(cm){cm.refresh();}, true);
4563 option("maxHighlightLength", 10000, resetModeState, true);
4564 option("moveInputWithCursor", true, function(cm, val) {
4565 if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
4566 });
4567
4568 option("tabindex", null, function(cm, val) {
4569 cm.display.input.tabIndex = val || "";
4570 });
4571 option("autofocus", null);
4572
4573 // MODE DEFINITION AND QUERYING
4574
4575 // Known modes, by name and by MIME
4576 var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
4577
4578 // Extra arguments are stored as the mode's dependencies, which is
4579 // used by (legacy) mechanisms like loadmode.js to automatically
4580 // load a mode. (Preferred mechanism is the require/define calls.)
4581 CodeMirror.defineMode = function(name, mode) {
4582 if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
4583 if (arguments.length > 2)
4584 mode.dependencies = Array.prototype.slice.call(arguments, 2);
4585 modes[name] = mode;
4586 };
4587
4588 CodeMirror.defineMIME = function(mime, spec) {
4589 mimeModes[mime] = spec;
4590 };
4591
4592 // Given a MIME type, a {name, ...options} config object, or a name
4593 // string, return a mode config object.
4594 CodeMirror.resolveMode = function(spec) {
4595 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
4596 spec = mimeModes[spec];
4597 } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
4598 var found = mimeModes[spec.name];
4599 if (typeof found == "string") found = {name: found};
4600 spec = createObj(found, spec);
4601 spec.name = found.name;
4602 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
4603 return CodeMirror.resolveMode("application/xml");
4604 }
4605 if (typeof spec == "string") return {name: spec};
4606 else return spec || {name: "null"};
4607 };
4608
4609 // Given a mode spec (anything that resolveMode accepts), find and
4610 // initialize an actual mode object.
4611 CodeMirror.getMode = function(options, spec) {
4612 var spec = CodeMirror.resolveMode(spec);
4613 var mfactory = modes[spec.name];
4614 if (!mfactory) return CodeMirror.getMode(options, "text/plain");
4615 var modeObj = mfactory(options, spec);
4616 if (modeExtensions.hasOwnProperty(spec.name)) {
4617 var exts = modeExtensions[spec.name];
4618 for (var prop in exts) {
4619 if (!exts.hasOwnProperty(prop)) continue;
4620 if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
4621 modeObj[prop] = exts[prop];
4622 }
4623 }
4624 modeObj.name = spec.name;
4625 if (spec.helperType) modeObj.helperType = spec.helperType;
4626 if (spec.modeProps) for (var prop in spec.modeProps)
4627 modeObj[prop] = spec.modeProps[prop];
4628
4629 return modeObj;
4630 };
4631
4632 // Minimal default mode.
4633 CodeMirror.defineMode("null", function() {
4634 return {token: function(stream) {stream.skipToEnd();}};
4635 });
4636 CodeMirror.defineMIME("text/plain", "null");
4637
4638 // This can be used to attach properties to mode objects from
4639 // outside the actual mode definition.
4640 var modeExtensions = CodeMirror.modeExtensions = {};
4641 CodeMirror.extendMode = function(mode, properties) {
4642 var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
4643 copyObj(properties, exts);
4644 };
4645
4646 // EXTENSIONS
4647
4648 CodeMirror.defineExtension = function(name, func) {
4649 CodeMirror.prototype[name] = func;
4650 };
4651 CodeMirror.defineDocExtension = function(name, func) {
4652 Doc.prototype[name] = func;
4653 };
4654 CodeMirror.defineOption = option;
4655
4656 var initHooks = [];
4657 CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
4658
4659 var helpers = CodeMirror.helpers = {};
4660 CodeMirror.registerHelper = function(type, name, value) {
4661 if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []};
4662 helpers[type][name] = value;
4663 };
4664 CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {
4665 CodeMirror.registerHelper(type, name, value);
4666 helpers[type]._global.push({pred: predicate, val: value});
4667 };
4668
4669 // MODE STATE HANDLING
4670
4671 // Utility functions for working with state. Exported because nested
4672 // modes need to do this for their inner modes.
4673
4674 var copyState = CodeMirror.copyState = function(mode, state) {
4675 if (state === true) return state;
4676 if (mode.copyState) return mode.copyState(state);
4677 var nstate = {};
4678 for (var n in state) {
4679 var val = state[n];
4680 if (val instanceof Array) val = val.concat([]);
4681 nstate[n] = val;
4682 }
4683 return nstate;
4684 };
4685
4686 var startState = CodeMirror.startState = function(mode, a1, a2) {
4687 return mode.startState ? mode.startState(a1, a2) : true;
4688 };
4689
4690 // Given a mode and a state (for that mode), find the inner mode and
4691 // state at the position that the state refers to.
4692 CodeMirror.innerMode = function(mode, state) {
4693 while (mode.innerMode) {
4694 var info = mode.innerMode(state);
4695 if (!info || info.mode == mode) break;
4696 state = info.state;
4697 mode = info.mode;
4698 }
4699 return info || {mode: mode, state: state};
4700 };
4701
4702 // STANDARD COMMANDS
4703
4704 // Commands are parameter-less actions that can be performed on an
4705 // editor, mostly used for keybindings.
4706 var commands = CodeMirror.commands = {
4707 selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);},
4708 singleSelection: function(cm) {
4709 cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll);
4710 },
4711 killLine: function(cm) {
4712 deleteNearSelection(cm, function(range) {
4713 if (range.empty()) {
4714 var len = getLine(cm.doc, range.head.line).text.length;
4715 if (range.head.ch == len && range.head.line < cm.lastLine())
4716 return {from: range.head, to: Pos(range.head.line + 1, 0)};
4717 else
4718 return {from: range.head, to: Pos(range.head.line, len)};
4719 } else {
4720 return {from: range.from(), to: range.to()};
4721 }
4722 });
4723 },
4724 deleteLine: function(cm) {
4725 deleteNearSelection(cm, function(range) {
4726 return {from: Pos(range.from().line, 0),
4727 to: clipPos(cm.doc, Pos(range.to().line + 1, 0))};
4728 });
4729 },
4730 delLineLeft: function(cm) {
4731 deleteNearSelection(cm, function(range) {
4732 return {from: Pos(range.from().line, 0), to: range.from()};
4733 });
4734 },
4735 delWrappedLineLeft: function(cm) {
4736 deleteNearSelection(cm, function(range) {
4737 var top = cm.charCoords(range.head, "div").top + 5;
4738 var leftPos = cm.coordsChar({left: 0, top: top}, "div");
4739 return {from: leftPos, to: range.from()};
4740 });
4741 },
4742 delWrappedLineRight: function(cm) {
4743 deleteNearSelection(cm, function(range) {
4744 var top = cm.charCoords(range.head, "div").top + 5;
4745 var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div");
4746 return {from: range.from(), to: rightPos };
4747 });
4748 },
4749 undo: function(cm) {cm.undo();},
4750 redo: function(cm) {cm.redo();},
4751 undoSelection: function(cm) {cm.undoSelection();},
4752 redoSelection: function(cm) {cm.redoSelection();},
4753 goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
4754 goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
4755 goLineStart: function(cm) {
4756 cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); },
4757 {origin: "+move", bias: 1});
4758 },
4759 goLineStartSmart: function(cm) {
4760 cm.extendSelectionsBy(function(range) {
4761 return lineStartSmart(cm, range.head);
4762 }, {origin: "+move", bias: 1});
4763 },
4764 goLineEnd: function(cm) {
4765 cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); },
4766 {origin: "+move", bias: -1});
4767 },
4768 goLineRight: function(cm) {
4769 cm.extendSelectionsBy(function(range) {
4770 var top = cm.charCoords(range.head, "div").top + 5;
4771 return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div");
4772 }, sel_move);
4773 },
4774 goLineLeft: function(cm) {
4775 cm.extendSelectionsBy(function(range) {
4776 var top = cm.charCoords(range.head, "div").top + 5;
4777 return cm.coordsChar({left: 0, top: top}, "div");
4778 }, sel_move);
4779 },
4780 goLineLeftSmart: function(cm) {
4781 cm.extendSelectionsBy(function(range) {
4782 var top = cm.charCoords(range.head, "div").top + 5;
4783 var pos = cm.coordsChar({left: 0, top: top}, "div");
4784 if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head);
4785 return pos;
4786 }, sel_move);
4787 },
4788 goLineUp: function(cm) {cm.moveV(-1, "line");},
4789 goLineDown: function(cm) {cm.moveV(1, "line");},
4790 goPageUp: function(cm) {cm.moveV(-1, "page");},
4791 goPageDown: function(cm) {cm.moveV(1, "page");},
4792 goCharLeft: function(cm) {cm.moveH(-1, "char");},
4793 goCharRight: function(cm) {cm.moveH(1, "char");},
4794 goColumnLeft: function(cm) {cm.moveH(-1, "column");},
4795 goColumnRight: function(cm) {cm.moveH(1, "column");},
4796 goWordLeft: function(cm) {cm.moveH(-1, "word");},
4797 goGroupRight: function(cm) {cm.moveH(1, "group");},
4798 goGroupLeft: function(cm) {cm.moveH(-1, "group");},
4799 goWordRight: function(cm) {cm.moveH(1, "word");},
4800 delCharBefore: function(cm) {cm.deleteH(-1, "char");},
4801 delCharAfter: function(cm) {cm.deleteH(1, "char");},
4802 delWordBefore: function(cm) {cm.deleteH(-1, "word");},
4803 delWordAfter: function(cm) {cm.deleteH(1, "word");},
4804 delGroupBefore: function(cm) {cm.deleteH(-1, "group");},
4805 delGroupAfter: function(cm) {cm.deleteH(1, "group");},
4806 indentAuto: function(cm) {cm.indentSelection("smart");},
4807 indentMore: function(cm) {cm.indentSelection("add");},
4808 indentLess: function(cm) {cm.indentSelection("subtract");},
4809 insertTab: function(cm) {cm.replaceSelection("\t");},
4810 insertSoftTab: function(cm) {
4811 var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize;
4812 for (var i = 0; i < ranges.length; i++) {
4813 var pos = ranges[i].from();
4814 var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize);
4815 spaces.push(new Array(tabSize - col % tabSize + 1).join(" "));
4816 }
4817 cm.replaceSelections(spaces);
4818 },
4819 defaultTab: function(cm) {
4820 if (cm.somethingSelected()) cm.indentSelection("add");
4821 else cm.execCommand("insertTab");
4822 },
4823 transposeChars: function(cm) {
4824 runInOp(cm, function() {
4825 var ranges = cm.listSelections(), newSel = [];
4826 for (var i = 0; i < ranges.length; i++) {
4827 var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text;
4828 if (line) {
4829 if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1);
4830 if (cur.ch > 0) {
4831 cur = new Pos(cur.line, cur.ch + 1);
4832 cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),
4833 Pos(cur.line, cur.ch - 2), cur, "+transpose");
4834 } else if (cur.line > cm.doc.first) {
4835 var prev = getLine(cm.doc, cur.line - 1).text;
4836 if (prev)
4837 cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1),
4838 Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose");
4839 }
4840 }
4841 newSel.push(new Range(cur, cur));
4842 }
4843 cm.setSelections(newSel);
4844 });
4845 },
4846 newlineAndIndent: function(cm) {
4847 runInOp(cm, function() {
4848 var len = cm.listSelections().length;
4849 for (var i = 0; i < len; i++) {
4850 var range = cm.listSelections()[i];
4851 cm.replaceRange("\n", range.anchor, range.head, "+input");
4852 cm.indentLine(range.from().line + 1, null, true);
4853 ensureCursorVisible(cm);
4854 }
4855 });
4856 },
4857 toggleOverwrite: function(cm) {cm.toggleOverwrite();}
4858 };
4859
4860
4861 // STANDARD KEYMAPS
4862
4863 var keyMap = CodeMirror.keyMap = {};
4864
4865 keyMap.basic = {
4866 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
4867 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
4868 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
4869 "Tab": "defaultTab", "Shift-Tab": "indentAuto",
4870 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
4871 "Esc": "singleSelection"
4872 };
4873 // Note that the save and find-related commands aren't defined by
4874 // default. User code or addons can define them. Unknown commands
4875 // are simply ignored.
4876 keyMap.pcDefault = {
4877 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
4878 "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown",
4879 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
4880 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
4881 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
4882 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
4883 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection",
4884 fallthrough: "basic"
4885 };
4886 // Very basic readline/emacs-style bindings, which are standard on Mac.
4887 keyMap.emacsy = {
4888 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
4889 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
4890 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
4891 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
4892 };
4893 keyMap.macDefault = {
4894 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
4895 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
4896 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore",
4897 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
4898 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
4899 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight",
4900 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd",
4901 fallthrough: ["basic", "emacsy"]
4902 };
4903 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
4904
4905 // KEYMAP DISPATCH
4906
4907 function normalizeKeyName(name) {
4908 var parts = name.split(/-(?!$)/), name = parts[parts.length - 1];
4909 var alt, ctrl, shift, cmd;
4910 for (var i = 0; i < parts.length - 1; i++) {
4911 var mod = parts[i];
4912 if (/^(cmd|meta|m)$/i.test(mod)) cmd = true;
4913 else if (/^a(lt)?$/i.test(mod)) alt = true;
4914 else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true;
4915 else if (/^s(hift)$/i.test(mod)) shift = true;
4916 else throw new Error("Unrecognized modifier name: " + mod);
4917 }
4918 if (alt) name = "Alt-" + name;
4919 if (ctrl) name = "Ctrl-" + name;
4920 if (cmd) name = "Cmd-" + name;
4921 if (shift) name = "Shift-" + name;
4922 return name;
4923 }
4924
4925 // This is a kludge to keep keymaps mostly working as raw objects
4926 // (backwards compatibility) while at the same time support features
4927 // like normalization and multi-stroke key bindings. It compiles a
4928 // new normalized keymap, and then updates the old object to reflect
4929 // this.
4930 CodeMirror.normalizeKeyMap = function(keymap) {
4931 var copy = {};
4932 for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) {
4933 var value = keymap[keyname];
4934 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue;
4935 if (value == "...") { delete keymap[keyname]; continue; }
4936
4937 var keys = map(keyname.split(" "), normalizeKeyName);
4938 for (var i = 0; i < keys.length; i++) {
4939 var val, name;
4940 if (i == keys.length - 1) {
4941 name = keyname;
4942 val = value;
4943 } else {
4944 name = keys.slice(0, i + 1).join(" ");
4945 val = "...";
4946 }
4947 var prev = copy[name];
4948 if (!prev) copy[name] = val;
4949 else if (prev != val) throw new Error("Inconsistent bindings for " + name);
4950 }
4951 delete keymap[keyname];
4952 }
4953 for (var prop in copy) keymap[prop] = copy[prop];
4954 return keymap;
4955 };
4956
4957 var lookupKey = CodeMirror.lookupKey = function(key, map, handle) {
4958 map = getKeyMap(map);
4959 var found = map.call ? map.call(key) : map[key];
4960 if (found === false) return "nothing";
4961 if (found === "...") return "multi";
4962 if (found != null && handle(found)) return "handled";
4963
4964 if (map.fallthrough) {
4965 if (Object.prototype.toString.call(map.fallthrough) != "[object Array]")
4966 return lookupKey(key, map.fallthrough, handle);
4967 for (var i = 0; i < map.fallthrough.length; i++) {
4968 var result = lookupKey(key, map.fallthrough[i], handle);
4969 if (result) return result;
4970 }
4971 }
4972 };
4973
4974 // Modifier key presses don't count as 'real' key presses for the
4975 // purpose of keymap fallthrough.
4976 var isModifierKey = CodeMirror.isModifierKey = function(value) {
4977 var name = typeof value == "string" ? value : keyNames[value.keyCode];
4978 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
4979 };
4980
4981 // Look up the name of a key as indicated by an event object.
4982 var keyName = CodeMirror.keyName = function(event, noShift) {
4983 if (presto && event.keyCode == 34 && event["char"]) return false;
4984 var base = keyNames[event.keyCode], name = base;
4985 if (name == null || event.altGraphKey) return false;
4986 if (event.altKey && base != "Alt") name = "Alt-" + name;
4987 if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name;
4988 if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name;
4989 if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name;
4990 return name;
4991 };
4992
4993 function getKeyMap(val) {
4994 return typeof val == "string" ? keyMap[val] : val;
4995 }
4996
4997 // FROMTEXTAREA
4998
4999 CodeMirror.fromTextArea = function(textarea, options) {
5000 if (!options) options = {};
5001 options.value = textarea.value;
5002 if (!options.tabindex && textarea.tabindex)
5003 options.tabindex = textarea.tabindex;
5004 if (!options.placeholder && textarea.placeholder)
5005 options.placeholder = textarea.placeholder;
5006 // Set autofocus to true if this textarea is focused, or if it has
5007 // autofocus and no other element is focused.
5008 if (options.autofocus == null) {
5009 var hasFocus = activeElt();
5010 options.autofocus = hasFocus == textarea ||
5011 textarea.getAttribute("autofocus") != null && hasFocus == document.body;
5012 }
5013
5014 function save() {textarea.value = cm.getValue();}
5015 if (textarea.form) {
5016 on(textarea.form, "submit", save);
5017 // Deplorable hack to make the submit method do the right thing.
5018 if (!options.leaveSubmitMethodAlone) {
5019 var form = textarea.form, realSubmit = form.submit;
5020 try {
5021 var wrappedSubmit = form.submit = function() {
5022 save();
5023 form.submit = realSubmit;
5024 form.submit();
5025 form.submit = wrappedSubmit;
5026 };
5027 } catch(e) {}
5028 }
5029 }
5030
5031 textarea.style.display = "none";
5032 var cm = CodeMirror(function(node) {
5033 textarea.parentNode.insertBefore(node, textarea.nextSibling);
5034 }, options);
5035 cm.save = save;
5036 cm.getTextArea = function() { return textarea; };
5037 cm.toTextArea = function() {
5038 cm.toTextArea = isNaN; // Prevent this from being ran twice
5039 save();
5040 textarea.parentNode.removeChild(cm.getWrapperElement());
5041 textarea.style.display = "";
5042 if (textarea.form) {
5043 off(textarea.form, "submit", save);
5044 if (typeof textarea.form.submit == "function")
5045 textarea.form.submit = realSubmit;
5046 }
5047 };
5048 return cm;
5049 };
5050
5051 // STRING STREAM
5052
5053 // Fed to the mode parsers, provides helper functions to make
5054 // parsers more succinct.
5055
5056 var StringStream = CodeMirror.StringStream = function(string, tabSize) {
5057 this.pos = this.start = 0;
5058 this.string = string;
5059 this.tabSize = tabSize || 8;
5060 this.lastColumnPos = this.lastColumnValue = 0;
5061 this.lineStart = 0;
5062 };
5063
5064 StringStream.prototype = {
5065 eol: function() {return this.pos >= this.string.length;},
5066 sol: function() {return this.pos == this.lineStart;},
5067 peek: function() {return this.string.charAt(this.pos) || undefined;},
5068 next: function() {
5069 if (this.pos < this.string.length)
5070 return this.string.charAt(this.pos++);
5071 },
5072 eat: function(match) {
5073 var ch = this.string.charAt(this.pos);
5074 if (typeof match == "string") var ok = ch == match;
5075 else var ok = ch && (match.test ? match.test(ch) : match(ch));
5076 if (ok) {++this.pos; return ch;}
5077 },
5078 eatWhile: function(match) {
5079 var start = this.pos;
5080 while (this.eat(match)){}
5081 return this.pos > start;
5082 },
5083 eatSpace: function() {
5084 var start = this.pos;
5085 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
5086 return this.pos > start;
5087 },
5088 skipToEnd: function() {this.pos = this.string.length;},
5089 skipTo: function(ch) {
5090 var found = this.string.indexOf(ch, this.pos);
5091 if (found > -1) {this.pos = found; return true;}
5092 },
5093 backUp: function(n) {this.pos -= n;},
5094 column: function() {
5095 if (this.lastColumnPos < this.start) {
5096 this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
5097 this.lastColumnPos = this.start;
5098 }
5099 return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
5100 },
5101 indentation: function() {
5102 return countColumn(this.string, null, this.tabSize) -
5103 (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
5104 },
5105 match: function(pattern, consume, caseInsensitive) {
5106 if (typeof pattern == "string") {
5107 var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
5108 var substr = this.string.substr(this.pos, pattern.length);
5109 if (cased(substr) == cased(pattern)) {
5110 if (consume !== false) this.pos += pattern.length;
5111 return true;
5112 }
5113 } else {
5114 var match = this.string.slice(this.pos).match(pattern);
5115 if (match && match.index > 0) return null;
5116 if (match && consume !== false) this.pos += match[0].length;
5117 return match;
5118 }
5119 },
5120 current: function(){return this.string.slice(this.start, this.pos);},
5121 hideFirstChars: function(n, inner) {
5122 this.lineStart += n;
5123 try { return inner(); }
5124 finally { this.lineStart -= n; }
5125 }
5126 };
5127
5128 // TEXTMARKERS
5129
5130 // Created with markText and setBookmark methods. A TextMarker is a
5131 // handle that can be used to clear or find a marked position in the
5132 // document. Line objects hold arrays (markedSpans) containing
5133 // {from, to, marker} object pointing to such marker objects, and
5134 // indicating that such a marker is present on that line. Multiple
5135 // lines may point to the same marker when it spans across lines.
5136 // The spans will have null for their from/to properties when the
5137 // marker continues beyond the start/end of the line. Markers have
5138 // links back to the lines they currently touch.
5139
5140 var TextMarker = CodeMirror.TextMarker = function(doc, type) {
5141 this.lines = [];
5142 this.type = type;
5143 this.doc = doc;
5144 };
5145 eventMixin(TextMarker);
5146
5147 // Clear the marker.
5148 TextMarker.prototype.clear = function() {
5149 if (this.explicitlyCleared) return;
5150 var cm = this.doc.cm, withOp = cm && !cm.curOp;
5151 if (withOp) startOperation(cm);
5152 if (hasHandler(this, "clear")) {
5153 var found = this.find();
5154 if (found) signalLater(this, "clear", found.from, found.to);
5155 }
5156 var min = null, max = null;
5157 for (var i = 0; i < this.lines.length; ++i) {
5158 var line = this.lines[i];
5159 var span = getMarkedSpanFor(line.markedSpans, this);
5160 if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text");
5161 else if (cm) {
5162 if (span.to != null) max = lineNo(line);
5163 if (span.from != null) min = lineNo(line);
5164 }
5165 line.markedSpans = removeMarkedSpan(line.markedSpans, span);
5166 if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm)
5167 updateLineHeight(line, textHeight(cm.display));
5168 }
5169 if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
5170 var visual = visualLine(this.lines[i]), len = lineLength(visual);
5171 if (len > cm.display.maxLineLength) {
5172 cm.display.maxLine = visual;
5173 cm.display.maxLineLength = len;
5174 cm.display.maxLineChanged = true;
5175 }
5176 }
5177
5178 if (min != null && cm && this.collapsed) regChange(cm, min, max + 1);
5179 this.lines.length = 0;
5180 this.explicitlyCleared = true;
5181 if (this.atomic && this.doc.cantEdit) {
5182 this.doc.cantEdit = false;
5183 if (cm) reCheckSelection(cm.doc);
5184 }
5185 if (cm) signalLater(cm, "markerCleared", cm, this);
5186 if (withOp) endOperation(cm);
5187 if (this.parent) this.parent.clear();
5188 };
5189
5190 // Find the position of the marker in the document. Returns a {from,
5191 // to} object by default. Side can be passed to get a specific side
5192 // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
5193 // Pos objects returned contain a line object, rather than a line
5194 // number (used to prevent looking up the same line twice).
5195 TextMarker.prototype.find = function(side, lineObj) {
5196 if (side == null && this.type == "bookmark") side = 1;
5197 var from, to;
5198 for (var i = 0; i < this.lines.length; ++i) {
5199 var line = this.lines[i];
5200 var span = getMarkedSpanFor(line.markedSpans, this);
5201 if (span.from != null) {
5202 from = Pos(lineObj ? line : lineNo(line), span.from);
5203 if (side == -1) return from;
5204 }
5205 if (span.to != null) {
5206 to = Pos(lineObj ? line : lineNo(line), span.to);
5207 if (side == 1) return to;
5208 }
5209 }
5210 return from && {from: from, to: to};
5211 };
5212
5213 // Signals that the marker's widget changed, and surrounding layout
5214 // should be recomputed.
5215 TextMarker.prototype.changed = function() {
5216 var pos = this.find(-1, true), widget = this, cm = this.doc.cm;
5217 if (!pos || !cm) return;
5218 runInOp(cm, function() {
5219 var line = pos.line, lineN = lineNo(pos.line);
5220 var view = findViewForLine(cm, lineN);
5221 if (view) {
5222 clearLineMeasurementCacheFor(view);
5223 cm.curOp.selectionChanged = cm.curOp.forceUpdate = true;
5224 }
5225 cm.curOp.updateMaxLine = true;
5226 if (!lineIsHidden(widget.doc, line) && widget.height != null) {
5227 var oldHeight = widget.height;
5228 widget.height = null;
5229 var dHeight = widgetHeight(widget) - oldHeight;
5230 if (dHeight)
5231 updateLineHeight(line, line.height + dHeight);
5232 }
5233 });
5234 };
5235
5236 TextMarker.prototype.attachLine = function(line) {
5237 if (!this.lines.length && this.doc.cm) {
5238 var op = this.doc.cm.curOp;
5239 if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
5240 (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this);
5241 }
5242 this.lines.push(line);
5243 };
5244 TextMarker.prototype.detachLine = function(line) {
5245 this.lines.splice(indexOf(this.lines, line), 1);
5246 if (!this.lines.length && this.doc.cm) {
5247 var op = this.doc.cm.curOp;
5248 (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);
5249 }
5250 };
5251
5252 // Collapsed markers have unique ids, in order to be able to order
5253 // them, which is needed for uniquely determining an outer marker
5254 // when they overlap (they may nest, but not partially overlap).
5255 var nextMarkerId = 0;
5256
5257 // Create a marker, wire it up to the right lines, and
5258 function markText(doc, from, to, options, type) {
5259 // Shared markers (across linked documents) are handled separately
5260 // (markTextShared will call out to this again, once per
5261 // document).
5262 if (options && options.shared) return markTextShared(doc, from, to, options, type);
5263 // Ensure we are in an operation.
5264 if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
5265
5266 var marker = new TextMarker(doc, type), diff = cmp(from, to);
5267 if (options) copyObj(options, marker, false);
5268 // Don't connect empty markers unless clearWhenEmpty is false
5269 if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
5270 return marker;
5271 if (marker.replacedWith) {
5272 // Showing up as a widget implies collapsed (widget replaces text)
5273 marker.collapsed = true;
5274 marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget");
5275 if (!options.handleMouseEvents) marker.widgetNode.ignoreEvents = true;
5276 if (options.insertLeft) marker.widgetNode.insertLeft = true;
5277 }
5278 if (marker.collapsed) {
5279 if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
5280 from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))
5281 throw new Error("Inserting collapsed marker partially overlapping an existing one");
5282 sawCollapsedSpans = true;
5283 }
5284
5285 if (marker.addToHistory)
5286 addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN);
5287
5288 var curLine = from.line, cm = doc.cm, updateMaxLine;
5289 doc.iter(curLine, to.line + 1, function(line) {
5290 if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)
5291 updateMaxLine = true;
5292 if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0);
5293 addMarkedSpan(line, new MarkedSpan(marker,
5294 curLine == from.line ? from.ch : null,
5295 curLine == to.line ? to.ch : null));
5296 ++curLine;
5297 });
5298 // lineIsHidden depends on the presence of the spans, so needs a second pass
5299 if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
5300 if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
5301 });
5302
5303 if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); });
5304
5305 if (marker.readOnly) {
5306 sawReadOnlySpans = true;
5307 if (doc.history.done.length || doc.history.undone.length)
5308 doc.clearHistory();
5309 }
5310 if (marker.collapsed) {
5311 marker.id = ++nextMarkerId;
5312 marker.atomic = true;
5313 }
5314 if (cm) {
5315 // Sync editor state
5316 if (updateMaxLine) cm.curOp.updateMaxLine = true;
5317 if (marker.collapsed)
5318 regChange(cm, from.line, to.line + 1);
5319 else if (marker.className || marker.title || marker.startStyle || marker.endStyle)
5320 for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text");
5321 if (marker.atomic) reCheckSelection(cm.doc);
5322 signalLater(cm, "markerAdded", cm, marker);
5323 }
5324 return marker;
5325 }
5326
5327 // SHARED TEXTMARKERS
5328
5329 // A shared marker spans multiple linked documents. It is
5330 // implemented as a meta-marker-object controlling multiple normal
5331 // markers.
5332 var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) {
5333 this.markers = markers;
5334 this.primary = primary;
5335 for (var i = 0; i < markers.length; ++i)
5336 markers[i].parent = this;
5337 };
5338 eventMixin(SharedTextMarker);
5339
5340 SharedTextMarker.prototype.clear = function() {
5341 if (this.explicitlyCleared) return;
5342 this.explicitlyCleared = true;
5343 for (var i = 0; i < this.markers.length; ++i)
5344 this.markers[i].clear();
5345 signalLater(this, "clear");
5346 };
5347 SharedTextMarker.prototype.find = function(side, lineObj) {
5348 return this.primary.find(side, lineObj);
5349 };
5350
5351 function markTextShared(doc, from, to, options, type) {
5352 options = copyObj(options);
5353 options.shared = false;
5354 var markers = [markText(doc, from, to, options, type)], primary = markers[0];
5355 var widget = options.widgetNode;
5356 linkedDocs(doc, function(doc) {
5357 if (widget) options.widgetNode = widget.cloneNode(true);
5358 markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
5359 for (var i = 0; i < doc.linked.length; ++i)
5360 if (doc.linked[i].isParent) return;
5361 primary = lst(markers);
5362 });
5363 return new SharedTextMarker(markers, primary);
5364 }
5365
5366 function findSharedMarkers(doc) {
5367 return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())),
5368 function(m) { return m.parent; });
5369 }
5370
5371 function copySharedMarkers(doc, markers) {
5372 for (var i = 0; i < markers.length; i++) {
5373 var marker = markers[i], pos = marker.find();
5374 var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to);
5375 if (cmp(mFrom, mTo)) {
5376 var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type);
5377 marker.markers.push(subMark);
5378 subMark.parent = marker;
5379 }
5380 }
5381 }
5382
5383 function detachSharedMarkers(markers) {
5384 for (var i = 0; i < markers.length; i++) {
5385 var marker = markers[i], linked = [marker.primary.doc];;
5386 linkedDocs(marker.primary.doc, function(d) { linked.push(d); });
5387 for (var j = 0; j < marker.markers.length; j++) {
5388 var subMarker = marker.markers[j];
5389 if (indexOf(linked, subMarker.doc) == -1) {
5390 subMarker.parent = null;
5391 marker.markers.splice(j--, 1);
5392 }
5393 }
5394 }
5395 }
5396
5397 // TEXTMARKER SPANS
5398
5399 function MarkedSpan(marker, from, to) {
5400 this.marker = marker;
5401 this.from = from; this.to = to;
5402 }
5403
5404 // Search an array of spans for a span matching the given marker.
5405 function getMarkedSpanFor(spans, marker) {
5406 if (spans) for (var i = 0; i < spans.length; ++i) {
5407 var span = spans[i];
5408 if (span.marker == marker) return span;
5409 }
5410 }
5411 // Remove a span from an array, returning undefined if no spans are
5412 // left (we don't store arrays for lines without spans).
5413 function removeMarkedSpan(spans, span) {
5414 for (var r, i = 0; i < spans.length; ++i)
5415 if (spans[i] != span) (r || (r = [])).push(spans[i]);
5416 return r;
5417 }
5418 // Add a span to a line.
5419 function addMarkedSpan(line, span) {
5420 line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
5421 span.marker.attachLine(line);
5422 }
5423
5424 // Used for the algorithm that adjusts markers for a change in the
5425 // document. These functions cut an array of spans at a given
5426 // character position, returning an array of remaining chunks (or
5427 // undefined if nothing remains).
5428 function markedSpansBefore(old, startCh, isInsert) {
5429 if (old) for (var i = 0, nw; i < old.length; ++i) {
5430 var span = old[i], marker = span.marker;
5431 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
5432 if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) {
5433 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
5434 (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to));
5435 }
5436 }
5437 return nw;
5438 }
5439 function markedSpansAfter(old, endCh, isInsert) {
5440 if (old) for (var i = 0, nw; i < old.length; ++i) {
5441 var span = old[i], marker = span.marker;
5442 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
5443 if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) {
5444 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
5445 (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh,
5446 span.to == null ? null : span.to - endCh));
5447 }
5448 }
5449 return nw;
5450 }
5451
5452 // Given a change object, compute the new set of marker spans that
5453 // cover the line in which the change took place. Removes spans
5454 // entirely within the change, reconnects spans belonging to the
5455 // same marker that appear on both sides of the change, and cuts off
5456 // spans partially within the change. Returns an array of span
5457 // arrays with one element for each line in (after) the change.
5458 function stretchSpansOverChange(doc, change) {
5459 var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
5460 var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
5461 if (!oldFirst && !oldLast) return null;
5462
5463 var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0;
5464 // Get the spans that 'stick out' on both sides
5465 var first = markedSpansBefore(oldFirst, startCh, isInsert);
5466 var last = markedSpansAfter(oldLast, endCh, isInsert);
5467
5468 // Next, merge those two ends
5469 var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);
5470 if (first) {
5471 // Fix up .to properties of first
5472 for (var i = 0; i < first.length; ++i) {
5473 var span = first[i];
5474 if (span.to == null) {
5475 var found = getMarkedSpanFor(last, span.marker);
5476 if (!found) span.to = startCh;
5477 else if (sameLine) span.to = found.to == null ? null : found.to + offset;
5478 }
5479 }
5480 }
5481 if (last) {
5482 // Fix up .from in last (or move them into first in case of sameLine)
5483 for (var i = 0; i < last.length; ++i) {
5484 var span = last[i];
5485 if (span.to != null) span.to += offset;
5486 if (span.from == null) {
5487 var found = getMarkedSpanFor(first, span.marker);
5488 if (!found) {
5489 span.from = offset;
5490 if (sameLine) (first || (first = [])).push(span);
5491 }
5492 } else {
5493 span.from += offset;
5494 if (sameLine) (first || (first = [])).push(span);
5495 }
5496 }
5497 }
5498 // Make sure we didn't create any zero-length spans
5499 if (first) first = clearEmptySpans(first);
5500 if (last && last != first) last = clearEmptySpans(last);
5501
5502 var newMarkers = [first];
5503 if (!sameLine) {
5504 // Fill gap with whole-line-spans
5505 var gap = change.text.length - 2, gapMarkers;
5506 if (gap > 0 && first)
5507 for (var i = 0; i < first.length; ++i)
5508 if (first[i].to == null)
5509 (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null));
5510 for (var i = 0; i < gap; ++i)
5511 newMarkers.push(gapMarkers);
5512 newMarkers.push(last);
5513 }
5514 return newMarkers;
5515 }
5516
5517 // Remove spans that are empty and don't have a clearWhenEmpty
5518 // option of false.
5519 function clearEmptySpans(spans) {
5520 for (var i = 0; i < spans.length; ++i) {
5521 var span = spans[i];
5522 if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)
5523 spans.splice(i--, 1);
5524 }
5525 if (!spans.length) return null;
5526 return spans;
5527 }
5528
5529 // Used for un/re-doing changes from the history. Combines the
5530 // result of computing the existing spans with the set of spans that
5531 // existed in the history (so that deleting around a span and then
5532 // undoing brings back the span).
5533 function mergeOldSpans(doc, change) {
5534 var old = getOldSpans(doc, change);
5535 var stretched = stretchSpansOverChange(doc, change);
5536 if (!old) return stretched;
5537 if (!stretched) return old;
5538
5539 for (var i = 0; i < old.length; ++i) {
5540 var oldCur = old[i], stretchCur = stretched[i];
5541 if (oldCur && stretchCur) {
5542 spans: for (var j = 0; j < stretchCur.length; ++j) {
5543 var span = stretchCur[j];
5544 for (var k = 0; k < oldCur.length; ++k)
5545 if (oldCur[k].marker == span.marker) continue spans;
5546 oldCur.push(span);
5547 }
5548 } else if (stretchCur) {
5549 old[i] = stretchCur;
5550 }
5551 }
5552 return old;
5553 }
5554
5555 // Used to 'clip' out readOnly ranges when making a change.
5556 function removeReadOnlyRanges(doc, from, to) {
5557 var markers = null;
5558 doc.iter(from.line, to.line + 1, function(line) {
5559 if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
5560 var mark = line.markedSpans[i].marker;
5561 if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
5562 (markers || (markers = [])).push(mark);
5563 }
5564 });
5565 if (!markers) return null;
5566 var parts = [{from: from, to: to}];
5567 for (var i = 0; i < markers.length; ++i) {
5568 var mk = markers[i], m = mk.find(0);
5569 for (var j = 0; j < parts.length; ++j) {
5570 var p = parts[j];
5571 if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue;
5572 var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to);
5573 if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)
5574 newParts.push({from: p.from, to: m.from});
5575 if (dto > 0 || !mk.inclusiveRight && !dto)
5576 newParts.push({from: m.to, to: p.to});
5577 parts.splice.apply(parts, newParts);
5578 j += newParts.length - 1;
5579 }
5580 }
5581 return parts;
5582 }
5583
5584 // Connect or disconnect spans from a line.
5585 function detachMarkedSpans(line) {
5586 var spans = line.markedSpans;
5587 if (!spans) return;
5588 for (var i = 0; i < spans.length; ++i)
5589 spans[i].marker.detachLine(line);
5590 line.markedSpans = null;
5591 }
5592 function attachMarkedSpans(line, spans) {
5593 if (!spans) return;
5594 for (var i = 0; i < spans.length; ++i)
5595 spans[i].marker.attachLine(line);
5596 line.markedSpans = spans;
5597 }
5598
5599 // Helpers used when computing which overlapping collapsed span
5600 // counts as the larger one.
5601 function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; }
5602 function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; }
5603
5604 // Returns a number indicating which of two overlapping collapsed
5605 // spans is larger (and thus includes the other). Falls back to
5606 // comparing ids when the spans cover exactly the same range.
5607 function compareCollapsedMarkers(a, b) {
5608 var lenDiff = a.lines.length - b.lines.length;
5609 if (lenDiff != 0) return lenDiff;
5610 var aPos = a.find(), bPos = b.find();
5611 var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b);
5612 if (fromCmp) return -fromCmp;
5613 var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b);
5614 if (toCmp) return toCmp;
5615 return b.id - a.id;
5616 }
5617
5618 // Find out whether a line ends or starts in a collapsed span. If
5619 // so, return the marker for that span.
5620 function collapsedSpanAtSide(line, start) {
5621 var sps = sawCollapsedSpans && line.markedSpans, found;
5622 if (sps) for (var sp, i = 0; i < sps.length; ++i) {
5623 sp = sps[i];
5624 if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&
5625 (!found || compareCollapsedMarkers(found, sp.marker) < 0))
5626 found = sp.marker;
5627 }
5628 return found;
5629 }
5630 function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); }
5631 function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); }
5632
5633 // Test whether there exists a collapsed span that partially
5634 // overlaps (covers the start or end, but not both) of a new span.
5635 // Such overlap is not allowed.
5636 function conflictingCollapsedRange(doc, lineNo, from, to, marker) {
5637 var line = getLine(doc, lineNo);
5638 var sps = sawCollapsedSpans && line.markedSpans;
5639 if (sps) for (var i = 0; i < sps.length; ++i) {
5640 var sp = sps[i];
5641 if (!sp.marker.collapsed) continue;
5642 var found = sp.marker.find(0);
5643 var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);
5644 var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);
5645 if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue;
5646 if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) ||
5647 fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight)))
5648 return true;
5649 }
5650 }
5651
5652 // A visual line is a line as drawn on the screen. Folding, for
5653 // example, can cause multiple logical lines to appear on the same
5654 // visual line. This finds the start of the visual line that the
5655 // given line is part of (usually that is the line itself).
5656 function visualLine(line) {
5657 var merged;
5658 while (merged = collapsedSpanAtStart(line))
5659 line = merged.find(-1, true).line;
5660 return line;
5661 }
5662
5663 // Returns an array of logical lines that continue the visual line
5664 // started by the argument, or undefined if there are no such lines.
5665 function visualLineContinued(line) {
5666 var merged, lines;
5667 while (merged = collapsedSpanAtEnd(line)) {
5668 line = merged.find(1, true).line;
5669 (lines || (lines = [])).push(line);
5670 }
5671 return lines;
5672 }
5673
5674 // Get the line number of the start of the visual line that the
5675 // given line number is part of.
5676 function visualLineNo(doc, lineN) {
5677 var line = getLine(doc, lineN), vis = visualLine(line);
5678 if (line == vis) return lineN;
5679 return lineNo(vis);
5680 }
5681 // Get the line number of the start of the next visual line after
5682 // the given line.
5683 function visualLineEndNo(doc, lineN) {
5684 if (lineN > doc.lastLine()) return lineN;
5685 var line = getLine(doc, lineN), merged;
5686 if (!lineIsHidden(doc, line)) return lineN;
5687 while (merged = collapsedSpanAtEnd(line))
5688 line = merged.find(1, true).line;
5689 return lineNo(line) + 1;
5690 }
5691
5692 // Compute whether a line is hidden. Lines count as hidden when they
5693 // are part of a visual line that starts with another line, or when
5694 // they are entirely covered by collapsed, non-widget span.
5695 function lineIsHidden(doc, line) {
5696 var sps = sawCollapsedSpans && line.markedSpans;
5697 if (sps) for (var sp, i = 0; i < sps.length; ++i) {
5698 sp = sps[i];
5699 if (!sp.marker.collapsed) continue;
5700 if (sp.from == null) return true;
5701 if (sp.marker.widgetNode) continue;
5702 if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
5703 return true;
5704 }
5705 }
5706 function lineIsHiddenInner(doc, line, span) {
5707 if (span.to == null) {
5708 var end = span.marker.find(1, true);
5709 return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker));
5710 }
5711 if (span.marker.inclusiveRight && span.to == line.text.length)
5712 return true;
5713 for (var sp, i = 0; i < line.markedSpans.length; ++i) {
5714 sp = line.markedSpans[i];
5715 if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&
5716 (sp.to == null || sp.to != span.from) &&
5717 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
5718 lineIsHiddenInner(doc, line, sp)) return true;
5719 }
5720 }
5721
5722 // LINE WIDGETS
5723
5724 // Line widgets are block elements displayed above or below a line.
5725
5726 var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
5727 if (options) for (var opt in options) if (options.hasOwnProperty(opt))
5728 this[opt] = options[opt];
5729 this.cm = cm;
5730 this.node = node;
5731 };
5732 eventMixin(LineWidget);
5733
5734 function adjustScrollWhenAboveVisible(cm, line, diff) {
5735 if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
5736 addToScrollPos(cm, null, diff);
5737 }
5738
5739 LineWidget.prototype.clear = function() {
5740 var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
5741 if (no == null || !ws) return;
5742 for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
5743 if (!ws.length) line.widgets = null;
5744 var height = widgetHeight(this);
5745 runInOp(cm, function() {
5746 adjustScrollWhenAboveVisible(cm, line, -height);
5747 regLineChange(cm, no, "widget");
5748 updateLineHeight(line, Math.max(0, line.height - height));
5749 });
5750 };
5751 LineWidget.prototype.changed = function() {
5752 var oldH = this.height, cm = this.cm, line = this.line;
5753 this.height = null;
5754 var diff = widgetHeight(this) - oldH;
5755 if (!diff) return;
5756 runInOp(cm, function() {
5757 cm.curOp.forceUpdate = true;
5758 adjustScrollWhenAboveVisible(cm, line, diff);
5759 updateLineHeight(line, line.height + diff);
5760 });
5761 };
5762
5763 function widgetHeight(widget) {
5764 if (widget.height != null) return widget.height;
5765 if (!contains(document.body, widget.node)) {
5766 var parentStyle = "position: relative;";
5767 if (widget.coverGutter)
5768 parentStyle += "margin-left: -" + widget.cm.getGutterElement().offsetWidth + "px;";
5769 removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, parentStyle));
5770 }
5771 return widget.height = widget.node.offsetHeight;
5772 }
5773
5774 function addLineWidget(cm, handle, node, options) {
5775 var widget = new LineWidget(cm, node, options);
5776 if (widget.noHScroll) cm.display.alignWidgets = true;
5777 changeLine(cm.doc, handle, "widget", function(line) {
5778 var widgets = line.widgets || (line.widgets = []);
5779 if (widget.insertAt == null) widgets.push(widget);
5780 else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
5781 widget.line = line;
5782 if (!lineIsHidden(cm.doc, line)) {
5783 var aboveVisible = heightAtLine(line) < cm.doc.scrollTop;
5784 updateLineHeight(line, line.height + widgetHeight(widget));
5785 if (aboveVisible) addToScrollPos(cm, null, widget.height);
5786 cm.curOp.forceUpdate = true;
5787 }
5788 return true;
5789 });
5790 return widget;
5791 }
5792
5793 // LINE DATA STRUCTURE
5794
5795 // Line objects. These hold state related to a line, including
5796 // highlighting info (the styles array).
5797 var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) {
5798 this.text = text;
5799 attachMarkedSpans(this, markedSpans);
5800 this.height = estimateHeight ? estimateHeight(this) : 1;
5801 };
5802 eventMixin(Line);
5803 Line.prototype.lineNo = function() { return lineNo(this); };
5804
5805 // Change the content (text, markers) of a line. Automatically
5806 // invalidates cached information and tries to re-estimate the
5807 // line's height.
5808 function updateLine(line, text, markedSpans, estimateHeight) {
5809 line.text = text;
5810 if (line.stateAfter) line.stateAfter = null;
5811 if (line.styles) line.styles = null;
5812 if (line.order != null) line.order = null;
5813 detachMarkedSpans(line);
5814 attachMarkedSpans(line, markedSpans);
5815 var estHeight = estimateHeight ? estimateHeight(line) : 1;
5816 if (estHeight != line.height) updateLineHeight(line, estHeight);
5817 }
5818
5819 // Detach a line from the document tree and its markers.
5820 function cleanUpLine(line) {
5821 line.parent = null;
5822 detachMarkedSpans(line);
5823 }
5824
5825 function extractLineClasses(type, output) {
5826 if (type) for (;;) {
5827 var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/);
5828 if (!lineClass) break;
5829 type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length);
5830 var prop = lineClass[1] ? "bgClass" : "textClass";
5831 if (output[prop] == null)
5832 output[prop] = lineClass[2];
5833 else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop]))
5834 output[prop] += " " + lineClass[2];
5835 }
5836 return type;
5837 }
5838
5839 function callBlankLine(mode, state) {
5840 if (mode.blankLine) return mode.blankLine(state);
5841 if (!mode.innerMode) return;
5842 var inner = CodeMirror.innerMode(mode, state);
5843 if (inner.mode.blankLine) return inner.mode.blankLine(inner.state);
5844 }
5845
5846 function readToken(mode, stream, state, inner) {
5847 for (var i = 0; i < 10; i++) {
5848 if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode;
5849 var style = mode.token(stream, state);
5850 if (stream.pos > stream.start) return style;
5851 }
5852 throw new Error("Mode " + mode.name + " failed to advance stream.");
5853 }
5854
5855 // Utility for getTokenAt and getLineTokens
5856 function takeToken(cm, pos, precise, asArray) {
5857 function getObj(copy) {
5858 return {start: stream.start, end: stream.pos,
5859 string: stream.current(),
5860 type: style || null,
5861 state: copy ? copyState(doc.mode, state) : state};
5862 }
5863
5864 var doc = cm.doc, mode = doc.mode, style;
5865 pos = clipPos(doc, pos);
5866 var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise);
5867 var stream = new StringStream(line.text, cm.options.tabSize), tokens;
5868 if (asArray) tokens = [];
5869 while ((asArray || stream.pos < pos.ch) && !stream.eol()) {
5870 stream.start = stream.pos;
5871 style = readToken(mode, stream, state);
5872 if (asArray) tokens.push(getObj(true));
5873 }
5874 return asArray ? tokens : getObj();
5875 }
5876
5877 // Run the given mode's parser over a line, calling f for each token.
5878 function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) {
5879 var flattenSpans = mode.flattenSpans;
5880 if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
5881 var curStart = 0, curStyle = null;
5882 var stream = new StringStream(text, cm.options.tabSize), style;
5883 var inner = cm.options.addModeClass && [null];
5884 if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses);
5885 while (!stream.eol()) {
5886 if (stream.pos > cm.options.maxHighlightLength) {
5887 flattenSpans = false;
5888 if (forceToEnd) processLine(cm, text, state, stream.pos);
5889 stream.pos = text.length;
5890 style = null;
5891 } else {
5892 style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses);
5893 }
5894 if (inner) {
5895 var mName = inner[0].name;
5896 if (mName) style = "m-" + (style ? mName + " " + style : mName);
5897 }
5898 if (!flattenSpans || curStyle != style) {
5899 if (curStart < stream.start) f(stream.start, curStyle);
5900 curStart = stream.start; curStyle = style;
5901 }
5902 stream.start = stream.pos;
5903 }
5904 while (curStart < stream.pos) {
5905 // Webkit seems to refuse to render text nodes longer than 57444 characters
5906 var pos = Math.min(stream.pos, curStart + 50000);
5907 f(pos, curStyle);
5908 curStart = pos;
5909 }
5910 }
5911
5912 // Compute a style array (an array starting with a mode generation
5913 // -- for invalidation -- followed by pairs of end positions and
5914 // style strings), which is used to highlight the tokens on the
5915 // line.
5916 function highlightLine(cm, line, state, forceToEnd) {
5917 // A styles array always starts with a number identifying the
5918 // mode/overlays that it is based on (for easy invalidation).
5919 var st = [cm.state.modeGen], lineClasses = {};
5920 // Compute the base array of styles
5921 runMode(cm, line.text, cm.doc.mode, state, function(end, style) {
5922 st.push(end, style);
5923 }, lineClasses, forceToEnd);
5924
5925 // Run overlays, adjust style array.
5926 for (var o = 0; o < cm.state.overlays.length; ++o) {
5927 var overlay = cm.state.overlays[o], i = 1, at = 0;
5928 runMode(cm, line.text, overlay.mode, true, function(end, style) {
5929 var start = i;
5930 // Ensure there's a token end at the current position, and that i points at it
5931 while (at < end) {
5932 var i_end = st[i];
5933 if (i_end > end)
5934 st.splice(i, 1, end, st[i+1], i_end);
5935 i += 2;
5936 at = Math.min(end, i_end);
5937 }
5938 if (!style) return;
5939 if (overlay.opaque) {
5940 st.splice(start, i - start, end, "cm-overlay " + style);
5941 i = start + 2;
5942 } else {
5943 for (; start < i; start += 2) {
5944 var cur = st[start+1];
5945 st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style;
5946 }
5947 }
5948 }, lineClasses);
5949 }
5950
5951 return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null};
5952 }
5953
5954 function getLineStyles(cm, line, updateFrontier) {
5955 if (!line.styles || line.styles[0] != cm.state.modeGen) {
5956 var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
5957 line.styles = result.styles;
5958 if (result.classes) line.styleClasses = result.classes;
5959 else if (line.styleClasses) line.styleClasses = null;
5960 if (updateFrontier === cm.doc.frontier) cm.doc.frontier++;
5961 }
5962 return line.styles;
5963 }
5964
5965 // Lightweight form of highlight -- proceed over this line and
5966 // update state, but don't save a style array. Used for lines that
5967 // aren't currently visible.
5968 function processLine(cm, text, state, startAt) {
5969 var mode = cm.doc.mode;
5970 var stream = new StringStream(text, cm.options.tabSize);
5971 stream.start = stream.pos = startAt || 0;
5972 if (text == "") callBlankLine(mode, state);
5973 while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
5974 readToken(mode, stream, state);
5975 stream.start = stream.pos;
5976 }
5977 }
5978
5979 // Convert a style as returned by a mode (either null, or a string
5980 // containing one or more styles) to a CSS style. This is cached,
5981 // and also looks for line-wide styles.
5982 var styleToClassCache = {}, styleToClassCacheWithMode = {};
5983 function interpretTokenStyle(style, options) {
5984 if (!style || /^\s*$/.test(style)) return null;
5985 var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache;
5986 return cache[style] ||
5987 (cache[style] = style.replace(/\S+/g, "cm-$&"));
5988 }
5989
5990 // Render the DOM representation of the text of a line. Also builds
5991 // up a 'line map', which points at the DOM nodes that represent
5992 // specific stretches of text, and is used by the measuring code.
5993 // The returned object contains the DOM node, this map, and
5994 // information about line-wide styles that were set by the mode.
5995 function buildLineContent(cm, lineView) {
5996 // The padding-right forces the element to have a 'border', which
5997 // is needed on Webkit to be able to get line-level bounding
5998 // rectangles for it (in measureChar).
5999 var content = elt("span", null, null, webkit ? "padding-right: .1px" : null);
6000 var builder = {pre: elt("pre", [content]), content: content, col: 0, pos: 0, cm: cm};
6001 lineView.measure = {};
6002
6003 // Iterate over the logical lines that make up this visual line.
6004 for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {
6005 var line = i ? lineView.rest[i - 1] : lineView.line, order;
6006 builder.pos = 0;
6007 builder.addToken = buildToken;
6008 // Optionally wire in some hacks into the token-rendering
6009 // algorithm, to deal with browser quirks.
6010 if ((ie || webkit) && cm.getOption("lineWrapping"))
6011 builder.addToken = buildTokenSplitSpaces(builder.addToken);
6012 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line)))
6013 builder.addToken = buildTokenBadBidi(builder.addToken, order);
6014 builder.map = [];
6015 var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line);
6016 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate));
6017 if (line.styleClasses) {
6018 if (line.styleClasses.bgClass)
6019 builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "");
6020 if (line.styleClasses.textClass)
6021 builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "");
6022 }
6023
6024 // Ensure at least a single node is present, for measuring.
6025 if (builder.map.length == 0)
6026 builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure)));
6027
6028 // Store the map and a cache object for the current logical line
6029 if (i == 0) {
6030 lineView.measure.map = builder.map;
6031 lineView.measure.cache = {};
6032 } else {
6033 (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map);
6034 (lineView.measure.caches || (lineView.measure.caches = [])).push({});
6035 }
6036 }
6037
6038 // See issue #2901
6039 if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className))
6040 builder.content.className = "cm-tab-wrap-hack";
6041
6042 signal(cm, "renderLine", cm, lineView.line, builder.pre);
6043 if (builder.pre.className)
6044 builder.textClass = joinClasses(builder.pre.className, builder.textClass || "");
6045
6046 return builder;
6047 }
6048
6049 function defaultSpecialCharPlaceholder(ch) {
6050 var token = elt("span", "\u2022", "cm-invalidchar");
6051 token.title = "\\u" + ch.charCodeAt(0).toString(16);
6052 return token;
6053 }
6054
6055 // Build up the DOM representation for a single token, and add it to
6056 // the line map. Takes care to render special characters separately.
6057 function buildToken(builder, text, style, startStyle, endStyle, title) {
6058 if (!text) return;
6059 var special = builder.cm.options.specialChars, mustWrap = false;
6060 if (!special.test(text)) {
6061 builder.col += text.length;
6062 var content = document.createTextNode(text);
6063 builder.map.push(builder.pos, builder.pos + text.length, content);
6064 if (ie && ie_version < 9) mustWrap = true;
6065 builder.pos += text.length;
6066 } else {
6067 var content = document.createDocumentFragment(), pos = 0;
6068 while (true) {
6069 special.lastIndex = pos;
6070 var m = special.exec(text);
6071 var skipped = m ? m.index - pos : text.length - pos;
6072 if (skipped) {
6073 var txt = document.createTextNode(text.slice(pos, pos + skipped));
6074 if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
6075 else content.appendChild(txt);
6076 builder.map.push(builder.pos, builder.pos + skipped, txt);
6077 builder.col += skipped;
6078 builder.pos += skipped;
6079 }
6080 if (!m) break;
6081 pos += skipped + 1;
6082 if (m[0] == "\t") {
6083 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
6084 var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
6085 builder.col += tabWidth;
6086 } else {
6087 var txt = builder.cm.options.specialCharPlaceholder(m[0]);
6088 if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
6089 else content.appendChild(txt);
6090 builder.col += 1;
6091 }
6092 builder.map.push(builder.pos, builder.pos + 1, txt);
6093 builder.pos++;
6094 }
6095 }
6096 if (style || startStyle || endStyle || mustWrap) {
6097 var fullStyle = style || "";
6098 if (startStyle) fullStyle += startStyle;
6099 if (endStyle) fullStyle += endStyle;
6100 var token = elt("span", [content], fullStyle);
6101 if (title) token.title = title;
6102 return builder.content.appendChild(token);
6103 }
6104 builder.content.appendChild(content);
6105 }
6106
6107 function buildTokenSplitSpaces(inner) {
6108 function split(old) {
6109 var out = " ";
6110 for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
6111 out += " ";
6112 return out;
6113 }
6114 return function(builder, text, style, startStyle, endStyle, title) {
6115 inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title);
6116 };
6117 }
6118
6119 // Work around nonsense dimensions being reported for stretches of
6120 // right-to-left text.
6121 function buildTokenBadBidi(inner, order) {
6122 return function(builder, text, style, startStyle, endStyle, title) {
6123 style = style ? style + " cm-force-border" : "cm-force-border";
6124 var start = builder.pos, end = start + text.length;
6125 for (;;) {
6126 // Find the part that overlaps with the start of this text
6127 for (var i = 0; i < order.length; i++) {
6128 var part = order[i];
6129 if (part.to > start && part.from <= start) break;
6130 }
6131 if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title);
6132 inner(builder, text.slice(0, part.to - start), style, startStyle, null, title);
6133 startStyle = null;
6134 text = text.slice(part.to - start);
6135 start = part.to;
6136 }
6137 };
6138 }
6139
6140 function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
6141 var widget = !ignoreWidget && marker.widgetNode;
6142 if (widget) {
6143 builder.map.push(builder.pos, builder.pos + size, widget);
6144 builder.content.appendChild(widget);
6145 }
6146 builder.pos += size;
6147 }
6148
6149 // Outputs a number of spans to make up a line, taking highlighting
6150 // and marked text into account.
6151 function insertLineContent(line, builder, styles) {
6152 var spans = line.markedSpans, allText = line.text, at = 0;
6153 if (!spans) {
6154 for (var i = 1; i < styles.length; i+=2)
6155 builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options));
6156 return;
6157 }
6158
6159 var len = allText.length, pos = 0, i = 1, text = "", style;
6160 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed;
6161 for (;;) {
6162 if (nextChange == pos) { // Update current marker set
6163 spanStyle = spanEndStyle = spanStartStyle = title = "";
6164 collapsed = null; nextChange = Infinity;
6165 var foundBookmarks = [];
6166 for (var j = 0; j < spans.length; ++j) {
6167 var sp = spans[j], m = sp.marker;
6168 if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
6169 if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
6170 if (m.className) spanStyle += " " + m.className;
6171 if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
6172 if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
6173 if (m.title && !title) title = m.title;
6174 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
6175 collapsed = sp;
6176 } else if (sp.from > pos && nextChange > sp.from) {
6177 nextChange = sp.from;
6178 }
6179 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m);
6180 }
6181 if (collapsed && (collapsed.from || 0) == pos) {
6182 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
6183 collapsed.marker, collapsed.from == null);
6184 if (collapsed.to == null) return;
6185 }
6186 if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
6187 buildCollapsedSpan(builder, 0, foundBookmarks[j]);
6188 }
6189 if (pos >= len) break;
6190
6191 var upto = Math.min(len, nextChange);
6192 while (true) {
6193 if (text) {
6194 var end = pos + text.length;
6195 if (!collapsed) {
6196 var tokenText = end > upto ? text.slice(0, upto - pos) : text;
6197 builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
6198 spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title);
6199 }
6200 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
6201 pos = end;
6202 spanStartStyle = "";
6203 }
6204 text = allText.slice(at, at = styles[i++]);
6205 style = interpretTokenStyle(styles[i++], builder.cm.options);
6206 }
6207 }
6208 }
6209
6210 // DOCUMENT DATA STRUCTURE
6211
6212 // By default, updates that start and end at the beginning of a line
6213 // are treated specially, in order to make the association of line
6214 // widgets and marker elements with the text behave more intuitive.
6215 function isWholeLineUpdate(doc, change) {
6216 return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" &&
6217 (!doc.cm || doc.cm.options.wholeLineUpdateBefore);
6218 }
6219
6220 // Perform a change on the document data structure.
6221 function updateDoc(doc, change, markedSpans, estimateHeight) {
6222 function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
6223 function update(line, text, spans) {
6224 updateLine(line, text, spans, estimateHeight);
6225 signalLater(line, "change", line, change);
6226 }
6227
6228 var from = change.from, to = change.to, text = change.text;
6229 var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
6230 var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
6231
6232 // Adjust the line structure
6233 if (isWholeLineUpdate(doc, change)) {
6234 // This is a whole-line replace. Treated specially to make
6235 // sure line objects move the way they are supposed to.
6236 for (var i = 0, added = []; i < text.length - 1; ++i)
6237 added.push(new Line(text[i], spansFor(i), estimateHeight));
6238 update(lastLine, lastLine.text, lastSpans);
6239 if (nlines) doc.remove(from.line, nlines);
6240 if (added.length) doc.insert(from.line, added);
6241 } else if (firstLine == lastLine) {
6242 if (text.length == 1) {
6243 update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
6244 } else {
6245 for (var added = [], i = 1; i < text.length - 1; ++i)
6246 added.push(new Line(text[i], spansFor(i), estimateHeight));
6247 added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
6248 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
6249 doc.insert(from.line + 1, added);
6250 }
6251 } else if (text.length == 1) {
6252 update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));
6253 doc.remove(from.line + 1, nlines);
6254 } else {
6255 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
6256 update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
6257 for (var i = 1, added = []; i < text.length - 1; ++i)
6258 added.push(new Line(text[i], spansFor(i), estimateHeight));
6259 if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
6260 doc.insert(from.line + 1, added);
6261 }
6262
6263 signalLater(doc, "change", doc, change);
6264 }
6265
6266 // The document is represented as a BTree consisting of leaves, with
6267 // chunk of lines in them, and branches, with up to ten leaves or
6268 // other branch nodes below them. The top node is always a branch
6269 // node, and is the document object itself (meaning it has
6270 // additional methods and properties).
6271 //
6272 // All nodes have parent links. The tree is used both to go from
6273 // line numbers to line objects, and to go from objects to numbers.
6274 // It also indexes by height, and is used to convert between height
6275 // and line object, and to find the total height of the document.
6276 //
6277 // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
6278
6279 function LeafChunk(lines) {
6280 this.lines = lines;
6281 this.parent = null;
6282 for (var i = 0, height = 0; i < lines.length; ++i) {
6283 lines[i].parent = this;
6284 height += lines[i].height;
6285 }
6286 this.height = height;
6287 }
6288
6289 LeafChunk.prototype = {
6290 chunkSize: function() { return this.lines.length; },
6291 // Remove the n lines at offset 'at'.
6292 removeInner: function(at, n) {
6293 for (var i = at, e = at + n; i < e; ++i) {
6294 var line = this.lines[i];
6295 this.height -= line.height;
6296 cleanUpLine(line);
6297 signalLater(line, "delete");
6298 }
6299 this.lines.splice(at, n);
6300 },
6301 // Helper used to collapse a small branch into a single leaf.
6302 collapse: function(lines) {
6303 lines.push.apply(lines, this.lines);
6304 },
6305 // Insert the given array of lines at offset 'at', count them as
6306 // having the given height.
6307 insertInner: function(at, lines, height) {
6308 this.height += height;
6309 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
6310 for (var i = 0; i < lines.length; ++i) lines[i].parent = this;
6311 },
6312 // Used to iterate over a part of the tree.
6313 iterN: function(at, n, op) {
6314 for (var e = at + n; at < e; ++at)
6315 if (op(this.lines[at])) return true;
6316 }
6317 };
6318
6319 function BranchChunk(children) {
6320 this.children = children;
6321 var size = 0, height = 0;
6322 for (var i = 0; i < children.length; ++i) {
6323 var ch = children[i];
6324 size += ch.chunkSize(); height += ch.height;
6325 ch.parent = this;
6326 }
6327 this.size = size;
6328 this.height = height;
6329 this.parent = null;
6330 }
6331
6332 BranchChunk.prototype = {
6333 chunkSize: function() { return this.size; },
6334 removeInner: function(at, n) {
6335 this.size -= n;
6336 for (var i = 0; i < this.children.length; ++i) {
6337 var child = this.children[i], sz = child.chunkSize();
6338 if (at < sz) {
6339 var rm = Math.min(n, sz - at), oldHeight = child.height;
6340 child.removeInner(at, rm);
6341 this.height -= oldHeight - child.height;
6342 if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
6343 if ((n -= rm) == 0) break;
6344 at = 0;
6345 } else at -= sz;
6346 }
6347 // If the result is smaller than 25 lines, ensure that it is a
6348 // single leaf node.
6349 if (this.size - n < 25 &&
6350 (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {
6351 var lines = [];
6352 this.collapse(lines);
6353 this.children = [new LeafChunk(lines)];
6354 this.children[0].parent = this;
6355 }
6356 },
6357 collapse: function(lines) {
6358 for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines);
6359 },
6360 insertInner: function(at, lines, height) {
6361 this.size += lines.length;
6362 this.height += height;
6363 for (var i = 0; i < this.children.length; ++i) {
6364 var child = this.children[i], sz = child.chunkSize();
6365 if (at <= sz) {
6366 child.insertInner(at, lines, height);
6367 if (child.lines && child.lines.length > 50) {
6368 while (child.lines.length > 50) {
6369 var spilled = child.lines.splice(child.lines.length - 25, 25);
6370 var newleaf = new LeafChunk(spilled);
6371 child.height -= newleaf.height;
6372 this.children.splice(i + 1, 0, newleaf);
6373 newleaf.parent = this;
6374 }
6375 this.maybeSpill();
6376 }
6377 break;
6378 }
6379 at -= sz;
6380 }
6381 },
6382 // When a node has grown, check whether it should be split.
6383 maybeSpill: function() {
6384 if (this.children.length <= 10) return;
6385 var me = this;
6386 do {
6387 var spilled = me.children.splice(me.children.length - 5, 5);
6388 var sibling = new BranchChunk(spilled);
6389 if (!me.parent) { // Become the parent node
6390 var copy = new BranchChunk(me.children);
6391 copy.parent = me;
6392 me.children = [copy, sibling];
6393 me = copy;
6394 } else {
6395 me.size -= sibling.size;
6396 me.height -= sibling.height;
6397 var myIndex = indexOf(me.parent.children, me);
6398 me.parent.children.splice(myIndex + 1, 0, sibling);
6399 }
6400 sibling.parent = me.parent;
6401 } while (me.children.length > 10);
6402 me.parent.maybeSpill();
6403 },
6404 iterN: function(at, n, op) {
6405 for (var i = 0; i < this.children.length; ++i) {
6406 var child = this.children[i], sz = child.chunkSize();
6407 if (at < sz) {
6408 var used = Math.min(n, sz - at);
6409 if (child.iterN(at, used, op)) return true;
6410 if ((n -= used) == 0) break;
6411 at = 0;
6412 } else at -= sz;
6413 }
6414 }
6415 };
6416
6417 var nextDocId = 0;
6418 var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
6419 if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
6420 if (firstLine == null) firstLine = 0;
6421
6422 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
6423 this.first = firstLine;
6424 this.scrollTop = this.scrollLeft = 0;
6425 this.cantEdit = false;
6426 this.cleanGeneration = 1;
6427 this.frontier = firstLine;
6428 var start = Pos(firstLine, 0);
6429 this.sel = simpleSelection(start);
6430 this.history = new History(null);
6431 this.id = ++nextDocId;
6432 this.modeOption = mode;
6433
6434 if (typeof text == "string") text = splitLines(text);
6435 updateDoc(this, {from: start, to: start, text: text});
6436 setSelection(this, simpleSelection(start), sel_dontScroll);
6437 };
6438
6439 Doc.prototype = createObj(BranchChunk.prototype, {
6440 constructor: Doc,
6441 // Iterate over the document. Supports two forms -- with only one
6442 // argument, it calls that for each line in the document. With
6443 // three, it iterates over the range given by the first two (with
6444 // the second being non-inclusive).
6445 iter: function(from, to, op) {
6446 if (op) this.iterN(from - this.first, to - from, op);
6447 else this.iterN(this.first, this.first + this.size, from);
6448 },
6449
6450 // Non-public interface for adding and removing lines.
6451 insert: function(at, lines) {
6452 var height = 0;
6453 for (var i = 0; i < lines.length; ++i) height += lines[i].height;
6454 this.insertInner(at - this.first, lines, height);
6455 },
6456 remove: function(at, n) { this.removeInner(at - this.first, n); },
6457
6458 // From here, the methods are part of the public interface. Most
6459 // are also available from CodeMirror (editor) instances.
6460
6461 getValue: function(lineSep) {
6462 var lines = getLines(this, this.first, this.first + this.size);
6463 if (lineSep === false) return lines;
6464 return lines.join(lineSep || "\n");
6465 },
6466 setValue: docMethodOp(function(code) {
6467 var top = Pos(this.first, 0), last = this.first + this.size - 1;
6468 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
6469 text: splitLines(code), origin: "setValue"}, true);
6470 setSelection(this, simpleSelection(top));
6471 }),
6472 replaceRange: function(code, from, to, origin) {
6473 from = clipPos(this, from);
6474 to = to ? clipPos(this, to) : from;
6475 replaceRange(this, code, from, to, origin);
6476 },
6477 getRange: function(from, to, lineSep) {
6478 var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
6479 if (lineSep === false) return lines;
6480 return lines.join(lineSep || "\n");
6481 },
6482
6483 getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
6484
6485 getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
6486 getLineNumber: function(line) {return lineNo(line);},
6487
6488 getLineHandleVisualStart: function(line) {
6489 if (typeof line == "number") line = getLine(this, line);
6490 return visualLine(line);
6491 },
6492
6493 lineCount: function() {return this.size;},
6494 firstLine: function() {return this.first;},
6495 lastLine: function() {return this.first + this.size - 1;},
6496
6497 clipPos: function(pos) {return clipPos(this, pos);},
6498
6499 getCursor: function(start) {
6500 var range = this.sel.primary(), pos;
6501 if (start == null || start == "head") pos = range.head;
6502 else if (start == "anchor") pos = range.anchor;
6503 else if (start == "end" || start == "to" || start === false) pos = range.to();
6504 else pos = range.from();
6505 return pos;
6506 },
6507 listSelections: function() { return this.sel.ranges; },
6508 somethingSelected: function() {return this.sel.somethingSelected();},
6509
6510 setCursor: docMethodOp(function(line, ch, options) {
6511 setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options);
6512 }),
6513 setSelection: docMethodOp(function(anchor, head, options) {
6514 setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options);
6515 }),
6516 extendSelection: docMethodOp(function(head, other, options) {
6517 extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);
6518 }),
6519 extendSelections: docMethodOp(function(heads, options) {
6520 extendSelections(this, clipPosArray(this, heads, options));
6521 }),
6522 extendSelectionsBy: docMethodOp(function(f, options) {
6523 extendSelections(this, map(this.sel.ranges, f), options);
6524 }),
6525 setSelections: docMethodOp(function(ranges, primary, options) {
6526 if (!ranges.length) return;
6527 for (var i = 0, out = []; i < ranges.length; i++)
6528 out[i] = new Range(clipPos(this, ranges[i].anchor),
6529 clipPos(this, ranges[i].head));
6530 if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex);
6531 setSelection(this, normalizeSelection(out, primary), options);
6532 }),
6533 addSelection: docMethodOp(function(anchor, head, options) {
6534 var ranges = this.sel.ranges.slice(0);
6535 ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)));
6536 setSelection(this, normalizeSelection(ranges, ranges.length - 1), options);
6537 }),
6538
6539 getSelection: function(lineSep) {
6540 var ranges = this.sel.ranges, lines;
6541 for (var i = 0; i < ranges.length; i++) {
6542 var sel = getBetween(this, ranges[i].from(), ranges[i].to());
6543 lines = lines ? lines.concat(sel) : sel;
6544 }
6545 if (lineSep === false) return lines;
6546 else return lines.join(lineSep || "\n");
6547 },
6548 getSelections: function(lineSep) {
6549 var parts = [], ranges = this.sel.ranges;
6550 for (var i = 0; i < ranges.length; i++) {
6551 var sel = getBetween(this, ranges[i].from(), ranges[i].to());
6552 if (lineSep !== false) sel = sel.join(lineSep || "\n");
6553 parts[i] = sel;
6554 }
6555 return parts;
6556 },
6557 replaceSelection: function(code, collapse, origin) {
6558 var dup = [];
6559 for (var i = 0; i < this.sel.ranges.length; i++)
6560 dup[i] = code;
6561 this.replaceSelections(dup, collapse, origin || "+input");
6562 },
6563 replaceSelections: docMethodOp(function(code, collapse, origin) {
6564 var changes = [], sel = this.sel;
6565 for (var i = 0; i < sel.ranges.length; i++) {
6566 var range = sel.ranges[i];
6567 changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin};
6568 }
6569 var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse);
6570 for (var i = changes.length - 1; i >= 0; i--)
6571 makeChange(this, changes[i]);
6572 if (newSel) setSelectionReplaceHistory(this, newSel);
6573 else if (this.cm) ensureCursorVisible(this.cm);
6574 }),
6575 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
6576 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
6577 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}),
6578 redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}),
6579
6580 setExtending: function(val) {this.extend = val;},
6581 getExtending: function() {return this.extend;},
6582
6583 historySize: function() {
6584 var hist = this.history, done = 0, undone = 0;
6585 for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done;
6586 for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone;
6587 return {undo: done, redo: undone};
6588 },
6589 clearHistory: function() {this.history = new History(this.history.maxGeneration);},
6590
6591 markClean: function() {
6592 this.cleanGeneration = this.changeGeneration(true);
6593 },
6594 changeGeneration: function(forceSplit) {
6595 if (forceSplit)
6596 this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null;
6597 return this.history.generation;
6598 },
6599 isClean: function (gen) {
6600 return this.history.generation == (gen || this.cleanGeneration);
6601 },
6602
6603 getHistory: function() {
6604 return {done: copyHistoryArray(this.history.done),
6605 undone: copyHistoryArray(this.history.undone)};
6606 },
6607 setHistory: function(histData) {
6608 var hist = this.history = new History(this.history.maxGeneration);
6609 hist.done = copyHistoryArray(histData.done.slice(0), null, true);
6610 hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);
6611 },
6612
6613 addLineClass: docMethodOp(function(handle, where, cls) {
6614 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) {
6615 var prop = where == "text" ? "textClass"
6616 : where == "background" ? "bgClass"
6617 : where == "gutter" ? "gutterClass" : "wrapClass";
6618 if (!line[prop]) line[prop] = cls;
6619 else if (classTest(cls).test(line[prop])) return false;
6620 else line[prop] += " " + cls;
6621 return true;
6622 });
6623 }),
6624 removeLineClass: docMethodOp(function(handle, where, cls) {
6625 return changeLine(this, handle, "class", function(line) {
6626 var prop = where == "text" ? "textClass"
6627 : where == "background" ? "bgClass"
6628 : where == "gutter" ? "gutterClass" : "wrapClass";
6629 var cur = line[prop];
6630 if (!cur) return false;
6631 else if (cls == null) line[prop] = null;
6632 else {
6633 var found = cur.match(classTest(cls));
6634 if (!found) return false;
6635 var end = found.index + found[0].length;
6636 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
6637 }
6638 return true;
6639 });
6640 }),
6641
6642 markText: function(from, to, options) {
6643 return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
6644 },
6645 setBookmark: function(pos, options) {
6646 var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
6647 insertLeft: options && options.insertLeft,
6648 clearWhenEmpty: false, shared: options && options.shared};
6649 pos = clipPos(this, pos);
6650 return markText(this, pos, pos, realOpts, "bookmark");
6651 },
6652 findMarksAt: function(pos) {
6653 pos = clipPos(this, pos);
6654 var markers = [], spans = getLine(this, pos.line).markedSpans;
6655 if (spans) for (var i = 0; i < spans.length; ++i) {
6656 var span = spans[i];
6657 if ((span.from == null || span.from <= pos.ch) &&
6658 (span.to == null || span.to >= pos.ch))
6659 markers.push(span.marker.parent || span.marker);
6660 }
6661 return markers;
6662 },
6663 findMarks: function(from, to, filter) {
6664 from = clipPos(this, from); to = clipPos(this, to);
6665 var found = [], lineNo = from.line;
6666 this.iter(from.line, to.line + 1, function(line) {
6667 var spans = line.markedSpans;
6668 if (spans) for (var i = 0; i < spans.length; i++) {
6669 var span = spans[i];
6670 if (!(lineNo == from.line && from.ch > span.to ||
6671 span.from == null && lineNo != from.line||
6672 lineNo == to.line && span.from > to.ch) &&
6673 (!filter || filter(span.marker)))
6674 found.push(span.marker.parent || span.marker);
6675 }
6676 ++lineNo;
6677 });
6678 return found;
6679 },
6680 getAllMarks: function() {
6681 var markers = [];
6682 this.iter(function(line) {
6683 var sps = line.markedSpans;
6684 if (sps) for (var i = 0; i < sps.length; ++i)
6685 if (sps[i].from != null) markers.push(sps[i].marker);
6686 });
6687 return markers;
6688 },
6689
6690 posFromIndex: function(off) {
6691 var ch, lineNo = this.first;
6692 this.iter(function(line) {
6693 var sz = line.text.length + 1;
6694 if (sz > off) { ch = off; return true; }
6695 off -= sz;
6696 ++lineNo;
6697 });
6698 return clipPos(this, Pos(lineNo, ch));
6699 },
6700 indexFromPos: function (coords) {
6701 coords = clipPos(this, coords);
6702 var index = coords.ch;
6703 if (coords.line < this.first || coords.ch < 0) return 0;
6704 this.iter(this.first, coords.line, function (line) {
6705 index += line.text.length + 1;
6706 });
6707 return index;
6708 },
6709
6710 copy: function(copyHistory) {
6711 var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
6712 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
6713 doc.sel = this.sel;
6714 doc.extend = false;
6715 if (copyHistory) {
6716 doc.history.undoDepth = this.history.undoDepth;
6717 doc.setHistory(this.getHistory());
6718 }
6719 return doc;
6720 },
6721
6722 linkedDoc: function(options) {
6723 if (!options) options = {};
6724 var from = this.first, to = this.first + this.size;
6725 if (options.from != null && options.from > from) from = options.from;
6726 if (options.to != null && options.to < to) to = options.to;
6727 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);
6728 if (options.sharedHist) copy.history = this.history;
6729 (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
6730 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
6731 copySharedMarkers(copy, findSharedMarkers(this));
6732 return copy;
6733 },
6734 unlinkDoc: function(other) {
6735 if (other instanceof CodeMirror) other = other.doc;
6736 if (this.linked) for (var i = 0; i < this.linked.length; ++i) {
6737 var link = this.linked[i];
6738 if (link.doc != other) continue;
6739 this.linked.splice(i, 1);
6740 other.unlinkDoc(this);
6741 detachSharedMarkers(findSharedMarkers(this));
6742 break;
6743 }
6744 // If the histories were shared, split them again
6745 if (other.history == this.history) {
6746 var splitIds = [other.id];
6747 linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);
6748 other.history = new History(null);
6749 other.history.done = copyHistoryArray(this.history.done, splitIds);
6750 other.history.undone = copyHistoryArray(this.history.undone, splitIds);
6751 }
6752 },
6753 iterLinkedDocs: function(f) {linkedDocs(this, f);},
6754
6755 getMode: function() {return this.mode;},
6756 getEditor: function() {return this.cm;}
6757 });
6758
6759 // Public alias.
6760 Doc.prototype.eachLine = Doc.prototype.iter;
6761
6762 // Set up methods on CodeMirror's prototype to redirect to the editor's document.
6763 var dontDelegate = "iter insert remove copy getEditor".split(" ");
6764 for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
6765 CodeMirror.prototype[prop] = (function(method) {
6766 return function() {return method.apply(this.doc, arguments);};
6767 })(Doc.prototype[prop]);
6768
6769 eventMixin(Doc);
6770
6771 // Call f for all linked documents.
6772 function linkedDocs(doc, f, sharedHistOnly) {
6773 function propagate(doc, skip, sharedHist) {
6774 if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {
6775 var rel = doc.linked[i];
6776 if (rel.doc == skip) continue;
6777 var shared = sharedHist && rel.sharedHist;
6778 if (sharedHistOnly && !shared) continue;
6779 f(rel.doc, shared);
6780 propagate(rel.doc, doc, shared);
6781 }
6782 }
6783 propagate(doc, null, true);
6784 }
6785
6786 // Attach a document to an editor.
6787 function attachDoc(cm, doc) {
6788 if (doc.cm) throw new Error("This document is already in use.");
6789 cm.doc = doc;
6790 doc.cm = cm;
6791 estimateLineHeights(cm);
6792 loadMode(cm);
6793 if (!cm.options.lineWrapping) findMaxLine(cm);
6794 cm.options.mode = doc.modeOption;
6795 regChange(cm);
6796 }
6797
6798 // LINE UTILITIES
6799
6800 // Find the line object corresponding to the given line number.
6801 function getLine(doc, n) {
6802 n -= doc.first;
6803 if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document.");
6804 for (var chunk = doc; !chunk.lines;) {
6805 for (var i = 0;; ++i) {
6806 var child = chunk.children[i], sz = child.chunkSize();
6807 if (n < sz) { chunk = child; break; }
6808 n -= sz;
6809 }
6810 }
6811 return chunk.lines[n];
6812 }
6813
6814 // Get the part of a document between two positions, as an array of
6815 // strings.
6816 function getBetween(doc, start, end) {
6817 var out = [], n = start.line;
6818 doc.iter(start.line, end.line + 1, function(line) {
6819 var text = line.text;
6820 if (n == end.line) text = text.slice(0, end.ch);
6821 if (n == start.line) text = text.slice(start.ch);
6822 out.push(text);
6823 ++n;
6824 });
6825 return out;
6826 }
6827 // Get the lines between from and to, as array of strings.
6828 function getLines(doc, from, to) {
6829 var out = [];
6830 doc.iter(from, to, function(line) { out.push(line.text); });
6831 return out;
6832 }
6833
6834 // Update the height of a line, propagating the height change
6835 // upwards to parent nodes.
6836 function updateLineHeight(line, height) {
6837 var diff = height - line.height;
6838 if (diff) for (var n = line; n; n = n.parent) n.height += diff;
6839 }
6840
6841 // Given a line object, find its line number by walking up through
6842 // its parent links.
6843 function lineNo(line) {
6844 if (line.parent == null) return null;
6845 var cur = line.parent, no = indexOf(cur.lines, line);
6846 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
6847 for (var i = 0;; ++i) {
6848 if (chunk.children[i] == cur) break;
6849 no += chunk.children[i].chunkSize();
6850 }
6851 }
6852 return no + cur.first;
6853 }
6854
6855 // Find the line at the given vertical position, using the height
6856 // information in the document tree.
6857 function lineAtHeight(chunk, h) {
6858 var n = chunk.first;
6859 outer: do {
6860 for (var i = 0; i < chunk.children.length; ++i) {
6861 var child = chunk.children[i], ch = child.height;
6862 if (h < ch) { chunk = child; continue outer; }
6863 h -= ch;
6864 n += child.chunkSize();
6865 }
6866 return n;
6867 } while (!chunk.lines);
6868 for (var i = 0; i < chunk.lines.length; ++i) {
6869 var line = chunk.lines[i], lh = line.height;
6870 if (h < lh) break;
6871 h -= lh;
6872 }
6873 return n + i;
6874 }
6875
6876
6877 // Find the height above the given line.
6878 function heightAtLine(lineObj) {
6879 lineObj = visualLine(lineObj);
6880
6881 var h = 0, chunk = lineObj.parent;
6882 for (var i = 0; i < chunk.lines.length; ++i) {
6883 var line = chunk.lines[i];
6884 if (line == lineObj) break;
6885 else h += line.height;
6886 }
6887 for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
6888 for (var i = 0; i < p.children.length; ++i) {
6889 var cur = p.children[i];
6890 if (cur == chunk) break;
6891 else h += cur.height;
6892 }
6893 }
6894 return h;
6895 }
6896
6897 // Get the bidi ordering for the given line (and cache it). Returns
6898 // false for lines that are fully left-to-right, and an array of
6899 // BidiSpan objects otherwise.
6900 function getOrder(line) {
6901 var order = line.order;
6902 if (order == null) order = line.order = bidiOrdering(line.text);
6903 return order;
6904 }
6905
6906 // HISTORY
6907
6908 function History(startGen) {
6909 // Arrays of change events and selections. Doing something adds an
6910 // event to done and clears undo. Undoing moves events from done
6911 // to undone, redoing moves them in the other direction.
6912 this.done = []; this.undone = [];
6913 this.undoDepth = Infinity;
6914 // Used to track when changes can be merged into a single undo
6915 // event
6916 this.lastModTime = this.lastSelTime = 0;
6917 this.lastOp = this.lastSelOp = null;
6918 this.lastOrigin = this.lastSelOrigin = null;
6919 // Used by the isClean() method
6920 this.generation = this.maxGeneration = startGen || 1;
6921 }
6922
6923 // Create a history change event from an updateDoc-style change
6924 // object.
6925 function historyChangeFromChange(doc, change) {
6926 var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
6927 attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
6928 linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
6929 return histChange;
6930 }
6931
6932 // Pop all selection events off the end of a history array. Stop at
6933 // a change event.
6934 function clearSelectionEvents(array) {
6935 while (array.length) {
6936 var last = lst(array);
6937 if (last.ranges) array.pop();
6938 else break;
6939 }
6940 }
6941
6942 // Find the top change event in the history. Pop off selection
6943 // events that are in the way.
6944 function lastChangeEvent(hist, force) {
6945 if (force) {
6946 clearSelectionEvents(hist.done);
6947 return lst(hist.done);
6948 } else if (hist.done.length && !lst(hist.done).ranges) {
6949 return lst(hist.done);
6950 } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {
6951 hist.done.pop();
6952 return lst(hist.done);
6953 }
6954 }
6955
6956 // Register a change in the history. Merges changes that are within
6957 // a single operation, ore are close together with an origin that
6958 // allows merging (starting with "+") into a single event.
6959 function addChangeToHistory(doc, change, selAfter, opId) {
6960 var hist = doc.history;
6961 hist.undone.length = 0;
6962 var time = +new Date, cur;
6963
6964 if ((hist.lastOp == opId ||
6965 hist.lastOrigin == change.origin && change.origin &&
6966 ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) ||
6967 change.origin.charAt(0) == "*")) &&
6968 (cur = lastChangeEvent(hist, hist.lastOp == opId))) {
6969 // Merge this change into the last event
6970 var last = lst(cur.changes);
6971 if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {
6972 // Optimized case for simple insertion -- don't want to add
6973 // new changesets for every character typed
6974 last.to = changeEnd(change);
6975 } else {
6976 // Add new sub-event
6977 cur.changes.push(historyChangeFromChange(doc, change));
6978 }
6979 } else {
6980 // Can not be merged, start a new event.
6981 var before = lst(hist.done);
6982 if (!before || !before.ranges)
6983 pushSelectionToHistory(doc.sel, hist.done);
6984 cur = {changes: [historyChangeFromChange(doc, change)],
6985 generation: hist.generation};
6986 hist.done.push(cur);
6987 while (hist.done.length > hist.undoDepth) {
6988 hist.done.shift();
6989 if (!hist.done[0].ranges) hist.done.shift();
6990 }
6991 }
6992 hist.done.push(selAfter);
6993 hist.generation = ++hist.maxGeneration;
6994 hist.lastModTime = hist.lastSelTime = time;
6995 hist.lastOp = hist.lastSelOp = opId;
6996 hist.lastOrigin = hist.lastSelOrigin = change.origin;
6997
6998 if (!last) signal(doc, "historyAdded");
6999 }
7000
7001 function selectionEventCanBeMerged(doc, origin, prev, sel) {
7002 var ch = origin.charAt(0);
7003 return ch == "*" ||
7004 ch == "+" &&
7005 prev.ranges.length == sel.ranges.length &&
7006 prev.somethingSelected() == sel.somethingSelected() &&
7007 new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500);
7008 }
7009
7010 // Called whenever the selection changes, sets the new selection as
7011 // the pending selection in the history, and pushes the old pending
7012 // selection into the 'done' array when it was significantly
7013 // different (in number of selected ranges, emptiness, or time).
7014 function addSelectionToHistory(doc, sel, opId, options) {
7015 var hist = doc.history, origin = options && options.origin;
7016
7017 // A new event is started when the previous origin does not match
7018 // the current, or the origins don't allow matching. Origins
7019 // starting with * are always merged, those starting with + are
7020 // merged when similar and close together in time.
7021 if (opId == hist.lastSelOp ||
7022 (origin && hist.lastSelOrigin == origin &&
7023 (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
7024 selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
7025 hist.done[hist.done.length - 1] = sel;
7026 else
7027 pushSelectionToHistory(sel, hist.done);
7028
7029 hist.lastSelTime = +new Date;
7030 hist.lastSelOrigin = origin;
7031 hist.lastSelOp = opId;
7032 if (options && options.clearRedo !== false)
7033 clearSelectionEvents(hist.undone);
7034 }
7035
7036 function pushSelectionToHistory(sel, dest) {
7037 var top = lst(dest);
7038 if (!(top && top.ranges && top.equals(sel)))
7039 dest.push(sel);
7040 }
7041
7042 // Used to store marked span information in the history.
7043 function attachLocalSpans(doc, change, from, to) {
7044 var existing = change["spans_" + doc.id], n = 0;
7045 doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) {
7046 if (line.markedSpans)
7047 (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans;
7048 ++n;
7049 });
7050 }
7051
7052 // When un/re-doing restores text containing marked spans, those
7053 // that have been explicitly cleared should not be restored.
7054 function removeClearedSpans(spans) {
7055 if (!spans) return null;
7056 for (var i = 0, out; i < spans.length; ++i) {
7057 if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
7058 else if (out) out.push(spans[i]);
7059 }
7060 return !out ? spans : out.length ? out : null;
7061 }
7062
7063 // Retrieve and filter the old marked spans stored in a change event.
7064 function getOldSpans(doc, change) {
7065 var found = change["spans_" + doc.id];
7066 if (!found) return null;
7067 for (var i = 0, nw = []; i < change.text.length; ++i)
7068 nw.push(removeClearedSpans(found[i]));
7069 return nw;
7070 }
7071
7072 // Used both to provide a JSON-safe object in .getHistory, and, when
7073 // detaching a document, to split the history in two
7074 function copyHistoryArray(events, newGroup, instantiateSel) {
7075 for (var i = 0, copy = []; i < events.length; ++i) {
7076 var event = events[i];
7077 if (event.ranges) {
7078 copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event);
7079 continue;
7080 }
7081 var changes = event.changes, newChanges = [];
7082 copy.push({changes: newChanges});
7083 for (var j = 0; j < changes.length; ++j) {
7084 var change = changes[j], m;
7085 newChanges.push({from: change.from, to: change.to, text: change.text});
7086 if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) {
7087 if (indexOf(newGroup, Number(m[1])) > -1) {
7088 lst(newChanges)[prop] = change[prop];
7089 delete change[prop];
7090 }
7091 }
7092 }
7093 }
7094 return copy;
7095 }
7096
7097 // Rebasing/resetting history to deal with externally-sourced changes
7098
7099 function rebaseHistSelSingle(pos, from, to, diff) {
7100 if (to < pos.line) {
7101 pos.line += diff;
7102 } else if (from < pos.line) {
7103 pos.line = from;
7104 pos.ch = 0;
7105 }
7106 }
7107
7108 // Tries to rebase an array of history events given a change in the
7109 // document. If the change touches the same lines as the event, the
7110 // event, and everything 'behind' it, is discarded. If the change is
7111 // before the event, the event's positions are updated. Uses a
7112 // copy-on-write scheme for the positions, to avoid having to
7113 // reallocate them all on every rebase, but also avoid problems with
7114 // shared position objects being unsafely updated.
7115 function rebaseHistArray(array, from, to, diff) {
7116 for (var i = 0; i < array.length; ++i) {
7117 var sub = array[i], ok = true;
7118 if (sub.ranges) {
7119 if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; }
7120 for (var j = 0; j < sub.ranges.length; j++) {
7121 rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff);
7122 rebaseHistSelSingle(sub.ranges[j].head, from, to, diff);
7123 }
7124 continue;
7125 }
7126 for (var j = 0; j < sub.changes.length; ++j) {
7127 var cur = sub.changes[j];
7128 if (to < cur.from.line) {
7129 cur.from = Pos(cur.from.line + diff, cur.from.ch);
7130 cur.to = Pos(cur.to.line + diff, cur.to.ch);
7131 } else if (from <= cur.to.line) {
7132 ok = false;
7133 break;
7134 }
7135 }
7136 if (!ok) {
7137 array.splice(0, i + 1);
7138 i = 0;
7139 }
7140 }
7141 }
7142
7143 function rebaseHist(hist, change) {
7144 var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;
7145 rebaseHistArray(hist.done, from, to, diff);
7146 rebaseHistArray(hist.undone, from, to, diff);
7147 }
7148
7149 // EVENT UTILITIES
7150
7151 // Due to the fact that we still support jurassic IE versions, some
7152 // compatibility wrappers are needed.
7153
7154 var e_preventDefault = CodeMirror.e_preventDefault = function(e) {
7155 if (e.preventDefault) e.preventDefault();
7156 else e.returnValue = false;
7157 };
7158 var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) {
7159 if (e.stopPropagation) e.stopPropagation();
7160 else e.cancelBubble = true;
7161 };
7162 function e_defaultPrevented(e) {
7163 return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;
7164 }
7165 var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);};
7166
7167 function e_target(e) {return e.target || e.srcElement;}
7168 function e_button(e) {
7169 var b = e.which;
7170 if (b == null) {
7171 if (e.button & 1) b = 1;
7172 else if (e.button & 2) b = 3;
7173 else if (e.button & 4) b = 2;
7174 }
7175 if (mac && e.ctrlKey && b == 1) b = 3;
7176 return b;
7177 }
7178
7179 // EVENT HANDLING
7180
7181 // Lightweight event framework. on/off also work on DOM nodes,
7182 // registering native DOM handlers.
7183
7184 var on = CodeMirror.on = function(emitter, type, f) {
7185 if (emitter.addEventListener)
7186 emitter.addEventListener(type, f, false);
7187 else if (emitter.attachEvent)
7188 emitter.attachEvent("on" + type, f);
7189 else {
7190 var map = emitter._handlers || (emitter._handlers = {});
7191 var arr = map[type] || (map[type] = []);
7192 arr.push(f);
7193 }
7194 };
7195
7196 var off = CodeMirror.off = function(emitter, type, f) {
7197 if (emitter.removeEventListener)
7198 emitter.removeEventListener(type, f, false);
7199 else if (emitter.detachEvent)
7200 emitter.detachEvent("on" + type, f);
7201 else {
7202 var arr = emitter._handlers && emitter._handlers[type];
7203 if (!arr) return;
7204 for (var i = 0; i < arr.length; ++i)
7205 if (arr[i] == f) { arr.splice(i, 1); break; }
7206 }
7207 };
7208
7209 var signal = CodeMirror.signal = function(emitter, type /*, values...*/) {
7210 var arr = emitter._handlers && emitter._handlers[type];
7211 if (!arr) return;
7212 var args = Array.prototype.slice.call(arguments, 2);
7213 for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
7214 };
7215
7216 var orphanDelayedCallbacks = null;
7217
7218 // Often, we want to signal events at a point where we are in the
7219 // middle of some work, but don't want the handler to start calling
7220 // other methods on the editor, which might be in an inconsistent
7221 // state or simply not expect any other events to happen.
7222 // signalLater looks whether there are any handlers, and schedules
7223 // them to be executed when the last operation ends, or, if no
7224 // operation is active, when a timeout fires.
7225 function signalLater(emitter, type /*, values...*/) {
7226 var arr = emitter._handlers && emitter._handlers[type];
7227 if (!arr) return;
7228 var args = Array.prototype.slice.call(arguments, 2), list;
7229 if (operationGroup) {
7230 list = operationGroup.delayedCallbacks;
7231 } else if (orphanDelayedCallbacks) {
7232 list = orphanDelayedCallbacks;
7233 } else {
7234 list = orphanDelayedCallbacks = [];
7235 setTimeout(fireOrphanDelayed, 0);
7236 }
7237 function bnd(f) {return function(){f.apply(null, args);};};
7238 for (var i = 0; i < arr.length; ++i)
7239 list.push(bnd(arr[i]));
7240 }
7241
7242 function fireOrphanDelayed() {
7243 var delayed = orphanDelayedCallbacks;
7244 orphanDelayedCallbacks = null;
7245 for (var i = 0; i < delayed.length; ++i) delayed[i]();
7246 }
7247
7248 // The DOM events that CodeMirror handles can be overridden by
7249 // registering a (non-DOM) handler on the editor for the event name,
7250 // and preventDefault-ing the event in that handler.
7251 function signalDOMEvent(cm, e, override) {
7252 if (typeof e == "string")
7253 e = {type: e, preventDefault: function() { this.defaultPrevented = true; }};
7254 signal(cm, override || e.type, cm, e);
7255 return e_defaultPrevented(e) || e.codemirrorIgnore;
7256 }
7257
7258 function signalCursorActivity(cm) {
7259 var arr = cm._handlers && cm._handlers.cursorActivity;
7260 if (!arr) return;
7261 var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);
7262 for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1)
7263 set.push(arr[i]);
7264 }
7265
7266 function hasHandler(emitter, type) {
7267 var arr = emitter._handlers && emitter._handlers[type];
7268 return arr && arr.length > 0;
7269 }
7270
7271 // Add on and off methods to a constructor's prototype, to make
7272 // registering events on such objects more convenient.
7273 function eventMixin(ctor) {
7274 ctor.prototype.on = function(type, f) {on(this, type, f);};
7275 ctor.prototype.off = function(type, f) {off(this, type, f);};
7276 }
7277
7278 // MISC UTILITIES
7279
7280 // Number of pixels added to scroller and sizer to hide scrollbar
7281 var scrollerCutOff = 30;
7282
7283 // Returned or thrown by various protocols to signal 'I'm not
7284 // handling this'.
7285 var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
7286
7287 // Reused option objects for setSelection & friends
7288 var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"};
7289
7290 function Delayed() {this.id = null;}
7291 Delayed.prototype.set = function(ms, f) {
7292 clearTimeout(this.id);
7293 this.id = setTimeout(f, ms);
7294 };
7295
7296 // Counts the column offset in a string, taking tabs into account.
7297 // Used mostly to find indentation.
7298 var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) {
7299 if (end == null) {
7300 end = string.search(/[^\s\u00a0]/);
7301 if (end == -1) end = string.length;
7302 }
7303 for (var i = startIndex || 0, n = startValue || 0;;) {
7304 var nextTab = string.indexOf("\t", i);
7305 if (nextTab < 0 || nextTab >= end)
7306 return n + (end - i);
7307 n += nextTab - i;
7308 n += tabSize - (n % tabSize);
7309 i = nextTab + 1;
7310 }
7311 };
7312
7313 // The inverse of countColumn -- find the offset that corresponds to
7314 // a particular column.
7315 function findColumn(string, goal, tabSize) {
7316 for (var pos = 0, col = 0;;) {
7317 var nextTab = string.indexOf("\t", pos);
7318 if (nextTab == -1) nextTab = string.length;
7319 var skipped = nextTab - pos;
7320 if (nextTab == string.length || col + skipped >= goal)
7321 return pos + Math.min(skipped, goal - col);
7322 col += nextTab - pos;
7323 col += tabSize - (col % tabSize);
7324 pos = nextTab + 1;
7325 if (col >= goal) return pos;
7326 }
7327 }
7328
7329 var spaceStrs = [""];
7330 function spaceStr(n) {
7331 while (spaceStrs.length <= n)
7332 spaceStrs.push(lst(spaceStrs) + " ");
7333 return spaceStrs[n];
7334 }
7335
7336 function lst(arr) { return arr[arr.length-1]; }
7337
7338 var selectInput = function(node) { node.select(); };
7339 if (ios) // Mobile Safari apparently has a bug where select() is broken.
7340 selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; };
7341 else if (ie) // Suppress mysterious IE10 errors
7342 selectInput = function(node) { try { node.select(); } catch(_e) {} };
7343
7344 function indexOf(array, elt) {
7345 for (var i = 0; i < array.length; ++i)
7346 if (array[i] == elt) return i;
7347 return -1;
7348 }
7349 if ([].indexOf) indexOf = function(array, elt) { return array.indexOf(elt); };
7350 function map(array, f) {
7351 var out = [];
7352 for (var i = 0; i < array.length; i++) out[i] = f(array[i], i);
7353 return out;
7354 }
7355 if ([].map) map = function(array, f) { return array.map(f); };
7356
7357 function createObj(base, props) {
7358 var inst;
7359 if (Object.create) {
7360 inst = Object.create(base);
7361 } else {
7362 var ctor = function() {};
7363 ctor.prototype = base;
7364 inst = new ctor();
7365 }
7366 if (props) copyObj(props, inst);
7367 return inst;
7368 };
7369
7370 function copyObj(obj, target, overwrite) {
7371 if (!target) target = {};
7372 for (var prop in obj)
7373 if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))
7374 target[prop] = obj[prop];
7375 return target;
7376 }
7377
7378 function bind(f) {
7379 var args = Array.prototype.slice.call(arguments, 1);
7380 return function(){return f.apply(null, args);};
7381 }
7382
7383 var nonASCIISingleCaseWordChar = /[\u00df\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
7384 var isWordCharBasic = CodeMirror.isWordChar = function(ch) {
7385 return /\w/.test(ch) || ch > "\x80" &&
7386 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
7387 };
7388 function isWordChar(ch, helper) {
7389 if (!helper) return isWordCharBasic(ch);
7390 if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true;
7391 return helper.test(ch);
7392 }
7393
7394 function isEmpty(obj) {
7395 for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
7396 return true;
7397 }
7398
7399 // Extending unicode characters. A series of a non-extending char +
7400 // any number of extending chars is treated as a single unit as far
7401 // as editing and measuring is concerned. This is not fully correct,
7402 // since some scripts/fonts/browsers also treat other configurations
7403 // of code points as a group.
7404 var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\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\u0f80-\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-\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\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\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\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]/;
7405 function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); }
7406
7407 // DOM UTILITIES
7408
7409 function elt(tag, content, className, style) {
7410 var e = document.createElement(tag);
7411 if (className) e.className = className;
7412 if (style) e.style.cssText = style;
7413 if (typeof content == "string") e.appendChild(document.createTextNode(content));
7414 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
7415 return e;
7416 }
7417
7418 var range;
7419 if (document.createRange) range = function(node, start, end) {
7420 var r = document.createRange();
7421 r.setEnd(node, end);
7422 r.setStart(node, start);
7423 return r;
7424 };
7425 else range = function(node, start, end) {
7426 var r = document.body.createTextRange();
7427 try { r.moveToElementText(node.parentNode); }
7428 catch(e) { return r; }
7429 r.collapse(true);
7430 r.moveEnd("character", end);
7431 r.moveStart("character", start);
7432 return r;
7433 };
7434
7435 function removeChildren(e) {
7436 for (var count = e.childNodes.length; count > 0; --count)
7437 e.removeChild(e.firstChild);
7438 return e;
7439 }
7440
7441 function removeChildrenAndAdd(parent, e) {
7442 return removeChildren(parent).appendChild(e);
7443 }
7444
7445 function contains(parent, child) {
7446 if (parent.contains)
7447 return parent.contains(child);
7448 while (child = child.parentNode)
7449 if (child == parent) return true;
7450 }
7451
7452 function activeElt() { return document.activeElement; }
7453 // Older versions of IE throws unspecified error when touching
7454 // document.activeElement in some cases (during loading, in iframe)
7455 if (ie && ie_version < 11) activeElt = function() {
7456 try { return document.activeElement; }
7457 catch(e) { return document.body; }
7458 };
7459
7460 function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); }
7461 var rmClass = CodeMirror.rmClass = function(node, cls) {
7462 var current = node.className;
7463 var match = classTest(cls).exec(current);
7464 if (match) {
7465 var after = current.slice(match.index + match[0].length);
7466 node.className = current.slice(0, match.index) + (after ? match[1] + after : "");
7467 }
7468 };
7469 var addClass = CodeMirror.addClass = function(node, cls) {
7470 var current = node.className;
7471 if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls;
7472 };
7473 function joinClasses(a, b) {
7474 var as = a.split(" ");
7475 for (var i = 0; i < as.length; i++)
7476 if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i];
7477 return b;
7478 }
7479
7480 // WINDOW-WIDE EVENTS
7481
7482 // These must be handled carefully, because naively registering a
7483 // handler for each editor will cause the editors to never be
7484 // garbage collected.
7485
7486 function forEachCodeMirror(f) {
7487 if (!document.body.getElementsByClassName) return;
7488 var byClass = document.body.getElementsByClassName("CodeMirror");
7489 for (var i = 0; i < byClass.length; i++) {
7490 var cm = byClass[i].CodeMirror;
7491 if (cm) f(cm);
7492 }
7493 }
7494
7495 var globalsRegistered = false;
7496 function ensureGlobalHandlers() {
7497 if (globalsRegistered) return;
7498 registerGlobalHandlers();
7499 globalsRegistered = true;
7500 }
7501 function registerGlobalHandlers() {
7502 // When the window resizes, we need to refresh active editors.
7503 var resizeTimer;
7504 on(window, "resize", function() {
7505 if (resizeTimer == null) resizeTimer = setTimeout(function() {
7506 resizeTimer = null;
7507 knownScrollbarWidth = null;
7508 forEachCodeMirror(onResize);
7509 }, 100);
7510 });
7511 // When the window loses focus, we want to show the editor as blurred
7512 on(window, "blur", function() {
7513 forEachCodeMirror(onBlur);
7514 });
7515 }
7516
7517 // FEATURE DETECTION
7518
7519 // Detect drag-and-drop
7520 var dragAndDrop = function() {
7521 // There is *some* kind of drag-and-drop support in IE6-8, but I
7522 // couldn't get it to work yet.
7523 if (ie && ie_version < 9) return false;
7524 var div = elt('div');
7525 return "draggable" in div || "dragDrop" in div;
7526 }();
7527
7528 var knownScrollbarWidth;
7529 function scrollbarWidth(measure) {
7530 if (knownScrollbarWidth != null) return knownScrollbarWidth;
7531 var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
7532 removeChildrenAndAdd(measure, test);
7533 if (test.offsetWidth)
7534 knownScrollbarWidth = test.offsetHeight - test.clientHeight;
7535 return knownScrollbarWidth || 0;
7536 }
7537
7538 var zwspSupported;
7539 function zeroWidthElement(measure) {
7540 if (zwspSupported == null) {
7541 var test = elt("span", "\u200b");
7542 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
7543 if (measure.firstChild.offsetHeight != 0)
7544 zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8);
7545 }
7546 if (zwspSupported) return elt("span", "\u200b");
7547 else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
7548 }
7549
7550 // Feature-detect IE's crummy client rect reporting for bidi text
7551 var badBidiRects;
7552 function hasBadBidiRects(measure) {
7553 if (badBidiRects != null) return badBidiRects;
7554 var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"));
7555 var r0 = range(txt, 0, 1).getBoundingClientRect();
7556 if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780)
7557 var r1 = range(txt, 1, 2).getBoundingClientRect();
7558 return badBidiRects = (r1.right - r0.right < 3);
7559 }
7560
7561 // See if "".split is the broken IE version, if so, provide an
7562 // alternative way to split lines.
7563 var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
7564 var pos = 0, result = [], l = string.length;
7565 while (pos <= l) {
7566 var nl = string.indexOf("\n", pos);
7567 if (nl == -1) nl = string.length;
7568 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
7569 var rt = line.indexOf("\r");
7570 if (rt != -1) {
7571 result.push(line.slice(0, rt));
7572 pos += rt + 1;
7573 } else {
7574 result.push(line);
7575 pos = nl + 1;
7576 }
7577 }
7578 return result;
7579 } : function(string){return string.split(/\r\n?|\n/);};
7580
7581 var hasSelection = window.getSelection ? function(te) {
7582 try { return te.selectionStart != te.selectionEnd; }
7583 catch(e) { return false; }
7584 } : function(te) {
7585 try {var range = te.ownerDocument.selection.createRange();}
7586 catch(e) {}
7587 if (!range || range.parentElement() != te) return false;
7588 return range.compareEndPoints("StartToEnd", range) != 0;
7589 };
7590
7591 var hasCopyEvent = (function() {
7592 var e = elt("div");
7593 if ("oncopy" in e) return true;
7594 e.setAttribute("oncopy", "return;");
7595 return typeof e.oncopy == "function";
7596 })();
7597
7598 var badZoomedRects = null;
7599 function hasBadZoomedRects(measure) {
7600 if (badZoomedRects != null) return badZoomedRects;
7601 var node = removeChildrenAndAdd(measure, elt("span", "x"));
7602 var normal = node.getBoundingClientRect();
7603 var fromRange = range(node, 0, 1).getBoundingClientRect();
7604 return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1;
7605 }
7606
7607 // KEY NAMES
7608
7609 var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
7610 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
7611 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
7612 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete",
7613 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
7614 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
7615 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"};
7616 CodeMirror.keyNames = keyNames;
7617 (function() {
7618 // Number keys
7619 for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i);
7620 // Alphabetic keys
7621 for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
7622 // Function keys
7623 for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
7624 })();
7625
7626 // BIDI HELPERS
7627
7628 function iterateBidiSections(order, from, to, f) {
7629 if (!order) return f(from, to, "ltr");
7630 var found = false;
7631 for (var i = 0; i < order.length; ++i) {
7632 var part = order[i];
7633 if (part.from < to && part.to > from || from == to && part.to == from) {
7634 f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
7635 found = true;
7636 }
7637 }
7638 if (!found) f(from, to, "ltr");
7639 }
7640
7641 function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
7642 function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
7643
7644 function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
7645 function lineRight(line) {
7646 var order = getOrder(line);
7647 if (!order) return line.text.length;
7648 return bidiRight(lst(order));
7649 }
7650
7651 function lineStart(cm, lineN) {
7652 var line = getLine(cm.doc, lineN);
7653 var visual = visualLine(line);
7654 if (visual != line) lineN = lineNo(visual);
7655 var order = getOrder(visual);
7656 var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
7657 return Pos(lineN, ch);
7658 }
7659 function lineEnd(cm, lineN) {
7660 var merged, line = getLine(cm.doc, lineN);
7661 while (merged = collapsedSpanAtEnd(line)) {
7662 line = merged.find(1, true).line;
7663 lineN = null;
7664 }
7665 var order = getOrder(line);
7666 var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
7667 return Pos(lineN == null ? lineNo(line) : lineN, ch);
7668 }
7669 function lineStartSmart(cm, pos) {
7670 var start = lineStart(cm, pos.line);
7671 var line = getLine(cm.doc, start.line);
7672 var order = getOrder(line);
7673 if (!order || order[0].level == 0) {
7674 var firstNonWS = Math.max(0, line.text.search(/\S/));
7675 var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;
7676 return Pos(start.line, inWS ? 0 : firstNonWS);
7677 }
7678 return start;
7679 }
7680
7681 function compareBidiLevel(order, a, b) {
7682 var linedir = order[0].level;
7683 if (a == linedir) return true;
7684 if (b == linedir) return false;
7685 return a < b;
7686 }
7687 var bidiOther;
7688 function getBidiPartAt(order, pos) {
7689 bidiOther = null;
7690 for (var i = 0, found; i < order.length; ++i) {
7691 var cur = order[i];
7692 if (cur.from < pos && cur.to > pos) return i;
7693 if ((cur.from == pos || cur.to == pos)) {
7694 if (found == null) {
7695 found = i;
7696 } else if (compareBidiLevel(order, cur.level, order[found].level)) {
7697 if (cur.from != cur.to) bidiOther = found;
7698 return i;
7699 } else {
7700 if (cur.from != cur.to) bidiOther = i;
7701 return found;
7702 }
7703 }
7704 }
7705 return found;
7706 }
7707
7708 function moveInLine(line, pos, dir, byUnit) {
7709 if (!byUnit) return pos + dir;
7710 do pos += dir;
7711 while (pos > 0 && isExtendingChar(line.text.charAt(pos)));
7712 return pos;
7713 }
7714
7715 // This is needed in order to move 'visually' through bi-directional
7716 // text -- i.e., pressing left should make the cursor go left, even
7717 // when in RTL text. The tricky part is the 'jumps', where RTL and
7718 // LTR text touch each other. This often requires the cursor offset
7719 // to move more than one unit, in order to visually move one unit.
7720 function moveVisually(line, start, dir, byUnit) {
7721 var bidi = getOrder(line);
7722 if (!bidi) return moveLogically(line, start, dir, byUnit);
7723 var pos = getBidiPartAt(bidi, start), part = bidi[pos];
7724 var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit);
7725
7726 for (;;) {
7727 if (target > part.from && target < part.to) return target;
7728 if (target == part.from || target == part.to) {
7729 if (getBidiPartAt(bidi, target) == pos) return target;
7730 part = bidi[pos += dir];
7731 return (dir > 0) == part.level % 2 ? part.to : part.from;
7732 } else {
7733 part = bidi[pos += dir];
7734 if (!part) return null;
7735 if ((dir > 0) == part.level % 2)
7736 target = moveInLine(line, part.to, -1, byUnit);
7737 else
7738 target = moveInLine(line, part.from, 1, byUnit);
7739 }
7740 }
7741 }
7742
7743 function moveLogically(line, start, dir, byUnit) {
7744 var target = start + dir;
7745 if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir;
7746 return target < 0 || target > line.text.length ? null : target;
7747 }
7748
7749 // Bidirectional ordering algorithm
7750 // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
7751 // that this (partially) implements.
7752
7753 // One-char codes used for character types:
7754 // L (L): Left-to-Right
7755 // R (R): Right-to-Left
7756 // r (AL): Right-to-Left Arabic
7757 // 1 (EN): European Number
7758 // + (ES): European Number Separator
7759 // % (ET): European Number Terminator
7760 // n (AN): Arabic Number
7761 // , (CS): Common Number Separator
7762 // m (NSM): Non-Spacing Mark
7763 // b (BN): Boundary Neutral
7764 // s (B): Paragraph Separator
7765 // t (S): Segment Separator
7766 // w (WS): Whitespace
7767 // N (ON): Other Neutrals
7768
7769 // Returns null if characters are ordered as they appear
7770 // (left-to-right), or an array of sections ({from, to, level}
7771 // objects) in the order in which they occur visually.
7772 var bidiOrdering = (function() {
7773 // Character types for codepoints 0 to 0xff
7774 var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";
7775 // Character types for codepoints 0x600 to 0x6ff
7776 var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm";
7777 function charType(code) {
7778 if (code <= 0xf7) return lowTypes.charAt(code);
7779 else if (0x590 <= code && code <= 0x5f4) return "R";
7780 else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600);
7781 else if (0x6ee <= code && code <= 0x8ac) return "r";
7782 else if (0x2000 <= code && code <= 0x200b) return "w";
7783 else if (code == 0x200c) return "b";
7784 else return "L";
7785 }
7786
7787 var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
7788 var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
7789 // Browsers seem to always treat the boundaries of block elements as being L.
7790 var outerType = "L";
7791
7792 function BidiSpan(level, from, to) {
7793 this.level = level;
7794 this.from = from; this.to = to;
7795 }
7796
7797 return function(str) {
7798 if (!bidiRE.test(str)) return false;
7799 var len = str.length, types = [];
7800 for (var i = 0, type; i < len; ++i)
7801 types.push(type = charType(str.charCodeAt(i)));
7802
7803 // W1. Examine each non-spacing mark (NSM) in the level run, and
7804 // change the type of the NSM to the type of the previous
7805 // character. If the NSM is at the start of the level run, it will
7806 // get the type of sor.
7807 for (var i = 0, prev = outerType; i < len; ++i) {
7808 var type = types[i];
7809 if (type == "m") types[i] = prev;
7810 else prev = type;
7811 }
7812
7813 // W2. Search backwards from each instance of a European number
7814 // until the first strong type (R, L, AL, or sor) is found. If an
7815 // AL is found, change the type of the European number to Arabic
7816 // number.
7817 // W3. Change all ALs to R.
7818 for (var i = 0, cur = outerType; i < len; ++i) {
7819 var type = types[i];
7820 if (type == "1" && cur == "r") types[i] = "n";
7821 else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
7822 }
7823
7824 // W4. A single European separator between two European numbers
7825 // changes to a European number. A single common separator between
7826 // two numbers of the same type changes to that type.
7827 for (var i = 1, prev = types[0]; i < len - 1; ++i) {
7828 var type = types[i];
7829 if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
7830 else if (type == "," && prev == types[i+1] &&
7831 (prev == "1" || prev == "n")) types[i] = prev;
7832 prev = type;
7833 }
7834
7835 // W5. A sequence of European terminators adjacent to European
7836 // numbers changes to all European numbers.
7837 // W6. Otherwise, separators and terminators change to Other
7838 // Neutral.
7839 for (var i = 0; i < len; ++i) {
7840 var type = types[i];
7841 if (type == ",") types[i] = "N";
7842 else if (type == "%") {
7843 for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
7844 var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N";
7845 for (var j = i; j < end; ++j) types[j] = replace;
7846 i = end - 1;
7847 }
7848 }
7849
7850 // W7. Search backwards from each instance of a European number
7851 // until the first strong type (R, L, or sor) is found. If an L is
7852 // found, then change the type of the European number to L.
7853 for (var i = 0, cur = outerType; i < len; ++i) {
7854 var type = types[i];
7855 if (cur == "L" && type == "1") types[i] = "L";
7856 else if (isStrong.test(type)) cur = type;
7857 }
7858
7859 // N1. A sequence of neutrals takes the direction of the
7860 // surrounding strong text if the text on both sides has the same
7861 // direction. European and Arabic numbers act as if they were R in
7862 // terms of their influence on neutrals. Start-of-level-run (sor)
7863 // and end-of-level-run (eor) are used at level run boundaries.
7864 // N2. Any remaining neutrals take the embedding direction.
7865 for (var i = 0; i < len; ++i) {
7866 if (isNeutral.test(types[i])) {
7867 for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
7868 var before = (i ? types[i-1] : outerType) == "L";
7869 var after = (end < len ? types[end] : outerType) == "L";
7870 var replace = before || after ? "L" : "R";
7871 for (var j = i; j < end; ++j) types[j] = replace;
7872 i = end - 1;
7873 }
7874 }
7875
7876 // Here we depart from the documented algorithm, in order to avoid
7877 // building up an actual levels array. Since there are only three
7878 // levels (0, 1, 2) in an implementation that doesn't take
7879 // explicit embedding into account, we can build up the order on
7880 // the fly, without following the level-based algorithm.
7881 var order = [], m;
7882 for (var i = 0; i < len;) {
7883 if (countsAsLeft.test(types[i])) {
7884 var start = i;
7885 for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
7886 order.push(new BidiSpan(0, start, i));
7887 } else {
7888 var pos = i, at = order.length;
7889 for (++i; i < len && types[i] != "L"; ++i) {}
7890 for (var j = pos; j < i;) {
7891 if (countsAsNum.test(types[j])) {
7892 if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j));
7893 var nstart = j;
7894 for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
7895 order.splice(at, 0, new BidiSpan(2, nstart, j));
7896 pos = j;
7897 } else ++j;
7898 }
7899 if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i));
7900 }
7901 }
7902 if (order[0].level == 1 && (m = str.match(/^\s+/))) {
7903 order[0].from = m[0].length;
7904 order.unshift(new BidiSpan(0, 0, m[0].length));
7905 }
7906 if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
7907 lst(order).to -= m[0].length;
7908 order.push(new BidiSpan(0, len - m[0].length, len));
7909 }
7910 if (order[0].level != lst(order).level)
7911 order.push(new BidiSpan(order[0].level, len, len));
7912
7913 return order;
7914 };
7915 })();
7916
7917 // THE END
7918
7919 CodeMirror.version = "4.8.0";
7920
7921 return CodeMirror;
7922 });
7923