[ Index ] |
PHP Cross Reference of MyBB 1.8.38 |
[Summary view] [Print] [Text view]
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 (global, factory) { 11 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 12 typeof define === 'function' && define.amd ? define(factory) : 13 (global.CodeMirror = factory()); 14 }(this, (function () { 'use strict'; 15 16 // Kludges for bugs and behavior differences that can't be feature 17 // detected are enabled based on userAgent etc sniffing. 18 var userAgent = navigator.userAgent 19 var platform = navigator.platform 20 21 var gecko = /gecko\/\d/i.test(userAgent) 22 var ie_upto10 = /MSIE \d/.test(userAgent) 23 var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent) 24 var edge = /Edge\/(\d+)/.exec(userAgent) 25 var ie = ie_upto10 || ie_11up || edge 26 var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]) 27 var webkit = !edge && /WebKit\//.test(userAgent) 28 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent) 29 var chrome = !edge && /Chrome\//.test(userAgent) 30 var presto = /Opera\//.test(userAgent) 31 var safari = /Apple Computer/.test(navigator.vendor) 32 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent) 33 var phantom = /PhantomJS/.test(userAgent) 34 35 var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent) 36 var android = /Android/.test(userAgent) 37 // This is woefully incomplete. Suggestions for alternative methods welcome. 38 var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent) 39 var mac = ios || /Mac/.test(platform) 40 var chromeOS = /\bCrOS\b/.test(userAgent) 41 var windows = /win/i.test(platform) 42 43 var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/) 44 if (presto_version) { presto_version = Number(presto_version[1]) } 45 if (presto_version && presto_version >= 15) { presto = false; webkit = true } 46 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X 47 var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)) 48 var captureRightClick = gecko || (ie && ie_version >= 9) 49 50 function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } 51 52 var rmClass = function(node, cls) { 53 var current = node.className 54 var match = classTest(cls).exec(current) 55 if (match) { 56 var after = current.slice(match.index + match[0].length) 57 node.className = current.slice(0, match.index) + (after ? match[1] + after : "") 58 } 59 } 60 61 function removeChildren(e) { 62 for (var count = e.childNodes.length; count > 0; --count) 63 { e.removeChild(e.firstChild) } 64 return e 65 } 66 67 function removeChildrenAndAdd(parent, e) { 68 return removeChildren(parent).appendChild(e) 69 } 70 71 function elt(tag, content, className, style) { 72 var e = document.createElement(tag) 73 if (className) { e.className = className } 74 if (style) { e.style.cssText = style } 75 if (typeof content == "string") { e.appendChild(document.createTextNode(content)) } 76 else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]) } } 77 return e 78 } 79 // wrapper for elt, which removes the elt from the accessibility tree 80 function eltP(tag, content, className, style) { 81 var e = elt(tag, content, className, style) 82 e.setAttribute("role", "presentation") 83 return e 84 } 85 86 var range 87 if (document.createRange) { range = function(node, start, end, endNode) { 88 var r = document.createRange() 89 r.setEnd(endNode || node, end) 90 r.setStart(node, start) 91 return r 92 } } 93 else { range = function(node, start, end) { 94 var r = document.body.createTextRange() 95 try { r.moveToElementText(node.parentNode) } 96 catch(e) { return r } 97 r.collapse(true) 98 r.moveEnd("character", end) 99 r.moveStart("character", start) 100 return r 101 } } 102 103 function contains(parent, child) { 104 if (child.nodeType == 3) // Android browser always returns false when child is a textnode 105 { child = child.parentNode } 106 if (parent.contains) 107 { return parent.contains(child) } 108 do { 109 if (child.nodeType == 11) { child = child.host } 110 if (child == parent) { return true } 111 } while (child = child.parentNode) 112 } 113 114 function activeElt() { 115 // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement. 116 // IE < 10 will throw when accessed while the page is loading or in an iframe. 117 // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable. 118 var activeElement 119 try { 120 activeElement = document.activeElement 121 } catch(e) { 122 activeElement = document.body || null 123 } 124 while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) 125 { activeElement = activeElement.shadowRoot.activeElement } 126 return activeElement 127 } 128 129 function addClass(node, cls) { 130 var current = node.className 131 if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls } 132 } 133 function joinClasses(a, b) { 134 var as = a.split(" ") 135 for (var i = 0; i < as.length; i++) 136 { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i] } } 137 return b 138 } 139 140 var selectInput = function(node) { node.select() } 141 if (ios) // Mobile Safari apparently has a bug where select() is broken. 142 { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length } } 143 else if (ie) // Suppress mysterious IE10 errors 144 { selectInput = function(node) { try { node.select() } catch(_e) {} } } 145 146 function bind(f) { 147 var args = Array.prototype.slice.call(arguments, 1) 148 return function(){return f.apply(null, args)} 149 } 150 151 function copyObj(obj, target, overwrite) { 152 if (!target) { target = {} } 153 for (var prop in obj) 154 { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) 155 { target[prop] = obj[prop] } } 156 return target 157 } 158 159 // Counts the column offset in a string, taking tabs into account. 160 // Used mostly to find indentation. 161 function countColumn(string, end, tabSize, startIndex, startValue) { 162 if (end == null) { 163 end = string.search(/[^\s\u00a0]/) 164 if (end == -1) { end = string.length } 165 } 166 for (var i = startIndex || 0, n = startValue || 0;;) { 167 var nextTab = string.indexOf("\t", i) 168 if (nextTab < 0 || nextTab >= end) 169 { return n + (end - i) } 170 n += nextTab - i 171 n += tabSize - (n % tabSize) 172 i = nextTab + 1 173 } 174 } 175 176 var Delayed = function() {this.id = null}; 177 Delayed.prototype.set = function (ms, f) { 178 clearTimeout(this.id) 179 this.id = setTimeout(f, ms) 180 }; 181 182 function indexOf(array, elt) { 183 for (var i = 0; i < array.length; ++i) 184 { if (array[i] == elt) { return i } } 185 return -1 186 } 187 188 // Number of pixels added to scroller and sizer to hide scrollbar 189 var scrollerGap = 30 190 191 // Returned or thrown by various protocols to signal 'I'm not 192 // handling this'. 193 var Pass = {toString: function(){return "CodeMirror.Pass"}} 194 195 // Reused option objects for setSelection & friends 196 var sel_dontScroll = {scroll: false}; 197 var sel_mouse = {origin: "*mouse"}; 198 var sel_move = {origin: "+move"}; 199 // The inverse of countColumn -- find the offset that corresponds to 200 // a particular column. 201 function findColumn(string, goal, tabSize) { 202 for (var pos = 0, col = 0;;) { 203 var nextTab = string.indexOf("\t", pos) 204 if (nextTab == -1) { nextTab = string.length } 205 var skipped = nextTab - pos 206 if (nextTab == string.length || col + skipped >= goal) 207 { return pos + Math.min(skipped, goal - col) } 208 col += nextTab - pos 209 col += tabSize - (col % tabSize) 210 pos = nextTab + 1 211 if (col >= goal) { return pos } 212 } 213 } 214 215 var spaceStrs = [""] 216 function spaceStr(n) { 217 while (spaceStrs.length <= n) 218 { spaceStrs.push(lst(spaceStrs) + " ") } 219 return spaceStrs[n] 220 } 221 222 function lst(arr) { return arr[arr.length-1] } 223 224 function map(array, f) { 225 var out = [] 226 for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i) } 227 return out 228 } 229 230 function insertSorted(array, value, score) { 231 var pos = 0, priority = score(value) 232 while (pos < array.length && score(array[pos]) <= priority) { pos++ } 233 array.splice(pos, 0, value) 234 } 235 236 function nothing() {} 237 238 function createObj(base, props) { 239 var inst 240 if (Object.create) { 241 inst = Object.create(base) 242 } else { 243 nothing.prototype = base 244 inst = new nothing() 245 } 246 if (props) { copyObj(props, inst) } 247 return inst 248 } 249 250 var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/ 251 function isWordCharBasic(ch) { 252 return /\w/.test(ch) || ch > "\x80" && 253 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)) 254 } 255 function isWordChar(ch, helper) { 256 if (!helper) { return isWordCharBasic(ch) } 257 if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true } 258 return helper.test(ch) 259 } 260 261 function isEmpty(obj) { 262 for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } } 263 return true 264 } 265 266 // Extending unicode characters. A series of a non-extending char + 267 // any number of extending chars is treated as a single unit as far 268 // as editing and measuring is concerned. This is not fully correct, 269 // since some scripts/fonts/browsers also treat other configurations 270 // of code points as a group. 271 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]/ 272 function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) } 273 274 // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range. 275 function skipExtendingChars(str, pos, dir) { 276 while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir } 277 return pos 278 } 279 280 // Returns the value from the range [`from`; `to`] that satisfies 281 // `pred` and is closest to `from`. Assumes that at least `to` 282 // satisfies `pred`. Supports `from` being greater than `to`. 283 function findFirst(pred, from, to) { 284 // At any point we are certain `to` satisfies `pred`, don't know 285 // whether `from` does. 286 var dir = from > to ? -1 : 1 287 for (;;) { 288 if (from == to) { return from } 289 var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF) 290 if (mid == from) { return pred(mid) ? from : to } 291 if (pred(mid)) { to = mid } 292 else { from = mid + dir } 293 } 294 } 295 296 // The display handles the DOM integration, both for input reading 297 // and content drawing. It holds references to DOM nodes and 298 // display-related state. 299 300 function Display(place, doc, input) { 301 var d = this 302 this.input = input 303 304 // Covers bottom-right square when both scrollbars are present. 305 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler") 306 d.scrollbarFiller.setAttribute("cm-not-content", "true") 307 // Covers bottom of gutter when coverGutterNextToScrollbar is on 308 // and h scrollbar is present. 309 d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler") 310 d.gutterFiller.setAttribute("cm-not-content", "true") 311 // Will contain the actual code, positioned to cover the viewport. 312 d.lineDiv = eltP("div", null, "CodeMirror-code") 313 // Elements are added to these to represent selection and cursors. 314 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1") 315 d.cursorDiv = elt("div", null, "CodeMirror-cursors") 316 // A visibility: hidden element used to find the size of things. 317 d.measure = elt("div", null, "CodeMirror-measure") 318 // When lines outside of the viewport are measured, they are drawn in this. 319 d.lineMeasure = elt("div", null, "CodeMirror-measure") 320 // Wraps everything that needs to exist inside the vertically-padded coordinate system 321 d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], 322 null, "position: relative; outline: none") 323 var lines = eltP("div", [d.lineSpace], "CodeMirror-lines") 324 // Moved around its parent to cover visible view. 325 d.mover = elt("div", [lines], null, "position: relative") 326 // Set to the height of the document, allowing scrolling. 327 d.sizer = elt("div", [d.mover], "CodeMirror-sizer") 328 d.sizerWidth = null 329 // Behavior of elts with overflow: auto and padding is 330 // inconsistent across browsers. This is used to ensure the 331 // scrollable area is big enough. 332 d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;") 333 // Will contain the gutters, if any. 334 d.gutters = elt("div", null, "CodeMirror-gutters") 335 d.lineGutter = null 336 // Actual scrollable element. 337 d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll") 338 d.scroller.setAttribute("tabIndex", "-1") 339 // The element in which the editor lives. 340 d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror") 341 342 // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) 343 if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0 } 344 if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true } 345 346 if (place) { 347 if (place.appendChild) { place.appendChild(d.wrapper) } 348 else { place(d.wrapper) } 349 } 350 351 // Current rendered range (may be bigger than the view window). 352 d.viewFrom = d.viewTo = doc.first 353 d.reportedViewFrom = d.reportedViewTo = doc.first 354 // Information about the rendered lines. 355 d.view = [] 356 d.renderedView = null 357 // Holds info about a single rendered line when it was rendered 358 // for measurement, while not in view. 359 d.externalMeasured = null 360 // Empty space (in pixels) above the view 361 d.viewOffset = 0 362 d.lastWrapHeight = d.lastWrapWidth = 0 363 d.updateLineNumbers = null 364 365 d.nativeBarWidth = d.barHeight = d.barWidth = 0 366 d.scrollbarsClipped = false 367 368 // Used to only resize the line number gutter when necessary (when 369 // the amount of lines crosses a boundary that makes its width change) 370 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null 371 // Set to true when a non-horizontal-scrolling line widget is 372 // added. As an optimization, line widget aligning is skipped when 373 // this is false. 374 d.alignWidgets = false 375 376 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null 377 378 // Tracks the maximum line length so that the horizontal scrollbar 379 // can be kept static when scrolling. 380 d.maxLine = null 381 d.maxLineLength = 0 382 d.maxLineChanged = false 383 384 // Used for measuring wheel scrolling granularity 385 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null 386 387 // True when shift is held down. 388 d.shift = false 389 390 // Used to track whether anything happened since the context menu 391 // was opened. 392 d.selForContextMenu = null 393 394 d.activeTouch = null 395 396 input.init(d) 397 } 398 399 // Find the line object corresponding to the given line number. 400 function getLine(doc, n) { 401 n -= doc.first 402 if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.first) + " in the document.") } 403 var chunk = doc 404 while (!chunk.lines) { 405 for (var i = 0;; ++i) { 406 var child = chunk.children[i], sz = child.chunkSize() 407 if (n < sz) { chunk = child; break } 408 n -= sz 409 } 410 } 411 return chunk.lines[n] 412 } 413 414 // Get the part of a document between two positions, as an array of 415 // strings. 416 function getBetween(doc, start, end) { 417 var out = [], n = start.line 418 doc.iter(start.line, end.line + 1, function (line) { 419 var text = line.text 420 if (n == end.line) { text = text.slice(0, end.ch) } 421 if (n == start.line) { text = text.slice(start.ch) } 422 out.push(text) 423 ++n 424 }) 425 return out 426 } 427 // Get the lines between from and to, as array of strings. 428 function getLines(doc, from, to) { 429 var out = [] 430 doc.iter(from, to, function (line) { out.push(line.text) }) // iter aborts when callback returns truthy value 431 return out 432 } 433 434 // Update the height of a line, propagating the height change 435 // upwards to parent nodes. 436 function updateLineHeight(line, height) { 437 var diff = height - line.height 438 if (diff) { for (var n = line; n; n = n.parent) { n.height += diff } } 439 } 440 441 // Given a line object, find its line number by walking up through 442 // its parent links. 443 function lineNo(line) { 444 if (line.parent == null) { return null } 445 var cur = line.parent, no = indexOf(cur.lines, line) 446 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { 447 for (var i = 0;; ++i) { 448 if (chunk.children[i] == cur) { break } 449 no += chunk.children[i].chunkSize() 450 } 451 } 452 return no + cur.first 453 } 454 455 // Find the line at the given vertical position, using the height 456 // information in the document tree. 457 function lineAtHeight(chunk, h) { 458 var n = chunk.first 459 outer: do { 460 for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) { 461 var child = chunk.children[i$1], ch = child.height 462 if (h < ch) { chunk = child; continue outer } 463 h -= ch 464 n += child.chunkSize() 465 } 466 return n 467 } while (!chunk.lines) 468 var i = 0 469 for (; i < chunk.lines.length; ++i) { 470 var line = chunk.lines[i], lh = line.height 471 if (h < lh) { break } 472 h -= lh 473 } 474 return n + i 475 } 476 477 function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} 478 479 function lineNumberFor(options, i) { 480 return String(options.lineNumberFormatter(i + options.firstLineNumber)) 481 } 482 483 // A Pos instance represents a position within the text. 484 function Pos(line, ch, sticky) { 485 if ( sticky === void 0 ) sticky = null; 486 487 if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) } 488 this.line = line 489 this.ch = ch 490 this.sticky = sticky 491 } 492 493 // Compare two positions, return 0 if they are the same, a negative 494 // number when a is less, and a positive number otherwise. 495 function cmp(a, b) { return a.line - b.line || a.ch - b.ch } 496 497 function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } 498 499 function copyPos(x) {return Pos(x.line, x.ch)} 500 function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } 501 function minPos(a, b) { return cmp(a, b) < 0 ? a : b } 502 503 // Most of the external API clips given positions to make sure they 504 // actually exist within the document. 505 function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} 506 function clipPos(doc, pos) { 507 if (pos.line < doc.first) { return Pos(doc.first, 0) } 508 var last = doc.first + doc.size - 1 509 if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) } 510 return clipToLen(pos, getLine(doc, pos.line).text.length) 511 } 512 function clipToLen(pos, linelen) { 513 var ch = pos.ch 514 if (ch == null || ch > linelen) { return Pos(pos.line, linelen) } 515 else if (ch < 0) { return Pos(pos.line, 0) } 516 else { return pos } 517 } 518 function clipPosArray(doc, array) { 519 var out = [] 520 for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]) } 521 return out 522 } 523 524 // Optimize some code when these features are not used. 525 var sawReadOnlySpans = false; 526 var sawCollapsedSpans = false; 527 function seeReadOnlySpans() { 528 sawReadOnlySpans = true 529 } 530 531 function seeCollapsedSpans() { 532 sawCollapsedSpans = true 533 } 534 535 // TEXTMARKER SPANS 536 537 function MarkedSpan(marker, from, to) { 538 this.marker = marker 539 this.from = from; this.to = to 540 } 541 542 // Search an array of spans for a span matching the given marker. 543 function getMarkedSpanFor(spans, marker) { 544 if (spans) { for (var i = 0; i < spans.length; ++i) { 545 var span = spans[i] 546 if (span.marker == marker) { return span } 547 } } 548 } 549 // Remove a span from an array, returning undefined if no spans are 550 // left (we don't store arrays for lines without spans). 551 function removeMarkedSpan(spans, span) { 552 var r 553 for (var i = 0; i < spans.length; ++i) 554 { if (spans[i] != span) { (r || (r = [])).push(spans[i]) } } 555 return r 556 } 557 // Add a span to a line. 558 function addMarkedSpan(line, span) { 559 line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span] 560 span.marker.attachLine(line) 561 } 562 563 // Used for the algorithm that adjusts markers for a change in the 564 // document. These functions cut an array of spans at a given 565 // character position, returning an array of remaining chunks (or 566 // undefined if nothing remains). 567 function markedSpansBefore(old, startCh, isInsert) { 568 var nw 569 if (old) { for (var i = 0; i < old.length; ++i) { 570 var span = old[i], marker = span.marker 571 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh) 572 if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { 573 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) 574 ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)) 575 } 576 } } 577 return nw 578 } 579 function markedSpansAfter(old, endCh, isInsert) { 580 var nw 581 if (old) { for (var i = 0; i < old.length; ++i) { 582 var span = old[i], marker = span.marker 583 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh) 584 if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { 585 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) 586 ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, 587 span.to == null ? null : span.to - endCh)) 588 } 589 } } 590 return nw 591 } 592 593 // Given a change object, compute the new set of marker spans that 594 // cover the line in which the change took place. Removes spans 595 // entirely within the change, reconnects spans belonging to the 596 // same marker that appear on both sides of the change, and cuts off 597 // spans partially within the change. Returns an array of span 598 // arrays with one element for each line in (after) the change. 599 function stretchSpansOverChange(doc, change) { 600 if (change.full) { return null } 601 var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans 602 var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans 603 if (!oldFirst && !oldLast) { return null } 604 605 var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0 606 // Get the spans that 'stick out' on both sides 607 var first = markedSpansBefore(oldFirst, startCh, isInsert) 608 var last = markedSpansAfter(oldLast, endCh, isInsert) 609 610 // Next, merge those two ends 611 var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0) 612 if (first) { 613 // Fix up .to properties of first 614 for (var i = 0; i < first.length; ++i) { 615 var span = first[i] 616 if (span.to == null) { 617 var found = getMarkedSpanFor(last, span.marker) 618 if (!found) { span.to = startCh } 619 else if (sameLine) { span.to = found.to == null ? null : found.to + offset } 620 } 621 } 622 } 623 if (last) { 624 // Fix up .from in last (or move them into first in case of sameLine) 625 for (var i$1 = 0; i$1 < last.length; ++i$1) { 626 var span$1 = last[i$1] 627 if (span$1.to != null) { span$1.to += offset } 628 if (span$1.from == null) { 629 var found$1 = getMarkedSpanFor(first, span$1.marker) 630 if (!found$1) { 631 span$1.from = offset 632 if (sameLine) { (first || (first = [])).push(span$1) } 633 } 634 } else { 635 span$1.from += offset 636 if (sameLine) { (first || (first = [])).push(span$1) } 637 } 638 } 639 } 640 // Make sure we didn't create any zero-length spans 641 if (first) { first = clearEmptySpans(first) } 642 if (last && last != first) { last = clearEmptySpans(last) } 643 644 var newMarkers = [first] 645 if (!sameLine) { 646 // Fill gap with whole-line-spans 647 var gap = change.text.length - 2, gapMarkers 648 if (gap > 0 && first) 649 { for (var i$2 = 0; i$2 < first.length; ++i$2) 650 { if (first[i$2].to == null) 651 { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)) } } } 652 for (var i$3 = 0; i$3 < gap; ++i$3) 653 { newMarkers.push(gapMarkers) } 654 newMarkers.push(last) 655 } 656 return newMarkers 657 } 658 659 // Remove spans that are empty and don't have a clearWhenEmpty 660 // option of false. 661 function clearEmptySpans(spans) { 662 for (var i = 0; i < spans.length; ++i) { 663 var span = spans[i] 664 if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) 665 { spans.splice(i--, 1) } 666 } 667 if (!spans.length) { return null } 668 return spans 669 } 670 671 // Used to 'clip' out readOnly ranges when making a change. 672 function removeReadOnlyRanges(doc, from, to) { 673 var markers = null 674 doc.iter(from.line, to.line + 1, function (line) { 675 if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { 676 var mark = line.markedSpans[i].marker 677 if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) 678 { (markers || (markers = [])).push(mark) } 679 } } 680 }) 681 if (!markers) { return null } 682 var parts = [{from: from, to: to}] 683 for (var i = 0; i < markers.length; ++i) { 684 var mk = markers[i], m = mk.find(0) 685 for (var j = 0; j < parts.length; ++j) { 686 var p = parts[j] 687 if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue } 688 var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to) 689 if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) 690 { newParts.push({from: p.from, to: m.from}) } 691 if (dto > 0 || !mk.inclusiveRight && !dto) 692 { newParts.push({from: m.to, to: p.to}) } 693 parts.splice.apply(parts, newParts) 694 j += newParts.length - 3 695 } 696 } 697 return parts 698 } 699 700 // Connect or disconnect spans from a line. 701 function detachMarkedSpans(line) { 702 var spans = line.markedSpans 703 if (!spans) { return } 704 for (var i = 0; i < spans.length; ++i) 705 { spans[i].marker.detachLine(line) } 706 line.markedSpans = null 707 } 708 function attachMarkedSpans(line, spans) { 709 if (!spans) { return } 710 for (var i = 0; i < spans.length; ++i) 711 { spans[i].marker.attachLine(line) } 712 line.markedSpans = spans 713 } 714 715 // Helpers used when computing which overlapping collapsed span 716 // counts as the larger one. 717 function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } 718 function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } 719 720 // Returns a number indicating which of two overlapping collapsed 721 // spans is larger (and thus includes the other). Falls back to 722 // comparing ids when the spans cover exactly the same range. 723 function compareCollapsedMarkers(a, b) { 724 var lenDiff = a.lines.length - b.lines.length 725 if (lenDiff != 0) { return lenDiff } 726 var aPos = a.find(), bPos = b.find() 727 var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b) 728 if (fromCmp) { return -fromCmp } 729 var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b) 730 if (toCmp) { return toCmp } 731 return b.id - a.id 732 } 733 734 // Find out whether a line ends or starts in a collapsed span. If 735 // so, return the marker for that span. 736 function collapsedSpanAtSide(line, start) { 737 var sps = sawCollapsedSpans && line.markedSpans, found 738 if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { 739 sp = sps[i] 740 if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && 741 (!found || compareCollapsedMarkers(found, sp.marker) < 0)) 742 { found = sp.marker } 743 } } 744 return found 745 } 746 function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } 747 function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } 748 749 // Test whether there exists a collapsed span that partially 750 // overlaps (covers the start or end, but not both) of a new span. 751 // Such overlap is not allowed. 752 function conflictingCollapsedRange(doc, lineNo, from, to, marker) { 753 var line = getLine(doc, lineNo) 754 var sps = sawCollapsedSpans && line.markedSpans 755 if (sps) { for (var i = 0; i < sps.length; ++i) { 756 var sp = sps[i] 757 if (!sp.marker.collapsed) { continue } 758 var found = sp.marker.find(0) 759 var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker) 760 var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker) 761 if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue } 762 if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || 763 fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) 764 { return true } 765 } } 766 } 767 768 // A visual line is a line as drawn on the screen. Folding, for 769 // example, can cause multiple logical lines to appear on the same 770 // visual line. This finds the start of the visual line that the 771 // given line is part of (usually that is the line itself). 772 function visualLine(line) { 773 var merged 774 while (merged = collapsedSpanAtStart(line)) 775 { line = merged.find(-1, true).line } 776 return line 777 } 778 779 function visualLineEnd(line) { 780 var merged 781 while (merged = collapsedSpanAtEnd(line)) 782 { line = merged.find(1, true).line } 783 return line 784 } 785 786 // Returns an array of logical lines that continue the visual line 787 // started by the argument, or undefined if there are no such lines. 788 function visualLineContinued(line) { 789 var merged, lines 790 while (merged = collapsedSpanAtEnd(line)) { 791 line = merged.find(1, true).line 792 ;(lines || (lines = [])).push(line) 793 } 794 return lines 795 } 796 797 // Get the line number of the start of the visual line that the 798 // given line number is part of. 799 function visualLineNo(doc, lineN) { 800 var line = getLine(doc, lineN), vis = visualLine(line) 801 if (line == vis) { return lineN } 802 return lineNo(vis) 803 } 804 805 // Get the line number of the start of the next visual line after 806 // the given line. 807 function visualLineEndNo(doc, lineN) { 808 if (lineN > doc.lastLine()) { return lineN } 809 var line = getLine(doc, lineN), merged 810 if (!lineIsHidden(doc, line)) { return lineN } 811 while (merged = collapsedSpanAtEnd(line)) 812 { line = merged.find(1, true).line } 813 return lineNo(line) + 1 814 } 815 816 // Compute whether a line is hidden. Lines count as hidden when they 817 // are part of a visual line that starts with another line, or when 818 // they are entirely covered by collapsed, non-widget span. 819 function lineIsHidden(doc, line) { 820 var sps = sawCollapsedSpans && line.markedSpans 821 if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { 822 sp = sps[i] 823 if (!sp.marker.collapsed) { continue } 824 if (sp.from == null) { return true } 825 if (sp.marker.widgetNode) { continue } 826 if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) 827 { return true } 828 } } 829 } 830 function lineIsHiddenInner(doc, line, span) { 831 if (span.to == null) { 832 var end = span.marker.find(1, true) 833 return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) 834 } 835 if (span.marker.inclusiveRight && span.to == line.text.length) 836 { return true } 837 for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) { 838 sp = line.markedSpans[i] 839 if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && 840 (sp.to == null || sp.to != span.from) && 841 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && 842 lineIsHiddenInner(doc, line, sp)) { return true } 843 } 844 } 845 846 // Find the height above the given line. 847 function heightAtLine(lineObj) { 848 lineObj = visualLine(lineObj) 849 850 var h = 0, chunk = lineObj.parent 851 for (var i = 0; i < chunk.lines.length; ++i) { 852 var line = chunk.lines[i] 853 if (line == lineObj) { break } 854 else { h += line.height } 855 } 856 for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { 857 for (var i$1 = 0; i$1 < p.children.length; ++i$1) { 858 var cur = p.children[i$1] 859 if (cur == chunk) { break } 860 else { h += cur.height } 861 } 862 } 863 return h 864 } 865 866 // Compute the character length of a line, taking into account 867 // collapsed ranges (see markText) that might hide parts, and join 868 // other lines onto it. 869 function lineLength(line) { 870 if (line.height == 0) { return 0 } 871 var len = line.text.length, merged, cur = line 872 while (merged = collapsedSpanAtStart(cur)) { 873 var found = merged.find(0, true) 874 cur = found.from.line 875 len += found.from.ch - found.to.ch 876 } 877 cur = line 878 while (merged = collapsedSpanAtEnd(cur)) { 879 var found$1 = merged.find(0, true) 880 len -= cur.text.length - found$1.from.ch 881 cur = found$1.to.line 882 len += cur.text.length - found$1.to.ch 883 } 884 return len 885 } 886 887 // Find the longest line in the document. 888 function findMaxLine(cm) { 889 var d = cm.display, doc = cm.doc 890 d.maxLine = getLine(doc, doc.first) 891 d.maxLineLength = lineLength(d.maxLine) 892 d.maxLineChanged = true 893 doc.iter(function (line) { 894 var len = lineLength(line) 895 if (len > d.maxLineLength) { 896 d.maxLineLength = len 897 d.maxLine = line 898 } 899 }) 900 } 901 902 // BIDI HELPERS 903 904 function iterateBidiSections(order, from, to, f) { 905 if (!order) { return f(from, to, "ltr", 0) } 906 var found = false 907 for (var i = 0; i < order.length; ++i) { 908 var part = order[i] 909 if (part.from < to && part.to > from || from == to && part.to == from) { 910 f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr", i) 911 found = true 912 } 913 } 914 if (!found) { f(from, to, "ltr") } 915 } 916 917 var bidiOther = null 918 function getBidiPartAt(order, ch, sticky) { 919 var found 920 bidiOther = null 921 for (var i = 0; i < order.length; ++i) { 922 var cur = order[i] 923 if (cur.from < ch && cur.to > ch) { return i } 924 if (cur.to == ch) { 925 if (cur.from != cur.to && sticky == "before") { found = i } 926 else { bidiOther = i } 927 } 928 if (cur.from == ch) { 929 if (cur.from != cur.to && sticky != "before") { found = i } 930 else { bidiOther = i } 931 } 932 } 933 return found != null ? found : bidiOther 934 } 935 936 // Bidirectional ordering algorithm 937 // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm 938 // that this (partially) implements. 939 940 // One-char codes used for character types: 941 // L (L): Left-to-Right 942 // R (R): Right-to-Left 943 // r (AL): Right-to-Left Arabic 944 // 1 (EN): European Number 945 // + (ES): European Number Separator 946 // % (ET): European Number Terminator 947 // n (AN): Arabic Number 948 // , (CS): Common Number Separator 949 // m (NSM): Non-Spacing Mark 950 // b (BN): Boundary Neutral 951 // s (B): Paragraph Separator 952 // t (S): Segment Separator 953 // w (WS): Whitespace 954 // N (ON): Other Neutrals 955 956 // Returns null if characters are ordered as they appear 957 // (left-to-right), or an array of sections ({from, to, level} 958 // objects) in the order in which they occur visually. 959 var bidiOrdering = (function() { 960 // Character types for codepoints 0 to 0xff 961 var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN" 962 // Character types for codepoints 0x600 to 0x6f9 963 var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111" 964 function charType(code) { 965 if (code <= 0xf7) { return lowTypes.charAt(code) } 966 else if (0x590 <= code && code <= 0x5f4) { return "R" } 967 else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) } 968 else if (0x6ee <= code && code <= 0x8ac) { return "r" } 969 else if (0x2000 <= code && code <= 0x200b) { return "w" } 970 else if (code == 0x200c) { return "b" } 971 else { return "L" } 972 } 973 974 var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/ 975 var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/ 976 977 function BidiSpan(level, from, to) { 978 this.level = level 979 this.from = from; this.to = to 980 } 981 982 return function(str, direction) { 983 var outerType = direction == "ltr" ? "L" : "R" 984 985 if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return false } 986 var len = str.length, types = [] 987 for (var i = 0; i < len; ++i) 988 { types.push(charType(str.charCodeAt(i))) } 989 990 // W1. Examine each non-spacing mark (NSM) in the level run, and 991 // change the type of the NSM to the type of the previous 992 // character. If the NSM is at the start of the level run, it will 993 // get the type of sor. 994 for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) { 995 var type = types[i$1] 996 if (type == "m") { types[i$1] = prev } 997 else { prev = type } 998 } 999 1000 // W2. Search backwards from each instance of a European number 1001 // until the first strong type (R, L, AL, or sor) is found. If an 1002 // AL is found, change the type of the European number to Arabic 1003 // number. 1004 // W3. Change all ALs to R. 1005 for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) { 1006 var type$1 = types[i$2] 1007 if (type$1 == "1" && cur == "r") { types[i$2] = "n" } 1008 else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "R" } } 1009 } 1010 1011 // W4. A single European separator between two European numbers 1012 // changes to a European number. A single common separator between 1013 // two numbers of the same type changes to that type. 1014 for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) { 1015 var type$2 = types[i$3] 1016 if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1" } 1017 else if (type$2 == "," && prev$1 == types[i$3+1] && 1018 (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1 } 1019 prev$1 = type$2 1020 } 1021 1022 // W5. A sequence of European terminators adjacent to European 1023 // numbers changes to all European numbers. 1024 // W6. Otherwise, separators and terminators change to Other 1025 // Neutral. 1026 for (var i$4 = 0; i$4 < len; ++i$4) { 1027 var type$3 = types[i$4] 1028 if (type$3 == ",") { types[i$4] = "N" } 1029 else if (type$3 == "%") { 1030 var end = (void 0) 1031 for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {} 1032 var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N" 1033 for (var j = i$4; j < end; ++j) { types[j] = replace } 1034 i$4 = end - 1 1035 } 1036 } 1037 1038 // W7. Search backwards from each instance of a European number 1039 // until the first strong type (R, L, or sor) is found. If an L is 1040 // found, then change the type of the European number to L. 1041 for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) { 1042 var type$4 = types[i$5] 1043 if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L" } 1044 else if (isStrong.test(type$4)) { cur$1 = type$4 } 1045 } 1046 1047 // N1. A sequence of neutrals takes the direction of the 1048 // surrounding strong text if the text on both sides has the same 1049 // direction. European and Arabic numbers act as if they were R in 1050 // terms of their influence on neutrals. Start-of-level-run (sor) 1051 // and end-of-level-run (eor) are used at level run boundaries. 1052 // N2. Any remaining neutrals take the embedding direction. 1053 for (var i$6 = 0; i$6 < len; ++i$6) { 1054 if (isNeutral.test(types[i$6])) { 1055 var end$1 = (void 0) 1056 for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {} 1057 var before = (i$6 ? types[i$6-1] : outerType) == "L" 1058 var after = (end$1 < len ? types[end$1] : outerType) == "L" 1059 var replace$1 = before == after ? (before ? "L" : "R") : outerType 1060 for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1 } 1061 i$6 = end$1 - 1 1062 } 1063 } 1064 1065 // Here we depart from the documented algorithm, in order to avoid 1066 // building up an actual levels array. Since there are only three 1067 // levels (0, 1, 2) in an implementation that doesn't take 1068 // explicit embedding into account, we can build up the order on 1069 // the fly, without following the level-based algorithm. 1070 var order = [], m 1071 for (var i$7 = 0; i$7 < len;) { 1072 if (countsAsLeft.test(types[i$7])) { 1073 var start = i$7 1074 for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {} 1075 order.push(new BidiSpan(0, start, i$7)) 1076 } else { 1077 var pos = i$7, at = order.length 1078 for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {} 1079 for (var j$2 = pos; j$2 < i$7;) { 1080 if (countsAsNum.test(types[j$2])) { 1081 if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)) } 1082 var nstart = j$2 1083 for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {} 1084 order.splice(at, 0, new BidiSpan(2, nstart, j$2)) 1085 pos = j$2 1086 } else { ++j$2 } 1087 } 1088 if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)) } 1089 } 1090 } 1091 if (order[0].level == 1 && (m = str.match(/^\s+/))) { 1092 order[0].from = m[0].length 1093 order.unshift(new BidiSpan(0, 0, m[0].length)) 1094 } 1095 if (lst(order).level == 1 && (m = str.match(/\s+$/))) { 1096 lst(order).to -= m[0].length 1097 order.push(new BidiSpan(0, len - m[0].length, len)) 1098 } 1099 1100 return direction == "rtl" ? order.reverse() : order 1101 } 1102 })() 1103 1104 // Get the bidi ordering for the given line (and cache it). Returns 1105 // false for lines that are fully left-to-right, and an array of 1106 // BidiSpan objects otherwise. 1107 function getOrder(line, direction) { 1108 var order = line.order 1109 if (order == null) { order = line.order = bidiOrdering(line.text, direction) } 1110 return order 1111 } 1112 1113 // EVENT HANDLING 1114 1115 // Lightweight event framework. on/off also work on DOM nodes, 1116 // registering native DOM handlers. 1117 1118 var noHandlers = [] 1119 1120 var on = function(emitter, type, f) { 1121 if (emitter.addEventListener) { 1122 emitter.addEventListener(type, f, false) 1123 } else if (emitter.attachEvent) { 1124 emitter.attachEvent("on" + type, f) 1125 } else { 1126 var map = emitter._handlers || (emitter._handlers = {}) 1127 map[type] = (map[type] || noHandlers).concat(f) 1128 } 1129 } 1130 1131 function getHandlers(emitter, type) { 1132 return emitter._handlers && emitter._handlers[type] || noHandlers 1133 } 1134 1135 function off(emitter, type, f) { 1136 if (emitter.removeEventListener) { 1137 emitter.removeEventListener(type, f, false) 1138 } else if (emitter.detachEvent) { 1139 emitter.detachEvent("on" + type, f) 1140 } else { 1141 var map = emitter._handlers, arr = map && map[type] 1142 if (arr) { 1143 var index = indexOf(arr, f) 1144 if (index > -1) 1145 { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)) } 1146 } 1147 } 1148 } 1149 1150 function signal(emitter, type /*, values...*/) { 1151 var handlers = getHandlers(emitter, type) 1152 if (!handlers.length) { return } 1153 var args = Array.prototype.slice.call(arguments, 2) 1154 for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args) } 1155 } 1156 1157 // The DOM events that CodeMirror handles can be overridden by 1158 // registering a (non-DOM) handler on the editor for the event name, 1159 // and preventDefault-ing the event in that handler. 1160 function signalDOMEvent(cm, e, override) { 1161 if (typeof e == "string") 1162 { e = {type: e, preventDefault: function() { this.defaultPrevented = true }} } 1163 signal(cm, override || e.type, cm, e) 1164 return e_defaultPrevented(e) || e.codemirrorIgnore 1165 } 1166 1167 function signalCursorActivity(cm) { 1168 var arr = cm._handlers && cm._handlers.cursorActivity 1169 if (!arr) { return } 1170 var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []) 1171 for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) 1172 { set.push(arr[i]) } } 1173 } 1174 1175 function hasHandler(emitter, type) { 1176 return getHandlers(emitter, type).length > 0 1177 } 1178 1179 // Add on and off methods to a constructor's prototype, to make 1180 // registering events on such objects more convenient. 1181 function eventMixin(ctor) { 1182 ctor.prototype.on = function(type, f) {on(this, type, f)} 1183 ctor.prototype.off = function(type, f) {off(this, type, f)} 1184 } 1185 1186 // Due to the fact that we still support jurassic IE versions, some 1187 // compatibility wrappers are needed. 1188 1189 function e_preventDefault(e) { 1190 if (e.preventDefault) { e.preventDefault() } 1191 else { e.returnValue = false } 1192 } 1193 function e_stopPropagation(e) { 1194 if (e.stopPropagation) { e.stopPropagation() } 1195 else { e.cancelBubble = true } 1196 } 1197 function e_defaultPrevented(e) { 1198 return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false 1199 } 1200 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e)} 1201 1202 function e_target(e) {return e.target || e.srcElement} 1203 function e_button(e) { 1204 var b = e.which 1205 if (b == null) { 1206 if (e.button & 1) { b = 1 } 1207 else if (e.button & 2) { b = 3 } 1208 else if (e.button & 4) { b = 2 } 1209 } 1210 if (mac && e.ctrlKey && b == 1) { b = 3 } 1211 return b 1212 } 1213 1214 // Detect drag-and-drop 1215 var dragAndDrop = function() { 1216 // There is *some* kind of drag-and-drop support in IE6-8, but I 1217 // couldn't get it to work yet. 1218 if (ie && ie_version < 9) { return false } 1219 var div = elt('div') 1220 return "draggable" in div || "dragDrop" in div 1221 }() 1222 1223 var zwspSupported 1224 function zeroWidthElement(measure) { 1225 if (zwspSupported == null) { 1226 var test = elt("span", "\u200b") 1227 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])) 1228 if (measure.firstChild.offsetHeight != 0) 1229 { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8) } 1230 } 1231 var node = zwspSupported ? elt("span", "\u200b") : 1232 elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px") 1233 node.setAttribute("cm-text", "") 1234 return node 1235 } 1236 1237 // Feature-detect IE's crummy client rect reporting for bidi text 1238 var badBidiRects 1239 function hasBadBidiRects(measure) { 1240 if (badBidiRects != null) { return badBidiRects } 1241 var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")) 1242 var r0 = range(txt, 0, 1).getBoundingClientRect() 1243 var r1 = range(txt, 1, 2).getBoundingClientRect() 1244 removeChildren(measure) 1245 if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780) 1246 return badBidiRects = (r1.right - r0.right < 3) 1247 } 1248 1249 // See if "".split is the broken IE version, if so, provide an 1250 // alternative way to split lines. 1251 var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { 1252 var pos = 0, result = [], l = string.length 1253 while (pos <= l) { 1254 var nl = string.indexOf("\n", pos) 1255 if (nl == -1) { nl = string.length } 1256 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl) 1257 var rt = line.indexOf("\r") 1258 if (rt != -1) { 1259 result.push(line.slice(0, rt)) 1260 pos += rt + 1 1261 } else { 1262 result.push(line) 1263 pos = nl + 1 1264 } 1265 } 1266 return result 1267 } : function (string) { return string.split(/\r\n?|\n/); } 1268 1269 var hasSelection = window.getSelection ? function (te) { 1270 try { return te.selectionStart != te.selectionEnd } 1271 catch(e) { return false } 1272 } : function (te) { 1273 var range 1274 try {range = te.ownerDocument.selection.createRange()} 1275 catch(e) {} 1276 if (!range || range.parentElement() != te) { return false } 1277 return range.compareEndPoints("StartToEnd", range) != 0 1278 } 1279 1280 var hasCopyEvent = (function () { 1281 var e = elt("div") 1282 if ("oncopy" in e) { return true } 1283 e.setAttribute("oncopy", "return;") 1284 return typeof e.oncopy == "function" 1285 })() 1286 1287 var badZoomedRects = null 1288 function hasBadZoomedRects(measure) { 1289 if (badZoomedRects != null) { return badZoomedRects } 1290 var node = removeChildrenAndAdd(measure, elt("span", "x")) 1291 var normal = node.getBoundingClientRect() 1292 var fromRange = range(node, 0, 1).getBoundingClientRect() 1293 return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 1294 } 1295 1296 var modes = {}; 1297 var mimeModes = {}; 1298 // Extra arguments are stored as the mode's dependencies, which is 1299 // used by (legacy) mechanisms like loadmode.js to automatically 1300 // load a mode. (Preferred mechanism is the require/define calls.) 1301 function defineMode(name, mode) { 1302 if (arguments.length > 2) 1303 { mode.dependencies = Array.prototype.slice.call(arguments, 2) } 1304 modes[name] = mode 1305 } 1306 1307 function defineMIME(mime, spec) { 1308 mimeModes[mime] = spec 1309 } 1310 1311 // Given a MIME type, a {name, ...options} config object, or a name 1312 // string, return a mode config object. 1313 function resolveMode(spec) { 1314 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { 1315 spec = mimeModes[spec] 1316 } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { 1317 var found = mimeModes[spec.name] 1318 if (typeof found == "string") { found = {name: found} } 1319 spec = createObj(found, spec) 1320 spec.name = found.name 1321 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { 1322 return resolveMode("application/xml") 1323 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { 1324 return resolveMode("application/json") 1325 } 1326 if (typeof spec == "string") { return {name: spec} } 1327 else { return spec || {name: "null"} } 1328 } 1329 1330 // Given a mode spec (anything that resolveMode accepts), find and 1331 // initialize an actual mode object. 1332 function getMode(options, spec) { 1333 spec = resolveMode(spec) 1334 var mfactory = modes[spec.name] 1335 if (!mfactory) { return getMode(options, "text/plain") } 1336 var modeObj = mfactory(options, spec) 1337 if (modeExtensions.hasOwnProperty(spec.name)) { 1338 var exts = modeExtensions[spec.name] 1339 for (var prop in exts) { 1340 if (!exts.hasOwnProperty(prop)) { continue } 1341 if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop] } 1342 modeObj[prop] = exts[prop] 1343 } 1344 } 1345 modeObj.name = spec.name 1346 if (spec.helperType) { modeObj.helperType = spec.helperType } 1347 if (spec.modeProps) { for (var prop$1 in spec.modeProps) 1348 { modeObj[prop$1] = spec.modeProps[prop$1] } } 1349 1350 return modeObj 1351 } 1352 1353 // This can be used to attach properties to mode objects from 1354 // outside the actual mode definition. 1355 var modeExtensions = {} 1356 function extendMode(mode, properties) { 1357 var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}) 1358 copyObj(properties, exts) 1359 } 1360 1361 function copyState(mode, state) { 1362 if (state === true) { return state } 1363 if (mode.copyState) { return mode.copyState(state) } 1364 var nstate = {} 1365 for (var n in state) { 1366 var val = state[n] 1367 if (val instanceof Array) { val = val.concat([]) } 1368 nstate[n] = val 1369 } 1370 return nstate 1371 } 1372 1373 // Given a mode and a state (for that mode), find the inner mode and 1374 // state at the position that the state refers to. 1375 function innerMode(mode, state) { 1376 var info 1377 while (mode.innerMode) { 1378 info = mode.innerMode(state) 1379 if (!info || info.mode == mode) { break } 1380 state = info.state 1381 mode = info.mode 1382 } 1383 return info || {mode: mode, state: state} 1384 } 1385 1386 function startState(mode, a1, a2) { 1387 return mode.startState ? mode.startState(a1, a2) : true 1388 } 1389 1390 // STRING STREAM 1391 1392 // Fed to the mode parsers, provides helper functions to make 1393 // parsers more succinct. 1394 1395 var StringStream = function(string, tabSize, lineOracle) { 1396 this.pos = this.start = 0 1397 this.string = string 1398 this.tabSize = tabSize || 8 1399 this.lastColumnPos = this.lastColumnValue = 0 1400 this.lineStart = 0 1401 this.lineOracle = lineOracle 1402 }; 1403 1404 StringStream.prototype.eol = function () {return this.pos >= this.string.length}; 1405 StringStream.prototype.sol = function () {return this.pos == this.lineStart}; 1406 StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined}; 1407 StringStream.prototype.next = function () { 1408 if (this.pos < this.string.length) 1409 { return this.string.charAt(this.pos++) } 1410 }; 1411 StringStream.prototype.eat = function (match) { 1412 var ch = this.string.charAt(this.pos) 1413 var ok 1414 if (typeof match == "string") { ok = ch == match } 1415 else { ok = ch && (match.test ? match.test(ch) : match(ch)) } 1416 if (ok) {++this.pos; return ch} 1417 }; 1418 StringStream.prototype.eatWhile = function (match) { 1419 var start = this.pos 1420 while (this.eat(match)){} 1421 return this.pos > start 1422 }; 1423 StringStream.prototype.eatSpace = function () { 1424 var this$1 = this; 1425 1426 var start = this.pos 1427 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos } 1428 return this.pos > start 1429 }; 1430 StringStream.prototype.skipToEnd = function () {this.pos = this.string.length}; 1431 StringStream.prototype.skipTo = function (ch) { 1432 var found = this.string.indexOf(ch, this.pos) 1433 if (found > -1) {this.pos = found; return true} 1434 }; 1435 StringStream.prototype.backUp = function (n) {this.pos -= n}; 1436 StringStream.prototype.column = function () { 1437 if (this.lastColumnPos < this.start) { 1438 this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue) 1439 this.lastColumnPos = this.start 1440 } 1441 return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) 1442 }; 1443 StringStream.prototype.indentation = function () { 1444 return countColumn(this.string, null, this.tabSize) - 1445 (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) 1446 }; 1447 StringStream.prototype.match = function (pattern, consume, caseInsensitive) { 1448 if (typeof pattern == "string") { 1449 var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; } 1450 var substr = this.string.substr(this.pos, pattern.length) 1451 if (cased(substr) == cased(pattern)) { 1452 if (consume !== false) { this.pos += pattern.length } 1453 return true 1454 } 1455 } else { 1456 var match = this.string.slice(this.pos).match(pattern) 1457 if (match && match.index > 0) { return null } 1458 if (match && consume !== false) { this.pos += match[0].length } 1459 return match 1460 } 1461 }; 1462 StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)}; 1463 StringStream.prototype.hideFirstChars = function (n, inner) { 1464 this.lineStart += n 1465 try { return inner() } 1466 finally { this.lineStart -= n } 1467 }; 1468 StringStream.prototype.lookAhead = function (n) { 1469 var oracle = this.lineOracle 1470 return oracle && oracle.lookAhead(n) 1471 }; 1472 1473 var SavedContext = function(state, lookAhead) { 1474 this.state = state 1475 this.lookAhead = lookAhead 1476 }; 1477 1478 var Context = function(doc, state, line, lookAhead) { 1479 this.state = state 1480 this.doc = doc 1481 this.line = line 1482 this.maxLookAhead = lookAhead || 0 1483 }; 1484 1485 Context.prototype.lookAhead = function (n) { 1486 var line = this.doc.getLine(this.line + n) 1487 if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n } 1488 return line 1489 }; 1490 1491 Context.prototype.nextLine = function () { 1492 this.line++ 1493 if (this.maxLookAhead > 0) { this.maxLookAhead-- } 1494 }; 1495 1496 Context.fromSaved = function (doc, saved, line) { 1497 if (saved instanceof SavedContext) 1498 { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) } 1499 else 1500 { return new Context(doc, copyState(doc.mode, saved), line) } 1501 }; 1502 1503 Context.prototype.save = function (copy) { 1504 var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state 1505 return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state 1506 }; 1507 1508 1509 // Compute a style array (an array starting with a mode generation 1510 // -- for invalidation -- followed by pairs of end positions and 1511 // style strings), which is used to highlight the tokens on the 1512 // line. 1513 function highlightLine(cm, line, context, forceToEnd) { 1514 // A styles array always starts with a number identifying the 1515 // mode/overlays that it is based on (for easy invalidation). 1516 var st = [cm.state.modeGen], lineClasses = {} 1517 // Compute the base array of styles 1518 runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); }, 1519 lineClasses, forceToEnd) 1520 var state = context.state 1521 1522 // Run overlays, adjust style array. 1523 var loop = function ( o ) { 1524 var overlay = cm.state.overlays[o], i = 1, at = 0 1525 context.state = true 1526 runMode(cm, line.text, overlay.mode, context, function (end, style) { 1527 var start = i 1528 // Ensure there's a token end at the current position, and that i points at it 1529 while (at < end) { 1530 var i_end = st[i] 1531 if (i_end > end) 1532 { st.splice(i, 1, end, st[i+1], i_end) } 1533 i += 2 1534 at = Math.min(end, i_end) 1535 } 1536 if (!style) { return } 1537 if (overlay.opaque) { 1538 st.splice(start, i - start, end, "overlay " + style) 1539 i = start + 2 1540 } else { 1541 for (; start < i; start += 2) { 1542 var cur = st[start+1] 1543 st[start+1] = (cur ? cur + " " : "") + "overlay " + style 1544 } 1545 } 1546 }, lineClasses) 1547 }; 1548 1549 for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); 1550 context.state = state 1551 1552 return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} 1553 } 1554 1555 function getLineStyles(cm, line, updateFrontier) { 1556 if (!line.styles || line.styles[0] != cm.state.modeGen) { 1557 var context = getContextBefore(cm, lineNo(line)) 1558 var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state) 1559 var result = highlightLine(cm, line, context) 1560 if (resetState) { context.state = resetState } 1561 line.stateAfter = context.save(!resetState) 1562 line.styles = result.styles 1563 if (result.classes) { line.styleClasses = result.classes } 1564 else if (line.styleClasses) { line.styleClasses = null } 1565 if (updateFrontier === cm.doc.highlightFrontier) 1566 { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier) } 1567 } 1568 return line.styles 1569 } 1570 1571 function getContextBefore(cm, n, precise) { 1572 var doc = cm.doc, display = cm.display 1573 if (!doc.mode.startState) { return new Context(doc, true, n) } 1574 var start = findStartLine(cm, n, precise) 1575 var saved = start > doc.first && getLine(doc, start - 1).stateAfter 1576 var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start) 1577 1578 doc.iter(start, n, function (line) { 1579 processLine(cm, line.text, context) 1580 var pos = context.line 1581 line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null 1582 context.nextLine() 1583 }) 1584 if (precise) { doc.modeFrontier = context.line } 1585 return context 1586 } 1587 1588 // Lightweight form of highlight -- proceed over this line and 1589 // update state, but don't save a style array. Used for lines that 1590 // aren't currently visible. 1591 function processLine(cm, text, context, startAt) { 1592 var mode = cm.doc.mode 1593 var stream = new StringStream(text, cm.options.tabSize, context) 1594 stream.start = stream.pos = startAt || 0 1595 if (text == "") { callBlankLine(mode, context.state) } 1596 while (!stream.eol()) { 1597 readToken(mode, stream, context.state) 1598 stream.start = stream.pos 1599 } 1600 } 1601 1602 function callBlankLine(mode, state) { 1603 if (mode.blankLine) { return mode.blankLine(state) } 1604 if (!mode.innerMode) { return } 1605 var inner = innerMode(mode, state) 1606 if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) } 1607 } 1608 1609 function readToken(mode, stream, state, inner) { 1610 for (var i = 0; i < 10; i++) { 1611 if (inner) { inner[0] = innerMode(mode, state).mode } 1612 var style = mode.token(stream, state) 1613 if (stream.pos > stream.start) { return style } 1614 } 1615 throw new Error("Mode " + mode.name + " failed to advance stream.") 1616 } 1617 1618 var Token = function(stream, type, state) { 1619 this.start = stream.start; this.end = stream.pos 1620 this.string = stream.current() 1621 this.type = type || null 1622 this.state = state 1623 }; 1624 1625 // Utility for getTokenAt and getLineTokens 1626 function takeToken(cm, pos, precise, asArray) { 1627 var doc = cm.doc, mode = doc.mode, style 1628 pos = clipPos(doc, pos) 1629 var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise) 1630 var stream = new StringStream(line.text, cm.options.tabSize, context), tokens 1631 if (asArray) { tokens = [] } 1632 while ((asArray || stream.pos < pos.ch) && !stream.eol()) { 1633 stream.start = stream.pos 1634 style = readToken(mode, stream, context.state) 1635 if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))) } 1636 } 1637 return asArray ? tokens : new Token(stream, style, context.state) 1638 } 1639 1640 function extractLineClasses(type, output) { 1641 if (type) { for (;;) { 1642 var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/) 1643 if (!lineClass) { break } 1644 type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length) 1645 var prop = lineClass[1] ? "bgClass" : "textClass" 1646 if (output[prop] == null) 1647 { output[prop] = lineClass[2] } 1648 else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) 1649 { output[prop] += " " + lineClass[2] } 1650 } } 1651 return type 1652 } 1653 1654 // Run the given mode's parser over a line, calling f for each token. 1655 function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { 1656 var flattenSpans = mode.flattenSpans 1657 if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans } 1658 var curStart = 0, curStyle = null 1659 var stream = new StringStream(text, cm.options.tabSize, context), style 1660 var inner = cm.options.addModeClass && [null] 1661 if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses) } 1662 while (!stream.eol()) { 1663 if (stream.pos > cm.options.maxHighlightLength) { 1664 flattenSpans = false 1665 if (forceToEnd) { processLine(cm, text, context, stream.pos) } 1666 stream.pos = text.length 1667 style = null 1668 } else { 1669 style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses) 1670 } 1671 if (inner) { 1672 var mName = inner[0].name 1673 if (mName) { style = "m-" + (style ? mName + " " + style : mName) } 1674 } 1675 if (!flattenSpans || curStyle != style) { 1676 while (curStart < stream.start) { 1677 curStart = Math.min(stream.start, curStart + 5000) 1678 f(curStart, curStyle) 1679 } 1680 curStyle = style 1681 } 1682 stream.start = stream.pos 1683 } 1684 while (curStart < stream.pos) { 1685 // Webkit seems to refuse to render text nodes longer than 57444 1686 // characters, and returns inaccurate measurements in nodes 1687 // starting around 5000 chars. 1688 var pos = Math.min(stream.pos, curStart + 5000) 1689 f(pos, curStyle) 1690 curStart = pos 1691 } 1692 } 1693 1694 // Finds the line to start with when starting a parse. Tries to 1695 // find a line with a stateAfter, so that it can start with a 1696 // valid state. If that fails, it returns the line with the 1697 // smallest indentation, which tends to need the least context to 1698 // parse correctly. 1699 function findStartLine(cm, n, precise) { 1700 var minindent, minline, doc = cm.doc 1701 var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100) 1702 for (var search = n; search > lim; --search) { 1703 if (search <= doc.first) { return doc.first } 1704 var line = getLine(doc, search - 1), after = line.stateAfter 1705 if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) 1706 { return search } 1707 var indented = countColumn(line.text, null, cm.options.tabSize) 1708 if (minline == null || minindent > indented) { 1709 minline = search - 1 1710 minindent = indented 1711 } 1712 } 1713 return minline 1714 } 1715 1716 function retreatFrontier(doc, n) { 1717 doc.modeFrontier = Math.min(doc.modeFrontier, n) 1718 if (doc.highlightFrontier < n - 10) { return } 1719 var start = doc.first 1720 for (var line = n - 1; line > start; line--) { 1721 var saved = getLine(doc, line).stateAfter 1722 // change is on 3 1723 // state on line 1 looked ahead 2 -- so saw 3 1724 // test 1 + 2 < 3 should cover this 1725 if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { 1726 start = line + 1 1727 break 1728 } 1729 } 1730 doc.highlightFrontier = Math.min(doc.highlightFrontier, start) 1731 } 1732 1733 // LINE DATA STRUCTURE 1734 1735 // Line objects. These hold state related to a line, including 1736 // highlighting info (the styles array). 1737 var Line = function(text, markedSpans, estimateHeight) { 1738 this.text = text 1739 attachMarkedSpans(this, markedSpans) 1740 this.height = estimateHeight ? estimateHeight(this) : 1 1741 }; 1742 1743 Line.prototype.lineNo = function () { return lineNo(this) }; 1744 eventMixin(Line) 1745 1746 // Change the content (text, markers) of a line. Automatically 1747 // invalidates cached information and tries to re-estimate the 1748 // line's height. 1749 function updateLine(line, text, markedSpans, estimateHeight) { 1750 line.text = text 1751 if (line.stateAfter) { line.stateAfter = null } 1752 if (line.styles) { line.styles = null } 1753 if (line.order != null) { line.order = null } 1754 detachMarkedSpans(line) 1755 attachMarkedSpans(line, markedSpans) 1756 var estHeight = estimateHeight ? estimateHeight(line) : 1 1757 if (estHeight != line.height) { updateLineHeight(line, estHeight) } 1758 } 1759 1760 // Detach a line from the document tree and its markers. 1761 function cleanUpLine(line) { 1762 line.parent = null 1763 detachMarkedSpans(line) 1764 } 1765 1766 // Convert a style as returned by a mode (either null, or a string 1767 // containing one or more styles) to a CSS style. This is cached, 1768 // and also looks for line-wide styles. 1769 var styleToClassCache = {}; 1770 var styleToClassCacheWithMode = {}; 1771 function interpretTokenStyle(style, options) { 1772 if (!style || /^\s*$/.test(style)) { return null } 1773 var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache 1774 return cache[style] || 1775 (cache[style] = style.replace(/\S+/g, "cm-$&")) 1776 } 1777 1778 // Render the DOM representation of the text of a line. Also builds 1779 // up a 'line map', which points at the DOM nodes that represent 1780 // specific stretches of text, and is used by the measuring code. 1781 // The returned object contains the DOM node, this map, and 1782 // information about line-wide styles that were set by the mode. 1783 function buildLineContent(cm, lineView) { 1784 // The padding-right forces the element to have a 'border', which 1785 // is needed on Webkit to be able to get line-level bounding 1786 // rectangles for it (in measureChar). 1787 var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null) 1788 var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, 1789 col: 0, pos: 0, cm: cm, 1790 trailingSpace: false, 1791 splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")} 1792 lineView.measure = {} 1793 1794 // Iterate over the logical lines that make up this visual line. 1795 for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { 1796 var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0) 1797 builder.pos = 0 1798 builder.addToken = buildToken 1799 // Optionally wire in some hacks into the token-rendering 1800 // algorithm, to deal with browser quirks. 1801 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) 1802 { builder.addToken = buildTokenBadBidi(builder.addToken, order) } 1803 builder.map = [] 1804 var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line) 1805 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)) 1806 if (line.styleClasses) { 1807 if (line.styleClasses.bgClass) 1808 { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "") } 1809 if (line.styleClasses.textClass) 1810 { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "") } 1811 } 1812 1813 // Ensure at least a single node is present, for measuring. 1814 if (builder.map.length == 0) 1815 { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))) } 1816 1817 // Store the map and a cache object for the current logical line 1818 if (i == 0) { 1819 lineView.measure.map = builder.map 1820 lineView.measure.cache = {} 1821 } else { 1822 ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) 1823 ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}) 1824 } 1825 } 1826 1827 // See issue #2901 1828 if (webkit) { 1829 var last = builder.content.lastChild 1830 if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) 1831 { builder.content.className = "cm-tab-wrap-hack" } 1832 } 1833 1834 signal(cm, "renderLine", cm, lineView.line, builder.pre) 1835 if (builder.pre.className) 1836 { builder.textClass = joinClasses(builder.pre.className, builder.textClass || "") } 1837 1838 return builder 1839 } 1840 1841 function defaultSpecialCharPlaceholder(ch) { 1842 var token = elt("span", "\u2022", "cm-invalidchar") 1843 token.title = "\\u" + ch.charCodeAt(0).toString(16) 1844 token.setAttribute("aria-label", token.title) 1845 return token 1846 } 1847 1848 // Build up the DOM representation for a single token, and add it to 1849 // the line map. Takes care to render special characters separately. 1850 function buildToken(builder, text, style, startStyle, endStyle, title, css) { 1851 if (!text) { return } 1852 var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text 1853 var special = builder.cm.state.specialChars, mustWrap = false 1854 var content 1855 if (!special.test(text)) { 1856 builder.col += text.length 1857 content = document.createTextNode(displayText) 1858 builder.map.push(builder.pos, builder.pos + text.length, content) 1859 if (ie && ie_version < 9) { mustWrap = true } 1860 builder.pos += text.length 1861 } else { 1862 content = document.createDocumentFragment() 1863 var pos = 0 1864 while (true) { 1865 special.lastIndex = pos 1866 var m = special.exec(text) 1867 var skipped = m ? m.index - pos : text.length - pos 1868 if (skipped) { 1869 var txt = document.createTextNode(displayText.slice(pos, pos + skipped)) 1870 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])) } 1871 else { content.appendChild(txt) } 1872 builder.map.push(builder.pos, builder.pos + skipped, txt) 1873 builder.col += skipped 1874 builder.pos += skipped 1875 } 1876 if (!m) { break } 1877 pos += skipped + 1 1878 var txt$1 = (void 0) 1879 if (m[0] == "\t") { 1880 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize 1881 txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")) 1882 txt$1.setAttribute("role", "presentation") 1883 txt$1.setAttribute("cm-text", "\t") 1884 builder.col += tabWidth 1885 } else if (m[0] == "\r" || m[0] == "\n") { 1886 txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")) 1887 txt$1.setAttribute("cm-text", m[0]) 1888 builder.col += 1 1889 } else { 1890 txt$1 = builder.cm.options.specialCharPlaceholder(m[0]) 1891 txt$1.setAttribute("cm-text", m[0]) 1892 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])) } 1893 else { content.appendChild(txt$1) } 1894 builder.col += 1 1895 } 1896 builder.map.push(builder.pos, builder.pos + 1, txt$1) 1897 builder.pos++ 1898 } 1899 } 1900 builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 1901 if (style || startStyle || endStyle || mustWrap || css) { 1902 var fullStyle = style || "" 1903 if (startStyle) { fullStyle += startStyle } 1904 if (endStyle) { fullStyle += endStyle } 1905 var token = elt("span", [content], fullStyle, css) 1906 if (title) { token.title = title } 1907 return builder.content.appendChild(token) 1908 } 1909 builder.content.appendChild(content) 1910 } 1911 1912 function splitSpaces(text, trailingBefore) { 1913 if (text.length > 1 && !/ /.test(text)) { return text } 1914 var spaceBefore = trailingBefore, result = "" 1915 for (var i = 0; i < text.length; i++) { 1916 var ch = text.charAt(i) 1917 if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) 1918 { ch = "\u00a0" } 1919 result += ch 1920 spaceBefore = ch == " " 1921 } 1922 return result 1923 } 1924 1925 // Work around nonsense dimensions being reported for stretches of 1926 // right-to-left text. 1927 function buildTokenBadBidi(inner, order) { 1928 return function (builder, text, style, startStyle, endStyle, title, css) { 1929 style = style ? style + " cm-force-border" : "cm-force-border" 1930 var start = builder.pos, end = start + text.length 1931 for (;;) { 1932 // Find the part that overlaps with the start of this text 1933 var part = (void 0) 1934 for (var i = 0; i < order.length; i++) { 1935 part = order[i] 1936 if (part.to > start && part.from <= start) { break } 1937 } 1938 if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, title, css) } 1939 inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css) 1940 startStyle = null 1941 text = text.slice(part.to - start) 1942 start = part.to 1943 } 1944 } 1945 } 1946 1947 function buildCollapsedSpan(builder, size, marker, ignoreWidget) { 1948 var widget = !ignoreWidget && marker.widgetNode 1949 if (widget) { builder.map.push(builder.pos, builder.pos + size, widget) } 1950 if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { 1951 if (!widget) 1952 { widget = builder.content.appendChild(document.createElement("span")) } 1953 widget.setAttribute("cm-marker", marker.id) 1954 } 1955 if (widget) { 1956 builder.cm.display.input.setUneditable(widget) 1957 builder.content.appendChild(widget) 1958 } 1959 builder.pos += size 1960 builder.trailingSpace = false 1961 } 1962 1963 // Outputs a number of spans to make up a line, taking highlighting 1964 // and marked text into account. 1965 function insertLineContent(line, builder, styles) { 1966 var spans = line.markedSpans, allText = line.text, at = 0 1967 if (!spans) { 1968 for (var i$1 = 1; i$1 < styles.length; i$1+=2) 1969 { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)) } 1970 return 1971 } 1972 1973 var len = allText.length, pos = 0, i = 1, text = "", style, css 1974 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed 1975 for (;;) { 1976 if (nextChange == pos) { // Update current marker set 1977 spanStyle = spanEndStyle = spanStartStyle = title = css = "" 1978 collapsed = null; nextChange = Infinity 1979 var foundBookmarks = [], endStyles = (void 0) 1980 for (var j = 0; j < spans.length; ++j) { 1981 var sp = spans[j], m = sp.marker 1982 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { 1983 foundBookmarks.push(m) 1984 } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { 1985 if (sp.to != null && sp.to != pos && nextChange > sp.to) { 1986 nextChange = sp.to 1987 spanEndStyle = "" 1988 } 1989 if (m.className) { spanStyle += " " + m.className } 1990 if (m.css) { css = (css ? css + ";" : "") + m.css } 1991 if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle } 1992 if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to) } 1993 if (m.title && !title) { title = m.title } 1994 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) 1995 { collapsed = sp } 1996 } else if (sp.from > pos && nextChange > sp.from) { 1997 nextChange = sp.from 1998 } 1999 } 2000 if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) 2001 { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1] } } } 2002 2003 if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2) 2004 { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]) } } 2005 if (collapsed && (collapsed.from || 0) == pos) { 2006 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, 2007 collapsed.marker, collapsed.from == null) 2008 if (collapsed.to == null) { return } 2009 if (collapsed.to == pos) { collapsed = false } 2010 } 2011 } 2012 if (pos >= len) { break } 2013 2014 var upto = Math.min(len, nextChange) 2015 while (true) { 2016 if (text) { 2017 var end = pos + text.length 2018 if (!collapsed) { 2019 var tokenText = end > upto ? text.slice(0, upto - pos) : text 2020 builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, 2021 spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css) 2022 } 2023 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} 2024 pos = end 2025 spanStartStyle = "" 2026 } 2027 text = allText.slice(at, at = styles[i++]) 2028 style = interpretTokenStyle(styles[i++], builder.cm.options) 2029 } 2030 } 2031 } 2032 2033 2034 // These objects are used to represent the visible (currently drawn) 2035 // part of the document. A LineView may correspond to multiple 2036 // logical lines, if those are connected by collapsed ranges. 2037 function LineView(doc, line, lineN) { 2038 // The starting line 2039 this.line = line 2040 // Continuing lines, if any 2041 this.rest = visualLineContinued(line) 2042 // Number of logical lines in this visual line 2043 this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1 2044 this.node = this.text = null 2045 this.hidden = lineIsHidden(doc, line) 2046 } 2047 2048 // Create a range of LineView objects for the given lines. 2049 function buildViewArray(cm, from, to) { 2050 var array = [], nextPos 2051 for (var pos = from; pos < to; pos = nextPos) { 2052 var view = new LineView(cm.doc, getLine(cm.doc, pos), pos) 2053 nextPos = pos + view.size 2054 array.push(view) 2055 } 2056 return array 2057 } 2058 2059 var operationGroup = null 2060 2061 function pushOperation(op) { 2062 if (operationGroup) { 2063 operationGroup.ops.push(op) 2064 } else { 2065 op.ownsGroup = operationGroup = { 2066 ops: [op], 2067 delayedCallbacks: [] 2068 } 2069 } 2070 } 2071 2072 function fireCallbacksForOps(group) { 2073 // Calls delayed callbacks and cursorActivity handlers until no 2074 // new ones appear 2075 var callbacks = group.delayedCallbacks, i = 0 2076 do { 2077 for (; i < callbacks.length; i++) 2078 { callbacks[i].call(null) } 2079 for (var j = 0; j < group.ops.length; j++) { 2080 var op = group.ops[j] 2081 if (op.cursorActivityHandlers) 2082 { while (op.cursorActivityCalled < op.cursorActivityHandlers.length) 2083 { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm) } } 2084 } 2085 } while (i < callbacks.length) 2086 } 2087 2088 function finishOperation(op, endCb) { 2089 var group = op.ownsGroup 2090 if (!group) { return } 2091 2092 try { fireCallbacksForOps(group) } 2093 finally { 2094 operationGroup = null 2095 endCb(group) 2096 } 2097 } 2098 2099 var orphanDelayedCallbacks = null 2100 2101 // Often, we want to signal events at a point where we are in the 2102 // middle of some work, but don't want the handler to start calling 2103 // other methods on the editor, which might be in an inconsistent 2104 // state or simply not expect any other events to happen. 2105 // signalLater looks whether there are any handlers, and schedules 2106 // them to be executed when the last operation ends, or, if no 2107 // operation is active, when a timeout fires. 2108 function signalLater(emitter, type /*, values...*/) { 2109 var arr = getHandlers(emitter, type) 2110 if (!arr.length) { return } 2111 var args = Array.prototype.slice.call(arguments, 2), list 2112 if (operationGroup) { 2113 list = operationGroup.delayedCallbacks 2114 } else if (orphanDelayedCallbacks) { 2115 list = orphanDelayedCallbacks 2116 } else { 2117 list = orphanDelayedCallbacks = [] 2118 setTimeout(fireOrphanDelayed, 0) 2119 } 2120 var loop = function ( i ) { 2121 list.push(function () { return arr[i].apply(null, args); }) 2122 }; 2123 2124 for (var i = 0; i < arr.length; ++i) 2125 loop( i ); 2126 } 2127 2128 function fireOrphanDelayed() { 2129 var delayed = orphanDelayedCallbacks 2130 orphanDelayedCallbacks = null 2131 for (var i = 0; i < delayed.length; ++i) { delayed[i]() } 2132 } 2133 2134 // When an aspect of a line changes, a string is added to 2135 // lineView.changes. This updates the relevant part of the line's 2136 // DOM structure. 2137 function updateLineForChanges(cm, lineView, lineN, dims) { 2138 for (var j = 0; j < lineView.changes.length; j++) { 2139 var type = lineView.changes[j] 2140 if (type == "text") { updateLineText(cm, lineView) } 2141 else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims) } 2142 else if (type == "class") { updateLineClasses(cm, lineView) } 2143 else if (type == "widget") { updateLineWidgets(cm, lineView, dims) } 2144 } 2145 lineView.changes = null 2146 } 2147 2148 // Lines with gutter elements, widgets or a background class need to 2149 // be wrapped, and have the extra elements added to the wrapper div 2150 function ensureLineWrapped(lineView) { 2151 if (lineView.node == lineView.text) { 2152 lineView.node = elt("div", null, null, "position: relative") 2153 if (lineView.text.parentNode) 2154 { lineView.text.parentNode.replaceChild(lineView.node, lineView.text) } 2155 lineView.node.appendChild(lineView.text) 2156 if (ie && ie_version < 8) { lineView.node.style.zIndex = 2 } 2157 } 2158 return lineView.node 2159 } 2160 2161 function updateLineBackground(cm, lineView) { 2162 var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass 2163 if (cls) { cls += " CodeMirror-linebackground" } 2164 if (lineView.background) { 2165 if (cls) { lineView.background.className = cls } 2166 else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null } 2167 } else if (cls) { 2168 var wrap = ensureLineWrapped(lineView) 2169 lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild) 2170 cm.display.input.setUneditable(lineView.background) 2171 } 2172 } 2173 2174 // Wrapper around buildLineContent which will reuse the structure 2175 // in display.externalMeasured when possible. 2176 function getLineContent(cm, lineView) { 2177 var ext = cm.display.externalMeasured 2178 if (ext && ext.line == lineView.line) { 2179 cm.display.externalMeasured = null 2180 lineView.measure = ext.measure 2181 return ext.built 2182 } 2183 return buildLineContent(cm, lineView) 2184 } 2185 2186 // Redraw the line's text. Interacts with the background and text 2187 // classes because the mode may output tokens that influence these 2188 // classes. 2189 function updateLineText(cm, lineView) { 2190 var cls = lineView.text.className 2191 var built = getLineContent(cm, lineView) 2192 if (lineView.text == lineView.node) { lineView.node = built.pre } 2193 lineView.text.parentNode.replaceChild(built.pre, lineView.text) 2194 lineView.text = built.pre 2195 if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { 2196 lineView.bgClass = built.bgClass 2197 lineView.textClass = built.textClass 2198 updateLineClasses(cm, lineView) 2199 } else if (cls) { 2200 lineView.text.className = cls 2201 } 2202 } 2203 2204 function updateLineClasses(cm, lineView) { 2205 updateLineBackground(cm, lineView) 2206 if (lineView.line.wrapClass) 2207 { ensureLineWrapped(lineView).className = lineView.line.wrapClass } 2208 else if (lineView.node != lineView.text) 2209 { lineView.node.className = "" } 2210 var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass 2211 lineView.text.className = textClass || "" 2212 } 2213 2214 function updateLineGutter(cm, lineView, lineN, dims) { 2215 if (lineView.gutter) { 2216 lineView.node.removeChild(lineView.gutter) 2217 lineView.gutter = null 2218 } 2219 if (lineView.gutterBackground) { 2220 lineView.node.removeChild(lineView.gutterBackground) 2221 lineView.gutterBackground = null 2222 } 2223 if (lineView.line.gutterClass) { 2224 var wrap = ensureLineWrapped(lineView) 2225 lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, 2226 ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px")) 2227 cm.display.input.setUneditable(lineView.gutterBackground) 2228 wrap.insertBefore(lineView.gutterBackground, lineView.text) 2229 } 2230 var markers = lineView.line.gutterMarkers 2231 if (cm.options.lineNumbers || markers) { 2232 var wrap$1 = ensureLineWrapped(lineView) 2233 var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")) 2234 cm.display.input.setUneditable(gutterWrap) 2235 wrap$1.insertBefore(gutterWrap, lineView.text) 2236 if (lineView.line.gutterClass) 2237 { gutterWrap.className += " " + lineView.line.gutterClass } 2238 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) 2239 { lineView.lineNumber = gutterWrap.appendChild( 2240 elt("div", lineNumberFor(cm.options, lineN), 2241 "CodeMirror-linenumber CodeMirror-gutter-elt", 2242 ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))) } 2243 if (markers) { for (var k = 0; k < cm.options.gutters.length; ++k) { 2244 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id] 2245 if (found) 2246 { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", 2247 ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))) } 2248 } } 2249 } 2250 } 2251 2252 function updateLineWidgets(cm, lineView, dims) { 2253 if (lineView.alignable) { lineView.alignable = null } 2254 for (var node = lineView.node.firstChild, next = (void 0); node; node = next) { 2255 next = node.nextSibling 2256 if (node.className == "CodeMirror-linewidget") 2257 { lineView.node.removeChild(node) } 2258 } 2259 insertLineWidgets(cm, lineView, dims) 2260 } 2261 2262 // Build a line's DOM representation from scratch 2263 function buildLineElement(cm, lineView, lineN, dims) { 2264 var built = getLineContent(cm, lineView) 2265 lineView.text = lineView.node = built.pre 2266 if (built.bgClass) { lineView.bgClass = built.bgClass } 2267 if (built.textClass) { lineView.textClass = built.textClass } 2268 2269 updateLineClasses(cm, lineView) 2270 updateLineGutter(cm, lineView, lineN, dims) 2271 insertLineWidgets(cm, lineView, dims) 2272 return lineView.node 2273 } 2274 2275 // A lineView may contain multiple logical lines (when merged by 2276 // collapsed spans). The widgets for all of them need to be drawn. 2277 function insertLineWidgets(cm, lineView, dims) { 2278 insertLineWidgetsFor(cm, lineView.line, lineView, dims, true) 2279 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) 2280 { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false) } } 2281 } 2282 2283 function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { 2284 if (!line.widgets) { return } 2285 var wrap = ensureLineWrapped(lineView) 2286 for (var i = 0, ws = line.widgets; i < ws.length; ++i) { 2287 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget") 2288 if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true") } 2289 positionLineWidget(widget, node, lineView, dims) 2290 cm.display.input.setUneditable(node) 2291 if (allowAbove && widget.above) 2292 { wrap.insertBefore(node, lineView.gutter || lineView.text) } 2293 else 2294 { wrap.appendChild(node) } 2295 signalLater(widget, "redraw") 2296 } 2297 } 2298 2299 function positionLineWidget(widget, node, lineView, dims) { 2300 if (widget.noHScroll) { 2301 ;(lineView.alignable || (lineView.alignable = [])).push(node) 2302 var width = dims.wrapperWidth 2303 node.style.left = dims.fixedPos + "px" 2304 if (!widget.coverGutter) { 2305 width -= dims.gutterTotalWidth 2306 node.style.paddingLeft = dims.gutterTotalWidth + "px" 2307 } 2308 node.style.width = width + "px" 2309 } 2310 if (widget.coverGutter) { 2311 node.style.zIndex = 5 2312 node.style.position = "relative" 2313 if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px" } 2314 } 2315 } 2316 2317 function widgetHeight(widget) { 2318 if (widget.height != null) { return widget.height } 2319 var cm = widget.doc.cm 2320 if (!cm) { return 0 } 2321 if (!contains(document.body, widget.node)) { 2322 var parentStyle = "position: relative;" 2323 if (widget.coverGutter) 2324 { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;" } 2325 if (widget.noHScroll) 2326 { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;" } 2327 removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)) 2328 } 2329 return widget.height = widget.node.parentNode.offsetHeight 2330 } 2331 2332 // Return true when the given mouse event happened in a widget 2333 function eventInWidget(display, e) { 2334 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { 2335 if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || 2336 (n.parentNode == display.sizer && n != display.mover)) 2337 { return true } 2338 } 2339 } 2340 2341 // POSITION MEASUREMENT 2342 2343 function paddingTop(display) {return display.lineSpace.offsetTop} 2344 function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} 2345 function paddingH(display) { 2346 if (display.cachedPaddingH) { return display.cachedPaddingH } 2347 var e = removeChildrenAndAdd(display.measure, elt("pre", "x")) 2348 var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle 2349 var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)} 2350 if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data } 2351 return data 2352 } 2353 2354 function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } 2355 function displayWidth(cm) { 2356 return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth 2357 } 2358 function displayHeight(cm) { 2359 return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight 2360 } 2361 2362 // Ensure the lineView.wrapping.heights array is populated. This is 2363 // an array of bottom offsets for the lines that make up a drawn 2364 // line. When lineWrapping is on, there might be more than one 2365 // height. 2366 function ensureLineHeights(cm, lineView, rect) { 2367 var wrapping = cm.options.lineWrapping 2368 var curWidth = wrapping && displayWidth(cm) 2369 if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { 2370 var heights = lineView.measure.heights = [] 2371 if (wrapping) { 2372 lineView.measure.width = curWidth 2373 var rects = lineView.text.firstChild.getClientRects() 2374 for (var i = 0; i < rects.length - 1; i++) { 2375 var cur = rects[i], next = rects[i + 1] 2376 if (Math.abs(cur.bottom - next.bottom) > 2) 2377 { heights.push((cur.bottom + next.top) / 2 - rect.top) } 2378 } 2379 } 2380 heights.push(rect.bottom - rect.top) 2381 } 2382 } 2383 2384 // Find a line map (mapping character offsets to text nodes) and a 2385 // measurement cache for the given line number. (A line view might 2386 // contain multiple lines when collapsed ranges are present.) 2387 function mapFromLineView(lineView, line, lineN) { 2388 if (lineView.line == line) 2389 { return {map: lineView.measure.map, cache: lineView.measure.cache} } 2390 for (var i = 0; i < lineView.rest.length; i++) 2391 { if (lineView.rest[i] == line) 2392 { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } } 2393 for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) 2394 { if (lineNo(lineView.rest[i$1]) > lineN) 2395 { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } } 2396 } 2397 2398 // Render a line into the hidden node display.externalMeasured. Used 2399 // when measurement is needed for a line that's not in the viewport. 2400 function updateExternalMeasurement(cm, line) { 2401 line = visualLine(line) 2402 var lineN = lineNo(line) 2403 var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN) 2404 view.lineN = lineN 2405 var built = view.built = buildLineContent(cm, view) 2406 view.text = built.pre 2407 removeChildrenAndAdd(cm.display.lineMeasure, built.pre) 2408 return view 2409 } 2410 2411 // Get a {top, bottom, left, right} box (in line-local coordinates) 2412 // for a given character. 2413 function measureChar(cm, line, ch, bias) { 2414 return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) 2415 } 2416 2417 // Find a line view that corresponds to the given line number. 2418 function findViewForLine(cm, lineN) { 2419 if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) 2420 { return cm.display.view[findViewIndex(cm, lineN)] } 2421 var ext = cm.display.externalMeasured 2422 if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) 2423 { return ext } 2424 } 2425 2426 // Measurement can be split in two steps, the set-up work that 2427 // applies to the whole line, and the measurement of the actual 2428 // character. Functions like coordsChar, that need to do a lot of 2429 // measurements in a row, can thus ensure that the set-up work is 2430 // only done once. 2431 function prepareMeasureForLine(cm, line) { 2432 var lineN = lineNo(line) 2433 var view = findViewForLine(cm, lineN) 2434 if (view && !view.text) { 2435 view = null 2436 } else if (view && view.changes) { 2437 updateLineForChanges(cm, view, lineN, getDimensions(cm)) 2438 cm.curOp.forceUpdate = true 2439 } 2440 if (!view) 2441 { view = updateExternalMeasurement(cm, line) } 2442 2443 var info = mapFromLineView(view, line, lineN) 2444 return { 2445 line: line, view: view, rect: null, 2446 map: info.map, cache: info.cache, before: info.before, 2447 hasHeights: false 2448 } 2449 } 2450 2451 // Given a prepared measurement object, measures the position of an 2452 // actual character (or fetches it from the cache). 2453 function measureCharPrepared(cm, prepared, ch, bias, varHeight) { 2454 if (prepared.before) { ch = -1 } 2455 var key = ch + (bias || ""), found 2456 if (prepared.cache.hasOwnProperty(key)) { 2457 found = prepared.cache[key] 2458 } else { 2459 if (!prepared.rect) 2460 { prepared.rect = prepared.view.text.getBoundingClientRect() } 2461 if (!prepared.hasHeights) { 2462 ensureLineHeights(cm, prepared.view, prepared.rect) 2463 prepared.hasHeights = true 2464 } 2465 found = measureCharInner(cm, prepared, ch, bias) 2466 if (!found.bogus) { prepared.cache[key] = found } 2467 } 2468 return {left: found.left, right: found.right, 2469 top: varHeight ? found.rtop : found.top, 2470 bottom: varHeight ? found.rbottom : found.bottom} 2471 } 2472 2473 var nullRect = {left: 0, right: 0, top: 0, bottom: 0} 2474 2475 function nodeAndOffsetInLineMap(map, ch, bias) { 2476 var node, start, end, collapse, mStart, mEnd 2477 // First, search the line map for the text node corresponding to, 2478 // or closest to, the target character. 2479 for (var i = 0; i < map.length; i += 3) { 2480 mStart = map[i] 2481 mEnd = map[i + 1] 2482 if (ch < mStart) { 2483 start = 0; end = 1 2484 collapse = "left" 2485 } else if (ch < mEnd) { 2486 start = ch - mStart 2487 end = start + 1 2488 } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { 2489 end = mEnd - mStart 2490 start = end - 1 2491 if (ch >= mEnd) { collapse = "right" } 2492 } 2493 if (start != null) { 2494 node = map[i + 2] 2495 if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) 2496 { collapse = bias } 2497 if (bias == "left" && start == 0) 2498 { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { 2499 node = map[(i -= 3) + 2] 2500 collapse = "left" 2501 } } 2502 if (bias == "right" && start == mEnd - mStart) 2503 { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { 2504 node = map[(i += 3) + 2] 2505 collapse = "right" 2506 } } 2507 break 2508 } 2509 } 2510 return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} 2511 } 2512 2513 function getUsefulRect(rects, bias) { 2514 var rect = nullRect 2515 if (bias == "left") { for (var i = 0; i < rects.length; i++) { 2516 if ((rect = rects[i]).left != rect.right) { break } 2517 } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { 2518 if ((rect = rects[i$1]).left != rect.right) { break } 2519 } } 2520 return rect 2521 } 2522 2523 function measureCharInner(cm, prepared, ch, bias) { 2524 var place = nodeAndOffsetInLineMap(prepared.map, ch, bias) 2525 var node = place.node, start = place.start, end = place.end, collapse = place.collapse 2526 2527 var rect 2528 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. 2529 for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned 2530 while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start } 2531 while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end } 2532 if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) 2533 { rect = node.parentNode.getBoundingClientRect() } 2534 else 2535 { rect = getUsefulRect(range(node, start, end).getClientRects(), bias) } 2536 if (rect.left || rect.right || start == 0) { break } 2537 end = start 2538 start = start - 1 2539 collapse = "right" 2540 } 2541 if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect) } 2542 } else { // If it is a widget, simply get the box for the whole widget. 2543 if (start > 0) { collapse = bias = "right" } 2544 var rects 2545 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) 2546 { rect = rects[bias == "right" ? rects.length - 1 : 0] } 2547 else 2548 { rect = node.getBoundingClientRect() } 2549 } 2550 if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { 2551 var rSpan = node.parentNode.getClientRects()[0] 2552 if (rSpan) 2553 { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom} } 2554 else 2555 { rect = nullRect } 2556 } 2557 2558 var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top 2559 var mid = (rtop + rbot) / 2 2560 var heights = prepared.view.measure.heights 2561 var i = 0 2562 for (; i < heights.length - 1; i++) 2563 { if (mid < heights[i]) { break } } 2564 var top = i ? heights[i - 1] : 0, bot = heights[i] 2565 var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, 2566 right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, 2567 top: top, bottom: bot} 2568 if (!rect.left && !rect.right) { result.bogus = true } 2569 if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot } 2570 2571 return result 2572 } 2573 2574 // Work around problem with bounding client rects on ranges being 2575 // returned incorrectly when zoomed on IE10 and below. 2576 function maybeUpdateRectForZooming(measure, rect) { 2577 if (!window.screen || screen.logicalXDPI == null || 2578 screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) 2579 { return rect } 2580 var scaleX = screen.logicalXDPI / screen.deviceXDPI 2581 var scaleY = screen.logicalYDPI / screen.deviceYDPI 2582 return {left: rect.left * scaleX, right: rect.right * scaleX, 2583 top: rect.top * scaleY, bottom: rect.bottom * scaleY} 2584 } 2585 2586 function clearLineMeasurementCacheFor(lineView) { 2587 if (lineView.measure) { 2588 lineView.measure.cache = {} 2589 lineView.measure.heights = null 2590 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) 2591 { lineView.measure.caches[i] = {} } } 2592 } 2593 } 2594 2595 function clearLineMeasurementCache(cm) { 2596 cm.display.externalMeasure = null 2597 removeChildren(cm.display.lineMeasure) 2598 for (var i = 0; i < cm.display.view.length; i++) 2599 { clearLineMeasurementCacheFor(cm.display.view[i]) } 2600 } 2601 2602 function clearCaches(cm) { 2603 clearLineMeasurementCache(cm) 2604 cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null 2605 if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true } 2606 cm.display.lineNumChars = null 2607 } 2608 2609 function pageScrollX() { 2610 // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 2611 // which causes page_Offset and bounding client rects to use 2612 // different reference viewports and invalidate our calculations. 2613 if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) } 2614 return window.pageXOffset || (document.documentElement || document.body).scrollLeft 2615 } 2616 function pageScrollY() { 2617 if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) } 2618 return window.pageYOffset || (document.documentElement || document.body).scrollTop 2619 } 2620 2621 function widgetTopHeight(lineObj) { 2622 var height = 0 2623 if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above) 2624 { height += widgetHeight(lineObj.widgets[i]) } } } 2625 return height 2626 } 2627 2628 // Converts a {top, bottom, left, right} box from line-local 2629 // coordinates into another coordinate system. Context may be one of 2630 // "line", "div" (display.lineDiv), "local"./null (editor), "window", 2631 // or "page". 2632 function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { 2633 if (!includeWidgets) { 2634 var height = widgetTopHeight(lineObj) 2635 rect.top += height; rect.bottom += height 2636 } 2637 if (context == "line") { return rect } 2638 if (!context) { context = "local" } 2639 var yOff = heightAtLine(lineObj) 2640 if (context == "local") { yOff += paddingTop(cm.display) } 2641 else { yOff -= cm.display.viewOffset } 2642 if (context == "page" || context == "window") { 2643 var lOff = cm.display.lineSpace.getBoundingClientRect() 2644 yOff += lOff.top + (context == "window" ? 0 : pageScrollY()) 2645 var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()) 2646 rect.left += xOff; rect.right += xOff 2647 } 2648 rect.top += yOff; rect.bottom += yOff 2649 return rect 2650 } 2651 2652 // Coverts a box from "div" coords to another coordinate system. 2653 // Context may be "window", "page", "div", or "local"./null. 2654 function fromCoordSystem(cm, coords, context) { 2655 if (context == "div") { return coords } 2656 var left = coords.left, top = coords.top 2657 // First move into "page" coordinate system 2658 if (context == "page") { 2659 left -= pageScrollX() 2660 top -= pageScrollY() 2661 } else if (context == "local" || !context) { 2662 var localBox = cm.display.sizer.getBoundingClientRect() 2663 left += localBox.left 2664 top += localBox.top 2665 } 2666 2667 var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect() 2668 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} 2669 } 2670 2671 function charCoords(cm, pos, context, lineObj, bias) { 2672 if (!lineObj) { lineObj = getLine(cm.doc, pos.line) } 2673 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) 2674 } 2675 2676 // Returns a box for a given cursor position, which may have an 2677 // 'other' property containing the position of the secondary cursor 2678 // on a bidi boundary. 2679 // A cursor Pos(line, char, "before") is on the same visual line as `char - 1` 2680 // and after `char - 1` in writing order of `char - 1` 2681 // A cursor Pos(line, char, "after") is on the same visual line as `char` 2682 // and before `char` in writing order of `char` 2683 // Examples (upper-case letters are RTL, lower-case are LTR): 2684 // Pos(0, 1, ...) 2685 // before after 2686 // ab a|b a|b 2687 // aB a|B aB| 2688 // Ab |Ab A|b 2689 // AB B|A B|A 2690 // Every position after the last character on a line is considered to stick 2691 // to the last character on the line. 2692 function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { 2693 lineObj = lineObj || getLine(cm.doc, pos.line) 2694 if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) } 2695 function get(ch, right) { 2696 var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight) 2697 if (right) { m.left = m.right; } else { m.right = m.left } 2698 return intoCoordSystem(cm, lineObj, m, context) 2699 } 2700 var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky 2701 if (ch >= lineObj.text.length) { 2702 ch = lineObj.text.length 2703 sticky = "before" 2704 } else if (ch <= 0) { 2705 ch = 0 2706 sticky = "after" 2707 } 2708 if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") } 2709 2710 function getBidi(ch, partPos, invert) { 2711 var part = order[partPos], right = part.level == 1 2712 return get(invert ? ch - 1 : ch, right != invert) 2713 } 2714 var partPos = getBidiPartAt(order, ch, sticky) 2715 var other = bidiOther 2716 var val = getBidi(ch, partPos, sticky == "before") 2717 if (other != null) { val.other = getBidi(ch, other, sticky != "before") } 2718 return val 2719 } 2720 2721 // Used to cheaply estimate the coordinates for a position. Used for 2722 // intermediate scroll updates. 2723 function estimateCoords(cm, pos) { 2724 var left = 0 2725 pos = clipPos(cm.doc, pos) 2726 if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch } 2727 var lineObj = getLine(cm.doc, pos.line) 2728 var top = heightAtLine(lineObj) + paddingTop(cm.display) 2729 return {left: left, right: left, top: top, bottom: top + lineObj.height} 2730 } 2731 2732 // Positions returned by coordsChar contain some extra information. 2733 // xRel is the relative x position of the input coordinates compared 2734 // to the found position (so xRel > 0 means the coordinates are to 2735 // the right of the character position, for example). When outside 2736 // is true, that means the coordinates lie outside the line's 2737 // vertical range. 2738 function PosWithInfo(line, ch, sticky, outside, xRel) { 2739 var pos = Pos(line, ch, sticky) 2740 pos.xRel = xRel 2741 if (outside) { pos.outside = true } 2742 return pos 2743 } 2744 2745 // Compute the character position closest to the given coordinates. 2746 // Input must be lineSpace-local ("div" coordinate system). 2747 function coordsChar(cm, x, y) { 2748 var doc = cm.doc 2749 y += cm.display.viewOffset 2750 if (y < 0) { return PosWithInfo(doc.first, 0, null, true, -1) } 2751 var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1 2752 if (lineN > last) 2753 { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1) } 2754 if (x < 0) { x = 0 } 2755 2756 var lineObj = getLine(doc, lineN) 2757 for (;;) { 2758 var found = coordsCharInner(cm, lineObj, lineN, x, y) 2759 var merged = collapsedSpanAtEnd(lineObj) 2760 var mergedPos = merged && merged.find(0, true) 2761 if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) 2762 { lineN = lineNo(lineObj = mergedPos.to.line) } 2763 else 2764 { return found } 2765 } 2766 } 2767 2768 function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { 2769 y -= widgetTopHeight(lineObj) 2770 var end = lineObj.text.length 2771 var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0) 2772 end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end) 2773 return {begin: begin, end: end} 2774 } 2775 2776 function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { 2777 if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) } 2778 var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top 2779 return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) 2780 } 2781 2782 // Returns true if the given side of a box is after the given 2783 // coordinates, in top-to-bottom, left-to-right order. 2784 function boxIsAfter(box, x, y, left) { 2785 return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x 2786 } 2787 2788 function coordsCharInner(cm, lineObj, lineNo, x, y) { 2789 // Move y into line-local coordinate space 2790 y -= heightAtLine(lineObj) 2791 var preparedMeasure = prepareMeasureForLine(cm, lineObj) 2792 // When directly calling `measureCharPrepared`, we have to adjust 2793 // for the widgets at this line. 2794 var widgetHeight = widgetTopHeight(lineObj) 2795 var begin = 0, end = lineObj.text.length, ltr = true 2796 2797 var order = getOrder(lineObj, cm.doc.direction) 2798 // If the line isn't plain left-to-right text, first figure out 2799 // which bidi section the coordinates fall into. 2800 if (order) { 2801 var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) 2802 (cm, lineObj, lineNo, preparedMeasure, order, x, y) 2803 ltr = part.level != 1 2804 // The awkward -1 offsets are needed because findFirst (called 2805 // on these below) will treat its first bound as inclusive, 2806 // second as exclusive, but we want to actually address the 2807 // characters in the part's range 2808 begin = ltr ? part.from : part.to - 1 2809 end = ltr ? part.to : part.from - 1 2810 } 2811 2812 // A binary search to find the first character whose bounding box 2813 // starts after the coordinates. If we run across any whose box wrap 2814 // the coordinates, store that. 2815 var chAround = null, boxAround = null 2816 var ch = findFirst(function (ch) { 2817 var box = measureCharPrepared(cm, preparedMeasure, ch) 2818 box.top += widgetHeight; box.bottom += widgetHeight 2819 if (!boxIsAfter(box, x, y, false)) { return false } 2820 if (box.top <= y && box.left <= x) { 2821 chAround = ch 2822 boxAround = box 2823 } 2824 return true 2825 }, begin, end) 2826 2827 var baseX, sticky, outside = false 2828 // If a box around the coordinates was found, use that 2829 if (boxAround) { 2830 // Distinguish coordinates nearer to the left or right side of the box 2831 var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr 2832 ch = chAround + (atStart ? 0 : 1) 2833 sticky = atStart ? "after" : "before" 2834 baseX = atLeft ? boxAround.left : boxAround.right 2835 } else { 2836 // (Adjust for extended bound, if necessary.) 2837 if (!ltr && (ch == end || ch == begin)) { ch++ } 2838 // To determine which side to associate with, get the box to the 2839 // left of the character and compare it's vertical position to the 2840 // coordinates 2841 sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : 2842 (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ? 2843 "after" : "before" 2844 // Now get accurate coordinates for this place, in order to get a 2845 // base X position 2846 var coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure) 2847 baseX = coords.left 2848 outside = y < coords.top || y >= coords.bottom 2849 } 2850 2851 ch = skipExtendingChars(lineObj.text, ch, 1) 2852 return PosWithInfo(lineNo, ch, sticky, outside, x - baseX) 2853 } 2854 2855 function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) { 2856 // Bidi parts are sorted left-to-right, and in a non-line-wrapping 2857 // situation, we can take this ordering to correspond to the visual 2858 // ordering. This finds the first part whose end is after the given 2859 // coordinates. 2860 var index = findFirst(function (i) { 2861 var part = order[i], ltr = part.level != 1 2862 return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"), 2863 "line", lineObj, preparedMeasure), x, y, true) 2864 }, 0, order.length - 1) 2865 var part = order[index] 2866 // If this isn't the first part, the part's start is also after 2867 // the coordinates, and the coordinates aren't on the same line as 2868 // that start, move one part back. 2869 if (index > 0) { 2870 var ltr = part.level != 1 2871 var start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"), 2872 "line", lineObj, preparedMeasure) 2873 if (boxIsAfter(start, x, y, true) && start.top > y) 2874 { part = order[index - 1] } 2875 } 2876 return part 2877 } 2878 2879 function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { 2880 // In a wrapped line, rtl text on wrapping boundaries can do things 2881 // that don't correspond to the ordering in our `order` array at 2882 // all, so a binary search doesn't work, and we want to return a 2883 // part that only spans one line so that the binary search in 2884 // coordsCharInner is safe. As such, we first find the extent of the 2885 // wrapped line, and then do a flat search in which we discard any 2886 // spans that aren't on the line. 2887 var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y); 2888 var begin = ref.begin; 2889 var end = ref.end; 2890 var part = null, closestDist = null 2891 for (var i = 0; i < order.length; i++) { 2892 var p = order[i] 2893 if (p.from >= end || p.to <= begin) { continue } 2894 var ltr = p.level != 1 2895 var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right 2896 // Weigh against spans ending before this, so that they are only 2897 // picked if nothing ends after 2898 var dist = endX < x ? x - endX + 1e9 : endX - x 2899 if (!part || closestDist > dist) { 2900 part = p 2901 closestDist = dist 2902 } 2903 } 2904 if (!part) { part = order[order.length - 1] } 2905 // Clip the part to the wrapped line. 2906 if (part.from < begin) { part = {from: begin, to: part.to, level: part.level} } 2907 if (part.to > end) { part = {from: part.from, to: end, level: part.level} } 2908 return part 2909 } 2910 2911 var measureText 2912 // Compute the default text height. 2913 function textHeight(display) { 2914 if (display.cachedTextHeight != null) { return display.cachedTextHeight } 2915 if (measureText == null) { 2916 measureText = elt("pre") 2917 // Measure a bunch of lines, for browsers that compute 2918 // fractional heights. 2919 for (var i = 0; i < 49; ++i) { 2920 measureText.appendChild(document.createTextNode("x")) 2921 measureText.appendChild(elt("br")) 2922 } 2923 measureText.appendChild(document.createTextNode("x")) 2924 } 2925 removeChildrenAndAdd(display.measure, measureText) 2926 var height = measureText.offsetHeight / 50 2927 if (height > 3) { display.cachedTextHeight = height } 2928 removeChildren(display.measure) 2929 return height || 1 2930 } 2931 2932 // Compute the default character width. 2933 function charWidth(display) { 2934 if (display.cachedCharWidth != null) { return display.cachedCharWidth } 2935 var anchor = elt("span", "xxxxxxxxxx") 2936 var pre = elt("pre", [anchor]) 2937 removeChildrenAndAdd(display.measure, pre) 2938 var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10 2939 if (width > 2) { display.cachedCharWidth = width } 2940 return width || 10 2941 } 2942 2943 // Do a bulk-read of the DOM positions and sizes needed to draw the 2944 // view, so that we don't interleave reading and writing to the DOM. 2945 function getDimensions(cm) { 2946 var d = cm.display, left = {}, width = {} 2947 var gutterLeft = d.gutters.clientLeft 2948 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { 2949 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft 2950 width[cm.options.gutters[i]] = n.clientWidth 2951 } 2952 return {fixedPos: compensateForHScroll(d), 2953 gutterTotalWidth: d.gutters.offsetWidth, 2954 gutterLeft: left, 2955 gutterWidth: width, 2956 wrapperWidth: d.wrapper.clientWidth} 2957 } 2958 2959 // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, 2960 // but using getBoundingClientRect to get a sub-pixel-accurate 2961 // result. 2962 function compensateForHScroll(display) { 2963 return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left 2964 } 2965 2966 // Returns a function that estimates the height of a line, to use as 2967 // first approximation until the line becomes visible (and is thus 2968 // properly measurable). 2969 function estimateHeight(cm) { 2970 var th = textHeight(cm.display), wrapping = cm.options.lineWrapping 2971 var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3) 2972 return function (line) { 2973 if (lineIsHidden(cm.doc, line)) { return 0 } 2974 2975 var widgetsHeight = 0 2976 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { 2977 if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height } 2978 } } 2979 2980 if (wrapping) 2981 { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th } 2982 else 2983 { return widgetsHeight + th } 2984 } 2985 } 2986 2987 function estimateLineHeights(cm) { 2988 var doc = cm.doc, est = estimateHeight(cm) 2989 doc.iter(function (line) { 2990 var estHeight = est(line) 2991 if (estHeight != line.height) { updateLineHeight(line, estHeight) } 2992 }) 2993 } 2994 2995 // Given a mouse event, find the corresponding position. If liberal 2996 // is false, it checks whether a gutter or scrollbar was clicked, 2997 // and returns null if it was. forRect is used by rectangular 2998 // selections, and tries to estimate a character position even for 2999 // coordinates beyond the right of the text. 3000 function posFromMouse(cm, e, liberal, forRect) { 3001 var display = cm.display 3002 if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null } 3003 3004 var x, y, space = display.lineSpace.getBoundingClientRect() 3005 // Fails unpredictably on IE[67] when mouse is dragged around quickly. 3006 try { x = e.clientX - space.left; y = e.clientY - space.top } 3007 catch (e) { return null } 3008 var coords = coordsChar(cm, x, y), line 3009 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { 3010 var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length 3011 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)) 3012 } 3013 return coords 3014 } 3015 3016 // Find the view element corresponding to a given line. Return null 3017 // when the line isn't visible. 3018 function findViewIndex(cm, n) { 3019 if (n >= cm.display.viewTo) { return null } 3020 n -= cm.display.viewFrom 3021 if (n < 0) { return null } 3022 var view = cm.display.view 3023 for (var i = 0; i < view.length; i++) { 3024 n -= view[i].size 3025 if (n < 0) { return i } 3026 } 3027 } 3028 3029 function updateSelection(cm) { 3030 cm.display.input.showSelection(cm.display.input.prepareSelection()) 3031 } 3032 3033 function prepareSelection(cm, primary) { 3034 if ( primary === void 0 ) primary = true; 3035 3036 var doc = cm.doc, result = {} 3037 var curFragment = result.cursors = document.createDocumentFragment() 3038 var selFragment = result.selection = document.createDocumentFragment() 3039 3040 for (var i = 0; i < doc.sel.ranges.length; i++) { 3041 if (!primary && i == doc.sel.primIndex) { continue } 3042 var range = doc.sel.ranges[i] 3043 if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue } 3044 var collapsed = range.empty() 3045 if (collapsed || cm.options.showCursorWhenSelecting) 3046 { drawSelectionCursor(cm, range.head, curFragment) } 3047 if (!collapsed) 3048 { drawSelectionRange(cm, range, selFragment) } 3049 } 3050 return result 3051 } 3052 3053 // Draws a cursor for the given range 3054 function drawSelectionCursor(cm, head, output) { 3055 var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine) 3056 3057 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")) 3058 cursor.style.left = pos.left + "px" 3059 cursor.style.top = pos.top + "px" 3060 cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px" 3061 3062 if (pos.other) { 3063 // Secondary cursor, shown when on a 'jump' in bi-directional text 3064 var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")) 3065 otherCursor.style.display = "" 3066 otherCursor.style.left = pos.other.left + "px" 3067 otherCursor.style.top = pos.other.top + "px" 3068 otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px" 3069 } 3070 } 3071 3072 function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } 3073 3074 // Draws the given range as a highlighted selection 3075 function drawSelectionRange(cm, range, output) { 3076 var display = cm.display, doc = cm.doc 3077 var fragment = document.createDocumentFragment() 3078 var padding = paddingH(cm.display), leftSide = padding.left 3079 var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right 3080 3081 function add(left, top, width, bottom) { 3082 if (top < 0) { top = 0 } 3083 top = Math.round(top) 3084 bottom = Math.round(bottom) 3085 fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px"))) 3086 } 3087 3088 function drawForLine(line, fromArg, toArg) { 3089 var lineObj = getLine(doc, line) 3090 var lineLen = lineObj.text.length 3091 var start, end 3092 function coords(ch, bias) { 3093 return charCoords(cm, Pos(line, ch), "div", lineObj, bias) 3094 } 3095 3096 var order = getOrder(lineObj, doc.direction) 3097 iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) { 3098 var fromPos = coords(from, dir == "ltr" ? "left" : "right") 3099 var toPos = coords(to - 1, dir == "ltr" ? "right" : "left") 3100 if (dir == "ltr") { 3101 var fromLeft = fromArg == null && from == 0 ? leftSide : fromPos.left 3102 var toRight = toArg == null && to == lineLen ? rightSide : toPos.right 3103 if (toPos.top - fromPos.top <= 3) { // Single line 3104 add(fromLeft, toPos.top, toRight - fromLeft, toPos.bottom) 3105 } else { // Multiple lines 3106 add(fromLeft, fromPos.top, null, fromPos.bottom) 3107 if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top) } 3108 add(leftSide, toPos.top, toPos.right, toPos.bottom) 3109 } 3110 } else if (from < to) { // RTL 3111 var fromRight = fromArg == null && from == 0 ? rightSide : fromPos.right 3112 var toLeft = toArg == null && to == lineLen ? leftSide : toPos.left 3113 if (toPos.top - fromPos.top <= 3) { // Single line 3114 add(toLeft, toPos.top, fromRight - toLeft, toPos.bottom) 3115 } else { // Multiple lines 3116 var topLeft = leftSide 3117 if (i) { 3118 var topEnd = wrappedLineExtentChar(cm, lineObj, null, from).end 3119 // The coordinates returned for an RTL wrapped space tend to 3120 // be complete bogus, so try to skip that here. 3121 topLeft = coords(topEnd - (/\s/.test(lineObj.text.charAt(topEnd - 1)) ? 2 : 1), "left").left 3122 } 3123 add(topLeft, fromPos.top, fromRight - topLeft, fromPos.bottom) 3124 if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top) } 3125 var botWidth = null 3126 if (i < order.length - 1 || true) { 3127 var botStart = wrappedLineExtentChar(cm, lineObj, null, to).begin 3128 botWidth = coords(botStart, "right").right - toLeft 3129 } 3130 add(toLeft, toPos.top, botWidth, toPos.bottom) 3131 } 3132 } 3133 3134 if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos } 3135 if (cmpCoords(toPos, start) < 0) { start = toPos } 3136 if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos } 3137 if (cmpCoords(toPos, end) < 0) { end = toPos } 3138 }) 3139 return {start: start, end: end} 3140 } 3141 3142 var sFrom = range.from(), sTo = range.to() 3143 if (sFrom.line == sTo.line) { 3144 drawForLine(sFrom.line, sFrom.ch, sTo.ch) 3145 } else { 3146 var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line) 3147 var singleVLine = visualLine(fromLine) == visualLine(toLine) 3148 var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end 3149 var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start 3150 if (singleVLine) { 3151 if (leftEnd.top < rightStart.top - 2) { 3152 add(leftEnd.right, leftEnd.top, null, leftEnd.bottom) 3153 add(leftSide, rightStart.top, rightStart.left, rightStart.bottom) 3154 } else { 3155 add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom) 3156 } 3157 } 3158 if (leftEnd.bottom < rightStart.top) 3159 { add(leftSide, leftEnd.bottom, null, rightStart.top) } 3160 } 3161 3162 output.appendChild(fragment) 3163 } 3164 3165 // Cursor-blinking 3166 function restartBlink(cm) { 3167 if (!cm.state.focused) { return } 3168 var display = cm.display 3169 clearInterval(display.blinker) 3170 var on = true 3171 display.cursorDiv.style.visibility = "" 3172 if (cm.options.cursorBlinkRate > 0) 3173 { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; }, 3174 cm.options.cursorBlinkRate) } 3175 else if (cm.options.cursorBlinkRate < 0) 3176 { display.cursorDiv.style.visibility = "hidden" } 3177 } 3178 3179 function ensureFocus(cm) { 3180 if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm) } 3181 } 3182 3183 function delayBlurEvent(cm) { 3184 cm.state.delayingBlurEvent = true 3185 setTimeout(function () { if (cm.state.delayingBlurEvent) { 3186 cm.state.delayingBlurEvent = false 3187 onBlur(cm) 3188 } }, 100) 3189 } 3190 3191 function onFocus(cm, e) { 3192 if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false } 3193 3194 if (cm.options.readOnly == "nocursor") { return } 3195 if (!cm.state.focused) { 3196 signal(cm, "focus", cm, e) 3197 cm.state.focused = true 3198 addClass(cm.display.wrapper, "CodeMirror-focused") 3199 // This test prevents this from firing when a context 3200 // menu is closed (since the input reset would kill the 3201 // select-all detection hack) 3202 if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { 3203 cm.display.input.reset() 3204 if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20) } // Issue #1730 3205 } 3206 cm.display.input.receivedFocus() 3207 } 3208 restartBlink(cm) 3209 } 3210 function onBlur(cm, e) { 3211 if (cm.state.delayingBlurEvent) { return } 3212 3213 if (cm.state.focused) { 3214 signal(cm, "blur", cm, e) 3215 cm.state.focused = false 3216 rmClass(cm.display.wrapper, "CodeMirror-focused") 3217 } 3218 clearInterval(cm.display.blinker) 3219 setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false } }, 150) 3220 } 3221 3222 // Read the actual heights of the rendered lines, and update their 3223 // stored heights to match. 3224 function updateHeightsInViewport(cm) { 3225 var display = cm.display 3226 var prevBottom = display.lineDiv.offsetTop 3227 for (var i = 0; i < display.view.length; i++) { 3228 var cur = display.view[i], height = (void 0) 3229 if (cur.hidden) { continue } 3230 if (ie && ie_version < 8) { 3231 var bot = cur.node.offsetTop + cur.node.offsetHeight 3232 height = bot - prevBottom 3233 prevBottom = bot 3234 } else { 3235 var box = cur.node.getBoundingClientRect() 3236 height = box.bottom - box.top 3237 } 3238 var diff = cur.line.height - height 3239 if (height < 2) { height = textHeight(display) } 3240 if (diff > .005 || diff < -.005) { 3241 updateLineHeight(cur.line, height) 3242 updateWidgetHeight(cur.line) 3243 if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) 3244 { updateWidgetHeight(cur.rest[j]) } } 3245 } 3246 } 3247 } 3248 3249 // Read and store the height of line widgets associated with the 3250 // given line. 3251 function updateWidgetHeight(line) { 3252 if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) 3253 { line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight } } 3254 } 3255 3256 // Compute the lines that are visible in a given viewport (defaults 3257 // the the current scroll position). viewport may contain top, 3258 // height, and ensure (see op.scrollToPos) properties. 3259 function visibleLines(display, doc, viewport) { 3260 var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop 3261 top = Math.floor(top - paddingTop(display)) 3262 var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight 3263 3264 var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom) 3265 // Ensure is a {from: {line, ch}, to: {line, ch}} object, and 3266 // forces those lines into the viewport (if possible). 3267 if (viewport && viewport.ensure) { 3268 var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line 3269 if (ensureFrom < from) { 3270 from = ensureFrom 3271 to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight) 3272 } else if (Math.min(ensureTo, doc.lastLine()) >= to) { 3273 from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight) 3274 to = ensureTo 3275 } 3276 } 3277 return {from: from, to: Math.max(to, from + 1)} 3278 } 3279 3280 // Re-align line numbers and gutter marks to compensate for 3281 // horizontal scrolling. 3282 function alignHorizontally(cm) { 3283 var display = cm.display, view = display.view 3284 if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return } 3285 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft 3286 var gutterW = display.gutters.offsetWidth, left = comp + "px" 3287 for (var i = 0; i < view.length; i++) { if (!view[i].hidden) { 3288 if (cm.options.fixedGutter) { 3289 if (view[i].gutter) 3290 { view[i].gutter.style.left = left } 3291 if (view[i].gutterBackground) 3292 { view[i].gutterBackground.style.left = left } 3293 } 3294 var align = view[i].alignable 3295 if (align) { for (var j = 0; j < align.length; j++) 3296 { align[j].style.left = left } } 3297 } } 3298 if (cm.options.fixedGutter) 3299 { display.gutters.style.left = (comp + gutterW) + "px" } 3300 } 3301 3302 // Used to ensure that the line number gutter is still the right 3303 // size for the current document size. Returns true when an update 3304 // is needed. 3305 function maybeUpdateLineNumberWidth(cm) { 3306 if (!cm.options.lineNumbers) { return false } 3307 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display 3308 if (last.length != display.lineNumChars) { 3309 var test = display.measure.appendChild(elt("div", [elt("div", last)], 3310 "CodeMirror-linenumber CodeMirror-gutter-elt")) 3311 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW 3312 display.lineGutter.style.width = "" 3313 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1 3314 display.lineNumWidth = display.lineNumInnerWidth + padding 3315 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1 3316 display.lineGutter.style.width = display.lineNumWidth + "px" 3317 updateGutterSpace(cm) 3318 return true 3319 } 3320 return false 3321 } 3322 3323 // SCROLLING THINGS INTO VIEW 3324 3325 // If an editor sits on the top or bottom of the window, partially 3326 // scrolled out of view, this ensures that the cursor is visible. 3327 function maybeScrollWindow(cm, rect) { 3328 if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } 3329 3330 var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null 3331 if (rect.top + box.top < 0) { doScroll = true } 3332 else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false } 3333 if (doScroll != null && !phantom) { 3334 var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;")) 3335 cm.display.lineSpace.appendChild(scrollNode) 3336 scrollNode.scrollIntoView(doScroll) 3337 cm.display.lineSpace.removeChild(scrollNode) 3338 } 3339 } 3340 3341 // Scroll a given position into view (immediately), verifying that 3342 // it actually became visible (as line heights are accurately 3343 // measured, the position of something may 'drift' during drawing). 3344 function scrollPosIntoView(cm, pos, end, margin) { 3345 if (margin == null) { margin = 0 } 3346 var rect 3347 if (!cm.options.lineWrapping && pos == end) { 3348 // Set pos and end to the cursor positions around the character pos sticks to 3349 // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch 3350 // If pos == Pos(_, 0, "before"), pos and end are unchanged 3351 pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos 3352 end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos 3353 } 3354 for (var limit = 0; limit < 5; limit++) { 3355 var changed = false 3356 var coords = cursorCoords(cm, pos) 3357 var endCoords = !end || end == pos ? coords : cursorCoords(cm, end) 3358 rect = {left: Math.min(coords.left, endCoords.left), 3359 top: Math.min(coords.top, endCoords.top) - margin, 3360 right: Math.max(coords.left, endCoords.left), 3361 bottom: Math.max(coords.bottom, endCoords.bottom) + margin} 3362 var scrollPos = calculateScrollPos(cm, rect) 3363 var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft 3364 if (scrollPos.scrollTop != null) { 3365 updateScrollTop(cm, scrollPos.scrollTop) 3366 if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true } 3367 } 3368 if (scrollPos.scrollLeft != null) { 3369 setScrollLeft(cm, scrollPos.scrollLeft) 3370 if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true } 3371 } 3372 if (!changed) { break } 3373 } 3374 return rect 3375 } 3376 3377 // Scroll a given set of coordinates into view (immediately). 3378 function scrollIntoView(cm, rect) { 3379 var scrollPos = calculateScrollPos(cm, rect) 3380 if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop) } 3381 if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft) } 3382 } 3383 3384 // Calculate a new scroll position needed to scroll the given 3385 // rectangle into view. Returns an object with scrollTop and 3386 // scrollLeft properties. When these are undefined, the 3387 // vertical/horizontal position does not need to be adjusted. 3388 function calculateScrollPos(cm, rect) { 3389 var display = cm.display, snapMargin = textHeight(cm.display) 3390 if (rect.top < 0) { rect.top = 0 } 3391 var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop 3392 var screen = displayHeight(cm), result = {} 3393 if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen } 3394 var docBottom = cm.doc.height + paddingVert(display) 3395 var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin 3396 if (rect.top < screentop) { 3397 result.scrollTop = atTop ? 0 : rect.top 3398 } else if (rect.bottom > screentop + screen) { 3399 var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen) 3400 if (newTop != screentop) { result.scrollTop = newTop } 3401 } 3402 3403 var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft 3404 var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0) 3405 var tooWide = rect.right - rect.left > screenw 3406 if (tooWide) { rect.right = rect.left + screenw } 3407 if (rect.left < 10) 3408 { result.scrollLeft = 0 } 3409 else if (rect.left < screenleft) 3410 { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)) } 3411 else if (rect.right > screenw + screenleft - 3) 3412 { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw } 3413 return result 3414 } 3415 3416 // Store a relative adjustment to the scroll position in the current 3417 // operation (to be applied when the operation finishes). 3418 function addToScrollTop(cm, top) { 3419 if (top == null) { return } 3420 resolveScrollToPos(cm) 3421 cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top 3422 } 3423 3424 // Make sure that at the end of the operation the current cursor is 3425 // shown. 3426 function ensureCursorVisible(cm) { 3427 resolveScrollToPos(cm) 3428 var cur = cm.getCursor() 3429 cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin} 3430 } 3431 3432 function scrollToCoords(cm, x, y) { 3433 if (x != null || y != null) { resolveScrollToPos(cm) } 3434 if (x != null) { cm.curOp.scrollLeft = x } 3435 if (y != null) { cm.curOp.scrollTop = y } 3436 } 3437 3438 function scrollToRange(cm, range) { 3439 resolveScrollToPos(cm) 3440 cm.curOp.scrollToPos = range 3441 } 3442 3443 // When an operation has its scrollToPos property set, and another 3444 // scroll action is applied before the end of the operation, this 3445 // 'simulates' scrolling that position into view in a cheap way, so 3446 // that the effect of intermediate scroll commands is not ignored. 3447 function resolveScrollToPos(cm) { 3448 var range = cm.curOp.scrollToPos 3449 if (range) { 3450 cm.curOp.scrollToPos = null 3451 var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to) 3452 scrollToCoordsRange(cm, from, to, range.margin) 3453 } 3454 } 3455 3456 function scrollToCoordsRange(cm, from, to, margin) { 3457 var sPos = calculateScrollPos(cm, { 3458 left: Math.min(from.left, to.left), 3459 top: Math.min(from.top, to.top) - margin, 3460 right: Math.max(from.right, to.right), 3461 bottom: Math.max(from.bottom, to.bottom) + margin 3462 }) 3463 scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop) 3464 } 3465 3466 // Sync the scrollable area and scrollbars, ensure the viewport 3467 // covers the visible area. 3468 function updateScrollTop(cm, val) { 3469 if (Math.abs(cm.doc.scrollTop - val) < 2) { return } 3470 if (!gecko) { updateDisplaySimple(cm, {top: val}) } 3471 setScrollTop(cm, val, true) 3472 if (gecko) { updateDisplaySimple(cm) } 3473 startWorker(cm, 100) 3474 } 3475 3476 function setScrollTop(cm, val, forceScroll) { 3477 val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val) 3478 if (cm.display.scroller.scrollTop == val && !forceScroll) { return } 3479 cm.doc.scrollTop = val 3480 cm.display.scrollbars.setScrollTop(val) 3481 if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val } 3482 } 3483 3484 // Sync scroller and scrollbar, ensure the gutter elements are 3485 // aligned. 3486 function setScrollLeft(cm, val, isScroller, forceScroll) { 3487 val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth) 3488 if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return } 3489 cm.doc.scrollLeft = val 3490 alignHorizontally(cm) 3491 if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val } 3492 cm.display.scrollbars.setScrollLeft(val) 3493 } 3494 3495 // SCROLLBARS 3496 3497 // Prepare DOM reads needed to update the scrollbars. Done in one 3498 // shot to minimize update/measure roundtrips. 3499 function measureForScrollbars(cm) { 3500 var d = cm.display, gutterW = d.gutters.offsetWidth 3501 var docH = Math.round(cm.doc.height + paddingVert(cm.display)) 3502 return { 3503 clientHeight: d.scroller.clientHeight, 3504 viewHeight: d.wrapper.clientHeight, 3505 scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, 3506 viewWidth: d.wrapper.clientWidth, 3507 barLeft: cm.options.fixedGutter ? gutterW : 0, 3508 docHeight: docH, 3509 scrollHeight: docH + scrollGap(cm) + d.barHeight, 3510 nativeBarWidth: d.nativeBarWidth, 3511 gutterWidth: gutterW 3512 } 3513 } 3514 3515 var NativeScrollbars = function(place, scroll, cm) { 3516 this.cm = cm 3517 var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar") 3518 var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar") 3519 place(vert); place(horiz) 3520 3521 on(vert, "scroll", function () { 3522 if (vert.clientHeight) { scroll(vert.scrollTop, "vertical") } 3523 }) 3524 on(horiz, "scroll", function () { 3525 if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal") } 3526 }) 3527 3528 this.checkedZeroWidth = false 3529 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). 3530 if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px" } 3531 }; 3532 3533 NativeScrollbars.prototype.update = function (measure) { 3534 var needsH = measure.scrollWidth > measure.clientWidth + 1 3535 var needsV = measure.scrollHeight > measure.clientHeight + 1 3536 var sWidth = measure.nativeBarWidth 3537 3538 if (needsV) { 3539 this.vert.style.display = "block" 3540 this.vert.style.bottom = needsH ? sWidth + "px" : "0" 3541 var totalHeight = measure.viewHeight - (needsH ? sWidth : 0) 3542 // A bug in IE8 can cause this value to be negative, so guard it. 3543 this.vert.firstChild.style.height = 3544 Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px" 3545 } else { 3546 this.vert.style.display = "" 3547 this.vert.firstChild.style.height = "0" 3548 } 3549 3550 if (needsH) { 3551 this.horiz.style.display = "block" 3552 this.horiz.style.right = needsV ? sWidth + "px" : "0" 3553 this.horiz.style.left = measure.barLeft + "px" 3554 var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0) 3555 this.horiz.firstChild.style.width = 3556 Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px" 3557 } else { 3558 this.horiz.style.display = "" 3559 this.horiz.firstChild.style.width = "0" 3560 } 3561 3562 if (!this.checkedZeroWidth && measure.clientHeight > 0) { 3563 if (sWidth == 0) { this.zeroWidthHack() } 3564 this.checkedZeroWidth = true 3565 } 3566 3567 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} 3568 }; 3569 3570 NativeScrollbars.prototype.setScrollLeft = function (pos) { 3571 if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos } 3572 if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz") } 3573 }; 3574 3575 NativeScrollbars.prototype.setScrollTop = function (pos) { 3576 if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos } 3577 if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert") } 3578 }; 3579 3580 NativeScrollbars.prototype.zeroWidthHack = function () { 3581 var w = mac && !mac_geMountainLion ? "12px" : "18px" 3582 this.horiz.style.height = this.vert.style.width = w 3583 this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none" 3584 this.disableHoriz = new Delayed 3585 this.disableVert = new Delayed 3586 }; 3587 3588 NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { 3589 bar.style.pointerEvents = "auto" 3590 function maybeDisable() { 3591 // To find out whether the scrollbar is still visible, we 3592 // check whether the element under the pixel in the bottom 3593 // right corner of the scrollbar box is the scrollbar box 3594 // itself (when the bar is still visible) or its filler child 3595 // (when the bar is hidden). If it is still visible, we keep 3596 // it enabled, if it's hidden, we disable pointer events. 3597 var box = bar.getBoundingClientRect() 3598 var elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) 3599 : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1) 3600 if (elt != bar) { bar.style.pointerEvents = "none" } 3601 else { delay.set(1000, maybeDisable) } 3602 } 3603 delay.set(1000, maybeDisable) 3604 }; 3605 3606 NativeScrollbars.prototype.clear = function () { 3607 var parent = this.horiz.parentNode 3608 parent.removeChild(this.horiz) 3609 parent.removeChild(this.vert) 3610 }; 3611 3612 var NullScrollbars = function () {}; 3613 3614 NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} }; 3615 NullScrollbars.prototype.setScrollLeft = function () {}; 3616 NullScrollbars.prototype.setScrollTop = function () {}; 3617 NullScrollbars.prototype.clear = function () {}; 3618 3619 function updateScrollbars(cm, measure) { 3620 if (!measure) { measure = measureForScrollbars(cm) } 3621 var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight 3622 updateScrollbarsInner(cm, measure) 3623 for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { 3624 if (startWidth != cm.display.barWidth && cm.options.lineWrapping) 3625 { updateHeightsInViewport(cm) } 3626 updateScrollbarsInner(cm, measureForScrollbars(cm)) 3627 startWidth = cm.display.barWidth; startHeight = cm.display.barHeight 3628 } 3629 } 3630 3631 // Re-synchronize the fake scrollbars with the actual size of the 3632 // content. 3633 function updateScrollbarsInner(cm, measure) { 3634 var d = cm.display 3635 var sizes = d.scrollbars.update(measure) 3636 3637 d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px" 3638 d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px" 3639 d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" 3640 3641 if (sizes.right && sizes.bottom) { 3642 d.scrollbarFiller.style.display = "block" 3643 d.scrollbarFiller.style.height = sizes.bottom + "px" 3644 d.scrollbarFiller.style.width = sizes.right + "px" 3645 } else { d.scrollbarFiller.style.display = "" } 3646 if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { 3647 d.gutterFiller.style.display = "block" 3648 d.gutterFiller.style.height = sizes.bottom + "px" 3649 d.gutterFiller.style.width = measure.gutterWidth + "px" 3650 } else { d.gutterFiller.style.display = "" } 3651 } 3652 3653 var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars} 3654 3655 function initScrollbars(cm) { 3656 if (cm.display.scrollbars) { 3657 cm.display.scrollbars.clear() 3658 if (cm.display.scrollbars.addClass) 3659 { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass) } 3660 } 3661 3662 cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) { 3663 cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller) 3664 // Prevent clicks in the scrollbars from killing focus 3665 on(node, "mousedown", function () { 3666 if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0) } 3667 }) 3668 node.setAttribute("cm-not-content", "true") 3669 }, function (pos, axis) { 3670 if (axis == "horizontal") { setScrollLeft(cm, pos) } 3671 else { updateScrollTop(cm, pos) } 3672 }, cm) 3673 if (cm.display.scrollbars.addClass) 3674 { addClass(cm.display.wrapper, cm.display.scrollbars.addClass) } 3675 } 3676 3677 // Operations are used to wrap a series of changes to the editor 3678 // state in such a way that each change won't have to update the 3679 // cursor and display (which would be awkward, slow, and 3680 // error-prone). Instead, display updates are batched and then all 3681 // combined and executed at once. 3682 3683 var nextOpId = 0 3684 // Start a new operation. 3685 function startOperation(cm) { 3686 cm.curOp = { 3687 cm: cm, 3688 viewChanged: false, // Flag that indicates that lines might need to be redrawn 3689 startHeight: cm.doc.height, // Used to detect need to update scrollbar 3690 forceUpdate: false, // Used to force a redraw 3691 updateInput: null, // Whether to reset the input textarea 3692 typing: false, // Whether this reset should be careful to leave existing text (for compositing) 3693 changeObjs: null, // Accumulated changes, for firing change events 3694 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on 3695 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already 3696 selectionChanged: false, // Whether the selection needs to be redrawn 3697 updateMaxLine: false, // Set when the widest line needs to be determined anew 3698 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet 3699 scrollToPos: null, // Used to scroll to a specific position 3700 focus: false, 3701 id: ++nextOpId // Unique ID 3702 } 3703 pushOperation(cm.curOp) 3704 } 3705 3706 // Finish an operation, updating the display and signalling delayed events 3707 function endOperation(cm) { 3708 var op = cm.curOp 3709 finishOperation(op, function (group) { 3710 for (var i = 0; i < group.ops.length; i++) 3711 { group.ops[i].cm.curOp = null } 3712 endOperations(group) 3713 }) 3714 } 3715 3716 // The DOM updates done when an operation finishes are batched so 3717 // that the minimum number of relayouts are required. 3718 function endOperations(group) { 3719 var ops = group.ops 3720 for (var i = 0; i < ops.length; i++) // Read DOM 3721 { endOperation_R1(ops[i]) } 3722 for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe) 3723 { endOperation_W1(ops[i$1]) } 3724 for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM 3725 { endOperation_R2(ops[i$2]) } 3726 for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe) 3727 { endOperation_W2(ops[i$3]) } 3728 for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM 3729 { endOperation_finish(ops[i$4]) } 3730 } 3731 3732 function endOperation_R1(op) { 3733 var cm = op.cm, display = cm.display 3734 maybeClipScrollbars(cm) 3735 if (op.updateMaxLine) { findMaxLine(cm) } 3736 3737 op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || 3738 op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || 3739 op.scrollToPos.to.line >= display.viewTo) || 3740 display.maxLineChanged && cm.options.lineWrapping 3741 op.update = op.mustUpdate && 3742 new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate) 3743 } 3744 3745 function endOperation_W1(op) { 3746 op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update) 3747 } 3748 3749 function endOperation_R2(op) { 3750 var cm = op.cm, display = cm.display 3751 if (op.updatedDisplay) { updateHeightsInViewport(cm) } 3752 3753 op.barMeasure = measureForScrollbars(cm) 3754 3755 // If the max line changed since it was last measured, measure it, 3756 // and ensure the document's width matches it. 3757 // updateDisplay_W2 will use these properties to do the actual resizing 3758 if (display.maxLineChanged && !cm.options.lineWrapping) { 3759 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3 3760 cm.display.sizerWidth = op.adjustWidthTo 3761 op.barMeasure.scrollWidth = 3762 Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth) 3763 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)) 3764 } 3765 3766 if (op.updatedDisplay || op.selectionChanged) 3767 { op.preparedSelection = display.input.prepareSelection() } 3768 } 3769 3770 function endOperation_W2(op) { 3771 var cm = op.cm 3772 3773 if (op.adjustWidthTo != null) { 3774 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px" 3775 if (op.maxScrollLeft < cm.doc.scrollLeft) 3776 { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true) } 3777 cm.display.maxLineChanged = false 3778 } 3779 3780 var takeFocus = op.focus && op.focus == activeElt() 3781 if (op.preparedSelection) 3782 { cm.display.input.showSelection(op.preparedSelection, takeFocus) } 3783 if (op.updatedDisplay || op.startHeight != cm.doc.height) 3784 { updateScrollbars(cm, op.barMeasure) } 3785 if (op.updatedDisplay) 3786 { setDocumentHeight(cm, op.barMeasure) } 3787 3788 if (op.selectionChanged) { restartBlink(cm) } 3789 3790 if (cm.state.focused && op.updateInput) 3791 { cm.display.input.reset(op.typing) } 3792 if (takeFocus) { ensureFocus(op.cm) } 3793 } 3794 3795 function endOperation_finish(op) { 3796 var cm = op.cm, display = cm.display, doc = cm.doc 3797 3798 if (op.updatedDisplay) { postUpdateDisplay(cm, op.update) } 3799 3800 // Abort mouse wheel delta measurement, when scrolling explicitly 3801 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) 3802 { display.wheelStartX = display.wheelStartY = null } 3803 3804 // Propagate the scroll position to the actual DOM scroller 3805 if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll) } 3806 3807 if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true) } 3808 // If we need to scroll a specific position into view, do so. 3809 if (op.scrollToPos) { 3810 var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), 3811 clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin) 3812 maybeScrollWindow(cm, rect) 3813 } 3814 3815 // Fire events for markers that are hidden/unidden by editing or 3816 // undoing 3817 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers 3818 if (hidden) { for (var i = 0; i < hidden.length; ++i) 3819 { if (!hidden[i].lines.length) { signal(hidden[i], "hide") } } } 3820 if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1) 3821 { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide") } } } 3822 3823 if (display.wrapper.offsetHeight) 3824 { doc.scrollTop = cm.display.scroller.scrollTop } 3825 3826 // Fire change events, and delayed event handlers 3827 if (op.changeObjs) 3828 { signal(cm, "changes", cm, op.changeObjs) } 3829 if (op.update) 3830 { op.update.finish() } 3831 } 3832 3833 // Run the given function in an operation 3834 function runInOp(cm, f) { 3835 if (cm.curOp) { return f() } 3836 startOperation(cm) 3837 try { return f() } 3838 finally { endOperation(cm) } 3839 } 3840 // Wraps a function in an operation. Returns the wrapped function. 3841 function operation(cm, f) { 3842 return function() { 3843 if (cm.curOp) { return f.apply(cm, arguments) } 3844 startOperation(cm) 3845 try { return f.apply(cm, arguments) } 3846 finally { endOperation(cm) } 3847 } 3848 } 3849 // Used to add methods to editor and doc instances, wrapping them in 3850 // operations. 3851 function methodOp(f) { 3852 return function() { 3853 if (this.curOp) { return f.apply(this, arguments) } 3854 startOperation(this) 3855 try { return f.apply(this, arguments) } 3856 finally { endOperation(this) } 3857 } 3858 } 3859 function docMethodOp(f) { 3860 return function() { 3861 var cm = this.cm 3862 if (!cm || cm.curOp) { return f.apply(this, arguments) } 3863 startOperation(cm) 3864 try { return f.apply(this, arguments) } 3865 finally { endOperation(cm) } 3866 } 3867 } 3868 3869 // Updates the display.view data structure for a given change to the 3870 // document. From and to are in pre-change coordinates. Lendiff is 3871 // the amount of lines added or subtracted by the change. This is 3872 // used for changes that span multiple lines, or change the way 3873 // lines are divided into visual lines. regLineChange (below) 3874 // registers single-line changes. 3875 function regChange(cm, from, to, lendiff) { 3876 if (from == null) { from = cm.doc.first } 3877 if (to == null) { to = cm.doc.first + cm.doc.size } 3878 if (!lendiff) { lendiff = 0 } 3879 3880 var display = cm.display 3881 if (lendiff && to < display.viewTo && 3882 (display.updateLineNumbers == null || display.updateLineNumbers > from)) 3883 { display.updateLineNumbers = from } 3884 3885 cm.curOp.viewChanged = true 3886 3887 if (from >= display.viewTo) { // Change after 3888 if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) 3889 { resetView(cm) } 3890 } else if (to <= display.viewFrom) { // Change before 3891 if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { 3892 resetView(cm) 3893 } else { 3894 display.viewFrom += lendiff 3895 display.viewTo += lendiff 3896 } 3897 } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap 3898 resetView(cm) 3899 } else if (from <= display.viewFrom) { // Top overlap 3900 var cut = viewCuttingPoint(cm, to, to + lendiff, 1) 3901 if (cut) { 3902 display.view = display.view.slice(cut.index) 3903 display.viewFrom = cut.lineN 3904 display.viewTo += lendiff 3905 } else { 3906 resetView(cm) 3907 } 3908 } else if (to >= display.viewTo) { // Bottom overlap 3909 var cut$1 = viewCuttingPoint(cm, from, from, -1) 3910 if (cut$1) { 3911 display.view = display.view.slice(0, cut$1.index) 3912 display.viewTo = cut$1.lineN 3913 } else { 3914 resetView(cm) 3915 } 3916 } else { // Gap in the middle 3917 var cutTop = viewCuttingPoint(cm, from, from, -1) 3918 var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1) 3919 if (cutTop && cutBot) { 3920 display.view = display.view.slice(0, cutTop.index) 3921 .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) 3922 .concat(display.view.slice(cutBot.index)) 3923 display.viewTo += lendiff 3924 } else { 3925 resetView(cm) 3926 } 3927 } 3928 3929 var ext = display.externalMeasured 3930 if (ext) { 3931 if (to < ext.lineN) 3932 { ext.lineN += lendiff } 3933 else if (from < ext.lineN + ext.size) 3934 { display.externalMeasured = null } 3935 } 3936 } 3937 3938 // Register a change to a single line. Type must be one of "text", 3939 // "gutter", "class", "widget" 3940 function regLineChange(cm, line, type) { 3941 cm.curOp.viewChanged = true 3942 var display = cm.display, ext = cm.display.externalMeasured 3943 if (ext && line >= ext.lineN && line < ext.lineN + ext.size) 3944 { display.externalMeasured = null } 3945 3946 if (line < display.viewFrom || line >= display.viewTo) { return } 3947 var lineView = display.view[findViewIndex(cm, line)] 3948 if (lineView.node == null) { return } 3949 var arr = lineView.changes || (lineView.changes = []) 3950 if (indexOf(arr, type) == -1) { arr.push(type) } 3951 } 3952 3953 // Clear the view. 3954 function resetView(cm) { 3955 cm.display.viewFrom = cm.display.viewTo = cm.doc.first 3956 cm.display.view = [] 3957 cm.display.viewOffset = 0 3958 } 3959 3960 function viewCuttingPoint(cm, oldN, newN, dir) { 3961 var index = findViewIndex(cm, oldN), diff, view = cm.display.view 3962 if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) 3963 { return {index: index, lineN: newN} } 3964 var n = cm.display.viewFrom 3965 for (var i = 0; i < index; i++) 3966 { n += view[i].size } 3967 if (n != oldN) { 3968 if (dir > 0) { 3969 if (index == view.length - 1) { return null } 3970 diff = (n + view[index].size) - oldN 3971 index++ 3972 } else { 3973 diff = n - oldN 3974 } 3975 oldN += diff; newN += diff 3976 } 3977 while (visualLineNo(cm.doc, newN) != newN) { 3978 if (index == (dir < 0 ? 0 : view.length - 1)) { return null } 3979 newN += dir * view[index - (dir < 0 ? 1 : 0)].size 3980 index += dir 3981 } 3982 return {index: index, lineN: newN} 3983 } 3984 3985 // Force the view to cover a given range, adding empty view element 3986 // or clipping off existing ones as needed. 3987 function adjustView(cm, from, to) { 3988 var display = cm.display, view = display.view 3989 if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { 3990 display.view = buildViewArray(cm, from, to) 3991 display.viewFrom = from 3992 } else { 3993 if (display.viewFrom > from) 3994 { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view) } 3995 else if (display.viewFrom < from) 3996 { display.view = display.view.slice(findViewIndex(cm, from)) } 3997 display.viewFrom = from 3998 if (display.viewTo < to) 3999 { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)) } 4000 else if (display.viewTo > to) 4001 { display.view = display.view.slice(0, findViewIndex(cm, to)) } 4002 } 4003 display.viewTo = to 4004 } 4005 4006 // Count the number of lines in the view whose DOM representation is 4007 // out of date (or nonexistent). 4008 function countDirtyView(cm) { 4009 var view = cm.display.view, dirty = 0 4010 for (var i = 0; i < view.length; i++) { 4011 var lineView = view[i] 4012 if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty } 4013 } 4014 return dirty 4015 } 4016 4017 // HIGHLIGHT WORKER 4018 4019 function startWorker(cm, time) { 4020 if (cm.doc.highlightFrontier < cm.display.viewTo) 4021 { cm.state.highlight.set(time, bind(highlightWorker, cm)) } 4022 } 4023 4024 function highlightWorker(cm) { 4025 var doc = cm.doc 4026 if (doc.highlightFrontier >= cm.display.viewTo) { return } 4027 var end = +new Date + cm.options.workTime 4028 var context = getContextBefore(cm, doc.highlightFrontier) 4029 var changedLines = [] 4030 4031 doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { 4032 if (context.line >= cm.display.viewFrom) { // Visible 4033 var oldStyles = line.styles 4034 var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null 4035 var highlighted = highlightLine(cm, line, context, true) 4036 if (resetState) { context.state = resetState } 4037 line.styles = highlighted.styles 4038 var oldCls = line.styleClasses, newCls = highlighted.classes 4039 if (newCls) { line.styleClasses = newCls } 4040 else if (oldCls) { line.styleClasses = null } 4041 var ischange = !oldStyles || oldStyles.length != line.styles.length || 4042 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass) 4043 for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i] } 4044 if (ischange) { changedLines.push(context.line) } 4045 line.stateAfter = context.save() 4046 context.nextLine() 4047 } else { 4048 if (line.text.length <= cm.options.maxHighlightLength) 4049 { processLine(cm, line.text, context) } 4050 line.stateAfter = context.line % 5 == 0 ? context.save() : null 4051 context.nextLine() 4052 } 4053 if (+new Date > end) { 4054 startWorker(cm, cm.options.workDelay) 4055 return true 4056 } 4057 }) 4058 doc.highlightFrontier = context.line 4059 doc.modeFrontier = Math.max(doc.modeFrontier, context.line) 4060 if (changedLines.length) { runInOp(cm, function () { 4061 for (var i = 0; i < changedLines.length; i++) 4062 { regLineChange(cm, changedLines[i], "text") } 4063 }) } 4064 } 4065 4066 // DISPLAY DRAWING 4067 4068 var DisplayUpdate = function(cm, viewport, force) { 4069 var display = cm.display 4070 4071 this.viewport = viewport 4072 // Store some values that we'll need later (but don't want to force a relayout for) 4073 this.visible = visibleLines(display, cm.doc, viewport) 4074 this.editorIsHidden = !display.wrapper.offsetWidth 4075 this.wrapperHeight = display.wrapper.clientHeight 4076 this.wrapperWidth = display.wrapper.clientWidth 4077 this.oldDisplayWidth = displayWidth(cm) 4078 this.force = force 4079 this.dims = getDimensions(cm) 4080 this.events = [] 4081 }; 4082 4083 DisplayUpdate.prototype.signal = function (emitter, type) { 4084 if (hasHandler(emitter, type)) 4085 { this.events.push(arguments) } 4086 }; 4087 DisplayUpdate.prototype.finish = function () { 4088 var this$1 = this; 4089 4090 for (var i = 0; i < this.events.length; i++) 4091 { signal.apply(null, this$1.events[i]) } 4092 }; 4093 4094 function maybeClipScrollbars(cm) { 4095 var display = cm.display 4096 if (!display.scrollbarsClipped && display.scroller.offsetWidth) { 4097 display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth 4098 display.heightForcer.style.height = scrollGap(cm) + "px" 4099 display.sizer.style.marginBottom = -display.nativeBarWidth + "px" 4100 display.sizer.style.borderRightWidth = scrollGap(cm) + "px" 4101 display.scrollbarsClipped = true 4102 } 4103 } 4104 4105 function selectionSnapshot(cm) { 4106 if (cm.hasFocus()) { return null } 4107 var active = activeElt() 4108 if (!active || !contains(cm.display.lineDiv, active)) { return null } 4109 var result = {activeElt: active} 4110 if (window.getSelection) { 4111 var sel = window.getSelection() 4112 if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { 4113 result.anchorNode = sel.anchorNode 4114 result.anchorOffset = sel.anchorOffset 4115 result.focusNode = sel.focusNode 4116 result.focusOffset = sel.focusOffset 4117 } 4118 } 4119 return result 4120 } 4121 4122 function restoreSelection(snapshot) { 4123 if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return } 4124 snapshot.activeElt.focus() 4125 if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { 4126 var sel = window.getSelection(), range = document.createRange() 4127 range.setEnd(snapshot.anchorNode, snapshot.anchorOffset) 4128 range.collapse(false) 4129 sel.removeAllRanges() 4130 sel.addRange(range) 4131 sel.extend(snapshot.focusNode, snapshot.focusOffset) 4132 } 4133 } 4134 4135 // Does the actual updating of the line display. Bails out 4136 // (returning false) when there is nothing to be done and forced is 4137 // false. 4138 function updateDisplayIfNeeded(cm, update) { 4139 var display = cm.display, doc = cm.doc 4140 4141 if (update.editorIsHidden) { 4142 resetView(cm) 4143 return false 4144 } 4145 4146 // Bail out if the visible area is already rendered and nothing changed. 4147 if (!update.force && 4148 update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && 4149 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && 4150 display.renderedView == display.view && countDirtyView(cm) == 0) 4151 { return false } 4152 4153 if (maybeUpdateLineNumberWidth(cm)) { 4154 resetView(cm) 4155 update.dims = getDimensions(cm) 4156 } 4157 4158 // Compute a suitable new viewport (from & to) 4159 var end = doc.first + doc.size 4160 var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first) 4161 var to = Math.min(end, update.visible.to + cm.options.viewportMargin) 4162 if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom) } 4163 if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo) } 4164 if (sawCollapsedSpans) { 4165 from = visualLineNo(cm.doc, from) 4166 to = visualLineEndNo(cm.doc, to) 4167 } 4168 4169 var different = from != display.viewFrom || to != display.viewTo || 4170 display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth 4171 adjustView(cm, from, to) 4172 4173 display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)) 4174 // Position the mover div to align with the current scroll position 4175 cm.display.mover.style.top = display.viewOffset + "px" 4176 4177 var toUpdate = countDirtyView(cm) 4178 if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && 4179 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) 4180 { return false } 4181 4182 // For big changes, we hide the enclosing element during the 4183 // update, since that speeds up the operations on most browsers. 4184 var selSnapshot = selectionSnapshot(cm) 4185 if (toUpdate > 4) { display.lineDiv.style.display = "none" } 4186 patchDisplay(cm, display.updateLineNumbers, update.dims) 4187 if (toUpdate > 4) { display.lineDiv.style.display = "" } 4188 display.renderedView = display.view 4189 // There might have been a widget with a focused element that got 4190 // hidden or updated, if so re-focus it. 4191 restoreSelection(selSnapshot) 4192 4193 // Prevent selection and cursors from interfering with the scroll 4194 // width and height. 4195 removeChildren(display.cursorDiv) 4196 removeChildren(display.selectionDiv) 4197 display.gutters.style.height = display.sizer.style.minHeight = 0 4198 4199 if (different) { 4200 display.lastWrapHeight = update.wrapperHeight 4201 display.lastWrapWidth = update.wrapperWidth 4202 startWorker(cm, 400) 4203 } 4204 4205 display.updateLineNumbers = null 4206 4207 return true 4208 } 4209 4210 function postUpdateDisplay(cm, update) { 4211 var viewport = update.viewport 4212 4213 for (var first = true;; first = false) { 4214 if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { 4215 // Clip forced viewport to actual scrollable area. 4216 if (viewport && viewport.top != null) 4217 { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)} } 4218 // Updated line heights might result in the drawn area not 4219 // actually covering the viewport. Keep looping until it does. 4220 update.visible = visibleLines(cm.display, cm.doc, viewport) 4221 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) 4222 { break } 4223 } 4224 if (!updateDisplayIfNeeded(cm, update)) { break } 4225 updateHeightsInViewport(cm) 4226 var barMeasure = measureForScrollbars(cm) 4227 updateSelection(cm) 4228 updateScrollbars(cm, barMeasure) 4229 setDocumentHeight(cm, barMeasure) 4230 update.force = false 4231 } 4232 4233 update.signal(cm, "update", cm) 4234 if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { 4235 update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo) 4236 cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo 4237 } 4238 } 4239 4240 function updateDisplaySimple(cm, viewport) { 4241 var update = new DisplayUpdate(cm, viewport) 4242 if (updateDisplayIfNeeded(cm, update)) { 4243 updateHeightsInViewport(cm) 4244 postUpdateDisplay(cm, update) 4245 var barMeasure = measureForScrollbars(cm) 4246 updateSelection(cm) 4247 updateScrollbars(cm, barMeasure) 4248 setDocumentHeight(cm, barMeasure) 4249 update.finish() 4250 } 4251 } 4252 4253 // Sync the actual display DOM structure with display.view, removing 4254 // nodes for lines that are no longer in view, and creating the ones 4255 // that are not there yet, and updating the ones that are out of 4256 // date. 4257 function patchDisplay(cm, updateNumbersFrom, dims) { 4258 var display = cm.display, lineNumbers = cm.options.lineNumbers 4259 var container = display.lineDiv, cur = container.firstChild 4260 4261 function rm(node) { 4262 var next = node.nextSibling 4263 // Works around a throw-scroll bug in OS X Webkit 4264 if (webkit && mac && cm.display.currentWheelTarget == node) 4265 { node.style.display = "none" } 4266 else 4267 { node.parentNode.removeChild(node) } 4268 return next 4269 } 4270 4271 var view = display.view, lineN = display.viewFrom 4272 // Loop over the elements in the view, syncing cur (the DOM nodes 4273 // in display.lineDiv) with the view as we go. 4274 for (var i = 0; i < view.length; i++) { 4275 var lineView = view[i] 4276 if (lineView.hidden) { 4277 } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet 4278 var node = buildLineElement(cm, lineView, lineN, dims) 4279 container.insertBefore(node, cur) 4280 } else { // Already drawn 4281 while (cur != lineView.node) { cur = rm(cur) } 4282 var updateNumber = lineNumbers && updateNumbersFrom != null && 4283 updateNumbersFrom <= lineN && lineView.lineNumber 4284 if (lineView.changes) { 4285 if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false } 4286 updateLineForChanges(cm, lineView, lineN, dims) 4287 } 4288 if (updateNumber) { 4289 removeChildren(lineView.lineNumber) 4290 lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))) 4291 } 4292 cur = lineView.node.nextSibling 4293 } 4294 lineN += lineView.size 4295 } 4296 while (cur) { cur = rm(cur) } 4297 } 4298 4299 function updateGutterSpace(cm) { 4300 var width = cm.display.gutters.offsetWidth 4301 cm.display.sizer.style.marginLeft = width + "px" 4302 } 4303 4304 function setDocumentHeight(cm, measure) { 4305 cm.display.sizer.style.minHeight = measure.docHeight + "px" 4306 cm.display.heightForcer.style.top = measure.docHeight + "px" 4307 cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px" 4308 } 4309 4310 // Rebuild the gutter elements, ensure the margin to the left of the 4311 // code matches their width. 4312 function updateGutters(cm) { 4313 var gutters = cm.display.gutters, specs = cm.options.gutters 4314 removeChildren(gutters) 4315 var i = 0 4316 for (; i < specs.length; ++i) { 4317 var gutterClass = specs[i] 4318 var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)) 4319 if (gutterClass == "CodeMirror-linenumbers") { 4320 cm.display.lineGutter = gElt 4321 gElt.style.width = (cm.display.lineNumWidth || 1) + "px" 4322 } 4323 } 4324 gutters.style.display = i ? "" : "none" 4325 updateGutterSpace(cm) 4326 } 4327 4328 // Make sure the gutters options contains the element 4329 // "CodeMirror-linenumbers" when the lineNumbers option is true. 4330 function setGuttersForLineNumbers(options) { 4331 var found = indexOf(options.gutters, "CodeMirror-linenumbers") 4332 if (found == -1 && options.lineNumbers) { 4333 options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]) 4334 } else if (found > -1 && !options.lineNumbers) { 4335 options.gutters = options.gutters.slice(0) 4336 options.gutters.splice(found, 1) 4337 } 4338 } 4339 4340 var wheelSamples = 0; 4341 var wheelPixelsPerUnit = null; 4342 // Fill in a browser-detected starting value on browsers where we 4343 // know one. These don't have to be accurate -- the result of them 4344 // being wrong would just be a slight flicker on the first wheel 4345 // scroll (if it is large enough). 4346 if (ie) { wheelPixelsPerUnit = -.53 } 4347 else if (gecko) { wheelPixelsPerUnit = 15 } 4348 else if (chrome) { wheelPixelsPerUnit = -.7 } 4349 else if (safari) { wheelPixelsPerUnit = -1/3 } 4350 4351 function wheelEventDelta(e) { 4352 var dx = e.wheelDeltaX, dy = e.wheelDeltaY 4353 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail } 4354 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail } 4355 else if (dy == null) { dy = e.wheelDelta } 4356 return {x: dx, y: dy} 4357 } 4358 function wheelEventPixels(e) { 4359 var delta = wheelEventDelta(e) 4360 delta.x *= wheelPixelsPerUnit 4361 delta.y *= wheelPixelsPerUnit 4362 return delta 4363 } 4364 4365 function onScrollWheel(cm, e) { 4366 var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y 4367 4368 var display = cm.display, scroll = display.scroller 4369 // Quit if there's nothing to scroll here 4370 var canScrollX = scroll.scrollWidth > scroll.clientWidth 4371 var canScrollY = scroll.scrollHeight > scroll.clientHeight 4372 if (!(dx && canScrollX || dy && canScrollY)) { return } 4373 4374 // Webkit browsers on OS X abort momentum scrolls when the target 4375 // of the scroll event is removed from the scrollable element. 4376 // This hack (see related code in patchDisplay) makes sure the 4377 // element is kept around. 4378 if (dy && mac && webkit) { 4379 outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { 4380 for (var i = 0; i < view.length; i++) { 4381 if (view[i].node == cur) { 4382 cm.display.currentWheelTarget = cur 4383 break outer 4384 } 4385 } 4386 } 4387 } 4388 4389 // On some browsers, horizontal scrolling will cause redraws to 4390 // happen before the gutter has been realigned, causing it to 4391 // wriggle around in a most unseemly way. When we have an 4392 // estimated pixels/delta value, we just handle horizontal 4393 // scrolling entirely here. It'll be slightly off from native, but 4394 // better than glitching out. 4395 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { 4396 if (dy && canScrollY) 4397 { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)) } 4398 setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit)) 4399 // Only prevent default scrolling if vertical scrolling is 4400 // actually possible. Otherwise, it causes vertical scroll 4401 // jitter on OSX trackpads when deltaX is small and deltaY 4402 // is large (issue #3579) 4403 if (!dy || (dy && canScrollY)) 4404 { e_preventDefault(e) } 4405 display.wheelStartX = null // Abort measurement, if in progress 4406 return 4407 } 4408 4409 // 'Project' the visible viewport to cover the area that is being 4410 // scrolled into view (if we know enough to estimate it). 4411 if (dy && wheelPixelsPerUnit != null) { 4412 var pixels = dy * wheelPixelsPerUnit 4413 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight 4414 if (pixels < 0) { top = Math.max(0, top + pixels - 50) } 4415 else { bot = Math.min(cm.doc.height, bot + pixels + 50) } 4416 updateDisplaySimple(cm, {top: top, bottom: bot}) 4417 } 4418 4419 if (wheelSamples < 20) { 4420 if (display.wheelStartX == null) { 4421 display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop 4422 display.wheelDX = dx; display.wheelDY = dy 4423 setTimeout(function () { 4424 if (display.wheelStartX == null) { return } 4425 var movedX = scroll.scrollLeft - display.wheelStartX 4426 var movedY = scroll.scrollTop - display.wheelStartY 4427 var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || 4428 (movedX && display.wheelDX && movedX / display.wheelDX) 4429 display.wheelStartX = display.wheelStartY = null 4430 if (!sample) { return } 4431 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1) 4432 ++wheelSamples 4433 }, 200) 4434 } else { 4435 display.wheelDX += dx; display.wheelDY += dy 4436 } 4437 } 4438 } 4439 4440 // Selection objects are immutable. A new one is created every time 4441 // the selection changes. A selection is one or more non-overlapping 4442 // (and non-touching) ranges, sorted, and an integer that indicates 4443 // which one is the primary selection (the one that's scrolled into 4444 // view, that getCursor returns, etc). 4445 var Selection = function(ranges, primIndex) { 4446 this.ranges = ranges 4447 this.primIndex = primIndex 4448 }; 4449 4450 Selection.prototype.primary = function () { return this.ranges[this.primIndex] }; 4451 4452 Selection.prototype.equals = function (other) { 4453 var this$1 = this; 4454 4455 if (other == this) { return true } 4456 if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false } 4457 for (var i = 0; i < this.ranges.length; i++) { 4458 var here = this$1.ranges[i], there = other.ranges[i] 4459 if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false } 4460 } 4461 return true 4462 }; 4463 4464 Selection.prototype.deepCopy = function () { 4465 var this$1 = this; 4466 4467 var out = [] 4468 for (var i = 0; i < this.ranges.length; i++) 4469 { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)) } 4470 return new Selection(out, this.primIndex) 4471 }; 4472 4473 Selection.prototype.somethingSelected = function () { 4474 var this$1 = this; 4475 4476 for (var i = 0; i < this.ranges.length; i++) 4477 { if (!this$1.ranges[i].empty()) { return true } } 4478 return false 4479 }; 4480 4481 Selection.prototype.contains = function (pos, end) { 4482 var this$1 = this; 4483 4484 if (!end) { end = pos } 4485 for (var i = 0; i < this.ranges.length; i++) { 4486 var range = this$1.ranges[i] 4487 if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) 4488 { return i } 4489 } 4490 return -1 4491 }; 4492 4493 var Range = function(anchor, head) { 4494 this.anchor = anchor; this.head = head 4495 }; 4496 4497 Range.prototype.from = function () { return minPos(this.anchor, this.head) }; 4498 Range.prototype.to = function () { return maxPos(this.anchor, this.head) }; 4499 Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch }; 4500 4501 // Take an unsorted, potentially overlapping set of ranges, and 4502 // build a selection out of it. 'Consumes' ranges array (modifying 4503 // it). 4504 function normalizeSelection(ranges, primIndex) { 4505 var prim = ranges[primIndex] 4506 ranges.sort(function (a, b) { return cmp(a.from(), b.from()); }) 4507 primIndex = indexOf(ranges, prim) 4508 for (var i = 1; i < ranges.length; i++) { 4509 var cur = ranges[i], prev = ranges[i - 1] 4510 if (cmp(prev.to(), cur.from()) >= 0) { 4511 var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()) 4512 var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head 4513 if (i <= primIndex) { --primIndex } 4514 ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)) 4515 } 4516 } 4517 return new Selection(ranges, primIndex) 4518 } 4519 4520 function simpleSelection(anchor, head) { 4521 return new Selection([new Range(anchor, head || anchor)], 0) 4522 } 4523 4524 // Compute the position of the end of a change (its 'to' property 4525 // refers to the pre-change end). 4526 function changeEnd(change) { 4527 if (!change.text) { return change.to } 4528 return Pos(change.from.line + change.text.length - 1, 4529 lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) 4530 } 4531 4532 // Adjust a position to refer to the post-change position of the 4533 // same text, or the end of the change if the change covers it. 4534 function adjustForChange(pos, change) { 4535 if (cmp(pos, change.from) < 0) { return pos } 4536 if (cmp(pos, change.to) <= 0) { return changeEnd(change) } 4537 4538 var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch 4539 if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch } 4540 return Pos(line, ch) 4541 } 4542 4543 function computeSelAfterChange(doc, change) { 4544 var out = [] 4545 for (var i = 0; i < doc.sel.ranges.length; i++) { 4546 var range = doc.sel.ranges[i] 4547 out.push(new Range(adjustForChange(range.anchor, change), 4548 adjustForChange(range.head, change))) 4549 } 4550 return normalizeSelection(out, doc.sel.primIndex) 4551 } 4552 4553 function offsetPos(pos, old, nw) { 4554 if (pos.line == old.line) 4555 { return Pos(nw.line, pos.ch - old.ch + nw.ch) } 4556 else 4557 { return Pos(nw.line + (pos.line - old.line), pos.ch) } 4558 } 4559 4560 // Used by replaceSelections to allow moving the selection to the 4561 // start or around the replaced test. Hint may be "start" or "around". 4562 function computeReplacedSel(doc, changes, hint) { 4563 var out = [] 4564 var oldPrev = Pos(doc.first, 0), newPrev = oldPrev 4565 for (var i = 0; i < changes.length; i++) { 4566 var change = changes[i] 4567 var from = offsetPos(change.from, oldPrev, newPrev) 4568 var to = offsetPos(changeEnd(change), oldPrev, newPrev) 4569 oldPrev = change.to 4570 newPrev = to 4571 if (hint == "around") { 4572 var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0 4573 out[i] = new Range(inv ? to : from, inv ? from : to) 4574 } else { 4575 out[i] = new Range(from, from) 4576 } 4577 } 4578 return new Selection(out, doc.sel.primIndex) 4579 } 4580 4581 // Used to get the editor into a consistent state again when options change. 4582 4583 function loadMode(cm) { 4584 cm.doc.mode = getMode(cm.options, cm.doc.modeOption) 4585 resetModeState(cm) 4586 } 4587 4588 function resetModeState(cm) { 4589 cm.doc.iter(function (line) { 4590 if (line.stateAfter) { line.stateAfter = null } 4591 if (line.styles) { line.styles = null } 4592 }) 4593 cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first 4594 startWorker(cm, 100) 4595 cm.state.modeGen++ 4596 if (cm.curOp) { regChange(cm) } 4597 } 4598 4599 // DOCUMENT DATA STRUCTURE 4600 4601 // By default, updates that start and end at the beginning of a line 4602 // are treated specially, in order to make the association of line 4603 // widgets and marker elements with the text behave more intuitive. 4604 function isWholeLineUpdate(doc, change) { 4605 return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && 4606 (!doc.cm || doc.cm.options.wholeLineUpdateBefore) 4607 } 4608 4609 // Perform a change on the document data structure. 4610 function updateDoc(doc, change, markedSpans, estimateHeight) { 4611 function spansFor(n) {return markedSpans ? markedSpans[n] : null} 4612 function update(line, text, spans) { 4613 updateLine(line, text, spans, estimateHeight) 4614 signalLater(line, "change", line, change) 4615 } 4616 function linesFor(start, end) { 4617 var result = [] 4618 for (var i = start; i < end; ++i) 4619 { result.push(new Line(text[i], spansFor(i), estimateHeight)) } 4620 return result 4621 } 4622 4623 var from = change.from, to = change.to, text = change.text 4624 var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line) 4625 var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line 4626 4627 // Adjust the line structure 4628 if (change.full) { 4629 doc.insert(0, linesFor(0, text.length)) 4630 doc.remove(text.length, doc.size - text.length) 4631 } else if (isWholeLineUpdate(doc, change)) { 4632 // This is a whole-line replace. Treated specially to make 4633 // sure line objects move the way they are supposed to. 4634 var added = linesFor(0, text.length - 1) 4635 update(lastLine, lastLine.text, lastSpans) 4636 if (nlines) { doc.remove(from.line, nlines) } 4637 if (added.length) { doc.insert(from.line, added) } 4638 } else if (firstLine == lastLine) { 4639 if (text.length == 1) { 4640 update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans) 4641 } else { 4642 var added$1 = linesFor(1, text.length - 1) 4643 added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)) 4644 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) 4645 doc.insert(from.line + 1, added$1) 4646 } 4647 } else if (text.length == 1) { 4648 update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)) 4649 doc.remove(from.line + 1, nlines) 4650 } else { 4651 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) 4652 update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans) 4653 var added$2 = linesFor(1, text.length - 1) 4654 if (nlines > 1) { doc.remove(from.line + 1, nlines - 1) } 4655 doc.insert(from.line + 1, added$2) 4656 } 4657 4658 signalLater(doc, "change", doc, change) 4659 } 4660 4661 // Call f for all linked documents. 4662 function linkedDocs(doc, f, sharedHistOnly) { 4663 function propagate(doc, skip, sharedHist) { 4664 if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) { 4665 var rel = doc.linked[i] 4666 if (rel.doc == skip) { continue } 4667 var shared = sharedHist && rel.sharedHist 4668 if (sharedHistOnly && !shared) { continue } 4669 f(rel.doc, shared) 4670 propagate(rel.doc, doc, shared) 4671 } } 4672 } 4673 propagate(doc, null, true) 4674 } 4675 4676 // Attach a document to an editor. 4677 function attachDoc(cm, doc) { 4678 if (doc.cm) { throw new Error("This document is already in use.") } 4679 cm.doc = doc 4680 doc.cm = cm 4681 estimateLineHeights(cm) 4682 loadMode(cm) 4683 setDirectionClass(cm) 4684 if (!cm.options.lineWrapping) { findMaxLine(cm) } 4685 cm.options.mode = doc.modeOption 4686 regChange(cm) 4687 } 4688 4689 function setDirectionClass(cm) { 4690 ;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl") 4691 } 4692 4693 function directionChanged(cm) { 4694 runInOp(cm, function () { 4695 setDirectionClass(cm) 4696 regChange(cm) 4697 }) 4698 } 4699 4700 function History(startGen) { 4701 // Arrays of change events and selections. Doing something adds an 4702 // event to done and clears undo. Undoing moves events from done 4703 // to undone, redoing moves them in the other direction. 4704 this.done = []; this.undone = [] 4705 this.undoDepth = Infinity 4706 // Used to track when changes can be merged into a single undo 4707 // event 4708 this.lastModTime = this.lastSelTime = 0 4709 this.lastOp = this.lastSelOp = null 4710 this.lastOrigin = this.lastSelOrigin = null 4711 // Used by the isClean() method 4712 this.generation = this.maxGeneration = startGen || 1 4713 } 4714 4715 // Create a history change event from an updateDoc-style change 4716 // object. 4717 function historyChangeFromChange(doc, change) { 4718 var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)} 4719 attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1) 4720 linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true) 4721 return histChange 4722 } 4723 4724 // Pop all selection events off the end of a history array. Stop at 4725 // a change event. 4726 function clearSelectionEvents(array) { 4727 while (array.length) { 4728 var last = lst(array) 4729 if (last.ranges) { array.pop() } 4730 else { break } 4731 } 4732 } 4733 4734 // Find the top change event in the history. Pop off selection 4735 // events that are in the way. 4736 function lastChangeEvent(hist, force) { 4737 if (force) { 4738 clearSelectionEvents(hist.done) 4739 return lst(hist.done) 4740 } else if (hist.done.length && !lst(hist.done).ranges) { 4741 return lst(hist.done) 4742 } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { 4743 hist.done.pop() 4744 return lst(hist.done) 4745 } 4746 } 4747 4748 // Register a change in the history. Merges changes that are within 4749 // a single operation, or are close together with an origin that 4750 // allows merging (starting with "+") into a single event. 4751 function addChangeToHistory(doc, change, selAfter, opId) { 4752 var hist = doc.history 4753 hist.undone.length = 0 4754 var time = +new Date, cur 4755 var last 4756 4757 if ((hist.lastOp == opId || 4758 hist.lastOrigin == change.origin && change.origin && 4759 ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || 4760 change.origin.charAt(0) == "*")) && 4761 (cur = lastChangeEvent(hist, hist.lastOp == opId))) { 4762 // Merge this change into the last event 4763 last = lst(cur.changes) 4764 if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { 4765 // Optimized case for simple insertion -- don't want to add 4766 // new changesets for every character typed 4767 last.to = changeEnd(change) 4768 } else { 4769 // Add new sub-event 4770 cur.changes.push(historyChangeFromChange(doc, change)) 4771 } 4772 } else { 4773 // Can not be merged, start a new event. 4774 var before = lst(hist.done) 4775 if (!before || !before.ranges) 4776 { pushSelectionToHistory(doc.sel, hist.done) } 4777 cur = {changes: [historyChangeFromChange(doc, change)], 4778 generation: hist.generation} 4779 hist.done.push(cur) 4780 while (hist.done.length > hist.undoDepth) { 4781 hist.done.shift() 4782 if (!hist.done[0].ranges) { hist.done.shift() } 4783 } 4784 } 4785 hist.done.push(selAfter) 4786 hist.generation = ++hist.maxGeneration 4787 hist.lastModTime = hist.lastSelTime = time 4788 hist.lastOp = hist.lastSelOp = opId 4789 hist.lastOrigin = hist.lastSelOrigin = change.origin 4790 4791 if (!last) { signal(doc, "historyAdded") } 4792 } 4793 4794 function selectionEventCanBeMerged(doc, origin, prev, sel) { 4795 var ch = origin.charAt(0) 4796 return ch == "*" || 4797 ch == "+" && 4798 prev.ranges.length == sel.ranges.length && 4799 prev.somethingSelected() == sel.somethingSelected() && 4800 new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) 4801 } 4802 4803 // Called whenever the selection changes, sets the new selection as 4804 // the pending selection in the history, and pushes the old pending 4805 // selection into the 'done' array when it was significantly 4806 // different (in number of selected ranges, emptiness, or time). 4807 function addSelectionToHistory(doc, sel, opId, options) { 4808 var hist = doc.history, origin = options && options.origin 4809 4810 // A new event is started when the previous origin does not match 4811 // the current, or the origins don't allow matching. Origins 4812 // starting with * are always merged, those starting with + are 4813 // merged when similar and close together in time. 4814 if (opId == hist.lastSelOp || 4815 (origin && hist.lastSelOrigin == origin && 4816 (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || 4817 selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) 4818 { hist.done[hist.done.length - 1] = sel } 4819 else 4820 { pushSelectionToHistory(sel, hist.done) } 4821 4822 hist.lastSelTime = +new Date 4823 hist.lastSelOrigin = origin 4824 hist.lastSelOp = opId 4825 if (options && options.clearRedo !== false) 4826 { clearSelectionEvents(hist.undone) } 4827 } 4828 4829 function pushSelectionToHistory(sel, dest) { 4830 var top = lst(dest) 4831 if (!(top && top.ranges && top.equals(sel))) 4832 { dest.push(sel) } 4833 } 4834 4835 // Used to store marked span information in the history. 4836 function attachLocalSpans(doc, change, from, to) { 4837 var existing = change["spans_" + doc.id], n = 0 4838 doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) { 4839 if (line.markedSpans) 4840 { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans } 4841 ++n 4842 }) 4843 } 4844 4845 // When un/re-doing restores text containing marked spans, those 4846 // that have been explicitly cleared should not be restored. 4847 function removeClearedSpans(spans) { 4848 if (!spans) { return null } 4849 var out 4850 for (var i = 0; i < spans.length; ++i) { 4851 if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i) } } 4852 else if (out) { out.push(spans[i]) } 4853 } 4854 return !out ? spans : out.length ? out : null 4855 } 4856 4857 // Retrieve and filter the old marked spans stored in a change event. 4858 function getOldSpans(doc, change) { 4859 var found = change["spans_" + doc.id] 4860 if (!found) { return null } 4861 var nw = [] 4862 for (var i = 0; i < change.text.length; ++i) 4863 { nw.push(removeClearedSpans(found[i])) } 4864 return nw 4865 } 4866 4867 // Used for un/re-doing changes from the history. Combines the 4868 // result of computing the existing spans with the set of spans that 4869 // existed in the history (so that deleting around a span and then 4870 // undoing brings back the span). 4871 function mergeOldSpans(doc, change) { 4872 var old = getOldSpans(doc, change) 4873 var stretched = stretchSpansOverChange(doc, change) 4874 if (!old) { return stretched } 4875 if (!stretched) { return old } 4876 4877 for (var i = 0; i < old.length; ++i) { 4878 var oldCur = old[i], stretchCur = stretched[i] 4879 if (oldCur && stretchCur) { 4880 spans: for (var j = 0; j < stretchCur.length; ++j) { 4881 var span = stretchCur[j] 4882 for (var k = 0; k < oldCur.length; ++k) 4883 { if (oldCur[k].marker == span.marker) { continue spans } } 4884 oldCur.push(span) 4885 } 4886 } else if (stretchCur) { 4887 old[i] = stretchCur 4888 } 4889 } 4890 return old 4891 } 4892 4893 // Used both to provide a JSON-safe object in .getHistory, and, when 4894 // detaching a document, to split the history in two 4895 function copyHistoryArray(events, newGroup, instantiateSel) { 4896 var copy = [] 4897 for (var i = 0; i < events.length; ++i) { 4898 var event = events[i] 4899 if (event.ranges) { 4900 copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event) 4901 continue 4902 } 4903 var changes = event.changes, newChanges = [] 4904 copy.push({changes: newChanges}) 4905 for (var j = 0; j < changes.length; ++j) { 4906 var change = changes[j], m = (void 0) 4907 newChanges.push({from: change.from, to: change.to, text: change.text}) 4908 if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) { 4909 if (indexOf(newGroup, Number(m[1])) > -1) { 4910 lst(newChanges)[prop] = change[prop] 4911 delete change[prop] 4912 } 4913 } } } 4914 } 4915 } 4916 return copy 4917 } 4918 4919 // The 'scroll' parameter given to many of these indicated whether 4920 // the new cursor position should be scrolled into view after 4921 // modifying the selection. 4922 4923 // If shift is held or the extend flag is set, extends a range to 4924 // include a given position (and optionally a second position). 4925 // Otherwise, simply returns the range between the given positions. 4926 // Used for cursor motion and such. 4927 function extendRange(range, head, other, extend) { 4928 if (extend) { 4929 var anchor = range.anchor 4930 if (other) { 4931 var posBefore = cmp(head, anchor) < 0 4932 if (posBefore != (cmp(other, anchor) < 0)) { 4933 anchor = head 4934 head = other 4935 } else if (posBefore != (cmp(head, other) < 0)) { 4936 head = other 4937 } 4938 } 4939 return new Range(anchor, head) 4940 } else { 4941 return new Range(other || head, head) 4942 } 4943 } 4944 4945 // Extend the primary selection range, discard the rest. 4946 function extendSelection(doc, head, other, options, extend) { 4947 if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend) } 4948 setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options) 4949 } 4950 4951 // Extend all selections (pos is an array of selections with length 4952 // equal the number of selections) 4953 function extendSelections(doc, heads, options) { 4954 var out = [] 4955 var extend = doc.cm && (doc.cm.display.shift || doc.extend) 4956 for (var i = 0; i < doc.sel.ranges.length; i++) 4957 { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend) } 4958 var newSel = normalizeSelection(out, doc.sel.primIndex) 4959 setSelection(doc, newSel, options) 4960 } 4961 4962 // Updates a single range in the selection. 4963 function replaceOneSelection(doc, i, range, options) { 4964 var ranges = doc.sel.ranges.slice(0) 4965 ranges[i] = range 4966 setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options) 4967 } 4968 4969 // Reset the selection to a single range. 4970 function setSimpleSelection(doc, anchor, head, options) { 4971 setSelection(doc, simpleSelection(anchor, head), options) 4972 } 4973 4974 // Give beforeSelectionChange handlers a change to influence a 4975 // selection update. 4976 function filterSelectionChange(doc, sel, options) { 4977 var obj = { 4978 ranges: sel.ranges, 4979 update: function(ranges) { 4980 var this$1 = this; 4981 4982 this.ranges = [] 4983 for (var i = 0; i < ranges.length; i++) 4984 { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), 4985 clipPos(doc, ranges[i].head)) } 4986 }, 4987 origin: options && options.origin 4988 } 4989 signal(doc, "beforeSelectionChange", doc, obj) 4990 if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj) } 4991 if (obj.ranges != sel.ranges) { return normalizeSelection(obj.ranges, obj.ranges.length - 1) } 4992 else { return sel } 4993 } 4994 4995 function setSelectionReplaceHistory(doc, sel, options) { 4996 var done = doc.history.done, last = lst(done) 4997 if (last && last.ranges) { 4998 done[done.length - 1] = sel 4999 setSelectionNoUndo(doc, sel, options) 5000 } else { 5001 setSelection(doc, sel, options) 5002 } 5003 } 5004 5005 // Set a new selection. 5006 function setSelection(doc, sel, options) { 5007 setSelectionNoUndo(doc, sel, options) 5008 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options) 5009 } 5010 5011 function setSelectionNoUndo(doc, sel, options) { 5012 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) 5013 { sel = filterSelectionChange(doc, sel, options) } 5014 5015 var bias = options && options.bias || 5016 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1) 5017 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)) 5018 5019 if (!(options && options.scroll === false) && doc.cm) 5020 { ensureCursorVisible(doc.cm) } 5021 } 5022 5023 function setSelectionInner(doc, sel) { 5024 if (sel.equals(doc.sel)) { return } 5025 5026 doc.sel = sel 5027 5028 if (doc.cm) { 5029 doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true 5030 signalCursorActivity(doc.cm) 5031 } 5032 signalLater(doc, "cursorActivity", doc) 5033 } 5034 5035 // Verify that the selection does not partially select any atomic 5036 // marked ranges. 5037 function reCheckSelection(doc) { 5038 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)) 5039 } 5040 5041 // Return a selection that does not partially select any atomic 5042 // ranges. 5043 function skipAtomicInSelection(doc, sel, bias, mayClear) { 5044 var out 5045 for (var i = 0; i < sel.ranges.length; i++) { 5046 var range = sel.ranges[i] 5047 var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i] 5048 var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear) 5049 var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear) 5050 if (out || newAnchor != range.anchor || newHead != range.head) { 5051 if (!out) { out = sel.ranges.slice(0, i) } 5052 out[i] = new Range(newAnchor, newHead) 5053 } 5054 } 5055 return out ? normalizeSelection(out, sel.primIndex) : sel 5056 } 5057 5058 function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { 5059 var line = getLine(doc, pos.line) 5060 if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { 5061 var sp = line.markedSpans[i], m = sp.marker 5062 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && 5063 (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { 5064 if (mayClear) { 5065 signal(m, "beforeCursorEnter") 5066 if (m.explicitlyCleared) { 5067 if (!line.markedSpans) { break } 5068 else {--i; continue} 5069 } 5070 } 5071 if (!m.atomic) { continue } 5072 5073 if (oldPos) { 5074 var near = m.find(dir < 0 ? 1 : -1), diff = (void 0) 5075 if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) 5076 { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null) } 5077 if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) 5078 { return skipAtomicInner(doc, near, pos, dir, mayClear) } 5079 } 5080 5081 var far = m.find(dir < 0 ? -1 : 1) 5082 if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) 5083 { far = movePos(doc, far, dir, far.line == pos.line ? line : null) } 5084 return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null 5085 } 5086 } } 5087 return pos 5088 } 5089 5090 // Ensure a given position is not inside an atomic range. 5091 function skipAtomic(doc, pos, oldPos, bias, mayClear) { 5092 var dir = bias || 1 5093 var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || 5094 (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || 5095 skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || 5096 (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)) 5097 if (!found) { 5098 doc.cantEdit = true 5099 return Pos(doc.first, 0) 5100 } 5101 return found 5102 } 5103 5104 function movePos(doc, pos, dir, line) { 5105 if (dir < 0 && pos.ch == 0) { 5106 if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) } 5107 else { return null } 5108 } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { 5109 if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) } 5110 else { return null } 5111 } else { 5112 return new Pos(pos.line, pos.ch + dir) 5113 } 5114 } 5115 5116 function selectAll(cm) { 5117 cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll) 5118 } 5119 5120 // UPDATING 5121 5122 // Allow "beforeChange" event handlers to influence a change 5123 function filterChange(doc, change, update) { 5124 var obj = { 5125 canceled: false, 5126 from: change.from, 5127 to: change.to, 5128 text: change.text, 5129 origin: change.origin, 5130 cancel: function () { return obj.canceled = true; } 5131 } 5132 if (update) { obj.update = function (from, to, text, origin) { 5133 if (from) { obj.from = clipPos(doc, from) } 5134 if (to) { obj.to = clipPos(doc, to) } 5135 if (text) { obj.text = text } 5136 if (origin !== undefined) { obj.origin = origin } 5137 } } 5138 signal(doc, "beforeChange", doc, obj) 5139 if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj) } 5140 5141 if (obj.canceled) { return null } 5142 return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} 5143 } 5144 5145 // Apply a change to a document, and add it to the document's 5146 // history, and propagating it to all linked documents. 5147 function makeChange(doc, change, ignoreReadOnly) { 5148 if (doc.cm) { 5149 if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) } 5150 if (doc.cm.state.suppressEdits) { return } 5151 } 5152 5153 if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { 5154 change = filterChange(doc, change, true) 5155 if (!change) { return } 5156 } 5157 5158 // Possibly split or suppress the update based on the presence 5159 // of read-only spans in its range. 5160 var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to) 5161 if (split) { 5162 for (var i = split.length - 1; i >= 0; --i) 5163 { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}) } 5164 } else { 5165 makeChangeInner(doc, change) 5166 } 5167 } 5168 5169 function makeChangeInner(doc, change) { 5170 if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return } 5171 var selAfter = computeSelAfterChange(doc, change) 5172 addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN) 5173 5174 makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)) 5175 var rebased = [] 5176 5177 linkedDocs(doc, function (doc, sharedHist) { 5178 if (!sharedHist && indexOf(rebased, doc.history) == -1) { 5179 rebaseHist(doc.history, change) 5180 rebased.push(doc.history) 5181 } 5182 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)) 5183 }) 5184 } 5185 5186 // Revert a change stored in a document's history. 5187 function makeChangeFromHistory(doc, type, allowSelectionOnly) { 5188 if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) { return } 5189 5190 var hist = doc.history, event, selAfter = doc.sel 5191 var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done 5192 5193 // Verify that there is a useable event (so that ctrl-z won't 5194 // needlessly clear selection events) 5195 var i = 0 5196 for (; i < source.length; i++) { 5197 event = source[i] 5198 if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) 5199 { break } 5200 } 5201 if (i == source.length) { return } 5202 hist.lastOrigin = hist.lastSelOrigin = null 5203 5204 for (;;) { 5205 event = source.pop() 5206 if (event.ranges) { 5207 pushSelectionToHistory(event, dest) 5208 if (allowSelectionOnly && !event.equals(doc.sel)) { 5209 setSelection(doc, event, {clearRedo: false}) 5210 return 5211 } 5212 selAfter = event 5213 } 5214 else { break } 5215 } 5216 5217 // Build up a reverse change object to add to the opposite history 5218 // stack (redo when undoing, and vice versa). 5219 var antiChanges = [] 5220 pushSelectionToHistory(selAfter, dest) 5221 dest.push({changes: antiChanges, generation: hist.generation}) 5222 hist.generation = event.generation || ++hist.maxGeneration 5223 5224 var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange") 5225 5226 var loop = function ( i ) { 5227 var change = event.changes[i] 5228 change.origin = type 5229 if (filter && !filterChange(doc, change, false)) { 5230 source.length = 0 5231 return {} 5232 } 5233 5234 antiChanges.push(historyChangeFromChange(doc, change)) 5235 5236 var after = i ? computeSelAfterChange(doc, change) : lst(source) 5237 makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)) 5238 if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}) } 5239 var rebased = [] 5240 5241 // Propagate to the linked documents 5242 linkedDocs(doc, function (doc, sharedHist) { 5243 if (!sharedHist && indexOf(rebased, doc.history) == -1) { 5244 rebaseHist(doc.history, change) 5245 rebased.push(doc.history) 5246 } 5247 makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)) 5248 }) 5249 }; 5250 5251 for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { 5252 var returned = loop( i$1 ); 5253 5254 if ( returned ) return returned.v; 5255 } 5256 } 5257 5258 // Sub-views need their line numbers shifted when text is added 5259 // above or below them in the parent document. 5260 function shiftDoc(doc, distance) { 5261 if (distance == 0) { return } 5262 doc.first += distance 5263 doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range( 5264 Pos(range.anchor.line + distance, range.anchor.ch), 5265 Pos(range.head.line + distance, range.head.ch) 5266 ); }), doc.sel.primIndex) 5267 if (doc.cm) { 5268 regChange(doc.cm, doc.first, doc.first - distance, distance) 5269 for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) 5270 { regLineChange(doc.cm, l, "gutter") } 5271 } 5272 } 5273 5274 // More lower-level change function, handling only a single document 5275 // (not linked ones). 5276 function makeChangeSingleDoc(doc, change, selAfter, spans) { 5277 if (doc.cm && !doc.cm.curOp) 5278 { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) } 5279 5280 if (change.to.line < doc.first) { 5281 shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)) 5282 return 5283 } 5284 if (change.from.line > doc.lastLine()) { return } 5285 5286 // Clip the change to the size of this doc 5287 if (change.from.line < doc.first) { 5288 var shift = change.text.length - 1 - (doc.first - change.from.line) 5289 shiftDoc(doc, shift) 5290 change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), 5291 text: [lst(change.text)], origin: change.origin} 5292 } 5293 var last = doc.lastLine() 5294 if (change.to.line > last) { 5295 change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), 5296 text: [change.text[0]], origin: change.origin} 5297 } 5298 5299 change.removed = getBetween(doc, change.from, change.to) 5300 5301 if (!selAfter) { selAfter = computeSelAfterChange(doc, change) } 5302 if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans) } 5303 else { updateDoc(doc, change, spans) } 5304 setSelectionNoUndo(doc, selAfter, sel_dontScroll) 5305 } 5306 5307 // Handle the interaction of a change to a document with the editor 5308 // that this document is part of. 5309 function makeChangeSingleDocInEditor(cm, change, spans) { 5310 var doc = cm.doc, display = cm.display, from = change.from, to = change.to 5311 5312 var recomputeMaxLength = false, checkWidthStart = from.line 5313 if (!cm.options.lineWrapping) { 5314 checkWidthStart = lineNo(visualLine(getLine(doc, from.line))) 5315 doc.iter(checkWidthStart, to.line + 1, function (line) { 5316 if (line == display.maxLine) { 5317 recomputeMaxLength = true 5318 return true 5319 } 5320 }) 5321 } 5322 5323 if (doc.sel.contains(change.from, change.to) > -1) 5324 { signalCursorActivity(cm) } 5325 5326 updateDoc(doc, change, spans, estimateHeight(cm)) 5327 5328 if (!cm.options.lineWrapping) { 5329 doc.iter(checkWidthStart, from.line + change.text.length, function (line) { 5330 var len = lineLength(line) 5331 if (len > display.maxLineLength) { 5332 display.maxLine = line 5333 display.maxLineLength = len 5334 display.maxLineChanged = true 5335 recomputeMaxLength = false 5336 } 5337 }) 5338 if (recomputeMaxLength) { cm.curOp.updateMaxLine = true } 5339 } 5340 5341 retreatFrontier(doc, from.line) 5342 startWorker(cm, 400) 5343 5344 var lendiff = change.text.length - (to.line - from.line) - 1 5345 // Remember that these lines changed, for updating the display 5346 if (change.full) 5347 { regChange(cm) } 5348 else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) 5349 { regLineChange(cm, from.line, "text") } 5350 else 5351 { regChange(cm, from.line, to.line + 1, lendiff) } 5352 5353 var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change") 5354 if (changeHandler || changesHandler) { 5355 var obj = { 5356 from: from, to: to, 5357 text: change.text, 5358 removed: change.removed, 5359 origin: change.origin 5360 } 5361 if (changeHandler) { signalLater(cm, "change", cm, obj) } 5362 if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj) } 5363 } 5364 cm.display.selForContextMenu = null 5365 } 5366 5367 function replaceRange(doc, code, from, to, origin) { 5368 if (!to) { to = from } 5369 if (cmp(to, from) < 0) { var assign; 5370 (assign = [to, from], from = assign[0], to = assign[1], assign) } 5371 if (typeof code == "string") { code = doc.splitLines(code) } 5372 makeChange(doc, {from: from, to: to, text: code, origin: origin}) 5373 } 5374 5375 // Rebasing/resetting history to deal with externally-sourced changes 5376 5377 function rebaseHistSelSingle(pos, from, to, diff) { 5378 if (to < pos.line) { 5379 pos.line += diff 5380 } else if (from < pos.line) { 5381 pos.line = from 5382 pos.ch = 0 5383 } 5384 } 5385 5386 // Tries to rebase an array of history events given a change in the 5387 // document. If the change touches the same lines as the event, the 5388 // event, and everything 'behind' it, is discarded. If the change is 5389 // before the event, the event's positions are updated. Uses a 5390 // copy-on-write scheme for the positions, to avoid having to 5391 // reallocate them all on every rebase, but also avoid problems with 5392 // shared position objects being unsafely updated. 5393 function rebaseHistArray(array, from, to, diff) { 5394 for (var i = 0; i < array.length; ++i) { 5395 var sub = array[i], ok = true 5396 if (sub.ranges) { 5397 if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true } 5398 for (var j = 0; j < sub.ranges.length; j++) { 5399 rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff) 5400 rebaseHistSelSingle(sub.ranges[j].head, from, to, diff) 5401 } 5402 continue 5403 } 5404 for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { 5405 var cur = sub.changes[j$1] 5406 if (to < cur.from.line) { 5407 cur.from = Pos(cur.from.line + diff, cur.from.ch) 5408 cur.to = Pos(cur.to.line + diff, cur.to.ch) 5409 } else if (from <= cur.to.line) { 5410 ok = false 5411 break 5412 } 5413 } 5414 if (!ok) { 5415 array.splice(0, i + 1) 5416 i = 0 5417 } 5418 } 5419 } 5420 5421 function rebaseHist(hist, change) { 5422 var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1 5423 rebaseHistArray(hist.done, from, to, diff) 5424 rebaseHistArray(hist.undone, from, to, diff) 5425 } 5426 5427 // Utility for applying a change to a line by handle or number, 5428 // returning the number and optionally registering the line as 5429 // changed. 5430 function changeLine(doc, handle, changeType, op) { 5431 var no = handle, line = handle 5432 if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)) } 5433 else { no = lineNo(handle) } 5434 if (no == null) { return null } 5435 if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType) } 5436 return line 5437 } 5438 5439 // The document is represented as a BTree consisting of leaves, with 5440 // chunk of lines in them, and branches, with up to ten leaves or 5441 // other branch nodes below them. The top node is always a branch 5442 // node, and is the document object itself (meaning it has 5443 // additional methods and properties). 5444 // 5445 // All nodes have parent links. The tree is used both to go from 5446 // line numbers to line objects, and to go from objects to numbers. 5447 // It also indexes by height, and is used to convert between height 5448 // and line object, and to find the total height of the document. 5449 // 5450 // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html 5451 5452 function LeafChunk(lines) { 5453 var this$1 = this; 5454 5455 this.lines = lines 5456 this.parent = null 5457 var height = 0 5458 for (var i = 0; i < lines.length; ++i) { 5459 lines[i].parent = this$1 5460 height += lines[i].height 5461 } 5462 this.height = height 5463 } 5464 5465 LeafChunk.prototype = { 5466 chunkSize: function chunkSize() { return this.lines.length }, 5467 5468 // Remove the n lines at offset 'at'. 5469 removeInner: function removeInner(at, n) { 5470 var this$1 = this; 5471 5472 for (var i = at, e = at + n; i < e; ++i) { 5473 var line = this$1.lines[i] 5474 this$1.height -= line.height 5475 cleanUpLine(line) 5476 signalLater(line, "delete") 5477 } 5478 this.lines.splice(at, n) 5479 }, 5480 5481 // Helper used to collapse a small branch into a single leaf. 5482 collapse: function collapse(lines) { 5483 lines.push.apply(lines, this.lines) 5484 }, 5485 5486 // Insert the given array of lines at offset 'at', count them as 5487 // having the given height. 5488 insertInner: function insertInner(at, lines, height) { 5489 var this$1 = this; 5490 5491 this.height += height 5492 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)) 5493 for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1 } 5494 }, 5495 5496 // Used to iterate over a part of the tree. 5497 iterN: function iterN(at, n, op) { 5498 var this$1 = this; 5499 5500 for (var e = at + n; at < e; ++at) 5501 { if (op(this$1.lines[at])) { return true } } 5502 } 5503 } 5504 5505 function BranchChunk(children) { 5506 var this$1 = this; 5507 5508 this.children = children 5509 var size = 0, height = 0 5510 for (var i = 0; i < children.length; ++i) { 5511 var ch = children[i] 5512 size += ch.chunkSize(); height += ch.height 5513 ch.parent = this$1 5514 } 5515 this.size = size 5516 this.height = height 5517 this.parent = null 5518 } 5519 5520 BranchChunk.prototype = { 5521 chunkSize: function chunkSize() { return this.size }, 5522 5523 removeInner: function removeInner(at, n) { 5524 var this$1 = this; 5525 5526 this.size -= n 5527 for (var i = 0; i < this.children.length; ++i) { 5528 var child = this$1.children[i], sz = child.chunkSize() 5529 if (at < sz) { 5530 var rm = Math.min(n, sz - at), oldHeight = child.height 5531 child.removeInner(at, rm) 5532 this$1.height -= oldHeight - child.height 5533 if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null } 5534 if ((n -= rm) == 0) { break } 5535 at = 0 5536 } else { at -= sz } 5537 } 5538 // If the result is smaller than 25 lines, ensure that it is a 5539 // single leaf node. 5540 if (this.size - n < 25 && 5541 (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { 5542 var lines = [] 5543 this.collapse(lines) 5544 this.children = [new LeafChunk(lines)] 5545 this.children[0].parent = this 5546 } 5547 }, 5548 5549 collapse: function collapse(lines) { 5550 var this$1 = this; 5551 5552 for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines) } 5553 }, 5554 5555 insertInner: function insertInner(at, lines, height) { 5556 var this$1 = this; 5557 5558 this.size += lines.length 5559 this.height += height 5560 for (var i = 0; i < this.children.length; ++i) { 5561 var child = this$1.children[i], sz = child.chunkSize() 5562 if (at <= sz) { 5563 child.insertInner(at, lines, height) 5564 if (child.lines && child.lines.length > 50) { 5565 // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. 5566 // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. 5567 var remaining = child.lines.length % 25 + 25 5568 for (var pos = remaining; pos < child.lines.length;) { 5569 var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) 5570 child.height -= leaf.height 5571 this$1.children.splice(++i, 0, leaf) 5572 leaf.parent = this$1 5573 } 5574 child.lines = child.lines.slice(0, remaining) 5575 this$1.maybeSpill() 5576 } 5577 break 5578 } 5579 at -= sz 5580 } 5581 }, 5582 5583 // When a node has grown, check whether it should be split. 5584 maybeSpill: function maybeSpill() { 5585 if (this.children.length <= 10) { return } 5586 var me = this 5587 do { 5588 var spilled = me.children.splice(me.children.length - 5, 5) 5589 var sibling = new BranchChunk(spilled) 5590 if (!me.parent) { // Become the parent node 5591 var copy = new BranchChunk(me.children) 5592 copy.parent = me 5593 me.children = [copy, sibling] 5594 me = copy 5595 } else { 5596 me.size -= sibling.size 5597 me.height -= sibling.height 5598 var myIndex = indexOf(me.parent.children, me) 5599 me.parent.children.splice(myIndex + 1, 0, sibling) 5600 } 5601 sibling.parent = me.parent 5602 } while (me.children.length > 10) 5603 me.parent.maybeSpill() 5604 }, 5605 5606 iterN: function iterN(at, n, op) { 5607 var this$1 = this; 5608 5609 for (var i = 0; i < this.children.length; ++i) { 5610 var child = this$1.children[i], sz = child.chunkSize() 5611 if (at < sz) { 5612 var used = Math.min(n, sz - at) 5613 if (child.iterN(at, used, op)) { return true } 5614 if ((n -= used) == 0) { break } 5615 at = 0 5616 } else { at -= sz } 5617 } 5618 } 5619 } 5620 5621 // Line widgets are block elements displayed above or below a line. 5622 5623 var LineWidget = function(doc, node, options) { 5624 var this$1 = this; 5625 5626 if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) 5627 { this$1[opt] = options[opt] } } } 5628 this.doc = doc 5629 this.node = node 5630 }; 5631 5632 LineWidget.prototype.clear = function () { 5633 var this$1 = this; 5634 5635 var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line) 5636 if (no == null || !ws) { return } 5637 for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1) } } 5638 if (!ws.length) { line.widgets = null } 5639 var height = widgetHeight(this) 5640 updateLineHeight(line, Math.max(0, line.height - height)) 5641 if (cm) { 5642 runInOp(cm, function () { 5643 adjustScrollWhenAboveVisible(cm, line, -height) 5644 regLineChange(cm, no, "widget") 5645 }) 5646 signalLater(cm, "lineWidgetCleared", cm, this, no) 5647 } 5648 }; 5649 5650 LineWidget.prototype.changed = function () { 5651 var this$1 = this; 5652 5653 var oldH = this.height, cm = this.doc.cm, line = this.line 5654 this.height = null 5655 var diff = widgetHeight(this) - oldH 5656 if (!diff) { return } 5657 updateLineHeight(line, line.height + diff) 5658 if (cm) { 5659 runInOp(cm, function () { 5660 cm.curOp.forceUpdate = true 5661 adjustScrollWhenAboveVisible(cm, line, diff) 5662 signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line)) 5663 }) 5664 } 5665 }; 5666 eventMixin(LineWidget) 5667 5668 function adjustScrollWhenAboveVisible(cm, line, diff) { 5669 if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) 5670 { addToScrollTop(cm, diff) } 5671 } 5672 5673 function addLineWidget(doc, handle, node, options) { 5674 var widget = new LineWidget(doc, node, options) 5675 var cm = doc.cm 5676 if (cm && widget.noHScroll) { cm.display.alignWidgets = true } 5677 changeLine(doc, handle, "widget", function (line) { 5678 var widgets = line.widgets || (line.widgets = []) 5679 if (widget.insertAt == null) { widgets.push(widget) } 5680 else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget) } 5681 widget.line = line 5682 if (cm && !lineIsHidden(doc, line)) { 5683 var aboveVisible = heightAtLine(line) < doc.scrollTop 5684 updateLineHeight(line, line.height + widgetHeight(widget)) 5685 if (aboveVisible) { addToScrollTop(cm, widget.height) } 5686 cm.curOp.forceUpdate = true 5687 } 5688 return true 5689 }) 5690 signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)) 5691 return widget 5692 } 5693 5694 // TEXTMARKERS 5695 5696 // Created with markText and setBookmark methods. A TextMarker is a 5697 // handle that can be used to clear or find a marked position in the 5698 // document. Line objects hold arrays (markedSpans) containing 5699 // {from, to, marker} object pointing to such marker objects, and 5700 // indicating that such a marker is present on that line. Multiple 5701 // lines may point to the same marker when it spans across lines. 5702 // The spans will have null for their from/to properties when the 5703 // marker continues beyond the start/end of the line. Markers have 5704 // links back to the lines they currently touch. 5705 5706 // Collapsed markers have unique ids, in order to be able to order 5707 // them, which is needed for uniquely determining an outer marker 5708 // when they overlap (they may nest, but not partially overlap). 5709 var nextMarkerId = 0 5710 5711 var TextMarker = function(doc, type) { 5712 this.lines = [] 5713 this.type = type 5714 this.doc = doc 5715 this.id = ++nextMarkerId 5716 }; 5717 5718 // Clear the marker. 5719 TextMarker.prototype.clear = function () { 5720 var this$1 = this; 5721 5722 if (this.explicitlyCleared) { return } 5723 var cm = this.doc.cm, withOp = cm && !cm.curOp 5724 if (withOp) { startOperation(cm) } 5725 if (hasHandler(this, "clear")) { 5726 var found = this.find() 5727 if (found) { signalLater(this, "clear", found.from, found.to) } 5728 } 5729 var min = null, max = null 5730 for (var i = 0; i < this.lines.length; ++i) { 5731 var line = this$1.lines[i] 5732 var span = getMarkedSpanFor(line.markedSpans, this$1) 5733 if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text") } 5734 else if (cm) { 5735 if (span.to != null) { max = lineNo(line) } 5736 if (span.from != null) { min = lineNo(line) } 5737 } 5738 line.markedSpans = removeMarkedSpan(line.markedSpans, span) 5739 if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm) 5740 { updateLineHeight(line, textHeight(cm.display)) } 5741 } 5742 if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) { 5743 var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual) 5744 if (len > cm.display.maxLineLength) { 5745 cm.display.maxLine = visual 5746 cm.display.maxLineLength = len 5747 cm.display.maxLineChanged = true 5748 } 5749 } } 5750 5751 if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1) } 5752 this.lines.length = 0 5753 this.explicitlyCleared = true 5754 if (this.atomic && this.doc.cantEdit) { 5755 this.doc.cantEdit = false 5756 if (cm) { reCheckSelection(cm.doc) } 5757 } 5758 if (cm) { signalLater(cm, "markerCleared", cm, this, min, max) } 5759 if (withOp) { endOperation(cm) } 5760 if (this.parent) { this.parent.clear() } 5761 }; 5762 5763 // Find the position of the marker in the document. Returns a {from, 5764 // to} object by default. Side can be passed to get a specific side 5765 // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the 5766 // Pos objects returned contain a line object, rather than a line 5767 // number (used to prevent looking up the same line twice). 5768 TextMarker.prototype.find = function (side, lineObj) { 5769 var this$1 = this; 5770 5771 if (side == null && this.type == "bookmark") { side = 1 } 5772 var from, to 5773 for (var i = 0; i < this.lines.length; ++i) { 5774 var line = this$1.lines[i] 5775 var span = getMarkedSpanFor(line.markedSpans, this$1) 5776 if (span.from != null) { 5777 from = Pos(lineObj ? line : lineNo(line), span.from) 5778 if (side == -1) { return from } 5779 } 5780 if (span.to != null) { 5781 to = Pos(lineObj ? line : lineNo(line), span.to) 5782 if (side == 1) { return to } 5783 } 5784 } 5785 return from && {from: from, to: to} 5786 }; 5787 5788 // Signals that the marker's widget changed, and surrounding layout 5789 // should be recomputed. 5790 TextMarker.prototype.changed = function () { 5791 var this$1 = this; 5792 5793 var pos = this.find(-1, true), widget = this, cm = this.doc.cm 5794 if (!pos || !cm) { return } 5795 runInOp(cm, function () { 5796 var line = pos.line, lineN = lineNo(pos.line) 5797 var view = findViewForLine(cm, lineN) 5798 if (view) { 5799 clearLineMeasurementCacheFor(view) 5800 cm.curOp.selectionChanged = cm.curOp.forceUpdate = true 5801 } 5802 cm.curOp.updateMaxLine = true 5803 if (!lineIsHidden(widget.doc, line) && widget.height != null) { 5804 var oldHeight = widget.height 5805 widget.height = null 5806 var dHeight = widgetHeight(widget) - oldHeight 5807 if (dHeight) 5808 { updateLineHeight(line, line.height + dHeight) } 5809 } 5810 signalLater(cm, "markerChanged", cm, this$1) 5811 }) 5812 }; 5813 5814 TextMarker.prototype.attachLine = function (line) { 5815 if (!this.lines.length && this.doc.cm) { 5816 var op = this.doc.cm.curOp 5817 if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) 5818 { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) } 5819 } 5820 this.lines.push(line) 5821 }; 5822 5823 TextMarker.prototype.detachLine = function (line) { 5824 this.lines.splice(indexOf(this.lines, line), 1) 5825 if (!this.lines.length && this.doc.cm) { 5826 var op = this.doc.cm.curOp 5827 ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this) 5828 } 5829 }; 5830 eventMixin(TextMarker) 5831 5832 // Create a marker, wire it up to the right lines, and 5833 function markText(doc, from, to, options, type) { 5834 // Shared markers (across linked documents) are handled separately 5835 // (markTextShared will call out to this again, once per 5836 // document). 5837 if (options && options.shared) { return markTextShared(doc, from, to, options, type) } 5838 // Ensure we are in an operation. 5839 if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) } 5840 5841 var marker = new TextMarker(doc, type), diff = cmp(from, to) 5842 if (options) { copyObj(options, marker, false) } 5843 // Don't connect empty markers unless clearWhenEmpty is false 5844 if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) 5845 { return marker } 5846 if (marker.replacedWith) { 5847 // Showing up as a widget implies collapsed (widget replaces text) 5848 marker.collapsed = true 5849 marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget") 5850 if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true") } 5851 if (options.insertLeft) { marker.widgetNode.insertLeft = true } 5852 } 5853 if (marker.collapsed) { 5854 if (conflictingCollapsedRange(doc, from.line, from, to, marker) || 5855 from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) 5856 { throw new Error("Inserting collapsed marker partially overlapping an existing one") } 5857 seeCollapsedSpans() 5858 } 5859 5860 if (marker.addToHistory) 5861 { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN) } 5862 5863 var curLine = from.line, cm = doc.cm, updateMaxLine 5864 doc.iter(curLine, to.line + 1, function (line) { 5865 if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) 5866 { updateMaxLine = true } 5867 if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0) } 5868 addMarkedSpan(line, new MarkedSpan(marker, 5869 curLine == from.line ? from.ch : null, 5870 curLine == to.line ? to.ch : null)) 5871 ++curLine 5872 }) 5873 // lineIsHidden depends on the presence of the spans, so needs a second pass 5874 if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) { 5875 if (lineIsHidden(doc, line)) { updateLineHeight(line, 0) } 5876 }) } 5877 5878 if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }) } 5879 5880 if (marker.readOnly) { 5881 seeReadOnlySpans() 5882 if (doc.history.done.length || doc.history.undone.length) 5883 { doc.clearHistory() } 5884 } 5885 if (marker.collapsed) { 5886 marker.id = ++nextMarkerId 5887 marker.atomic = true 5888 } 5889 if (cm) { 5890 // Sync editor state 5891 if (updateMaxLine) { cm.curOp.updateMaxLine = true } 5892 if (marker.collapsed) 5893 { regChange(cm, from.line, to.line + 1) } 5894 else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) 5895 { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text") } } 5896 if (marker.atomic) { reCheckSelection(cm.doc) } 5897 signalLater(cm, "markerAdded", cm, marker) 5898 } 5899 return marker 5900 } 5901 5902 // SHARED TEXTMARKERS 5903 5904 // A shared marker spans multiple linked documents. It is 5905 // implemented as a meta-marker-object controlling multiple normal 5906 // markers. 5907 var SharedTextMarker = function(markers, primary) { 5908 var this$1 = this; 5909 5910 this.markers = markers 5911 this.primary = primary 5912 for (var i = 0; i < markers.length; ++i) 5913 { markers[i].parent = this$1 } 5914 }; 5915 5916 SharedTextMarker.prototype.clear = function () { 5917 var this$1 = this; 5918 5919 if (this.explicitlyCleared) { return } 5920 this.explicitlyCleared = true 5921 for (var i = 0; i < this.markers.length; ++i) 5922 { this$1.markers[i].clear() } 5923 signalLater(this, "clear") 5924 }; 5925 5926 SharedTextMarker.prototype.find = function (side, lineObj) { 5927 return this.primary.find(side, lineObj) 5928 }; 5929 eventMixin(SharedTextMarker) 5930 5931 function markTextShared(doc, from, to, options, type) { 5932 options = copyObj(options) 5933 options.shared = false 5934 var markers = [markText(doc, from, to, options, type)], primary = markers[0] 5935 var widget = options.widgetNode 5936 linkedDocs(doc, function (doc) { 5937 if (widget) { options.widgetNode = widget.cloneNode(true) } 5938 markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)) 5939 for (var i = 0; i < doc.linked.length; ++i) 5940 { if (doc.linked[i].isParent) { return } } 5941 primary = lst(markers) 5942 }) 5943 return new SharedTextMarker(markers, primary) 5944 } 5945 5946 function findSharedMarkers(doc) { 5947 return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; }) 5948 } 5949 5950 function copySharedMarkers(doc, markers) { 5951 for (var i = 0; i < markers.length; i++) { 5952 var marker = markers[i], pos = marker.find() 5953 var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to) 5954 if (cmp(mFrom, mTo)) { 5955 var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type) 5956 marker.markers.push(subMark) 5957 subMark.parent = marker 5958 } 5959 } 5960 } 5961 5962 function detachSharedMarkers(markers) { 5963 var loop = function ( i ) { 5964 var marker = markers[i], linked = [marker.primary.doc] 5965 linkedDocs(marker.primary.doc, function (d) { return linked.push(d); }) 5966 for (var j = 0; j < marker.markers.length; j++) { 5967 var subMarker = marker.markers[j] 5968 if (indexOf(linked, subMarker.doc) == -1) { 5969 subMarker.parent = null 5970 marker.markers.splice(j--, 1) 5971 } 5972 } 5973 }; 5974 5975 for (var i = 0; i < markers.length; i++) loop( i ); 5976 } 5977 5978 var nextDocId = 0 5979 var Doc = function(text, mode, firstLine, lineSep, direction) { 5980 if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) } 5981 if (firstLine == null) { firstLine = 0 } 5982 5983 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]) 5984 this.first = firstLine 5985 this.scrollTop = this.scrollLeft = 0 5986 this.cantEdit = false 5987 this.cleanGeneration = 1 5988 this.modeFrontier = this.highlightFrontier = firstLine 5989 var start = Pos(firstLine, 0) 5990 this.sel = simpleSelection(start) 5991 this.history = new History(null) 5992 this.id = ++nextDocId 5993 this.modeOption = mode 5994 this.lineSep = lineSep 5995 this.direction = (direction == "rtl") ? "rtl" : "ltr" 5996 this.extend = false 5997 5998 if (typeof text == "string") { text = this.splitLines(text) } 5999 updateDoc(this, {from: start, to: start, text: text}) 6000 setSelection(this, simpleSelection(start), sel_dontScroll) 6001 } 6002 6003 Doc.prototype = createObj(BranchChunk.prototype, { 6004 constructor: Doc, 6005 // Iterate over the document. Supports two forms -- with only one 6006 // argument, it calls that for each line in the document. With 6007 // three, it iterates over the range given by the first two (with 6008 // the second being non-inclusive). 6009 iter: function(from, to, op) { 6010 if (op) { this.iterN(from - this.first, to - from, op) } 6011 else { this.iterN(this.first, this.first + this.size, from) } 6012 }, 6013 6014 // Non-public interface for adding and removing lines. 6015 insert: function(at, lines) { 6016 var height = 0 6017 for (var i = 0; i < lines.length; ++i) { height += lines[i].height } 6018 this.insertInner(at - this.first, lines, height) 6019 }, 6020 remove: function(at, n) { this.removeInner(at - this.first, n) }, 6021 6022 // From here, the methods are part of the public interface. Most 6023 // are also available from CodeMirror (editor) instances. 6024 6025 getValue: function(lineSep) { 6026 var lines = getLines(this, this.first, this.first + this.size) 6027 if (lineSep === false) { return lines } 6028 return lines.join(lineSep || this.lineSeparator()) 6029 }, 6030 setValue: docMethodOp(function(code) { 6031 var top = Pos(this.first, 0), last = this.first + this.size - 1 6032 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), 6033 text: this.splitLines(code), origin: "setValue", full: true}, true) 6034 if (this.cm) { scrollToCoords(this.cm, 0, 0) } 6035 setSelection(this, simpleSelection(top), sel_dontScroll) 6036 }), 6037 replaceRange: function(code, from, to, origin) { 6038 from = clipPos(this, from) 6039 to = to ? clipPos(this, to) : from 6040 replaceRange(this, code, from, to, origin) 6041 }, 6042 getRange: function(from, to, lineSep) { 6043 var lines = getBetween(this, clipPos(this, from), clipPos(this, to)) 6044 if (lineSep === false) { return lines } 6045 return lines.join(lineSep || this.lineSeparator()) 6046 }, 6047 6048 getLine: function(line) {var l = this.getLineHandle(line); return l && l.text}, 6049 6050 getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }}, 6051 getLineNumber: function(line) {return lineNo(line)}, 6052 6053 getLineHandleVisualStart: function(line) { 6054 if (typeof line == "number") { line = getLine(this, line) } 6055 return visualLine(line) 6056 }, 6057 6058 lineCount: function() {return this.size}, 6059 firstLine: function() {return this.first}, 6060 lastLine: function() {return this.first + this.size - 1}, 6061 6062 clipPos: function(pos) {return clipPos(this, pos)}, 6063 6064 getCursor: function(start) { 6065 var range = this.sel.primary(), pos 6066 if (start == null || start == "head") { pos = range.head } 6067 else if (start == "anchor") { pos = range.anchor } 6068 else if (start == "end" || start == "to" || start === false) { pos = range.to() } 6069 else { pos = range.from() } 6070 return pos 6071 }, 6072 listSelections: function() { return this.sel.ranges }, 6073 somethingSelected: function() {return this.sel.somethingSelected()}, 6074 6075 setCursor: docMethodOp(function(line, ch, options) { 6076 setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options) 6077 }), 6078 setSelection: docMethodOp(function(anchor, head, options) { 6079 setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options) 6080 }), 6081 extendSelection: docMethodOp(function(head, other, options) { 6082 extendSelection(this, clipPos(this, head), other && clipPos(this, other), options) 6083 }), 6084 extendSelections: docMethodOp(function(heads, options) { 6085 extendSelections(this, clipPosArray(this, heads), options) 6086 }), 6087 extendSelectionsBy: docMethodOp(function(f, options) { 6088 var heads = map(this.sel.ranges, f) 6089 extendSelections(this, clipPosArray(this, heads), options) 6090 }), 6091 setSelections: docMethodOp(function(ranges, primary, options) { 6092 var this$1 = this; 6093 6094 if (!ranges.length) { return } 6095 var out = [] 6096 for (var i = 0; i < ranges.length; i++) 6097 { out[i] = new Range(clipPos(this$1, ranges[i].anchor), 6098 clipPos(this$1, ranges[i].head)) } 6099 if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex) } 6100 setSelection(this, normalizeSelection(out, primary), options) 6101 }), 6102 addSelection: docMethodOp(function(anchor, head, options) { 6103 var ranges = this.sel.ranges.slice(0) 6104 ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))) 6105 setSelection(this, normalizeSelection(ranges, ranges.length - 1), options) 6106 }), 6107 6108 getSelection: function(lineSep) { 6109 var this$1 = this; 6110 6111 var ranges = this.sel.ranges, lines 6112 for (var i = 0; i < ranges.length; i++) { 6113 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) 6114 lines = lines ? lines.concat(sel) : sel 6115 } 6116 if (lineSep === false) { return lines } 6117 else { return lines.join(lineSep || this.lineSeparator()) } 6118 }, 6119 getSelections: function(lineSep) { 6120 var this$1 = this; 6121 6122 var parts = [], ranges = this.sel.ranges 6123 for (var i = 0; i < ranges.length; i++) { 6124 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) 6125 if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()) } 6126 parts[i] = sel 6127 } 6128 return parts 6129 }, 6130 replaceSelection: function(code, collapse, origin) { 6131 var dup = [] 6132 for (var i = 0; i < this.sel.ranges.length; i++) 6133 { dup[i] = code } 6134 this.replaceSelections(dup, collapse, origin || "+input") 6135 }, 6136 replaceSelections: docMethodOp(function(code, collapse, origin) { 6137 var this$1 = this; 6138 6139 var changes = [], sel = this.sel 6140 for (var i = 0; i < sel.ranges.length; i++) { 6141 var range = sel.ranges[i] 6142 changes[i] = {from: range.from(), to: range.to(), text: this$1.splitLines(code[i]), origin: origin} 6143 } 6144 var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse) 6145 for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) 6146 { makeChange(this$1, changes[i$1]) } 6147 if (newSel) { setSelectionReplaceHistory(this, newSel) } 6148 else if (this.cm) { ensureCursorVisible(this.cm) } 6149 }), 6150 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}), 6151 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}), 6152 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true)}), 6153 redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true)}), 6154 6155 setExtending: function(val) {this.extend = val}, 6156 getExtending: function() {return this.extend}, 6157 6158 historySize: function() { 6159 var hist = this.history, done = 0, undone = 0 6160 for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done } } 6161 for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone } } 6162 return {undo: done, redo: undone} 6163 }, 6164 clearHistory: function() {this.history = new History(this.history.maxGeneration)}, 6165 6166 markClean: function() { 6167 this.cleanGeneration = this.changeGeneration(true) 6168 }, 6169 changeGeneration: function(forceSplit) { 6170 if (forceSplit) 6171 { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null } 6172 return this.history.generation 6173 }, 6174 isClean: function (gen) { 6175 return this.history.generation == (gen || this.cleanGeneration) 6176 }, 6177 6178 getHistory: function() { 6179 return {done: copyHistoryArray(this.history.done), 6180 undone: copyHistoryArray(this.history.undone)} 6181 }, 6182 setHistory: function(histData) { 6183 var hist = this.history = new History(this.history.maxGeneration) 6184 hist.done = copyHistoryArray(histData.done.slice(0), null, true) 6185 hist.undone = copyHistoryArray(histData.undone.slice(0), null, true) 6186 }, 6187 6188 setGutterMarker: docMethodOp(function(line, gutterID, value) { 6189 return changeLine(this, line, "gutter", function (line) { 6190 var markers = line.gutterMarkers || (line.gutterMarkers = {}) 6191 markers[gutterID] = value 6192 if (!value && isEmpty(markers)) { line.gutterMarkers = null } 6193 return true 6194 }) 6195 }), 6196 6197 clearGutter: docMethodOp(function(gutterID) { 6198 var this$1 = this; 6199 6200 this.iter(function (line) { 6201 if (line.gutterMarkers && line.gutterMarkers[gutterID]) { 6202 changeLine(this$1, line, "gutter", function () { 6203 line.gutterMarkers[gutterID] = null 6204 if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null } 6205 return true 6206 }) 6207 } 6208 }) 6209 }), 6210 6211 lineInfo: function(line) { 6212 var n 6213 if (typeof line == "number") { 6214 if (!isLine(this, line)) { return null } 6215 n = line 6216 line = getLine(this, line) 6217 if (!line) { return null } 6218 } else { 6219 n = lineNo(line) 6220 if (n == null) { return null } 6221 } 6222 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, 6223 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, 6224 widgets: line.widgets} 6225 }, 6226 6227 addLineClass: docMethodOp(function(handle, where, cls) { 6228 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { 6229 var prop = where == "text" ? "textClass" 6230 : where == "background" ? "bgClass" 6231 : where == "gutter" ? "gutterClass" : "wrapClass" 6232 if (!line[prop]) { line[prop] = cls } 6233 else if (classTest(cls).test(line[prop])) { return false } 6234 else { line[prop] += " " + cls } 6235 return true 6236 }) 6237 }), 6238 removeLineClass: docMethodOp(function(handle, where, cls) { 6239 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { 6240 var prop = where == "text" ? "textClass" 6241 : where == "background" ? "bgClass" 6242 : where == "gutter" ? "gutterClass" : "wrapClass" 6243 var cur = line[prop] 6244 if (!cur) { return false } 6245 else if (cls == null) { line[prop] = null } 6246 else { 6247 var found = cur.match(classTest(cls)) 6248 if (!found) { return false } 6249 var end = found.index + found[0].length 6250 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null 6251 } 6252 return true 6253 }) 6254 }), 6255 6256 addLineWidget: docMethodOp(function(handle, node, options) { 6257 return addLineWidget(this, handle, node, options) 6258 }), 6259 removeLineWidget: function(widget) { widget.clear() }, 6260 6261 markText: function(from, to, options) { 6262 return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") 6263 }, 6264 setBookmark: function(pos, options) { 6265 var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), 6266 insertLeft: options && options.insertLeft, 6267 clearWhenEmpty: false, shared: options && options.shared, 6268 handleMouseEvents: options && options.handleMouseEvents} 6269 pos = clipPos(this, pos) 6270 return markText(this, pos, pos, realOpts, "bookmark") 6271 }, 6272 findMarksAt: function(pos) { 6273 pos = clipPos(this, pos) 6274 var markers = [], spans = getLine(this, pos.line).markedSpans 6275 if (spans) { for (var i = 0; i < spans.length; ++i) { 6276 var span = spans[i] 6277 if ((span.from == null || span.from <= pos.ch) && 6278 (span.to == null || span.to >= pos.ch)) 6279 { markers.push(span.marker.parent || span.marker) } 6280 } } 6281 return markers 6282 }, 6283 findMarks: function(from, to, filter) { 6284 from = clipPos(this, from); to = clipPos(this, to) 6285 var found = [], lineNo = from.line 6286 this.iter(from.line, to.line + 1, function (line) { 6287 var spans = line.markedSpans 6288 if (spans) { for (var i = 0; i < spans.length; i++) { 6289 var span = spans[i] 6290 if (!(span.to != null && lineNo == from.line && from.ch >= span.to || 6291 span.from == null && lineNo != from.line || 6292 span.from != null && lineNo == to.line && span.from >= to.ch) && 6293 (!filter || filter(span.marker))) 6294 { found.push(span.marker.parent || span.marker) } 6295 } } 6296 ++lineNo 6297 }) 6298 return found 6299 }, 6300 getAllMarks: function() { 6301 var markers = [] 6302 this.iter(function (line) { 6303 var sps = line.markedSpans 6304 if (sps) { for (var i = 0; i < sps.length; ++i) 6305 { if (sps[i].from != null) { markers.push(sps[i].marker) } } } 6306 }) 6307 return markers 6308 }, 6309 6310 posFromIndex: function(off) { 6311 var ch, lineNo = this.first, sepSize = this.lineSeparator().length 6312 this.iter(function (line) { 6313 var sz = line.text.length + sepSize 6314 if (sz > off) { ch = off; return true } 6315 off -= sz 6316 ++lineNo 6317 }) 6318 return clipPos(this, Pos(lineNo, ch)) 6319 }, 6320 indexFromPos: function (coords) { 6321 coords = clipPos(this, coords) 6322 var index = coords.ch 6323 if (coords.line < this.first || coords.ch < 0) { return 0 } 6324 var sepSize = this.lineSeparator().length 6325 this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value 6326 index += line.text.length + sepSize 6327 }) 6328 return index 6329 }, 6330 6331 copy: function(copyHistory) { 6332 var doc = new Doc(getLines(this, this.first, this.first + this.size), 6333 this.modeOption, this.first, this.lineSep, this.direction) 6334 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft 6335 doc.sel = this.sel 6336 doc.extend = false 6337 if (copyHistory) { 6338 doc.history.undoDepth = this.history.undoDepth 6339 doc.setHistory(this.getHistory()) 6340 } 6341 return doc 6342 }, 6343 6344 linkedDoc: function(options) { 6345 if (!options) { options = {} } 6346 var from = this.first, to = this.first + this.size 6347 if (options.from != null && options.from > from) { from = options.from } 6348 if (options.to != null && options.to < to) { to = options.to } 6349 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction) 6350 if (options.sharedHist) { copy.history = this.history 6351 ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}) 6352 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}] 6353 copySharedMarkers(copy, findSharedMarkers(this)) 6354 return copy 6355 }, 6356 unlinkDoc: function(other) { 6357 var this$1 = this; 6358 6359 if (other instanceof CodeMirror) { other = other.doc } 6360 if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { 6361 var link = this$1.linked[i] 6362 if (link.doc != other) { continue } 6363 this$1.linked.splice(i, 1) 6364 other.unlinkDoc(this$1) 6365 detachSharedMarkers(findSharedMarkers(this$1)) 6366 break 6367 } } 6368 // If the histories were shared, split them again 6369 if (other.history == this.history) { 6370 var splitIds = [other.id] 6371 linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true) 6372 other.history = new History(null) 6373 other.history.done = copyHistoryArray(this.history.done, splitIds) 6374 other.history.undone = copyHistoryArray(this.history.undone, splitIds) 6375 } 6376 }, 6377 iterLinkedDocs: function(f) {linkedDocs(this, f)}, 6378 6379 getMode: function() {return this.mode}, 6380 getEditor: function() {return this.cm}, 6381 6382 splitLines: function(str) { 6383 if (this.lineSep) { return str.split(this.lineSep) } 6384 return splitLinesAuto(str) 6385 }, 6386 lineSeparator: function() { return this.lineSep || "\n" }, 6387 6388 setDirection: docMethodOp(function (dir) { 6389 if (dir != "rtl") { dir = "ltr" } 6390 if (dir == this.direction) { return } 6391 this.direction = dir 6392 this.iter(function (line) { return line.order = null; }) 6393 if (this.cm) { directionChanged(this.cm) } 6394 }) 6395 }) 6396 6397 // Public alias. 6398 Doc.prototype.eachLine = Doc.prototype.iter 6399 6400 // Kludge to work around strange IE behavior where it'll sometimes 6401 // re-fire a series of drag-related events right after the drop (#1551) 6402 var lastDrop = 0 6403 6404 function onDrop(e) { 6405 var cm = this 6406 clearDragCursor(cm) 6407 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) 6408 { return } 6409 e_preventDefault(e) 6410 if (ie) { lastDrop = +new Date } 6411 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files 6412 if (!pos || cm.isReadOnly()) { return } 6413 // Might be a file drop, in which case we simply extract the text 6414 // and insert it. 6415 if (files && files.length && window.FileReader && window.File) { 6416 var n = files.length, text = Array(n), read = 0 6417 var loadFile = function (file, i) { 6418 if (cm.options.allowDropFileTypes && 6419 indexOf(cm.options.allowDropFileTypes, file.type) == -1) 6420 { return } 6421 6422 var reader = new FileReader 6423 reader.onload = operation(cm, function () { 6424 var content = reader.result 6425 if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = "" } 6426 text[i] = content 6427 if (++read == n) { 6428 pos = clipPos(cm.doc, pos) 6429 var change = {from: pos, to: pos, 6430 text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), 6431 origin: "paste"} 6432 makeChange(cm.doc, change) 6433 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))) 6434 } 6435 }) 6436 reader.readAsText(file) 6437 } 6438 for (var i = 0; i < n; ++i) { loadFile(files[i], i) } 6439 } else { // Normal drop 6440 // Don't do a replace if the drop happened inside of the selected text. 6441 if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { 6442 cm.state.draggingText(e) 6443 // Ensure the editor is re-focused 6444 setTimeout(function () { return cm.display.input.focus(); }, 20) 6445 return 6446 } 6447 try { 6448 var text$1 = e.dataTransfer.getData("Text") 6449 if (text$1) { 6450 var selected 6451 if (cm.state.draggingText && !cm.state.draggingText.copy) 6452 { selected = cm.listSelections() } 6453 setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)) 6454 if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1) 6455 { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag") } } 6456 cm.replaceSelection(text$1, "around", "paste") 6457 cm.display.input.focus() 6458 } 6459 } 6460 catch(e){} 6461 } 6462 } 6463 6464 function onDragStart(cm, e) { 6465 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } 6466 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } 6467 6468 e.dataTransfer.setData("Text", cm.getSelection()) 6469 e.dataTransfer.effectAllowed = "copyMove" 6470 6471 // Use dummy image instead of default browsers image. 6472 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. 6473 if (e.dataTransfer.setDragImage && !safari) { 6474 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;") 6475 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" 6476 if (presto) { 6477 img.width = img.height = 1 6478 cm.display.wrapper.appendChild(img) 6479 // Force a relayout, or Opera won't use our image for some obscure reason 6480 img._top = img.offsetTop 6481 } 6482 e.dataTransfer.setDragImage(img, 0, 0) 6483 if (presto) { img.parentNode.removeChild(img) } 6484 } 6485 } 6486 6487 function onDragOver(cm, e) { 6488 var pos = posFromMouse(cm, e) 6489 if (!pos) { return } 6490 var frag = document.createDocumentFragment() 6491 drawSelectionCursor(cm, pos, frag) 6492 if (!cm.display.dragCursor) { 6493 cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors") 6494 cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv) 6495 } 6496 removeChildrenAndAdd(cm.display.dragCursor, frag) 6497 } 6498 6499 function clearDragCursor(cm) { 6500 if (cm.display.dragCursor) { 6501 cm.display.lineSpace.removeChild(cm.display.dragCursor) 6502 cm.display.dragCursor = null 6503 } 6504 } 6505 6506 // These must be handled carefully, because naively registering a 6507 // handler for each editor will cause the editors to never be 6508 // garbage collected. 6509 6510 function forEachCodeMirror(f) { 6511 if (!document.getElementsByClassName) { return } 6512 var byClass = document.getElementsByClassName("CodeMirror") 6513 for (var i = 0; i < byClass.length; i++) { 6514 var cm = byClass[i].CodeMirror 6515 if (cm) { f(cm) } 6516 } 6517 } 6518 6519 var globalsRegistered = false 6520 function ensureGlobalHandlers() { 6521 if (globalsRegistered) { return } 6522 registerGlobalHandlers() 6523 globalsRegistered = true 6524 } 6525 function registerGlobalHandlers() { 6526 // When the window resizes, we need to refresh active editors. 6527 var resizeTimer 6528 on(window, "resize", function () { 6529 if (resizeTimer == null) { resizeTimer = setTimeout(function () { 6530 resizeTimer = null 6531 forEachCodeMirror(onResize) 6532 }, 100) } 6533 }) 6534 // When the window loses focus, we want to show the editor as blurred 6535 on(window, "blur", function () { return forEachCodeMirror(onBlur); }) 6536 } 6537 // Called when the window resizes 6538 function onResize(cm) { 6539 var d = cm.display 6540 if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) 6541 { return } 6542 // Might be a text scaling operation, clear size caches. 6543 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null 6544 d.scrollbarsClipped = false 6545 cm.setSize() 6546 } 6547 6548 var keyNames = { 6549 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 6550 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 6551 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", 6552 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 6553 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", 6554 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 6555 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", 6556 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" 6557 } 6558 6559 // Number keys 6560 for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i) } 6561 // Alphabetic keys 6562 for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1) } 6563 // Function keys 6564 for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2 } 6565 6566 var keyMap = {} 6567 6568 keyMap.basic = { 6569 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", 6570 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", 6571 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", 6572 "Tab": "defaultTab", "Shift-Tab": "indentAuto", 6573 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", 6574 "Esc": "singleSelection" 6575 } 6576 // Note that the save and find-related commands aren't defined by 6577 // default. User code or addons can define them. Unknown commands 6578 // are simply ignored. 6579 keyMap.pcDefault = { 6580 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", 6581 "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", 6582 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", 6583 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", 6584 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", 6585 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", 6586 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", 6587 fallthrough: "basic" 6588 } 6589 // Very basic readline/emacs-style bindings, which are standard on Mac. 6590 keyMap.emacsy = { 6591 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", 6592 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", 6593 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", 6594 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", 6595 "Ctrl-O": "openLine" 6596 } 6597 keyMap.macDefault = { 6598 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", 6599 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", 6600 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", 6601 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", 6602 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", 6603 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", 6604 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", 6605 fallthrough: ["basic", "emacsy"] 6606 } 6607 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault 6608 6609 // KEYMAP DISPATCH 6610 6611 function normalizeKeyName(name) { 6612 var parts = name.split(/-(?!$)/) 6613 name = parts[parts.length - 1] 6614 var alt, ctrl, shift, cmd 6615 for (var i = 0; i < parts.length - 1; i++) { 6616 var mod = parts[i] 6617 if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true } 6618 else if (/^a(lt)?$/i.test(mod)) { alt = true } 6619 else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true } 6620 else if (/^s(hift)?$/i.test(mod)) { shift = true } 6621 else { throw new Error("Unrecognized modifier name: " + mod) } 6622 } 6623 if (alt) { name = "Alt-" + name } 6624 if (ctrl) { name = "Ctrl-" + name } 6625 if (cmd) { name = "Cmd-" + name } 6626 if (shift) { name = "Shift-" + name } 6627 return name 6628 } 6629 6630 // This is a kludge to keep keymaps mostly working as raw objects 6631 // (backwards compatibility) while at the same time support features 6632 // like normalization and multi-stroke key bindings. It compiles a 6633 // new normalized keymap, and then updates the old object to reflect 6634 // this. 6635 function normalizeKeyMap(keymap) { 6636 var copy = {} 6637 for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) { 6638 var value = keymap[keyname] 6639 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue } 6640 if (value == "...") { delete keymap[keyname]; continue } 6641 6642 var keys = map(keyname.split(" "), normalizeKeyName) 6643 for (var i = 0; i < keys.length; i++) { 6644 var val = (void 0), name = (void 0) 6645 if (i == keys.length - 1) { 6646 name = keys.join(" ") 6647 val = value 6648 } else { 6649 name = keys.slice(0, i + 1).join(" ") 6650 val = "..." 6651 } 6652 var prev = copy[name] 6653 if (!prev) { copy[name] = val } 6654 else if (prev != val) { throw new Error("Inconsistent bindings for " + name) } 6655 } 6656 delete keymap[keyname] 6657 } } 6658 for (var prop in copy) { keymap[prop] = copy[prop] } 6659 return keymap 6660 } 6661 6662 function lookupKey(key, map, handle, context) { 6663 map = getKeyMap(map) 6664 var found = map.call ? map.call(key, context) : map[key] 6665 if (found === false) { return "nothing" } 6666 if (found === "...") { return "multi" } 6667 if (found != null && handle(found)) { return "handled" } 6668 6669 if (map.fallthrough) { 6670 if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") 6671 { return lookupKey(key, map.fallthrough, handle, context) } 6672 for (var i = 0; i < map.fallthrough.length; i++) { 6673 var result = lookupKey(key, map.fallthrough[i], handle, context) 6674 if (result) { return result } 6675 } 6676 } 6677 } 6678 6679 // Modifier key presses don't count as 'real' key presses for the 6680 // purpose of keymap fallthrough. 6681 function isModifierKey(value) { 6682 var name = typeof value == "string" ? value : keyNames[value.keyCode] 6683 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" 6684 } 6685 6686 function addModifierNames(name, event, noShift) { 6687 var base = name 6688 if (event.altKey && base != "Alt") { name = "Alt-" + name } 6689 if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name } 6690 if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name } 6691 if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name } 6692 return name 6693 } 6694 6695 // Look up the name of a key as indicated by an event object. 6696 function keyName(event, noShift) { 6697 if (presto && event.keyCode == 34 && event["char"]) { return false } 6698 var name = keyNames[event.keyCode] 6699 if (name == null || event.altGraphKey) { return false } 6700 return addModifierNames(name, event, noShift) 6701 } 6702 6703 function getKeyMap(val) { 6704 return typeof val == "string" ? keyMap[val] : val 6705 } 6706 6707 // Helper for deleting text near the selection(s), used to implement 6708 // backspace, delete, and similar functionality. 6709 function deleteNearSelection(cm, compute) { 6710 var ranges = cm.doc.sel.ranges, kill = [] 6711 // Build up a set of ranges to kill first, merging overlapping 6712 // ranges. 6713 for (var i = 0; i < ranges.length; i++) { 6714 var toKill = compute(ranges[i]) 6715 while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { 6716 var replaced = kill.pop() 6717 if (cmp(replaced.from, toKill.from) < 0) { 6718 toKill.from = replaced.from 6719 break 6720 } 6721 } 6722 kill.push(toKill) 6723 } 6724 // Next, remove those actual ranges. 6725 runInOp(cm, function () { 6726 for (var i = kill.length - 1; i >= 0; i--) 6727 { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete") } 6728 ensureCursorVisible(cm) 6729 }) 6730 } 6731 6732 function moveCharLogically(line, ch, dir) { 6733 var target = skipExtendingChars(line.text, ch + dir, dir) 6734 return target < 0 || target > line.text.length ? null : target 6735 } 6736 6737 function moveLogically(line, start, dir) { 6738 var ch = moveCharLogically(line, start.ch, dir) 6739 return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") 6740 } 6741 6742 function endOfLine(visually, cm, lineObj, lineNo, dir) { 6743 if (visually) { 6744 var order = getOrder(lineObj, cm.doc.direction) 6745 if (order) { 6746 var part = dir < 0 ? lst(order) : order[0] 6747 var moveInStorageOrder = (dir < 0) == (part.level == 1) 6748 var sticky = moveInStorageOrder ? "after" : "before" 6749 var ch 6750 // With a wrapped rtl chunk (possibly spanning multiple bidi parts), 6751 // it could be that the last bidi part is not on the last visual line, 6752 // since visual lines contain content order-consecutive chunks. 6753 // Thus, in rtl, we are looking for the first (content-order) character 6754 // in the rtl chunk that is on the last line (that is, the same line 6755 // as the last (content-order) character). 6756 if (part.level > 0) { 6757 var prep = prepareMeasureForLine(cm, lineObj) 6758 ch = dir < 0 ? lineObj.text.length - 1 : 0 6759 var targetTop = measureCharPrepared(cm, prep, ch).top 6760 ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch) 6761 if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1) } 6762 } else { ch = dir < 0 ? part.to : part.from } 6763 return new Pos(lineNo, ch, sticky) 6764 } 6765 } 6766 return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") 6767 } 6768 6769 function moveVisually(cm, line, start, dir) { 6770 var bidi = getOrder(line, cm.doc.direction) 6771 if (!bidi) { return moveLogically(line, start, dir) } 6772 if (start.ch >= line.text.length) { 6773 start.ch = line.text.length 6774 start.sticky = "before" 6775 } else if (start.ch <= 0) { 6776 start.ch = 0 6777 start.sticky = "after" 6778 } 6779 var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos] 6780 if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { 6781 // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, 6782 // nothing interesting happens. 6783 return moveLogically(line, start, dir) 6784 } 6785 6786 var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); } 6787 var prep 6788 var getWrappedLineExtent = function (ch) { 6789 if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } 6790 prep = prep || prepareMeasureForLine(cm, line) 6791 return wrappedLineExtentChar(cm, line, prep, ch) 6792 } 6793 var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch) 6794 6795 if (cm.doc.direction == "rtl" || part.level == 1) { 6796 var moveInStorageOrder = (part.level == 1) == (dir < 0) 6797 var ch = mv(start, moveInStorageOrder ? 1 : -1) 6798 if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { 6799 // Case 2: We move within an rtl part or in an rtl editor on the same visual line 6800 var sticky = moveInStorageOrder ? "before" : "after" 6801 return new Pos(start.line, ch, sticky) 6802 } 6803 } 6804 6805 // Case 3: Could not move within this bidi part in this visual line, so leave 6806 // the current bidi part 6807 6808 var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { 6809 var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder 6810 ? new Pos(start.line, mv(ch, 1), "before") 6811 : new Pos(start.line, ch, "after"); } 6812 6813 for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { 6814 var part = bidi[partPos] 6815 var moveInStorageOrder = (dir > 0) == (part.level != 1) 6816 var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1) 6817 if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) } 6818 ch = moveInStorageOrder ? part.from : mv(part.to, -1) 6819 if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } 6820 } 6821 } 6822 6823 // Case 3a: Look for other bidi parts on the same visual line 6824 var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent) 6825 if (res) { return res } 6826 6827 // Case 3b: Look for other bidi parts on the next visual line 6828 var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1) 6829 if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { 6830 res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)) 6831 if (res) { return res } 6832 } 6833 6834 // Case 4: Nowhere to move 6835 return null 6836 } 6837 6838 // Commands are parameter-less actions that can be performed on an 6839 // editor, mostly used for keybindings. 6840 var commands = { 6841 selectAll: selectAll, 6842 singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); }, 6843 killLine: function (cm) { return deleteNearSelection(cm, function (range) { 6844 if (range.empty()) { 6845 var len = getLine(cm.doc, range.head.line).text.length 6846 if (range.head.ch == len && range.head.line < cm.lastLine()) 6847 { return {from: range.head, to: Pos(range.head.line + 1, 0)} } 6848 else 6849 { return {from: range.head, to: Pos(range.head.line, len)} } 6850 } else { 6851 return {from: range.from(), to: range.to()} 6852 } 6853 }); }, 6854 deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({ 6855 from: Pos(range.from().line, 0), 6856 to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) 6857 }); }); }, 6858 delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({ 6859 from: Pos(range.from().line, 0), to: range.from() 6860 }); }); }, 6861 delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { 6862 var top = cm.charCoords(range.head, "div").top + 5 6863 var leftPos = cm.coordsChar({left: 0, top: top}, "div") 6864 return {from: leftPos, to: range.from()} 6865 }); }, 6866 delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) { 6867 var top = cm.charCoords(range.head, "div").top + 5 6868 var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") 6869 return {from: range.from(), to: rightPos } 6870 }); }, 6871 undo: function (cm) { return cm.undo(); }, 6872 redo: function (cm) { return cm.redo(); }, 6873 undoSelection: function (cm) { return cm.undoSelection(); }, 6874 redoSelection: function (cm) { return cm.redoSelection(); }, 6875 goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); }, 6876 goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); }, 6877 goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); }, 6878 {origin: "+move", bias: 1} 6879 ); }, 6880 goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); }, 6881 {origin: "+move", bias: 1} 6882 ); }, 6883 goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); }, 6884 {origin: "+move", bias: -1} 6885 ); }, 6886 goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { 6887 var top = cm.cursorCoords(range.head, "div").top + 5 6888 return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") 6889 }, sel_move); }, 6890 goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { 6891 var top = cm.cursorCoords(range.head, "div").top + 5 6892 return cm.coordsChar({left: 0, top: top}, "div") 6893 }, sel_move); }, 6894 goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) { 6895 var top = cm.cursorCoords(range.head, "div").top + 5 6896 var pos = cm.coordsChar({left: 0, top: top}, "div") 6897 if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) } 6898 return pos 6899 }, sel_move); }, 6900 goLineUp: function (cm) { return cm.moveV(-1, "line"); }, 6901 goLineDown: function (cm) { return cm.moveV(1, "line"); }, 6902 goPageUp: function (cm) { return cm.moveV(-1, "page"); }, 6903 goPageDown: function (cm) { return cm.moveV(1, "page"); }, 6904 goCharLeft: function (cm) { return cm.moveH(-1, "char"); }, 6905 goCharRight: function (cm) { return cm.moveH(1, "char"); }, 6906 goColumnLeft: function (cm) { return cm.moveH(-1, "column"); }, 6907 goColumnRight: function (cm) { return cm.moveH(1, "column"); }, 6908 goWordLeft: function (cm) { return cm.moveH(-1, "word"); }, 6909 goGroupRight: function (cm) { return cm.moveH(1, "group"); }, 6910 goGroupLeft: function (cm) { return cm.moveH(-1, "group"); }, 6911 goWordRight: function (cm) { return cm.moveH(1, "word"); }, 6912 delCharBefore: function (cm) { return cm.deleteH(-1, "char"); }, 6913 delCharAfter: function (cm) { return cm.deleteH(1, "char"); }, 6914 delWordBefore: function (cm) { return cm.deleteH(-1, "word"); }, 6915 delWordAfter: function (cm) { return cm.deleteH(1, "word"); }, 6916 delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); }, 6917 delGroupAfter: function (cm) { return cm.deleteH(1, "group"); }, 6918 indentAuto: function (cm) { return cm.indentSelection("smart"); }, 6919 indentMore: function (cm) { return cm.indentSelection("add"); }, 6920 indentLess: function (cm) { return cm.indentSelection("subtract"); }, 6921 insertTab: function (cm) { return cm.replaceSelection("\t"); }, 6922 insertSoftTab: function (cm) { 6923 var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize 6924 for (var i = 0; i < ranges.length; i++) { 6925 var pos = ranges[i].from() 6926 var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize) 6927 spaces.push(spaceStr(tabSize - col % tabSize)) 6928 } 6929 cm.replaceSelections(spaces) 6930 }, 6931 defaultTab: function (cm) { 6932 if (cm.somethingSelected()) { cm.indentSelection("add") } 6933 else { cm.execCommand("insertTab") } 6934 }, 6935 // Swap the two chars left and right of each selection's head. 6936 // Move cursor behind the two swapped characters afterwards. 6937 // 6938 // Doesn't consider line feeds a character. 6939 // Doesn't scan more than one line above to find a character. 6940 // Doesn't do anything on an empty line. 6941 // Doesn't do anything with non-empty selections. 6942 transposeChars: function (cm) { return runInOp(cm, function () { 6943 var ranges = cm.listSelections(), newSel = [] 6944 for (var i = 0; i < ranges.length; i++) { 6945 if (!ranges[i].empty()) { continue } 6946 var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text 6947 if (line) { 6948 if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1) } 6949 if (cur.ch > 0) { 6950 cur = new Pos(cur.line, cur.ch + 1) 6951 cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), 6952 Pos(cur.line, cur.ch - 2), cur, "+transpose") 6953 } else if (cur.line > cm.doc.first) { 6954 var prev = getLine(cm.doc, cur.line - 1).text 6955 if (prev) { 6956 cur = new Pos(cur.line, 1) 6957 cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + 6958 prev.charAt(prev.length - 1), 6959 Pos(cur.line - 1, prev.length - 1), cur, "+transpose") 6960 } 6961 } 6962 } 6963 newSel.push(new Range(cur, cur)) 6964 } 6965 cm.setSelections(newSel) 6966 }); }, 6967 newlineAndIndent: function (cm) { return runInOp(cm, function () { 6968 var sels = cm.listSelections() 6969 for (var i = sels.length - 1; i >= 0; i--) 6970 { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input") } 6971 sels = cm.listSelections() 6972 for (var i$1 = 0; i$1 < sels.length; i$1++) 6973 { cm.indentLine(sels[i$1].from().line, null, true) } 6974 ensureCursorVisible(cm) 6975 }); }, 6976 openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, 6977 toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } 6978 } 6979 6980 6981 function lineStart(cm, lineN) { 6982 var line = getLine(cm.doc, lineN) 6983 var visual = visualLine(line) 6984 if (visual != line) { lineN = lineNo(visual) } 6985 return endOfLine(true, cm, visual, lineN, 1) 6986 } 6987 function lineEnd(cm, lineN) { 6988 var line = getLine(cm.doc, lineN) 6989 var visual = visualLineEnd(line) 6990 if (visual != line) { lineN = lineNo(visual) } 6991 return endOfLine(true, cm, line, lineN, -1) 6992 } 6993 function lineStartSmart(cm, pos) { 6994 var start = lineStart(cm, pos.line) 6995 var line = getLine(cm.doc, start.line) 6996 var order = getOrder(line, cm.doc.direction) 6997 if (!order || order[0].level == 0) { 6998 var firstNonWS = Math.max(0, line.text.search(/\S/)) 6999 var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch 7000 return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) 7001 } 7002 return start 7003 } 7004 7005 // Run a handler that was bound to a key. 7006 function doHandleBinding(cm, bound, dropShift) { 7007 if (typeof bound == "string") { 7008 bound = commands[bound] 7009 if (!bound) { return false } 7010 } 7011 // Ensure previous input has been read, so that the handler sees a 7012 // consistent view of the document 7013 cm.display.input.ensurePolled() 7014 var prevShift = cm.display.shift, done = false 7015 try { 7016 if (cm.isReadOnly()) { cm.state.suppressEdits = true } 7017 if (dropShift) { cm.display.shift = false } 7018 done = bound(cm) != Pass 7019 } finally { 7020 cm.display.shift = prevShift 7021 cm.state.suppressEdits = false 7022 } 7023 return done 7024 } 7025 7026 function lookupKeyForEditor(cm, name, handle) { 7027 for (var i = 0; i < cm.state.keyMaps.length; i++) { 7028 var result = lookupKey(name, cm.state.keyMaps[i], handle, cm) 7029 if (result) { return result } 7030 } 7031 return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) 7032 || lookupKey(name, cm.options.keyMap, handle, cm) 7033 } 7034 7035 // Note that, despite the name, this function is also used to check 7036 // for bound mouse clicks. 7037 7038 var stopSeq = new Delayed 7039 function dispatchKey(cm, name, e, handle) { 7040 var seq = cm.state.keySeq 7041 if (seq) { 7042 if (isModifierKey(name)) { return "handled" } 7043 stopSeq.set(50, function () { 7044 if (cm.state.keySeq == seq) { 7045 cm.state.keySeq = null 7046 cm.display.input.reset() 7047 } 7048 }) 7049 name = seq + " " + name 7050 } 7051 var result = lookupKeyForEditor(cm, name, handle) 7052 7053 if (result == "multi") 7054 { cm.state.keySeq = name } 7055 if (result == "handled") 7056 { signalLater(cm, "keyHandled", cm, name, e) } 7057 7058 if (result == "handled" || result == "multi") { 7059 e_preventDefault(e) 7060 restartBlink(cm) 7061 } 7062 7063 if (seq && !result && /\'$/.test(name)) { 7064 e_preventDefault(e) 7065 return true 7066 } 7067 return !!result 7068 } 7069 7070 // Handle a key from the keydown event. 7071 function handleKeyBinding(cm, e) { 7072 var name = keyName(e, true) 7073 if (!name) { return false } 7074 7075 if (e.shiftKey && !cm.state.keySeq) { 7076 // First try to resolve full name (including 'Shift-'). Failing 7077 // that, see if there is a cursor-motion command (starting with 7078 // 'go') bound to the keyname without 'Shift-'. 7079 return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); }) 7080 || dispatchKey(cm, name, e, function (b) { 7081 if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) 7082 { return doHandleBinding(cm, b) } 7083 }) 7084 } else { 7085 return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); }) 7086 } 7087 } 7088 7089 // Handle a key from the keypress event 7090 function handleCharBinding(cm, e, ch) { 7091 return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); }) 7092 } 7093 7094 var lastStoppedKey = null 7095 function onKeyDown(e) { 7096 var cm = this 7097 cm.curOp.focus = activeElt() 7098 if (signalDOMEvent(cm, e)) { return } 7099 // IE does strange things with escape. 7100 if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false } 7101 var code = e.keyCode 7102 cm.display.shift = code == 16 || e.shiftKey 7103 var handled = handleKeyBinding(cm, e) 7104 if (presto) { 7105 lastStoppedKey = handled ? code : null 7106 // Opera has no cut event... we try to at least catch the key combo 7107 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) 7108 { cm.replaceSelection("", null, "cut") } 7109 } 7110 7111 // Turn mouse into crosshair when Alt is held on Mac. 7112 if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) 7113 { showCrossHair(cm) } 7114 } 7115 7116 function showCrossHair(cm) { 7117 var lineDiv = cm.display.lineDiv 7118 addClass(lineDiv, "CodeMirror-crosshair") 7119 7120 function up(e) { 7121 if (e.keyCode == 18 || !e.altKey) { 7122 rmClass(lineDiv, "CodeMirror-crosshair") 7123 off(document, "keyup", up) 7124 off(document, "mouseover", up) 7125 } 7126 } 7127 on(document, "keyup", up) 7128 on(document, "mouseover", up) 7129 } 7130 7131 function onKeyUp(e) { 7132 if (e.keyCode == 16) { this.doc.sel.shift = false } 7133 signalDOMEvent(this, e) 7134 } 7135 7136 function onKeyPress(e) { 7137 var cm = this 7138 if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return } 7139 var keyCode = e.keyCode, charCode = e.charCode 7140 if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} 7141 if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return } 7142 var ch = String.fromCharCode(charCode == null ? keyCode : charCode) 7143 // Some browsers fire keypress events for backspace 7144 if (ch == "\x08") { return } 7145 if (handleCharBinding(cm, e, ch)) { return } 7146 cm.display.input.onKeyPress(e) 7147 } 7148 7149 var DOUBLECLICK_DELAY = 400 7150 7151 var PastClick = function(time, pos, button) { 7152 this.time = time 7153 this.pos = pos 7154 this.button = button 7155 }; 7156 7157 PastClick.prototype.compare = function (time, pos, button) { 7158 return this.time + DOUBLECLICK_DELAY > time && 7159 cmp(pos, this.pos) == 0 && button == this.button 7160 }; 7161 7162 var lastClick; 7163 var lastDoubleClick; 7164 function clickRepeat(pos, button) { 7165 var now = +new Date 7166 if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { 7167 lastClick = lastDoubleClick = null 7168 return "triple" 7169 } else if (lastClick && lastClick.compare(now, pos, button)) { 7170 lastDoubleClick = new PastClick(now, pos, button) 7171 lastClick = null 7172 return "double" 7173 } else { 7174 lastClick = new PastClick(now, pos, button) 7175 lastDoubleClick = null 7176 return "single" 7177 } 7178 } 7179 7180 // A mouse down can be a single click, double click, triple click, 7181 // start of selection drag, start of text drag, new cursor 7182 // (ctrl-click), rectangle drag (alt-drag), or xwin 7183 // middle-click-paste. Or it might be a click on something we should 7184 // not interfere with, such as a scrollbar or widget. 7185 function onMouseDown(e) { 7186 var cm = this, display = cm.display 7187 if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return } 7188 display.input.ensurePolled() 7189 display.shift = e.shiftKey 7190 7191 if (eventInWidget(display, e)) { 7192 if (!webkit) { 7193 // Briefly turn off draggability, to allow widgets to do 7194 // normal dragging things. 7195 display.scroller.draggable = false 7196 setTimeout(function () { return display.scroller.draggable = true; }, 100) 7197 } 7198 return 7199 } 7200 if (clickInGutter(cm, e)) { return } 7201 var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single" 7202 window.focus() 7203 7204 // #3261: make sure, that we're not starting a second selection 7205 if (button == 1 && cm.state.selectingText) 7206 { cm.state.selectingText(e) } 7207 7208 if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return } 7209 7210 if (button == 1) { 7211 if (pos) { leftButtonDown(cm, pos, repeat, e) } 7212 else if (e_target(e) == display.scroller) { e_preventDefault(e) } 7213 } else if (button == 2) { 7214 if (pos) { extendSelection(cm.doc, pos) } 7215 setTimeout(function () { return display.input.focus(); }, 20) 7216 } else if (button == 3) { 7217 if (captureRightClick) { onContextMenu(cm, e) } 7218 else { delayBlurEvent(cm) } 7219 } 7220 } 7221 7222 function handleMappedButton(cm, button, pos, repeat, event) { 7223 var name = "Click" 7224 if (repeat == "double") { name = "Double" + name } 7225 else if (repeat == "triple") { name = "Triple" + name } 7226 name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name 7227 7228 return dispatchKey(cm, addModifierNames(name, event), event, function (bound) { 7229 if (typeof bound == "string") { bound = commands[bound] } 7230 if (!bound) { return false } 7231 var done = false 7232 try { 7233 if (cm.isReadOnly()) { cm.state.suppressEdits = true } 7234 done = bound(cm, pos) != Pass 7235 } finally { 7236 cm.state.suppressEdits = false 7237 } 7238 return done 7239 }) 7240 } 7241 7242 function configureMouse(cm, repeat, event) { 7243 var option = cm.getOption("configureMouse") 7244 var value = option ? option(cm, repeat, event) : {} 7245 if (value.unit == null) { 7246 var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey 7247 value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line" 7248 } 7249 if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey } 7250 if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey } 7251 if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey) } 7252 return value 7253 } 7254 7255 function leftButtonDown(cm, pos, repeat, event) { 7256 if (ie) { setTimeout(bind(ensureFocus, cm), 0) } 7257 else { cm.curOp.focus = activeElt() } 7258 7259 var behavior = configureMouse(cm, repeat, event) 7260 7261 var sel = cm.doc.sel, contained 7262 if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && 7263 repeat == "single" && (contained = sel.contains(pos)) > -1 && 7264 (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && 7265 (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) 7266 { leftButtonStartDrag(cm, event, pos, behavior) } 7267 else 7268 { leftButtonSelect(cm, event, pos, behavior) } 7269 } 7270 7271 // Start a text drag. When it ends, see if any dragging actually 7272 // happen, and treat as a click if it didn't. 7273 function leftButtonStartDrag(cm, event, pos, behavior) { 7274 var display = cm.display, moved = false 7275 var dragEnd = operation(cm, function (e) { 7276 if (webkit) { display.scroller.draggable = false } 7277 cm.state.draggingText = false 7278 off(document, "mouseup", dragEnd) 7279 off(document, "mousemove", mouseMove) 7280 off(display.scroller, "dragstart", dragStart) 7281 off(display.scroller, "drop", dragEnd) 7282 if (!moved) { 7283 e_preventDefault(e) 7284 if (!behavior.addNew) 7285 { extendSelection(cm.doc, pos, null, null, behavior.extend) } 7286 // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) 7287 if (webkit || ie && ie_version == 9) 7288 { setTimeout(function () {document.body.focus(); display.input.focus()}, 20) } 7289 else 7290 { display.input.focus() } 7291 } 7292 }) 7293 var mouseMove = function(e2) { 7294 moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10 7295 } 7296 var dragStart = function () { return moved = true; } 7297 // Let the drag handler handle this. 7298 if (webkit) { display.scroller.draggable = true } 7299 cm.state.draggingText = dragEnd 7300 dragEnd.copy = !behavior.moveOnDrag 7301 // IE's approach to draggable 7302 if (display.scroller.dragDrop) { display.scroller.dragDrop() } 7303 on(document, "mouseup", dragEnd) 7304 on(document, "mousemove", mouseMove) 7305 on(display.scroller, "dragstart", dragStart) 7306 on(display.scroller, "drop", dragEnd) 7307 7308 delayBlurEvent(cm) 7309 setTimeout(function () { return display.input.focus(); }, 20) 7310 } 7311 7312 function rangeForUnit(cm, pos, unit) { 7313 if (unit == "char") { return new Range(pos, pos) } 7314 if (unit == "word") { return cm.findWordAt(pos) } 7315 if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } 7316 var result = unit(cm, pos) 7317 return new Range(result.from, result.to) 7318 } 7319 7320 // Normal selection, as opposed to text dragging. 7321 function leftButtonSelect(cm, event, start, behavior) { 7322 var display = cm.display, doc = cm.doc 7323 e_preventDefault(event) 7324 7325 var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges 7326 if (behavior.addNew && !behavior.extend) { 7327 ourIndex = doc.sel.contains(start) 7328 if (ourIndex > -1) 7329 { ourRange = ranges[ourIndex] } 7330 else 7331 { ourRange = new Range(start, start) } 7332 } else { 7333 ourRange = doc.sel.primary() 7334 ourIndex = doc.sel.primIndex 7335 } 7336 7337 if (behavior.unit == "rectangle") { 7338 if (!behavior.addNew) { ourRange = new Range(start, start) } 7339 start = posFromMouse(cm, event, true, true) 7340 ourIndex = -1 7341 } else { 7342 var range = rangeForUnit(cm, start, behavior.unit) 7343 if (behavior.extend) 7344 { ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend) } 7345 else 7346 { ourRange = range } 7347 } 7348 7349 if (!behavior.addNew) { 7350 ourIndex = 0 7351 setSelection(doc, new Selection([ourRange], 0), sel_mouse) 7352 startSel = doc.sel 7353 } else if (ourIndex == -1) { 7354 ourIndex = ranges.length 7355 setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), 7356 {scroll: false, origin: "*mouse"}) 7357 } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { 7358 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), 7359 {scroll: false, origin: "*mouse"}) 7360 startSel = doc.sel 7361 } else { 7362 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse) 7363 } 7364 7365 var lastPos = start 7366 function extendTo(pos) { 7367 if (cmp(lastPos, pos) == 0) { return } 7368 lastPos = pos 7369 7370 if (behavior.unit == "rectangle") { 7371 var ranges = [], tabSize = cm.options.tabSize 7372 var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize) 7373 var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize) 7374 var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol) 7375 for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); 7376 line <= end; line++) { 7377 var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize) 7378 if (left == right) 7379 { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))) } 7380 else if (text.length > leftPos) 7381 { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))) } 7382 } 7383 if (!ranges.length) { ranges.push(new Range(start, start)) } 7384 setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), 7385 {origin: "*mouse", scroll: false}) 7386 cm.scrollIntoView(pos) 7387 } else { 7388 var oldRange = ourRange 7389 var range = rangeForUnit(cm, pos, behavior.unit) 7390 var anchor = oldRange.anchor, head 7391 if (cmp(range.anchor, anchor) > 0) { 7392 head = range.head 7393 anchor = minPos(oldRange.from(), range.anchor) 7394 } else { 7395 head = range.anchor 7396 anchor = maxPos(oldRange.to(), range.head) 7397 } 7398 var ranges$1 = startSel.ranges.slice(0) 7399 ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head)) 7400 setSelection(doc, normalizeSelection(ranges$1, ourIndex), sel_mouse) 7401 } 7402 } 7403 7404 var editorSize = display.wrapper.getBoundingClientRect() 7405 // Used to ensure timeout re-tries don't fire when another extend 7406 // happened in the meantime (clearTimeout isn't reliable -- at 7407 // least on Chrome, the timeouts still happen even when cleared, 7408 // if the clear happens after their scheduled firing time). 7409 var counter = 0 7410 7411 function extend(e) { 7412 var curCount = ++counter 7413 var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle") 7414 if (!cur) { return } 7415 if (cmp(cur, lastPos) != 0) { 7416 cm.curOp.focus = activeElt() 7417 extendTo(cur) 7418 var visible = visibleLines(display, doc) 7419 if (cur.line >= visible.to || cur.line < visible.from) 7420 { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e) }}), 150) } 7421 } else { 7422 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0 7423 if (outside) { setTimeout(operation(cm, function () { 7424 if (counter != curCount) { return } 7425 display.scroller.scrollTop += outside 7426 extend(e) 7427 }), 50) } 7428 } 7429 } 7430 7431 function done(e) { 7432 cm.state.selectingText = false 7433 counter = Infinity 7434 e_preventDefault(e) 7435 display.input.focus() 7436 off(document, "mousemove", move) 7437 off(document, "mouseup", up) 7438 doc.history.lastSelOrigin = null 7439 } 7440 7441 var move = operation(cm, function (e) { 7442 if (!e_button(e)) { done(e) } 7443 else { extend(e) } 7444 }) 7445 var up = operation(cm, done) 7446 cm.state.selectingText = up 7447 on(document, "mousemove", move) 7448 on(document, "mouseup", up) 7449 } 7450 7451 // Used when mouse-selecting to adjust the anchor to the proper side 7452 // of a bidi jump depending on the visual position of the head. 7453 function bidiSimplify(cm, range) { 7454 var anchor = range.anchor; 7455 var head = range.head; 7456 var anchorLine = getLine(cm.doc, anchor.line) 7457 if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range } 7458 var order = getOrder(anchorLine) 7459 if (!order) { return range } 7460 var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index] 7461 if (part.from != anchor.ch && part.to != anchor.ch) { return range } 7462 var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1) 7463 if (boundary == 0 || boundary == order.length) { return range } 7464 7465 // Compute the relative visual position of the head compared to the 7466 // anchor (<0 is to the left, >0 to the right) 7467 var leftSide 7468 if (head.line != anchor.line) { 7469 leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0 7470 } else { 7471 var headIndex = getBidiPartAt(order, head.ch, head.sticky) 7472 var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1) 7473 if (headIndex == boundary - 1 || headIndex == boundary) 7474 { leftSide = dir < 0 } 7475 else 7476 { leftSide = dir > 0 } 7477 } 7478 7479 var usePart = order[boundary + (leftSide ? -1 : 0)] 7480 var from = leftSide == (usePart.level == 1) 7481 var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before" 7482 return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head) 7483 } 7484 7485 7486 // Determines whether an event happened in the gutter, and fires the 7487 // handlers for the corresponding event. 7488 function gutterEvent(cm, e, type, prevent) { 7489 var mX, mY 7490 if (e.touches) { 7491 mX = e.touches[0].clientX 7492 mY = e.touches[0].clientY 7493 } else { 7494 try { mX = e.clientX; mY = e.clientY } 7495 catch(e) { return false } 7496 } 7497 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false } 7498 if (prevent) { e_preventDefault(e) } 7499 7500 var display = cm.display 7501 var lineBox = display.lineDiv.getBoundingClientRect() 7502 7503 if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) } 7504 mY -= lineBox.top - display.viewOffset 7505 7506 for (var i = 0; i < cm.options.gutters.length; ++i) { 7507 var g = display.gutters.childNodes[i] 7508 if (g && g.getBoundingClientRect().right >= mX) { 7509 var line = lineAtHeight(cm.doc, mY) 7510 var gutter = cm.options.gutters[i] 7511 signal(cm, type, cm, line, gutter, e) 7512 return e_defaultPrevented(e) 7513 } 7514 } 7515 } 7516 7517 function clickInGutter(cm, e) { 7518 return gutterEvent(cm, e, "gutterClick", true) 7519 } 7520 7521 // CONTEXT MENU HANDLING 7522 7523 // To make the context menu work, we need to briefly unhide the 7524 // textarea (making it as unobtrusive as possible) to let the 7525 // right-click take effect on it. 7526 function onContextMenu(cm, e) { 7527 if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } 7528 if (signalDOMEvent(cm, e, "contextmenu")) { return } 7529 cm.display.input.onContextMenu(e) 7530 } 7531 7532 function contextMenuInGutter(cm, e) { 7533 if (!hasHandler(cm, "gutterContextMenu")) { return false } 7534 return gutterEvent(cm, e, "gutterContextMenu", false) 7535 } 7536 7537 function themeChanged(cm) { 7538 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + 7539 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-") 7540 clearCaches(cm) 7541 } 7542 7543 var Init = {toString: function(){return "CodeMirror.Init"}} 7544 7545 var defaults = {} 7546 var optionHandlers = {} 7547 7548 function defineOptions(CodeMirror) { 7549 var optionHandlers = CodeMirror.optionHandlers 7550 7551 function option(name, deflt, handle, notOnInit) { 7552 CodeMirror.defaults[name] = deflt 7553 if (handle) { optionHandlers[name] = 7554 notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old) }} : handle } 7555 } 7556 7557 CodeMirror.defineOption = option 7558 7559 // Passed to option handlers when there is no old value. 7560 CodeMirror.Init = Init 7561 7562 // These two are, on init, called from the constructor because they 7563 // have to be initialized before the editor can start at all. 7564 option("value", "", function (cm, val) { return cm.setValue(val); }, true) 7565 option("mode", null, function (cm, val) { 7566 cm.doc.modeOption = val 7567 loadMode(cm) 7568 }, true) 7569 7570 option("indentUnit", 2, loadMode, true) 7571 option("indentWithTabs", false) 7572 option("smartIndent", true) 7573 option("tabSize", 4, function (cm) { 7574 resetModeState(cm) 7575 clearCaches(cm) 7576 regChange(cm) 7577 }, true) 7578 option("lineSeparator", null, function (cm, val) { 7579 cm.doc.lineSep = val 7580 if (!val) { return } 7581 var newBreaks = [], lineNo = cm.doc.first 7582 cm.doc.iter(function (line) { 7583 for (var pos = 0;;) { 7584 var found = line.text.indexOf(val, pos) 7585 if (found == -1) { break } 7586 pos = found + val.length 7587 newBreaks.push(Pos(lineNo, found)) 7588 } 7589 lineNo++ 7590 }) 7591 for (var i = newBreaks.length - 1; i >= 0; i--) 7592 { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) } 7593 }) 7594 option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) { 7595 cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g") 7596 if (old != Init) { cm.refresh() } 7597 }) 7598 option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true) 7599 option("electricChars", true) 7600 option("inputStyle", mobile ? "contenteditable" : "textarea", function () { 7601 throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME 7602 }, true) 7603 option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true) 7604 option("rtlMoveVisually", !windows) 7605 option("wholeLineUpdateBefore", true) 7606 7607 option("theme", "default", function (cm) { 7608 themeChanged(cm) 7609 guttersChanged(cm) 7610 }, true) 7611 option("keyMap", "default", function (cm, val, old) { 7612 var next = getKeyMap(val) 7613 var prev = old != Init && getKeyMap(old) 7614 if (prev && prev.detach) { prev.detach(cm, next) } 7615 if (next.attach) { next.attach(cm, prev || null) } 7616 }) 7617 option("extraKeys", null) 7618 option("configureMouse", null) 7619 7620 option("lineWrapping", false, wrappingChanged, true) 7621 option("gutters", [], function (cm) { 7622 setGuttersForLineNumbers(cm.options) 7623 guttersChanged(cm) 7624 }, true) 7625 option("fixedGutter", true, function (cm, val) { 7626 cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0" 7627 cm.refresh() 7628 }, true) 7629 option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true) 7630 option("scrollbarStyle", "native", function (cm) { 7631 initScrollbars(cm) 7632 updateScrollbars(cm) 7633 cm.display.scrollbars.setScrollTop(cm.doc.scrollTop) 7634 cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft) 7635 }, true) 7636 option("lineNumbers", false, function (cm) { 7637 setGuttersForLineNumbers(cm.options) 7638 guttersChanged(cm) 7639 }, true) 7640 option("firstLineNumber", 1, guttersChanged, true) 7641 option("lineNumberFormatter", function (integer) { return integer; }, guttersChanged, true) 7642 option("showCursorWhenSelecting", false, updateSelection, true) 7643 7644 option("resetSelectionOnContextMenu", true) 7645 option("lineWiseCopyCut", true) 7646 option("pasteLinesPerSelection", true) 7647 7648 option("readOnly", false, function (cm, val) { 7649 if (val == "nocursor") { 7650 onBlur(cm) 7651 cm.display.input.blur() 7652 } 7653 cm.display.input.readOnlyChanged(val) 7654 }) 7655 option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset() }}, true) 7656 option("dragDrop", true, dragDropChanged) 7657 option("allowDropFileTypes", null) 7658 7659 option("cursorBlinkRate", 530) 7660 option("cursorScrollMargin", 0) 7661 option("cursorHeight", 1, updateSelection, true) 7662 option("singleCursorHeightPerLine", true, updateSelection, true) 7663 option("workTime", 100) 7664 option("workDelay", 100) 7665 option("flattenSpans", true, resetModeState, true) 7666 option("addModeClass", false, resetModeState, true) 7667 option("pollInterval", 100) 7668 option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; }) 7669 option("historyEventDelay", 1250) 7670 option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true) 7671 option("maxHighlightLength", 10000, resetModeState, true) 7672 option("moveInputWithCursor", true, function (cm, val) { 7673 if (!val) { cm.display.input.resetPosition() } 7674 }) 7675 7676 option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; }) 7677 option("autofocus", null) 7678 option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true) 7679 } 7680 7681 function guttersChanged(cm) { 7682 updateGutters(cm) 7683 regChange(cm) 7684 alignHorizontally(cm) 7685 } 7686 7687 function dragDropChanged(cm, value, old) { 7688 var wasOn = old && old != Init 7689 if (!value != !wasOn) { 7690 var funcs = cm.display.dragFunctions 7691 var toggle = value ? on : off 7692 toggle(cm.display.scroller, "dragstart", funcs.start) 7693 toggle(cm.display.scroller, "dragenter", funcs.enter) 7694 toggle(cm.display.scroller, "dragover", funcs.over) 7695 toggle(cm.display.scroller, "dragleave", funcs.leave) 7696 toggle(cm.display.scroller, "drop", funcs.drop) 7697 } 7698 } 7699 7700 function wrappingChanged(cm) { 7701 if (cm.options.lineWrapping) { 7702 addClass(cm.display.wrapper, "CodeMirror-wrap") 7703 cm.display.sizer.style.minWidth = "" 7704 cm.display.sizerWidth = null 7705 } else { 7706 rmClass(cm.display.wrapper, "CodeMirror-wrap") 7707 findMaxLine(cm) 7708 } 7709 estimateLineHeights(cm) 7710 regChange(cm) 7711 clearCaches(cm) 7712 setTimeout(function () { return updateScrollbars(cm); }, 100) 7713 } 7714 7715 // A CodeMirror instance represents an editor. This is the object 7716 // that user code is usually dealing with. 7717 7718 function CodeMirror(place, options) { 7719 var this$1 = this; 7720 7721 if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) } 7722 7723 this.options = options = options ? copyObj(options) : {} 7724 // Determine effective options based on given values and defaults. 7725 copyObj(defaults, options, false) 7726 setGuttersForLineNumbers(options) 7727 7728 var doc = options.value 7729 if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction) } 7730 this.doc = doc 7731 7732 var input = new CodeMirror.inputStyles[options.inputStyle](this) 7733 var display = this.display = new Display(place, doc, input) 7734 display.wrapper.CodeMirror = this 7735 updateGutters(this) 7736 themeChanged(this) 7737 if (options.lineWrapping) 7738 { this.display.wrapper.className += " CodeMirror-wrap" } 7739 initScrollbars(this) 7740 7741 this.state = { 7742 keyMaps: [], // stores maps added by addKeyMap 7743 overlays: [], // highlighting overlays, as added by addOverlay 7744 modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info 7745 overwrite: false, 7746 delayingBlurEvent: false, 7747 focused: false, 7748 suppressEdits: false, // used to disable editing during key handlers when in readOnly mode 7749 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll 7750 selectingText: false, 7751 draggingText: false, 7752 highlight: new Delayed(), // stores highlight worker timeout 7753 keySeq: null, // Unfinished key sequence 7754 specialChars: null 7755 } 7756 7757 if (options.autofocus && !mobile) { display.input.focus() } 7758 7759 // Override magic textarea content restore that IE sometimes does 7760 // on our hidden textarea on reload 7761 if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20) } 7762 7763 registerEventHandlers(this) 7764 ensureGlobalHandlers() 7765 7766 startOperation(this) 7767 this.curOp.forceUpdate = true 7768 attachDoc(this, doc) 7769 7770 if ((options.autofocus && !mobile) || this.hasFocus()) 7771 { setTimeout(bind(onFocus, this), 20) } 7772 else 7773 { onBlur(this) } 7774 7775 for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) 7776 { optionHandlers[opt](this$1, options[opt], Init) } } 7777 maybeUpdateLineNumberWidth(this) 7778 if (options.finishInit) { options.finishInit(this) } 7779 for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1) } 7780 endOperation(this) 7781 // Suppress optimizelegibility in Webkit, since it breaks text 7782 // measuring on line wrapping boundaries. 7783 if (webkit && options.lineWrapping && 7784 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") 7785 { display.lineDiv.style.textRendering = "auto" } 7786 } 7787 7788 // The default configuration options. 7789 CodeMirror.defaults = defaults 7790 // Functions to run when options are changed. 7791 CodeMirror.optionHandlers = optionHandlers 7792 7793 // Attach the necessary event handlers when initializing the editor 7794 function registerEventHandlers(cm) { 7795 var d = cm.display 7796 on(d.scroller, "mousedown", operation(cm, onMouseDown)) 7797 // Older IE's will not fire a second mousedown for a double click 7798 if (ie && ie_version < 11) 7799 { on(d.scroller, "dblclick", operation(cm, function (e) { 7800 if (signalDOMEvent(cm, e)) { return } 7801 var pos = posFromMouse(cm, e) 7802 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return } 7803 e_preventDefault(e) 7804 var word = cm.findWordAt(pos) 7805 extendSelection(cm.doc, word.anchor, word.head) 7806 })) } 7807 else 7808 { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }) } 7809 // Some browsers fire contextmenu *after* opening the menu, at 7810 // which point we can't mess with it anymore. Context menu is 7811 // handled in onMouseDown for these browsers. 7812 if (!captureRightClick) { on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }) } 7813 7814 // Used to suppress mouse event handling when a touch happens 7815 var touchFinished, prevTouch = {end: 0} 7816 function finishTouch() { 7817 if (d.activeTouch) { 7818 touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000) 7819 prevTouch = d.activeTouch 7820 prevTouch.end = +new Date 7821 } 7822 } 7823 function isMouseLikeTouchEvent(e) { 7824 if (e.touches.length != 1) { return false } 7825 var touch = e.touches[0] 7826 return touch.radiusX <= 1 && touch.radiusY <= 1 7827 } 7828 function farAway(touch, other) { 7829 if (other.left == null) { return true } 7830 var dx = other.left - touch.left, dy = other.top - touch.top 7831 return dx * dx + dy * dy > 20 * 20 7832 } 7833 on(d.scroller, "touchstart", function (e) { 7834 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { 7835 d.input.ensurePolled() 7836 clearTimeout(touchFinished) 7837 var now = +new Date 7838 d.activeTouch = {start: now, moved: false, 7839 prev: now - prevTouch.end <= 300 ? prevTouch : null} 7840 if (e.touches.length == 1) { 7841 d.activeTouch.left = e.touches[0].pageX 7842 d.activeTouch.top = e.touches[0].pageY 7843 } 7844 } 7845 }) 7846 on(d.scroller, "touchmove", function () { 7847 if (d.activeTouch) { d.activeTouch.moved = true } 7848 }) 7849 on(d.scroller, "touchend", function (e) { 7850 var touch = d.activeTouch 7851 if (touch && !eventInWidget(d, e) && touch.left != null && 7852 !touch.moved && new Date - touch.start < 300) { 7853 var pos = cm.coordsChar(d.activeTouch, "page"), range 7854 if (!touch.prev || farAway(touch, touch.prev)) // Single tap 7855 { range = new Range(pos, pos) } 7856 else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap 7857 { range = cm.findWordAt(pos) } 7858 else // Triple tap 7859 { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } 7860 cm.setSelection(range.anchor, range.head) 7861 cm.focus() 7862 e_preventDefault(e) 7863 } 7864 finishTouch() 7865 }) 7866 on(d.scroller, "touchcancel", finishTouch) 7867 7868 // Sync scrolling between fake scrollbars and real scrollable 7869 // area, ensure viewport is updated when scrolling. 7870 on(d.scroller, "scroll", function () { 7871 if (d.scroller.clientHeight) { 7872 updateScrollTop(cm, d.scroller.scrollTop) 7873 setScrollLeft(cm, d.scroller.scrollLeft, true) 7874 signal(cm, "scroll", cm) 7875 } 7876 }) 7877 7878 // Listen to wheel events in order to try and update the viewport on time. 7879 on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); }) 7880 on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); }) 7881 7882 // Prevent wrapper from ever scrolling 7883 on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }) 7884 7885 d.dragFunctions = { 7886 enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e) }}, 7887 over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e) }}, 7888 start: function (e) { return onDragStart(cm, e); }, 7889 drop: operation(cm, onDrop), 7890 leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }} 7891 } 7892 7893 var inp = d.input.getField() 7894 on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); }) 7895 on(inp, "keydown", operation(cm, onKeyDown)) 7896 on(inp, "keypress", operation(cm, onKeyPress)) 7897 on(inp, "focus", function (e) { return onFocus(cm, e); }) 7898 on(inp, "blur", function (e) { return onBlur(cm, e); }) 7899 } 7900 7901 var initHooks = [] 7902 CodeMirror.defineInitHook = function (f) { return initHooks.push(f); } 7903 7904 // Indent the given line. The how parameter can be "smart", 7905 // "add"/null, "subtract", or "prev". When aggressive is false 7906 // (typically set to true for forced single-line indents), empty 7907 // lines are not indented, and places where the mode returns Pass 7908 // are left alone. 7909 function indentLine(cm, n, how, aggressive) { 7910 var doc = cm.doc, state 7911 if (how == null) { how = "add" } 7912 if (how == "smart") { 7913 // Fall back to "prev" when the mode doesn't have an indentation 7914 // method. 7915 if (!doc.mode.indent) { how = "prev" } 7916 else { state = getContextBefore(cm, n).state } 7917 } 7918 7919 var tabSize = cm.options.tabSize 7920 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize) 7921 if (line.stateAfter) { line.stateAfter = null } 7922 var curSpaceString = line.text.match(/^\s*/)[0], indentation 7923 if (!aggressive && !/\S/.test(line.text)) { 7924 indentation = 0 7925 how = "not" 7926 } else if (how == "smart") { 7927 indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text) 7928 if (indentation == Pass || indentation > 150) { 7929 if (!aggressive) { return } 7930 how = "prev" 7931 } 7932 } 7933 if (how == "prev") { 7934 if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize) } 7935 else { indentation = 0 } 7936 } else if (how == "add") { 7937 indentation = curSpace + cm.options.indentUnit 7938 } else if (how == "subtract") { 7939 indentation = curSpace - cm.options.indentUnit 7940 } else if (typeof how == "number") { 7941 indentation = curSpace + how 7942 } 7943 indentation = Math.max(0, indentation) 7944 7945 var indentString = "", pos = 0 7946 if (cm.options.indentWithTabs) 7947 { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t"} } 7948 if (pos < indentation) { indentString += spaceStr(indentation - pos) } 7949 7950 if (indentString != curSpaceString) { 7951 replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input") 7952 line.stateAfter = null 7953 return true 7954 } else { 7955 // Ensure that, if the cursor was in the whitespace at the start 7956 // of the line, it is moved to the end of that space. 7957 for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { 7958 var range = doc.sel.ranges[i$1] 7959 if (range.head.line == n && range.head.ch < curSpaceString.length) { 7960 var pos$1 = Pos(n, curSpaceString.length) 7961 replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)) 7962 break 7963 } 7964 } 7965 } 7966 } 7967 7968 // This will be set to a {lineWise: bool, text: [string]} object, so 7969 // that, when pasting, we know what kind of selections the copied 7970 // text was made out of. 7971 var lastCopied = null 7972 7973 function setLastCopied(newLastCopied) { 7974 lastCopied = newLastCopied 7975 } 7976 7977 function applyTextInput(cm, inserted, deleted, sel, origin) { 7978 var doc = cm.doc 7979 cm.display.shift = false 7980 if (!sel) { sel = doc.sel } 7981 7982 var paste = cm.state.pasteIncoming || origin == "paste" 7983 var textLines = splitLinesAuto(inserted), multiPaste = null 7984 // When pasing N lines into N selections, insert one line per selection 7985 if (paste && sel.ranges.length > 1) { 7986 if (lastCopied && lastCopied.text.join("\n") == inserted) { 7987 if (sel.ranges.length % lastCopied.text.length == 0) { 7988 multiPaste = [] 7989 for (var i = 0; i < lastCopied.text.length; i++) 7990 { multiPaste.push(doc.splitLines(lastCopied.text[i])) } 7991 } 7992 } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { 7993 multiPaste = map(textLines, function (l) { return [l]; }) 7994 } 7995 } 7996 7997 var updateInput 7998 // Normal behavior is to insert the new text into every selection 7999 for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { 8000 var range = sel.ranges[i$1] 8001 var from = range.from(), to = range.to() 8002 if (range.empty()) { 8003 if (deleted && deleted > 0) // Handle deletion 8004 { from = Pos(from.line, from.ch - deleted) } 8005 else if (cm.state.overwrite && !paste) // Handle overwrite 8006 { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)) } 8007 else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) 8008 { from = to = Pos(from.line, 0) } 8009 } 8010 updateInput = cm.curOp.updateInput 8011 var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, 8012 origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")} 8013 makeChange(cm.doc, changeEvent) 8014 signalLater(cm, "inputRead", cm, changeEvent) 8015 } 8016 if (inserted && !paste) 8017 { triggerElectric(cm, inserted) } 8018 8019 ensureCursorVisible(cm) 8020 cm.curOp.updateInput = updateInput 8021 cm.curOp.typing = true 8022 cm.state.pasteIncoming = cm.state.cutIncoming = false 8023 } 8024 8025 function handlePaste(e, cm) { 8026 var pasted = e.clipboardData && e.clipboardData.getData("Text") 8027 if (pasted) { 8028 e.preventDefault() 8029 if (!cm.isReadOnly() && !cm.options.disableInput) 8030 { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }) } 8031 return true 8032 } 8033 } 8034 8035 function triggerElectric(cm, inserted) { 8036 // When an 'electric' character is inserted, immediately trigger a reindent 8037 if (!cm.options.electricChars || !cm.options.smartIndent) { return } 8038 var sel = cm.doc.sel 8039 8040 for (var i = sel.ranges.length - 1; i >= 0; i--) { 8041 var range = sel.ranges[i] 8042 if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) { continue } 8043 var mode = cm.getModeAt(range.head) 8044 var indented = false 8045 if (mode.electricChars) { 8046 for (var j = 0; j < mode.electricChars.length; j++) 8047 { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { 8048 indented = indentLine(cm, range.head.line, "smart") 8049 break 8050 } } 8051 } else if (mode.electricInput) { 8052 if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) 8053 { indented = indentLine(cm, range.head.line, "smart") } 8054 } 8055 if (indented) { signalLater(cm, "electricInput", cm, range.head.line) } 8056 } 8057 } 8058 8059 function copyableRanges(cm) { 8060 var text = [], ranges = [] 8061 for (var i = 0; i < cm.doc.sel.ranges.length; i++) { 8062 var line = cm.doc.sel.ranges[i].head.line 8063 var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)} 8064 ranges.push(lineRange) 8065 text.push(cm.getRange(lineRange.anchor, lineRange.head)) 8066 } 8067 return {text: text, ranges: ranges} 8068 } 8069 8070 function disableBrowserMagic(field, spellcheck) { 8071 field.setAttribute("autocorrect", "off") 8072 field.setAttribute("autocapitalize", "off") 8073 field.setAttribute("spellcheck", !!spellcheck) 8074 } 8075 8076 function hiddenTextarea() { 8077 var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none") 8078 var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;") 8079 // The textarea is kept positioned near the cursor to prevent the 8080 // fact that it'll be scrolled into view on input from scrolling 8081 // our fake cursor out of view. On webkit, when wrap=off, paste is 8082 // very slow. So make the area wide instead. 8083 if (webkit) { te.style.width = "1000px" } 8084 else { te.setAttribute("wrap", "off") } 8085 // If border: 0; -- iOS fails to open keyboard (issue #1287) 8086 if (ios) { te.style.border = "1px solid black" } 8087 disableBrowserMagic(te) 8088 return div 8089 } 8090 8091 // The publicly visible API. Note that methodOp(f) means 8092 // 'wrap f in an operation, performed on its `this` parameter'. 8093 8094 // This is not the complete set of editor methods. Most of the 8095 // methods defined on the Doc type are also injected into 8096 // CodeMirror.prototype, for backwards compatibility and 8097 // convenience. 8098 8099 function addEditorMethods(CodeMirror) { 8100 var optionHandlers = CodeMirror.optionHandlers 8101 8102 var helpers = CodeMirror.helpers = {} 8103 8104 CodeMirror.prototype = { 8105 constructor: CodeMirror, 8106 focus: function(){window.focus(); this.display.input.focus()}, 8107 8108 setOption: function(option, value) { 8109 var options = this.options, old = options[option] 8110 if (options[option] == value && option != "mode") { return } 8111 options[option] = value 8112 if (optionHandlers.hasOwnProperty(option)) 8113 { operation(this, optionHandlers[option])(this, value, old) } 8114 signal(this, "optionChange", this, option) 8115 }, 8116 8117 getOption: function(option) {return this.options[option]}, 8118 getDoc: function() {return this.doc}, 8119 8120 addKeyMap: function(map, bottom) { 8121 this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)) 8122 }, 8123 removeKeyMap: function(map) { 8124 var maps = this.state.keyMaps 8125 for (var i = 0; i < maps.length; ++i) 8126 { if (maps[i] == map || maps[i].name == map) { 8127 maps.splice(i, 1) 8128 return true 8129 } } 8130 }, 8131 8132 addOverlay: methodOp(function(spec, options) { 8133 var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec) 8134 if (mode.startState) { throw new Error("Overlays may not be stateful.") } 8135 insertSorted(this.state.overlays, 8136 {mode: mode, modeSpec: spec, opaque: options && options.opaque, 8137 priority: (options && options.priority) || 0}, 8138 function (overlay) { return overlay.priority; }) 8139 this.state.modeGen++ 8140 regChange(this) 8141 }), 8142 removeOverlay: methodOp(function(spec) { 8143 var this$1 = this; 8144 8145 var overlays = this.state.overlays 8146 for (var i = 0; i < overlays.length; ++i) { 8147 var cur = overlays[i].modeSpec 8148 if (cur == spec || typeof spec == "string" && cur.name == spec) { 8149 overlays.splice(i, 1) 8150 this$1.state.modeGen++ 8151 regChange(this$1) 8152 return 8153 } 8154 } 8155 }), 8156 8157 indentLine: methodOp(function(n, dir, aggressive) { 8158 if (typeof dir != "string" && typeof dir != "number") { 8159 if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev" } 8160 else { dir = dir ? "add" : "subtract" } 8161 } 8162 if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive) } 8163 }), 8164 indentSelection: methodOp(function(how) { 8165 var this$1 = this; 8166 8167 var ranges = this.doc.sel.ranges, end = -1 8168 for (var i = 0; i < ranges.length; i++) { 8169 var range = ranges[i] 8170 if (!range.empty()) { 8171 var from = range.from(), to = range.to() 8172 var start = Math.max(end, from.line) 8173 end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1 8174 for (var j = start; j < end; ++j) 8175 { indentLine(this$1, j, how) } 8176 var newRanges = this$1.doc.sel.ranges 8177 if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) 8178 { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll) } 8179 } else if (range.head.line > end) { 8180 indentLine(this$1, range.head.line, how, true) 8181 end = range.head.line 8182 if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1) } 8183 } 8184 } 8185 }), 8186 8187 // Fetch the parser token for a given character. Useful for hacks 8188 // that want to inspect the mode state (say, for completion). 8189 getTokenAt: function(pos, precise) { 8190 return takeToken(this, pos, precise) 8191 }, 8192 8193 getLineTokens: function(line, precise) { 8194 return takeToken(this, Pos(line), precise, true) 8195 }, 8196 8197 getTokenTypeAt: function(pos) { 8198 pos = clipPos(this.doc, pos) 8199 var styles = getLineStyles(this, getLine(this.doc, pos.line)) 8200 var before = 0, after = (styles.length - 1) / 2, ch = pos.ch 8201 var type 8202 if (ch == 0) { type = styles[2] } 8203 else { for (;;) { 8204 var mid = (before + after) >> 1 8205 if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid } 8206 else if (styles[mid * 2 + 1] < ch) { before = mid + 1 } 8207 else { type = styles[mid * 2 + 2]; break } 8208 } } 8209 var cut = type ? type.indexOf("overlay ") : -1 8210 return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) 8211 }, 8212 8213 getModeAt: function(pos) { 8214 var mode = this.doc.mode 8215 if (!mode.innerMode) { return mode } 8216 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode 8217 }, 8218 8219 getHelper: function(pos, type) { 8220 return this.getHelpers(pos, type)[0] 8221 }, 8222 8223 getHelpers: function(pos, type) { 8224 var this$1 = this; 8225 8226 var found = [] 8227 if (!helpers.hasOwnProperty(type)) { return found } 8228 var help = helpers[type], mode = this.getModeAt(pos) 8229 if (typeof mode[type] == "string") { 8230 if (help[mode[type]]) { found.push(help[mode[type]]) } 8231 } else if (mode[type]) { 8232 for (var i = 0; i < mode[type].length; i++) { 8233 var val = help[mode[type][i]] 8234 if (val) { found.push(val) } 8235 } 8236 } else if (mode.helperType && help[mode.helperType]) { 8237 found.push(help[mode.helperType]) 8238 } else if (help[mode.name]) { 8239 found.push(help[mode.name]) 8240 } 8241 for (var i$1 = 0; i$1 < help._global.length; i$1++) { 8242 var cur = help._global[i$1] 8243 if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1) 8244 { found.push(cur.val) } 8245 } 8246 return found 8247 }, 8248 8249 getStateAfter: function(line, precise) { 8250 var doc = this.doc 8251 line = clipLine(doc, line == null ? doc.first + doc.size - 1: line) 8252 return getContextBefore(this, line + 1, precise).state 8253 }, 8254 8255 cursorCoords: function(start, mode) { 8256 var pos, range = this.doc.sel.primary() 8257 if (start == null) { pos = range.head } 8258 else if (typeof start == "object") { pos = clipPos(this.doc, start) } 8259 else { pos = start ? range.from() : range.to() } 8260 return cursorCoords(this, pos, mode || "page") 8261 }, 8262 8263 charCoords: function(pos, mode) { 8264 return charCoords(this, clipPos(this.doc, pos), mode || "page") 8265 }, 8266 8267 coordsChar: function(coords, mode) { 8268 coords = fromCoordSystem(this, coords, mode || "page") 8269 return coordsChar(this, coords.left, coords.top) 8270 }, 8271 8272 lineAtHeight: function(height, mode) { 8273 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top 8274 return lineAtHeight(this.doc, height + this.display.viewOffset) 8275 }, 8276 heightAtLine: function(line, mode, includeWidgets) { 8277 var end = false, lineObj 8278 if (typeof line == "number") { 8279 var last = this.doc.first + this.doc.size - 1 8280 if (line < this.doc.first) { line = this.doc.first } 8281 else if (line > last) { line = last; end = true } 8282 lineObj = getLine(this.doc, line) 8283 } else { 8284 lineObj = line 8285 } 8286 return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + 8287 (end ? this.doc.height - heightAtLine(lineObj) : 0) 8288 }, 8289 8290 defaultTextHeight: function() { return textHeight(this.display) }, 8291 defaultCharWidth: function() { return charWidth(this.display) }, 8292 8293 getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, 8294 8295 addWidget: function(pos, node, scroll, vert, horiz) { 8296 var display = this.display 8297 pos = cursorCoords(this, clipPos(this.doc, pos)) 8298 var top = pos.bottom, left = pos.left 8299 node.style.position = "absolute" 8300 node.setAttribute("cm-ignore-events", "true") 8301 this.display.input.setUneditable(node) 8302 display.sizer.appendChild(node) 8303 if (vert == "over") { 8304 top = pos.top 8305 } else if (vert == "above" || vert == "near") { 8306 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), 8307 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth) 8308 // Default to positioning above (if specified and possible); otherwise default to positioning below 8309 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) 8310 { top = pos.top - node.offsetHeight } 8311 else if (pos.bottom + node.offsetHeight <= vspace) 8312 { top = pos.bottom } 8313 if (left + node.offsetWidth > hspace) 8314 { left = hspace - node.offsetWidth } 8315 } 8316 node.style.top = top + "px" 8317 node.style.left = node.style.right = "" 8318 if (horiz == "right") { 8319 left = display.sizer.clientWidth - node.offsetWidth 8320 node.style.right = "0px" 8321 } else { 8322 if (horiz == "left") { left = 0 } 8323 else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2 } 8324 node.style.left = left + "px" 8325 } 8326 if (scroll) 8327 { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}) } 8328 }, 8329 8330 triggerOnKeyDown: methodOp(onKeyDown), 8331 triggerOnKeyPress: methodOp(onKeyPress), 8332 triggerOnKeyUp: onKeyUp, 8333 triggerOnMouseDown: methodOp(onMouseDown), 8334 8335 execCommand: function(cmd) { 8336 if (commands.hasOwnProperty(cmd)) 8337 { return commands[cmd].call(null, this) } 8338 }, 8339 8340 triggerElectric: methodOp(function(text) { triggerElectric(this, text) }), 8341 8342 findPosH: function(from, amount, unit, visually) { 8343 var this$1 = this; 8344 8345 var dir = 1 8346 if (amount < 0) { dir = -1; amount = -amount } 8347 var cur = clipPos(this.doc, from) 8348 for (var i = 0; i < amount; ++i) { 8349 cur = findPosH(this$1.doc, cur, dir, unit, visually) 8350 if (cur.hitSide) { break } 8351 } 8352 return cur 8353 }, 8354 8355 moveH: methodOp(function(dir, unit) { 8356 var this$1 = this; 8357 8358 this.extendSelectionsBy(function (range) { 8359 if (this$1.display.shift || this$1.doc.extend || range.empty()) 8360 { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rtlMoveVisually) } 8361 else 8362 { return dir < 0 ? range.from() : range.to() } 8363 }, sel_move) 8364 }), 8365 8366 deleteH: methodOp(function(dir, unit) { 8367 var sel = this.doc.sel, doc = this.doc 8368 if (sel.somethingSelected()) 8369 { doc.replaceSelection("", null, "+delete") } 8370 else 8371 { deleteNearSelection(this, function (range) { 8372 var other = findPosH(doc, range.head, dir, unit, false) 8373 return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other} 8374 }) } 8375 }), 8376 8377 findPosV: function(from, amount, unit, goalColumn) { 8378 var this$1 = this; 8379 8380 var dir = 1, x = goalColumn 8381 if (amount < 0) { dir = -1; amount = -amount } 8382 var cur = clipPos(this.doc, from) 8383 for (var i = 0; i < amount; ++i) { 8384 var coords = cursorCoords(this$1, cur, "div") 8385 if (x == null) { x = coords.left } 8386 else { coords.left = x } 8387 cur = findPosV(this$1, coords, dir, unit) 8388 if (cur.hitSide) { break } 8389 } 8390 return cur 8391 }, 8392 8393 moveV: methodOp(function(dir, unit) { 8394 var this$1 = this; 8395 8396 var doc = this.doc, goals = [] 8397 var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected() 8398 doc.extendSelectionsBy(function (range) { 8399 if (collapse) 8400 { return dir < 0 ? range.from() : range.to() } 8401 var headPos = cursorCoords(this$1, range.head, "div") 8402 if (range.goalColumn != null) { headPos.left = range.goalColumn } 8403 goals.push(headPos.left) 8404 var pos = findPosV(this$1, headPos, dir, unit) 8405 if (unit == "page" && range == doc.sel.primary()) 8406 { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top) } 8407 return pos 8408 }, sel_move) 8409 if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) 8410 { doc.sel.ranges[i].goalColumn = goals[i] } } 8411 }), 8412 8413 // Find the word at the given position (as returned by coordsChar). 8414 findWordAt: function(pos) { 8415 var doc = this.doc, line = getLine(doc, pos.line).text 8416 var start = pos.ch, end = pos.ch 8417 if (line) { 8418 var helper = this.getHelper(pos, "wordChars") 8419 if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end } 8420 var startChar = line.charAt(start) 8421 var check = isWordChar(startChar, helper) 8422 ? function (ch) { return isWordChar(ch, helper); } 8423 : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } 8424 : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); } 8425 while (start > 0 && check(line.charAt(start - 1))) { --start } 8426 while (end < line.length && check(line.charAt(end))) { ++end } 8427 } 8428 return new Range(Pos(pos.line, start), Pos(pos.line, end)) 8429 }, 8430 8431 toggleOverwrite: function(value) { 8432 if (value != null && value == this.state.overwrite) { return } 8433 if (this.state.overwrite = !this.state.overwrite) 8434 { addClass(this.display.cursorDiv, "CodeMirror-overwrite") } 8435 else 8436 { rmClass(this.display.cursorDiv, "CodeMirror-overwrite") } 8437 8438 signal(this, "overwriteToggle", this, this.state.overwrite) 8439 }, 8440 hasFocus: function() { return this.display.input.getField() == activeElt() }, 8441 isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, 8442 8443 scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y) }), 8444 getScrollInfo: function() { 8445 var scroller = this.display.scroller 8446 return {left: scroller.scrollLeft, top: scroller.scrollTop, 8447 height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, 8448 width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, 8449 clientHeight: displayHeight(this), clientWidth: displayWidth(this)} 8450 }, 8451 8452 scrollIntoView: methodOp(function(range, margin) { 8453 if (range == null) { 8454 range = {from: this.doc.sel.primary().head, to: null} 8455 if (margin == null) { margin = this.options.cursorScrollMargin } 8456 } else if (typeof range == "number") { 8457 range = {from: Pos(range, 0), to: null} 8458 } else if (range.from == null) { 8459 range = {from: range, to: null} 8460 } 8461 if (!range.to) { range.to = range.from } 8462 range.margin = margin || 0 8463 8464 if (range.from.line != null) { 8465 scrollToRange(this, range) 8466 } else { 8467 scrollToCoordsRange(this, range.from, range.to, range.margin) 8468 } 8469 }), 8470 8471 setSize: methodOp(function(width, height) { 8472 var this$1 = this; 8473 8474 var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; } 8475 if (width != null) { this.display.wrapper.style.width = interpret(width) } 8476 if (height != null) { this.display.wrapper.style.height = interpret(height) } 8477 if (this.options.lineWrapping) { clearLineMeasurementCache(this) } 8478 var lineNo = this.display.viewFrom 8479 this.doc.iter(lineNo, this.display.viewTo, function (line) { 8480 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) 8481 { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widget"); break } } } 8482 ++lineNo 8483 }) 8484 this.curOp.forceUpdate = true 8485 signal(this, "refresh", this) 8486 }), 8487 8488 operation: function(f){return runInOp(this, f)}, 8489 startOperation: function(){return startOperation(this)}, 8490 endOperation: function(){return endOperation(this)}, 8491 8492 refresh: methodOp(function() { 8493 var oldHeight = this.display.cachedTextHeight 8494 regChange(this) 8495 this.curOp.forceUpdate = true 8496 clearCaches(this) 8497 scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop) 8498 updateGutterSpace(this) 8499 if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) 8500 { estimateLineHeights(this) } 8501 signal(this, "refresh", this) 8502 }), 8503 8504 swapDoc: methodOp(function(doc) { 8505 var old = this.doc 8506 old.cm = null 8507 attachDoc(this, doc) 8508 clearCaches(this) 8509 this.display.input.reset() 8510 scrollToCoords(this, doc.scrollLeft, doc.scrollTop) 8511 this.curOp.forceScroll = true 8512 signalLater(this, "swapDoc", this, old) 8513 return old 8514 }), 8515 8516 getInputField: function(){return this.display.input.getField()}, 8517 getWrapperElement: function(){return this.display.wrapper}, 8518 getScrollerElement: function(){return this.display.scroller}, 8519 getGutterElement: function(){return this.display.gutters} 8520 } 8521 eventMixin(CodeMirror) 8522 8523 CodeMirror.registerHelper = function(type, name, value) { 8524 if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []} } 8525 helpers[type][name] = value 8526 } 8527 CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { 8528 CodeMirror.registerHelper(type, name, value) 8529 helpers[type]._global.push({pred: predicate, val: value}) 8530 } 8531 } 8532 8533 // Used for horizontal relative motion. Dir is -1 or 1 (left or 8534 // right), unit can be "char", "column" (like char, but doesn't 8535 // cross line boundaries), "word" (across next word), or "group" (to 8536 // the start of next group of word or non-word-non-whitespace 8537 // chars). The visually param controls whether, in right-to-left 8538 // text, direction 1 means to move towards the next index in the 8539 // string, or towards the character to the right of the current 8540 // position. The resulting position will have a hitSide=true 8541 // property if it reached the end of the document. 8542 function findPosH(doc, pos, dir, unit, visually) { 8543 var oldPos = pos 8544 var origDir = dir 8545 var lineObj = getLine(doc, pos.line) 8546 function findNextLine() { 8547 var l = pos.line + dir 8548 if (l < doc.first || l >= doc.first + doc.size) { return false } 8549 pos = new Pos(l, pos.ch, pos.sticky) 8550 return lineObj = getLine(doc, l) 8551 } 8552 function moveOnce(boundToLine) { 8553 var next 8554 if (visually) { 8555 next = moveVisually(doc.cm, lineObj, pos, dir) 8556 } else { 8557 next = moveLogically(lineObj, pos, dir) 8558 } 8559 if (next == null) { 8560 if (!boundToLine && findNextLine()) 8561 { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir) } 8562 else 8563 { return false } 8564 } else { 8565 pos = next 8566 } 8567 return true 8568 } 8569 8570 if (unit == "char") { 8571 moveOnce() 8572 } else if (unit == "column") { 8573 moveOnce(true) 8574 } else if (unit == "word" || unit == "group") { 8575 var sawType = null, group = unit == "group" 8576 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars") 8577 for (var first = true;; first = false) { 8578 if (dir < 0 && !moveOnce(!first)) { break } 8579 var cur = lineObj.text.charAt(pos.ch) || "\n" 8580 var type = isWordChar(cur, helper) ? "w" 8581 : group && cur == "\n" ? "n" 8582 : !group || /\s/.test(cur) ? null 8583 : "p" 8584 if (group && !first && !type) { type = "s" } 8585 if (sawType && sawType != type) { 8586 if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after"} 8587 break 8588 } 8589 8590 if (type) { sawType = type } 8591 if (dir > 0 && !moveOnce(!first)) { break } 8592 } 8593 } 8594 var result = skipAtomic(doc, pos, oldPos, origDir, true) 8595 if (equalCursorPos(oldPos, result)) { result.hitSide = true } 8596 return result 8597 } 8598 8599 // For relative vertical movement. Dir may be -1 or 1. Unit can be 8600 // "page" or "line". The resulting position will have a hitSide=true 8601 // property if it reached the end of the document. 8602 function findPosV(cm, pos, dir, unit) { 8603 var doc = cm.doc, x = pos.left, y 8604 if (unit == "page") { 8605 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight) 8606 var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3) 8607 y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount 8608 8609 } else if (unit == "line") { 8610 y = dir > 0 ? pos.bottom + 3 : pos.top - 3 8611 } 8612 var target 8613 for (;;) { 8614 target = coordsChar(cm, x, y) 8615 if (!target.outside) { break } 8616 if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } 8617 y += dir * 5 8618 } 8619 return target 8620 } 8621 8622 // CONTENTEDITABLE INPUT STYLE 8623 8624 var ContentEditableInput = function(cm) { 8625 this.cm = cm 8626 this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null 8627 this.polling = new Delayed() 8628 this.composing = null 8629 this.gracePeriod = false 8630 this.readDOMTimeout = null 8631 }; 8632 8633 ContentEditableInput.prototype.init = function (display) { 8634 var this$1 = this; 8635 8636 var input = this, cm = input.cm 8637 var div = input.div = display.lineDiv 8638 disableBrowserMagic(div, cm.options.spellcheck) 8639 8640 on(div, "paste", function (e) { 8641 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } 8642 // IE doesn't fire input events, so we schedule a read for the pasted content in this way 8643 if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20) } 8644 }) 8645 8646 on(div, "compositionstart", function (e) { 8647 this$1.composing = {data: e.data, done: false} 8648 }) 8649 on(div, "compositionupdate", function (e) { 8650 if (!this$1.composing) { this$1.composing = {data: e.data, done: false} } 8651 }) 8652 on(div, "compositionend", function (e) { 8653 if (this$1.composing) { 8654 if (e.data != this$1.composing.data) { this$1.readFromDOMSoon() } 8655 this$1.composing.done = true 8656 } 8657 }) 8658 8659 on(div, "touchstart", function () { return input.forceCompositionEnd(); }) 8660 8661 on(div, "input", function () { 8662 if (!this$1.composing) { this$1.readFromDOMSoon() } 8663 }) 8664 8665 function onCopyCut(e) { 8666 if (signalDOMEvent(cm, e)) { return } 8667 if (cm.somethingSelected()) { 8668 setLastCopied({lineWise: false, text: cm.getSelections()}) 8669 if (e.type == "cut") { cm.replaceSelection("", null, "cut") } 8670 } else if (!cm.options.lineWiseCopyCut) { 8671 return 8672 } else { 8673 var ranges = copyableRanges(cm) 8674 setLastCopied({lineWise: true, text: ranges.text}) 8675 if (e.type == "cut") { 8676 cm.operation(function () { 8677 cm.setSelections(ranges.ranges, 0, sel_dontScroll) 8678 cm.replaceSelection("", null, "cut") 8679 }) 8680 } 8681 } 8682 if (e.clipboardData) { 8683 e.clipboardData.clearData() 8684 var content = lastCopied.text.join("\n") 8685 // iOS exposes the clipboard API, but seems to discard content inserted into it 8686 e.clipboardData.setData("Text", content) 8687 if (e.clipboardData.getData("Text") == content) { 8688 e.preventDefault() 8689 return 8690 } 8691 } 8692 // Old-fashioned briefly-focus-a-textarea hack 8693 var kludge = hiddenTextarea(), te = kludge.firstChild 8694 cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild) 8695 te.value = lastCopied.text.join("\n") 8696 var hadFocus = document.activeElement 8697 selectInput(te) 8698 setTimeout(function () { 8699 cm.display.lineSpace.removeChild(kludge) 8700 hadFocus.focus() 8701 if (hadFocus == div) { input.showPrimarySelection() } 8702 }, 50) 8703 } 8704 on(div, "copy", onCopyCut) 8705 on(div, "cut", onCopyCut) 8706 }; 8707 8708 ContentEditableInput.prototype.prepareSelection = function () { 8709 var result = prepareSelection(this.cm, false) 8710 result.focus = this.cm.state.focused 8711 return result 8712 }; 8713 8714 ContentEditableInput.prototype.showSelection = function (info, takeFocus) { 8715 if (!info || !this.cm.display.view.length) { return } 8716 if (info.focus || takeFocus) { this.showPrimarySelection() } 8717 this.showMultipleSelections(info) 8718 }; 8719 8720 ContentEditableInput.prototype.showPrimarySelection = function () { 8721 var sel = window.getSelection(), cm = this.cm, prim = cm.doc.sel.primary() 8722 var from = prim.from(), to = prim.to() 8723 8724 if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { 8725 sel.removeAllRanges() 8726 return 8727 } 8728 8729 var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) 8730 var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset) 8731 if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && 8732 cmp(minPos(curAnchor, curFocus), from) == 0 && 8733 cmp(maxPos(curAnchor, curFocus), to) == 0) 8734 { return } 8735 8736 var view = cm.display.view 8737 var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || 8738 {node: view[0].measure.map[2], offset: 0} 8739 var end = to.line < cm.display.viewTo && posToDOM(cm, to) 8740 if (!end) { 8741 var measure = view[view.length - 1].measure 8742 var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map 8743 end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]} 8744 } 8745 8746 if (!start || !end) { 8747 sel.removeAllRanges() 8748 return 8749 } 8750 8751 var old = sel.rangeCount && sel.getRangeAt(0), rng 8752 try { rng = range(start.node, start.offset, end.offset, end.node) } 8753 catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible 8754 if (rng) { 8755 if (!gecko && cm.state.focused) { 8756 sel.collapse(start.node, start.offset) 8757 if (!rng.collapsed) { 8758 sel.removeAllRanges() 8759 sel.addRange(rng) 8760 } 8761 } else { 8762 sel.removeAllRanges() 8763 sel.addRange(rng) 8764 } 8765 if (old && sel.anchorNode == null) { sel.addRange(old) } 8766 else if (gecko) { this.startGracePeriod() } 8767 } 8768 this.rememberSelection() 8769 }; 8770 8771 ContentEditableInput.prototype.startGracePeriod = function () { 8772 var this$1 = this; 8773 8774 clearTimeout(this.gracePeriod) 8775 this.gracePeriod = setTimeout(function () { 8776 this$1.gracePeriod = false 8777 if (this$1.selectionChanged()) 8778 { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }) } 8779 }, 20) 8780 }; 8781 8782 ContentEditableInput.prototype.showMultipleSelections = function (info) { 8783 removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors) 8784 removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection) 8785 }; 8786 8787 ContentEditableInput.prototype.rememberSelection = function () { 8788 var sel = window.getSelection() 8789 this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset 8790 this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset 8791 }; 8792 8793 ContentEditableInput.prototype.selectionInEditor = function () { 8794 var sel = window.getSelection() 8795 if (!sel.rangeCount) { return false } 8796 var node = sel.getRangeAt(0).commonAncestorContainer 8797 return contains(this.div, node) 8798 }; 8799 8800 ContentEditableInput.prototype.focus = function () { 8801 if (this.cm.options.readOnly != "nocursor") { 8802 if (!this.selectionInEditor()) 8803 { this.showSelection(this.prepareSelection(), true) } 8804 this.div.focus() 8805 } 8806 }; 8807 ContentEditableInput.prototype.blur = function () { this.div.blur() }; 8808 ContentEditableInput.prototype.getField = function () { return this.div }; 8809 8810 ContentEditableInput.prototype.supportsTouch = function () { return true }; 8811 8812 ContentEditableInput.prototype.receivedFocus = function () { 8813 var input = this 8814 if (this.selectionInEditor()) 8815 { this.pollSelection() } 8816 else 8817 { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }) } 8818 8819 function poll() { 8820 if (input.cm.state.focused) { 8821 input.pollSelection() 8822 input.polling.set(input.cm.options.pollInterval, poll) 8823 } 8824 } 8825 this.polling.set(this.cm.options.pollInterval, poll) 8826 }; 8827 8828 ContentEditableInput.prototype.selectionChanged = function () { 8829 var sel = window.getSelection() 8830 return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || 8831 sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset 8832 }; 8833 8834 ContentEditableInput.prototype.pollSelection = function () { 8835 if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return } 8836 var sel = window.getSelection(), cm = this.cm 8837 // On Android Chrome (version 56, at least), backspacing into an 8838 // uneditable block element will put the cursor in that element, 8839 // and then, because it's not editable, hide the virtual keyboard. 8840 // Because Android doesn't allow us to actually detect backspace 8841 // presses in a sane way, this code checks for when that happens 8842 // and simulates a backspace press in this case. 8843 if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anchorNode)) { 8844 this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}) 8845 this.blur() 8846 this.focus() 8847 return 8848 } 8849 if (this.composing) { return } 8850 this.rememberSelection() 8851 var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) 8852 var head = domToPos(cm, sel.focusNode, sel.focusOffset) 8853 if (anchor && head) { runInOp(cm, function () { 8854 setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll) 8855 if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true } 8856 }) } 8857 }; 8858 8859 ContentEditableInput.prototype.pollContent = function () { 8860 if (this.readDOMTimeout != null) { 8861 clearTimeout(this.readDOMTimeout) 8862 this.readDOMTimeout = null 8863 } 8864 8865 var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary() 8866 var from = sel.from(), to = sel.to() 8867 if (from.ch == 0 && from.line > cm.firstLine()) 8868 { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) } 8869 if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) 8870 { to = Pos(to.line + 1, 0) } 8871 if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false } 8872 8873 var fromIndex, fromLine, fromNode 8874 if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { 8875 fromLine = lineNo(display.view[0].line) 8876 fromNode = display.view[0].node 8877 } else { 8878 fromLine = lineNo(display.view[fromIndex].line) 8879 fromNode = display.view[fromIndex - 1].node.nextSibling 8880 } 8881 var toIndex = findViewIndex(cm, to.line) 8882 var toLine, toNode 8883 if (toIndex == display.view.length - 1) { 8884 toLine = display.viewTo - 1 8885 toNode = display.lineDiv.lastChild 8886 } else { 8887 toLine = lineNo(display.view[toIndex + 1].line) - 1 8888 toNode = display.view[toIndex + 1].node.previousSibling 8889 } 8890 8891 if (!fromNode) { return false } 8892 var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)) 8893 var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)) 8894 while (newText.length > 1 && oldText.length > 1) { 8895 if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- } 8896 else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ } 8897 else { break } 8898 } 8899 8900 var cutFront = 0, cutEnd = 0 8901 var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length) 8902 while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) 8903 { ++cutFront } 8904 var newBot = lst(newText), oldBot = lst(oldText) 8905 var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), 8906 oldBot.length - (oldText.length == 1 ? cutFront : 0)) 8907 while (cutEnd < maxCutEnd && 8908 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) 8909 { ++cutEnd } 8910 // Try to move start of change to start of selection if ambiguous 8911 if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { 8912 while (cutFront && cutFront > from.ch && 8913 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { 8914 cutFront-- 8915 cutEnd++ 8916 } 8917 } 8918 8919 newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "") 8920 newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "") 8921 8922 var chFrom = Pos(fromLine, cutFront) 8923 var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0) 8924 if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { 8925 replaceRange(cm.doc, newText, chFrom, chTo, "+input") 8926 return true 8927 } 8928 }; 8929 8930 ContentEditableInput.prototype.ensurePolled = function () { 8931 this.forceCompositionEnd() 8932 }; 8933 ContentEditableInput.prototype.reset = function () { 8934 this.forceCompositionEnd() 8935 }; 8936 ContentEditableInput.prototype.forceCompositionEnd = function () { 8937 if (!this.composing) { return } 8938 clearTimeout(this.readDOMTimeout) 8939 this.composing = null 8940 this.updateFromDOM() 8941 this.div.blur() 8942 this.div.focus() 8943 }; 8944 ContentEditableInput.prototype.readFromDOMSoon = function () { 8945 var this$1 = this; 8946 8947 if (this.readDOMTimeout != null) { return } 8948 this.readDOMTimeout = setTimeout(function () { 8949 this$1.readDOMTimeout = null 8950 if (this$1.composing) { 8951 if (this$1.composing.done) { this$1.composing = null } 8952 else { return } 8953 } 8954 this$1.updateFromDOM() 8955 }, 80) 8956 }; 8957 8958 ContentEditableInput.prototype.updateFromDOM = function () { 8959 var this$1 = this; 8960 8961 if (this.cm.isReadOnly() || !this.pollContent()) 8962 { runInOp(this.cm, function () { return regChange(this$1.cm); }) } 8963 }; 8964 8965 ContentEditableInput.prototype.setUneditable = function (node) { 8966 node.contentEditable = "false" 8967 }; 8968 8969 ContentEditableInput.prototype.onKeyPress = function (e) { 8970 if (e.charCode == 0) { return } 8971 e.preventDefault() 8972 if (!this.cm.isReadOnly()) 8973 { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0) } 8974 }; 8975 8976 ContentEditableInput.prototype.readOnlyChanged = function (val) { 8977 this.div.contentEditable = String(val != "nocursor") 8978 }; 8979 8980 ContentEditableInput.prototype.onContextMenu = function () {}; 8981 ContentEditableInput.prototype.resetPosition = function () {}; 8982 8983 ContentEditableInput.prototype.needsContentAttribute = true 8984 8985 function posToDOM(cm, pos) { 8986 var view = findViewForLine(cm, pos.line) 8987 if (!view || view.hidden) { return null } 8988 var line = getLine(cm.doc, pos.line) 8989 var info = mapFromLineView(view, line, pos.line) 8990 8991 var order = getOrder(line, cm.doc.direction), side = "left" 8992 if (order) { 8993 var partPos = getBidiPartAt(order, pos.ch) 8994 side = partPos % 2 ? "right" : "left" 8995 } 8996 var result = nodeAndOffsetInLineMap(info.map, pos.ch, side) 8997 result.offset = result.collapse == "right" ? result.end : result.start 8998 return result 8999 } 9000 9001 function isInGutter(node) { 9002 for (var scan = node; scan; scan = scan.parentNode) 9003 { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } } 9004 return false 9005 } 9006 9007 function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } 9008 9009 function domTextBetween(cm, from, to, fromLine, toLine) { 9010 var text = "", closing = false, lineSep = cm.doc.lineSeparator() 9011 function recognizeMarker(id) { return function (marker) { return marker.id == id; } } 9012 function close() { 9013 if (closing) { 9014 text += lineSep 9015 closing = false 9016 } 9017 } 9018 function addText(str) { 9019 if (str) { 9020 close() 9021 text += str 9022 } 9023 } 9024 function walk(node) { 9025 if (node.nodeType == 1) { 9026 var cmText = node.getAttribute("cm-text") 9027 if (cmText != null) { 9028 addText(cmText || node.textContent.replace(/\u200b/g, "")) 9029 return 9030 } 9031 var markerID = node.getAttribute("cm-marker"), range 9032 if (markerID) { 9033 var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)) 9034 if (found.length && (range = found[0].find(0))) 9035 { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)) } 9036 return 9037 } 9038 if (node.getAttribute("contenteditable") == "false") { return } 9039 var isBlock = /^(pre|div|p)$/i.test(node.nodeName) 9040 if (isBlock) { close() } 9041 for (var i = 0; i < node.childNodes.length; i++) 9042 { walk(node.childNodes[i]) } 9043 if (isBlock) { closing = true } 9044 } else if (node.nodeType == 3) { 9045 addText(node.nodeValue) 9046 } 9047 } 9048 for (;;) { 9049 walk(from) 9050 if (from == to) { break } 9051 from = from.nextSibling 9052 } 9053 return text 9054 } 9055 9056 function domToPos(cm, node, offset) { 9057 var lineNode 9058 if (node == cm.display.lineDiv) { 9059 lineNode = cm.display.lineDiv.childNodes[offset] 9060 if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) } 9061 node = null; offset = 0 9062 } else { 9063 for (lineNode = node;; lineNode = lineNode.parentNode) { 9064 if (!lineNode || lineNode == cm.display.lineDiv) { return null } 9065 if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break } 9066 } 9067 } 9068 for (var i = 0; i < cm.display.view.length; i++) { 9069 var lineView = cm.display.view[i] 9070 if (lineView.node == lineNode) 9071 { return locateNodeInLineView(lineView, node, offset) } 9072 } 9073 } 9074 9075 function locateNodeInLineView(lineView, node, offset) { 9076 var wrapper = lineView.text.firstChild, bad = false 9077 if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) } 9078 if (node == wrapper) { 9079 bad = true 9080 node = wrapper.childNodes[offset] 9081 offset = 0 9082 if (!node) { 9083 var line = lineView.rest ? lst(lineView.rest) : lineView.line 9084 return badPos(Pos(lineNo(line), line.text.length), bad) 9085 } 9086 } 9087 9088 var textNode = node.nodeType == 3 ? node : null, topNode = node 9089 if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { 9090 textNode = node.firstChild 9091 if (offset) { offset = textNode.nodeValue.length } 9092 } 9093 while (topNode.parentNode != wrapper) { topNode = topNode.parentNode } 9094 var measure = lineView.measure, maps = measure.maps 9095 9096 function find(textNode, topNode, offset) { 9097 for (var i = -1; i < (maps ? maps.length : 0); i++) { 9098 var map = i < 0 ? measure.map : maps[i] 9099 for (var j = 0; j < map.length; j += 3) { 9100 var curNode = map[j + 2] 9101 if (curNode == textNode || curNode == topNode) { 9102 var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]) 9103 var ch = map[j] + offset 9104 if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0)] } 9105 return Pos(line, ch) 9106 } 9107 } 9108 } 9109 } 9110 var found = find(textNode, topNode, offset) 9111 if (found) { return badPos(found, bad) } 9112 9113 // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems 9114 for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { 9115 found = find(after, after.firstChild, 0) 9116 if (found) 9117 { return badPos(Pos(found.line, found.ch - dist), bad) } 9118 else 9119 { dist += after.textContent.length } 9120 } 9121 for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) { 9122 found = find(before, before.firstChild, -1) 9123 if (found) 9124 { return badPos(Pos(found.line, found.ch + dist$1), bad) } 9125 else 9126 { dist$1 += before.textContent.length } 9127 } 9128 } 9129 9130 // TEXTAREA INPUT STYLE 9131 9132 var TextareaInput = function(cm) { 9133 this.cm = cm 9134 // See input.poll and input.reset 9135 this.prevInput = "" 9136 9137 // Flag that indicates whether we expect input to appear real soon 9138 // now (after some event like 'keypress' or 'input') and are 9139 // polling intensively. 9140 this.pollingFast = false 9141 // Self-resetting timeout for the poller 9142 this.polling = new Delayed() 9143 // Used to work around IE issue with selection being forgotten when focus moves away from textarea 9144 this.hasSelection = false 9145 this.composing = null 9146 }; 9147 9148 TextareaInput.prototype.init = function (display) { 9149 var this$1 = this; 9150 9151 var input = this, cm = this.cm 9152 9153 // Wraps and hides input textarea 9154 var div = this.wrapper = hiddenTextarea() 9155 // The semihidden textarea that is focused when the editor is 9156 // focused, and receives input. 9157 var te = this.textarea = div.firstChild 9158 display.wrapper.insertBefore(div, display.wrapper.firstChild) 9159 9160 // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) 9161 if (ios) { te.style.width = "0px" } 9162 9163 on(te, "input", function () { 9164 if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null } 9165 input.poll() 9166 }) 9167 9168 on(te, "paste", function (e) { 9169 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } 9170 9171 cm.state.pasteIncoming = true 9172 input.fastPoll() 9173 }) 9174 9175 function prepareCopyCut(e) { 9176 if (signalDOMEvent(cm, e)) { return } 9177 if (cm.somethingSelected()) { 9178 setLastCopied({lineWise: false, text: cm.getSelections()}) 9179 } else if (!cm.options.lineWiseCopyCut) { 9180 return 9181 } else { 9182 var ranges = copyableRanges(cm) 9183 setLastCopied({lineWise: true, text: ranges.text}) 9184 if (e.type == "cut") { 9185 cm.setSelections(ranges.ranges, null, sel_dontScroll) 9186 } else { 9187 input.prevInput = "" 9188 te.value = ranges.text.join("\n") 9189 selectInput(te) 9190 } 9191 } 9192 if (e.type == "cut") { cm.state.cutIncoming = true } 9193 } 9194 on(te, "cut", prepareCopyCut) 9195 on(te, "copy", prepareCopyCut) 9196 9197 on(display.scroller, "paste", function (e) { 9198 if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } 9199 cm.state.pasteIncoming = true 9200 input.focus() 9201 }) 9202 9203 // Prevent normal selection in the editor (we handle our own) 9204 on(display.lineSpace, "selectstart", function (e) { 9205 if (!eventInWidget(display, e)) { e_preventDefault(e) } 9206 }) 9207 9208 on(te, "compositionstart", function () { 9209 var start = cm.getCursor("from") 9210 if (input.composing) { input.composing.range.clear() } 9211 input.composing = { 9212 start: start, 9213 range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) 9214 } 9215 }) 9216 on(te, "compositionend", function () { 9217 if (input.composing) { 9218 input.poll() 9219 input.composing.range.clear() 9220 input.composing = null 9221 } 9222 }) 9223 }; 9224 9225 TextareaInput.prototype.prepareSelection = function () { 9226 // Redraw the selection and/or cursor 9227 var cm = this.cm, display = cm.display, doc = cm.doc 9228 var result = prepareSelection(cm) 9229 9230 // Move the hidden textarea near the cursor to prevent scrolling artifacts 9231 if (cm.options.moveInputWithCursor) { 9232 var headPos = cursorCoords(cm, doc.sel.primary().head, "div") 9233 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect() 9234 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, 9235 headPos.top + lineOff.top - wrapOff.top)) 9236 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, 9237 headPos.left + lineOff.left - wrapOff.left)) 9238 } 9239 9240 return result 9241 }; 9242 9243 TextareaInput.prototype.showSelection = function (drawn) { 9244 var cm = this.cm, display = cm.display 9245 removeChildrenAndAdd(display.cursorDiv, drawn.cursors) 9246 removeChildrenAndAdd(display.selectionDiv, drawn.selection) 9247 if (drawn.teTop != null) { 9248 this.wrapper.style.top = drawn.teTop + "px" 9249 this.wrapper.style.left = drawn.teLeft + "px" 9250 } 9251 }; 9252 9253 // Reset the input to correspond to the selection (or to be empty, 9254 // when not typing and nothing is selected) 9255 TextareaInput.prototype.reset = function (typing) { 9256 if (this.contextMenuPending || this.composing) { return } 9257 var cm = this.cm 9258 if (cm.somethingSelected()) { 9259 this.prevInput = "" 9260 var content = cm.getSelection() 9261 this.textarea.value = content 9262 if (cm.state.focused) { selectInput(this.textarea) } 9263 if (ie && ie_version >= 9) { this.hasSelection = content } 9264 } else if (!typing) { 9265 this.prevInput = this.textarea.value = "" 9266 if (ie && ie_version >= 9) { this.hasSelection = null } 9267 } 9268 }; 9269 9270 TextareaInput.prototype.getField = function () { return this.textarea }; 9271 9272 TextareaInput.prototype.supportsTouch = function () { return false }; 9273 9274 TextareaInput.prototype.focus = function () { 9275 if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { 9276 try { this.textarea.focus() } 9277 catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM 9278 } 9279 }; 9280 9281 TextareaInput.prototype.blur = function () { this.textarea.blur() }; 9282 9283 TextareaInput.prototype.resetPosition = function () { 9284 this.wrapper.style.top = this.wrapper.style.left = 0 9285 }; 9286 9287 TextareaInput.prototype.receivedFocus = function () { this.slowPoll() }; 9288 9289 // Poll for input changes, using the normal rate of polling. This 9290 // runs as long as the editor is focused. 9291 TextareaInput.prototype.slowPoll = function () { 9292 var this$1 = this; 9293 9294 if (this.pollingFast) { return } 9295 this.polling.set(this.cm.options.pollInterval, function () { 9296 this$1.poll() 9297 if (this$1.cm.state.focused) { this$1.slowPoll() } 9298 }) 9299 }; 9300 9301 // When an event has just come in that is likely to add or change 9302 // something in the input textarea, we poll faster, to ensure that 9303 // the change appears on the screen quickly. 9304 TextareaInput.prototype.fastPoll = function () { 9305 var missed = false, input = this 9306 input.pollingFast = true 9307 function p() { 9308 var changed = input.poll() 9309 if (!changed && !missed) {missed = true; input.polling.set(60, p)} 9310 else {input.pollingFast = false; input.slowPoll()} 9311 } 9312 input.polling.set(20, p) 9313 }; 9314 9315 // Read input from the textarea, and update the document to match. 9316 // When something is selected, it is present in the textarea, and 9317 // selected (unless it is huge, in which case a placeholder is 9318 // used). When nothing is selected, the cursor sits after previously 9319 // seen text (can be empty), which is stored in prevInput (we must 9320 // not reset the textarea when typing, because that breaks IME). 9321 TextareaInput.prototype.poll = function () { 9322 var this$1 = this; 9323 9324 var cm = this.cm, input = this.textarea, prevInput = this.prevInput 9325 // Since this is called a *lot*, try to bail out as cheaply as 9326 // possible when it is clear that nothing happened. hasSelection 9327 // will be the case when there is a lot of text in the textarea, 9328 // in which case reading its value would be expensive. 9329 if (this.contextMenuPending || !cm.state.focused || 9330 (hasSelection(input) && !prevInput && !this.composing) || 9331 cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) 9332 { return false } 9333 9334 var text = input.value 9335 // If nothing changed, bail. 9336 if (text == prevInput && !cm.somethingSelected()) { return false } 9337 // Work around nonsensical selection resetting in IE9/10, and 9338 // inexplicable appearance of private area unicode characters on 9339 // some key combos in Mac (#2689). 9340 if (ie && ie_version >= 9 && this.hasSelection === text || 9341 mac && /[\uf700-\uf7ff]/.test(text)) { 9342 cm.display.input.reset() 9343 return false 9344 } 9345 9346 if (cm.doc.sel == cm.display.selForContextMenu) { 9347 var first = text.charCodeAt(0) 9348 if (first == 0x200b && !prevInput) { prevInput = "\u200b" } 9349 if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } 9350 } 9351 // Find the part of the input that is actually new 9352 var same = 0, l = Math.min(prevInput.length, text.length) 9353 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same } 9354 9355 runInOp(cm, function () { 9356 applyTextInput(cm, text.slice(same), prevInput.length - same, 9357 null, this$1.composing ? "*compose" : null) 9358 9359 // Don't leave long text in the textarea, since it makes further polling slow 9360 if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = "" } 9361 else { this$1.prevInput = text } 9362 9363 if (this$1.composing) { 9364 this$1.composing.range.clear() 9365 this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"), 9366 {className: "CodeMirror-composing"}) 9367 } 9368 }) 9369 return true 9370 }; 9371 9372 TextareaInput.prototype.ensurePolled = function () { 9373 if (this.pollingFast && this.poll()) { this.pollingFast = false } 9374 }; 9375 9376 TextareaInput.prototype.onKeyPress = function () { 9377 if (ie && ie_version >= 9) { this.hasSelection = null } 9378 this.fastPoll() 9379 }; 9380 9381 TextareaInput.prototype.onContextMenu = function (e) { 9382 var input = this, cm = input.cm, display = cm.display, te = input.textarea 9383 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop 9384 if (!pos || presto) { return } // Opera is difficult. 9385 9386 // Reset the current text selection only if the click is done outside of the selection 9387 // and 'resetSelectionOnContextMenu' option is true. 9388 var reset = cm.options.resetSelectionOnContextMenu 9389 if (reset && cm.doc.sel.contains(pos) == -1) 9390 { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) } 9391 9392 var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText 9393 input.wrapper.style.cssText = "position: absolute" 9394 var wrapperBox = input.wrapper.getBoundingClientRect() 9395 te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);" 9396 var oldScrollY 9397 if (webkit) { oldScrollY = window.scrollY } // Work around Chrome issue (#2712) 9398 display.input.focus() 9399 if (webkit) { window.scrollTo(null, oldScrollY) } 9400 display.input.reset() 9401 // Adds "Select all" to context menu in FF 9402 if (!cm.somethingSelected()) { te.value = input.prevInput = " " } 9403 input.contextMenuPending = true 9404 display.selForContextMenu = cm.doc.sel 9405 clearTimeout(display.detectingSelectAll) 9406 9407 // Select-all will be greyed out if there's nothing to select, so 9408 // this adds a zero-width space so that we can later check whether 9409 // it got selected. 9410 function prepareSelectAllHack() { 9411 if (te.selectionStart != null) { 9412 var selected = cm.somethingSelected() 9413 var extval = "\u200b" + (selected ? te.value : "") 9414 te.value = "\u21da" // Used to catch context-menu undo 9415 te.value = extval 9416 input.prevInput = selected ? "" : "\u200b" 9417 te.selectionStart = 1; te.selectionEnd = extval.length 9418 // Re-set this, in case some other handler touched the 9419 // selection in the meantime. 9420 display.selForContextMenu = cm.doc.sel 9421 } 9422 } 9423 function rehide() { 9424 input.contextMenuPending = false 9425 input.wrapper.style.cssText = oldWrapperCSS 9426 te.style.cssText = oldCSS 9427 if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos) } 9428 9429 // Try to detect the user choosing select-all 9430 if (te.selectionStart != null) { 9431 if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack() } 9432 var i = 0, poll = function () { 9433 if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && 9434 te.selectionEnd > 0 && input.prevInput == "\u200b") { 9435 operation(cm, selectAll)(cm) 9436 } else if (i++ < 10) { 9437 display.detectingSelectAll = setTimeout(poll, 500) 9438 } else { 9439 display.selForContextMenu = null 9440 display.input.reset() 9441 } 9442 } 9443 display.detectingSelectAll = setTimeout(poll, 200) 9444 } 9445 } 9446 9447 if (ie && ie_version >= 9) { prepareSelectAllHack() } 9448 if (captureRightClick) { 9449 e_stop(e) 9450 var mouseup = function () { 9451 off(window, "mouseup", mouseup) 9452 setTimeout(rehide, 20) 9453 } 9454 on(window, "mouseup", mouseup) 9455 } else { 9456 setTimeout(rehide, 50) 9457 } 9458 }; 9459 9460 TextareaInput.prototype.readOnlyChanged = function (val) { 9461 if (!val) { this.reset() } 9462 this.textarea.disabled = val == "nocursor" 9463 }; 9464 9465 TextareaInput.prototype.setUneditable = function () {}; 9466 9467 TextareaInput.prototype.needsContentAttribute = false 9468 9469 function fromTextArea(textarea, options) { 9470 options = options ? copyObj(options) : {} 9471 options.value = textarea.value 9472 if (!options.tabindex && textarea.tabIndex) 9473 { options.tabindex = textarea.tabIndex } 9474 if (!options.placeholder && textarea.placeholder) 9475 { options.placeholder = textarea.placeholder } 9476 // Set autofocus to true if this textarea is focused, or if it has 9477 // autofocus and no other element is focused. 9478 if (options.autofocus == null) { 9479 var hasFocus = activeElt() 9480 options.autofocus = hasFocus == textarea || 9481 textarea.getAttribute("autofocus") != null && hasFocus == document.body 9482 } 9483 9484 function save() {textarea.value = cm.getValue()} 9485 9486 var realSubmit 9487 if (textarea.form) { 9488 on(textarea.form, "submit", save) 9489 // Deplorable hack to make the submit method do the right thing. 9490 if (!options.leaveSubmitMethodAlone) { 9491 var form = textarea.form 9492 realSubmit = form.submit 9493 try { 9494 var wrappedSubmit = form.submit = function () { 9495 save() 9496 form.submit = realSubmit 9497 form.submit() 9498 form.submit = wrappedSubmit 9499 } 9500 } catch(e) {} 9501 } 9502 } 9503 9504 options.finishInit = function (cm) { 9505 cm.save = save 9506 cm.getTextArea = function () { return textarea; } 9507 cm.toTextArea = function () { 9508 cm.toTextArea = isNaN // Prevent this from being ran twice 9509 save() 9510 textarea.parentNode.removeChild(cm.getWrapperElement()) 9511 textarea.style.display = "" 9512 if (textarea.form) { 9513 off(textarea.form, "submit", save) 9514 if (typeof textarea.form.submit == "function") 9515 { textarea.form.submit = realSubmit } 9516 } 9517 } 9518 } 9519 9520 textarea.style.display = "none" 9521 var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); }, 9522 options) 9523 return cm 9524 } 9525 9526 function addLegacyProps(CodeMirror) { 9527 CodeMirror.off = off 9528 CodeMirror.on = on 9529 CodeMirror.wheelEventPixels = wheelEventPixels 9530 CodeMirror.Doc = Doc 9531 CodeMirror.splitLines = splitLinesAuto 9532 CodeMirror.countColumn = countColumn 9533 CodeMirror.findColumn = findColumn 9534 CodeMirror.isWordChar = isWordCharBasic 9535 CodeMirror.Pass = Pass 9536 CodeMirror.signal = signal 9537 CodeMirror.Line = Line 9538 CodeMirror.changeEnd = changeEnd 9539 CodeMirror.scrollbarModel = scrollbarModel 9540 CodeMirror.Pos = Pos 9541 CodeMirror.cmpPos = cmp 9542 CodeMirror.modes = modes 9543 CodeMirror.mimeModes = mimeModes 9544 CodeMirror.resolveMode = resolveMode 9545 CodeMirror.getMode = getMode 9546 CodeMirror.modeExtensions = modeExtensions 9547 CodeMirror.extendMode = extendMode 9548 CodeMirror.copyState = copyState 9549 CodeMirror.startState = startState 9550 CodeMirror.innerMode = innerMode 9551 CodeMirror.commands = commands 9552 CodeMirror.keyMap = keyMap 9553 CodeMirror.keyName = keyName 9554 CodeMirror.isModifierKey = isModifierKey 9555 CodeMirror.lookupKey = lookupKey 9556 CodeMirror.normalizeKeyMap = normalizeKeyMap 9557 CodeMirror.StringStream = StringStream 9558 CodeMirror.SharedTextMarker = SharedTextMarker 9559 CodeMirror.TextMarker = TextMarker 9560 CodeMirror.LineWidget = LineWidget 9561 CodeMirror.e_preventDefault = e_preventDefault 9562 CodeMirror.e_stopPropagation = e_stopPropagation 9563 CodeMirror.e_stop = e_stop 9564 CodeMirror.addClass = addClass 9565 CodeMirror.contains = contains 9566 CodeMirror.rmClass = rmClass 9567 CodeMirror.keyNames = keyNames 9568 } 9569 9570 // EDITOR CONSTRUCTOR 9571 9572 defineOptions(CodeMirror) 9573 9574 addEditorMethods(CodeMirror) 9575 9576 // Set up methods on CodeMirror's prototype to redirect to the editor's document. 9577 var dontDelegate = "iter insert remove copy getEditor constructor".split(" ") 9578 for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) 9579 { CodeMirror.prototype[prop] = (function(method) { 9580 return function() {return method.apply(this.doc, arguments)} 9581 })(Doc.prototype[prop]) } } 9582 9583 eventMixin(Doc) 9584 9585 // INPUT HANDLING 9586 9587 CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput} 9588 9589 // MODE DEFINITION AND QUERYING 9590 9591 // Extra arguments are stored as the mode's dependencies, which is 9592 // used by (legacy) mechanisms like loadmode.js to automatically 9593 // load a mode. (Preferred mechanism is the require/define calls.) 9594 CodeMirror.defineMode = function(name/*, mode, …*/) { 9595 if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name } 9596 defineMode.apply(this, arguments) 9597 } 9598 9599 CodeMirror.defineMIME = defineMIME 9600 9601 // Minimal default mode. 9602 CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); }) 9603 CodeMirror.defineMIME("text/plain", "null") 9604 9605 // EXTENSIONS 9606 9607 CodeMirror.defineExtension = function (name, func) { 9608 CodeMirror.prototype[name] = func 9609 } 9610 CodeMirror.defineDocExtension = function (name, func) { 9611 Doc.prototype[name] = func 9612 } 9613 9614 CodeMirror.fromTextArea = fromTextArea 9615 9616 addLegacyProps(CodeMirror) 9617 9618 CodeMirror.version = "5.30.0" 9619 9620 return CodeMirror; 9621 9622 })));
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
2005 - 2021 © MyBB.de | Alle Rechte vorbehalten! | Sponsor: netcup | Cross-referenced by PHPXref |