File modules/editor/codemirror/lib/codemirror.js
Last commit: Sun Dec 17 01:14:09 2017 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.
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 (direction == "ltr") { 1092 if (order[0].level == 1 && (m = str.match(/^\s+/))) { 1093 order[0].from = m[0].length 1094 order.unshift(new BidiSpan(0, 0, m[0].length)) 1095 } 1096 if (lst(order).level == 1 && (m = str.match(/\s+$/))) { 1097 lst(order).to -= m[0].length 1098 order.push(new BidiSpan(0, len - m[0].length, len)) 1099 } 1100 } 1101 1102 return direction == "rtl" ? order.reverse() : order 1103 } 1104 })() 1105 1106 // Get the bidi ordering for the given line (and cache it). Returns 1107 // false for lines that are fully left-to-right, and an array of 1108 // BidiSpan objects otherwise. 1109 function getOrder(line, direction) { 1110 var order = line.order 1111 if (order == null) { order = line.order = bidiOrdering(line.text, direction) } 1112 return order 1113 } 1114 1115 // EVENT HANDLING 1116 1117 // Lightweight event framework. on/off also work on DOM nodes, 1118 // registering native DOM handlers. 1119 1120 var noHandlers = [] 1121 1122 var on = function(emitter, type, f) { 1123 if (emitter.addEventListener) { 1124 emitter.addEventListener(type, f, false) 1125 } else if (emitter.attachEvent) { 1126 emitter.attachEvent("on" + type, f) 1127 } else { 1128 var map = emitter._handlers || (emitter._handlers = {}) 1129 map[type] = (map[type] || noHandlers).concat(f) 1130 } 1131 } 1132 1133 function getHandlers(emitter, type) { 1134 return emitter._handlers && emitter._handlers[type] || noHandlers 1135 } 1136 1137 function off(emitter, type, f) { 1138 if (emitter.removeEventListener) { 1139 emitter.removeEventListener(type, f, false) 1140 } else if (emitter.detachEvent) { 1141 emitter.detachEvent("on" + type, f) 1142 } else { 1143 var map = emitter._handlers, arr = map && map[type] 1144 if (arr) { 1145 var index = indexOf(arr, f) 1146 if (index > -1) 1147 { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)) } 1148 } 1149 } 1150 } 1151 1152 function signal(emitter, type /*, values...*/) { 1153 var handlers = getHandlers(emitter, type) 1154 if (!handlers.length) { return } 1155 var args = Array.prototype.slice.call(arguments, 2) 1156 for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args) } 1157 } 1158 1159 // The DOM events that CodeMirror handles can be overridden by 1160 // registering a (non-DOM) handler on the editor for the event name, 1161 // and preventDefault-ing the event in that handler. 1162 function signalDOMEvent(cm, e, override) { 1163 if (typeof e == "string") 1164 { e = {type: e, preventDefault: function() { this.defaultPrevented = true }} } 1165 signal(cm, override || e.type, cm, e) 1166 return e_defaultPrevented(e) || e.codemirrorIgnore 1167 } 1168 1169 function signalCursorActivity(cm) { 1170 var arr = cm._handlers && cm._handlers.cursorActivity 1171 if (!arr) { return } 1172 var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []) 1173 for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) 1174 { set.push(arr[i]) } } 1175 } 1176 1177 function hasHandler(emitter, type) { 1178 return getHandlers(emitter, type).length > 0 1179 } 1180 1181 // Add on and off methods to a constructor's prototype, to make 1182 // registering events on such objects more convenient. 1183 function eventMixin(ctor) { 1184 ctor.prototype.on = function(type, f) {on(this, type, f)} 1185 ctor.prototype.off = function(type, f) {off(this, type, f)} 1186 } 1187 1188 // Due to the fact that we still support jurassic IE versions, some 1189 // compatibility wrappers are needed. 1190 1191 function e_preventDefault(e) { 1192 if (e.preventDefault) { e.preventDefault() } 1193 else { e.returnValue = false } 1194 } 1195 function e_stopPropagation(e) { 1196 if (e.stopPropagation) { e.stopPropagation() } 1197 else { e.cancelBubble = true } 1198 } 1199 function e_defaultPrevented(e) { 1200 return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false 1201 } 1202 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e)} 1203 1204 function e_target(e) {return e.target || e.srcElement} 1205 function e_button(e) { 1206 var b = e.which 1207 if (b == null) { 1208 if (e.button & 1) { b = 1 } 1209 else if (e.button & 2) { b = 3 } 1210 else if (e.button & 4) { b = 2 } 1211 } 1212 if (mac && e.ctrlKey && b == 1) { b = 3 } 1213 return b 1214 } 1215 1216 // Detect drag-and-drop 1217 var dragAndDrop = function() { 1218 // There is *some* kind of drag-and-drop support in IE6-8, but I 1219 // couldn't get it to work yet. 1220 if (ie && ie_version < 9) { return false } 1221 var div = elt('div') 1222 return "draggable" in div || "dragDrop" in div 1223 }() 1224 1225 var zwspSupported 1226 function zeroWidthElement(measure) { 1227 if (zwspSupported == null) { 1228 var test = elt("span", "\u200b") 1229 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])) 1230 if (measure.firstChild.offsetHeight != 0) 1231 { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8) } 1232 } 1233 var node = zwspSupported ? elt("span", "\u200b") : 1234 elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px") 1235 node.setAttribute("cm-text", "") 1236 return node 1237 } 1238 1239 // Feature-detect IE's crummy client rect reporting for bidi text 1240 var badBidiRects 1241 function hasBadBidiRects(measure) { 1242 if (badBidiRects != null) { return badBidiRects } 1243 var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")) 1244 var r0 = range(txt, 0, 1).getBoundingClientRect() 1245 var r1 = range(txt, 1, 2).getBoundingClientRect() 1246 removeChildren(measure) 1247 if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780) 1248 return badBidiRects = (r1.right - r0.right < 3) 1249 } 1250 1251 // See if "".split is the broken IE version, if so, provide an 1252 // alternative way to split lines. 1253 var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { 1254 var pos = 0, result = [], l = string.length 1255 while (pos <= l) { 1256 var nl = string.indexOf("\n", pos) 1257 if (nl == -1) { nl = string.length } 1258 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl) 1259 var rt = line.indexOf("\r") 1260 if (rt != -1) { 1261 result.push(line.slice(0, rt)) 1262 pos += rt + 1 1263 } else { 1264 result.push(line) 1265 pos = nl + 1 1266 } 1267 } 1268 return result 1269 } : function (string) { return string.split(/\r\n?|\n/); } 1270 1271 var hasSelection = window.getSelection ? function (te) { 1272 try { return te.selectionStart != te.selectionEnd } 1273 catch(e) { return false } 1274 } : function (te) { 1275 var range 1276 try {range = te.ownerDocument.selection.createRange()} 1277 catch(e) {} 1278 if (!range || range.parentElement() != te) { return false } 1279 return range.compareEndPoints("StartToEnd", range) != 0 1280 } 1281 1282 var hasCopyEvent = (function () { 1283 var e = elt("div") 1284 if ("oncopy" in e) { return true } 1285 e.setAttribute("oncopy", "return;") 1286 return typeof e.oncopy == "function" 1287 })() 1288 1289 var badZoomedRects = null 1290 function hasBadZoomedRects(measure) { 1291 if (badZoomedRects != null) { return badZoomedRects } 1292 var node = removeChildrenAndAdd(measure, elt("span", "x")) 1293 var normal = node.getBoundingClientRect() 1294 var fromRange = range(node, 0, 1).getBoundingClientRect() 1295 return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 1296 } 1297 1298 var modes = {}; 1299 var mimeModes = {}; 1300 // Extra arguments are stored as the mode's dependencies, which is 1301 // used by (legacy) mechanisms like loadmode.js to automatically 1302 // load a mode. (Preferred mechanism is the require/define calls.) 1303 function defineMode(name, mode) { 1304 if (arguments.length > 2) 1305 { mode.dependencies = Array.prototype.slice.call(arguments, 2) } 1306 modes[name] = mode 1307 } 1308 1309 function defineMIME(mime, spec) { 1310 mimeModes[mime] = spec 1311 } 1312 1313 // Given a MIME type, a {name, ...options} config object, or a name 1314 // string, return a mode config object. 1315 function resolveMode(spec) { 1316 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { 1317 spec = mimeModes[spec] 1318 } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { 1319 var found = mimeModes[spec.name] 1320 if (typeof found == "string") { found = {name: found} } 1321 spec = createObj(found, spec) 1322 spec.name = found.name 1323 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { 1324 return resolveMode("application/xml") 1325 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { 1326 return resolveMode("application/json") 1327 } 1328 if (typeof spec == "string") { return {name: spec} } 1329 else { return spec || {name: "null"} } 1330 } 1331 1332 // Given a mode spec (anything that resolveMode accepts), find and 1333 // initialize an actual mode object. 1334 function getMode(options, spec) { 1335 spec = resolveMode(spec) 1336 var mfactory = modes[spec.name] 1337 if (!mfactory) { return getMode(options, "text/plain") } 1338 var modeObj = mfactory(options, spec) 1339 if (modeExtensions.hasOwnProperty(spec.name)) { 1340 var exts = modeExtensions[spec.name] 1341 for (var prop in exts) { 1342 if (!exts.hasOwnProperty(prop)) { continue } 1343 if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop] } 1344 modeObj[prop] = exts[prop] 1345 } 1346 } 1347 modeObj.name = spec.name 1348 if (spec.helperType) { modeObj.helperType = spec.helperType } 1349 if (spec.modeProps) { for (var prop$1 in spec.modeProps) 1350 { modeObj[prop$1] = spec.modeProps[prop$1] } } 1351 1352 return modeObj 1353 } 1354 1355 // This can be used to attach properties to mode objects from 1356 // outside the actual mode definition. 1357 var modeExtensions = {} 1358 function extendMode(mode, properties) { 1359 var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}) 1360 copyObj(properties, exts) 1361 } 1362 1363 function copyState(mode, state) { 1364 if (state === true) { return state } 1365 if (mode.copyState) { return mode.copyState(state) } 1366 var nstate = {} 1367 for (var n in state) { 1368 var val = state[n] 1369 if (val instanceof Array) { val = val.concat([]) } 1370 nstate[n] = val 1371 } 1372 return nstate 1373 } 1374 1375 // Given a mode and a state (for that mode), find the inner mode and 1376 // state at the position that the state refers to. 1377 function innerMode(mode, state) { 1378 var info 1379 while (mode.innerMode) { 1380 info = mode.innerMode(state) 1381 if (!info || info.mode == mode) { break } 1382 state = info.state 1383 mode = info.mode 1384 } 1385 return info || {mode: mode, state: state} 1386 } 1387 1388 function startState(mode, a1, a2) { 1389 return mode.startState ? mode.startState(a1, a2) : true 1390 } 1391 1392 // STRING STREAM 1393 1394 // Fed to the mode parsers, provides helper functions to make 1395 // parsers more succinct. 1396 1397 var StringStream = function(string, tabSize, lineOracle) { 1398 this.pos = this.start = 0 1399 this.string = string 1400 this.tabSize = tabSize || 8 1401 this.lastColumnPos = this.lastColumnValue = 0 1402 this.lineStart = 0 1403 this.lineOracle = lineOracle 1404 }; 1405 1406 StringStream.prototype.eol = function () {return this.pos >= this.string.length}; 1407 StringStream.prototype.sol = function () {return this.pos == this.lineStart}; 1408 StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined}; 1409 StringStream.prototype.next = function () { 1410 if (this.pos < this.string.length) 1411 { return this.string.charAt(this.pos++) } 1412 }; 1413 StringStream.prototype.eat = function (match) { 1414 var ch = this.string.charAt(this.pos) 1415 var ok 1416 if (typeof match == "string") { ok = ch == match } 1417 else { ok = ch && (match.test ? match.test(ch) : match(ch)) } 1418 if (ok) {++this.pos; return ch} 1419 }; 1420 StringStream.prototype.eatWhile = function (match) { 1421 var start = this.pos 1422 while (this.eat(match)){} 1423 return this.pos > start 1424 }; 1425 StringStream.prototype.eatSpace = function () { 1426 var this$1 = this; 1427 1428 var start = this.pos 1429 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos } 1430 return this.pos > start 1431 }; 1432 StringStream.prototype.skipToEnd = function () {this.pos = this.string.length}; 1433 StringStream.prototype.skipTo = function (ch) { 1434 var found = this.string.indexOf(ch, this.pos) 1435 if (found > -1) {this.pos = found; return true} 1436 }; 1437 StringStream.prototype.backUp = function (n) {this.pos -= n}; 1438 StringStream.prototype.column = function () { 1439 if (this.lastColumnPos < this.start) { 1440 this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue) 1441 this.lastColumnPos = this.start 1442 } 1443 return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) 1444 }; 1445 StringStream.prototype.indentation = function () { 1446 return countColumn(this.string, null, this.tabSize) - 1447 (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) 1448 }; 1449 StringStream.prototype.match = function (pattern, consume, caseInsensitive) { 1450 if (typeof pattern == "string") { 1451 var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; } 1452 var substr = this.string.substr(this.pos, pattern.length) 1453 if (cased(substr) == cased(pattern)) { 1454 if (consume !== false) { this.pos += pattern.length } 1455 return true 1456 } 1457 } else { 1458 var match = this.string.slice(this.pos).match(pattern) 1459 if (match && match.index > 0) { return null } 1460 if (match && consume !== false) { this.pos += match[0].length } 1461 return match 1462 } 1463 }; 1464 StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)}; 1465 StringStream.prototype.hideFirstChars = function (n, inner) { 1466 this.lineStart += n 1467 try { return inner() } 1468 finally { this.lineStart -= n } 1469 }; 1470 StringStream.prototype.lookAhead = function (n) { 1471 var oracle = this.lineOracle 1472 return oracle && oracle.lookAhead(n) 1473 }; 1474 StringStream.prototype.baseToken = function () { 1475 var oracle = this.lineOracle 1476 return oracle && oracle.baseToken(this.pos) 1477 }; 1478 1479 var SavedContext = function(state, lookAhead) { 1480 this.state = state 1481 this.lookAhead = lookAhead 1482 }; 1483 1484 var Context = function(doc, state, line, lookAhead) { 1485 this.state = state 1486 this.doc = doc 1487 this.line = line 1488 this.maxLookAhead = lookAhead || 0 1489 this.baseTokens = null 1490 this.baseTokenPos = 1 1491 }; 1492 1493 Context.prototype.lookAhead = function (n) { 1494 var line = this.doc.getLine(this.line + n) 1495 if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n } 1496 return line 1497 }; 1498 1499 Context.prototype.baseToken = function (n) { 1500 var this$1 = this; 1501 1502 if (!this.baseTokens) { return null } 1503 while (this.baseTokens[this.baseTokenPos] <= n) 1504 { this$1.baseTokenPos += 2 } 1505 var type = this.baseTokens[this.baseTokenPos + 1] 1506 return {type: type && type.replace(/( |^)overlay .*/, ""), 1507 size: this.baseTokens[this.baseTokenPos] - n} 1508 }; 1509 1510 Context.prototype.nextLine = function () { 1511 this.line++ 1512 if (this.maxLookAhead > 0) { this.maxLookAhead-- } 1513 }; 1514 1515 Context.fromSaved = function (doc, saved, line) { 1516 if (saved instanceof SavedContext) 1517 { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) } 1518 else 1519 { return new Context(doc, copyState(doc.mode, saved), line) } 1520 }; 1521 1522 Context.prototype.save = function (copy) { 1523 var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state 1524 return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state 1525 }; 1526 1527 1528 // Compute a style array (an array starting with a mode generation 1529 // -- for invalidation -- followed by pairs of end positions and 1530 // style strings), which is used to highlight the tokens on the 1531 // line. 1532 function highlightLine(cm, line, context, forceToEnd) { 1533 // A styles array always starts with a number identifying the 1534 // mode/overlays that it is based on (for easy invalidation). 1535 var st = [cm.state.modeGen], lineClasses = {} 1536 // Compute the base array of styles 1537 runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); }, 1538 lineClasses, forceToEnd) 1539 var state = context.state 1540 1541 // Run overlays, adjust style array. 1542 var loop = function ( o ) { 1543 context.baseTokens = st 1544 var overlay = cm.state.overlays[o], i = 1, at = 0 1545 context.state = true 1546 runMode(cm, line.text, overlay.mode, context, function (end, style) { 1547 var start = i 1548 // Ensure there's a token end at the current position, and that i points at it 1549 while (at < end) { 1550 var i_end = st[i] 1551 if (i_end > end) 1552 { st.splice(i, 1, end, st[i+1], i_end) } 1553 i += 2 1554 at = Math.min(end, i_end) 1555 } 1556 if (!style) { return } 1557 if (overlay.opaque) { 1558 st.splice(start, i - start, end, "overlay " + style) 1559 i = start + 2 1560 } else { 1561 for (; start < i; start += 2) { 1562 var cur = st[start+1] 1563 st[start+1] = (cur ? cur + " " : "") + "overlay " + style 1564 } 1565 } 1566 }, lineClasses) 1567 context.state = state 1568 context.baseTokens = null 1569 context.baseTokenPos = 1 1570 }; 1571 1572 for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); 1573 1574 return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} 1575 } 1576 1577 function getLineStyles(cm, line, updateFrontier) { 1578 if (!line.styles || line.styles[0] != cm.state.modeGen) { 1579 var context = getContextBefore(cm, lineNo(line)) 1580 var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state) 1581 var result = highlightLine(cm, line, context) 1582 if (resetState) { context.state = resetState } 1583 line.stateAfter = context.save(!resetState) 1584 line.styles = result.styles 1585 if (result.classes) { line.styleClasses = result.classes } 1586 else if (line.styleClasses) { line.styleClasses = null } 1587 if (updateFrontier === cm.doc.highlightFrontier) 1588 { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier) } 1589 } 1590 return line.styles 1591 } 1592 1593 function getContextBefore(cm, n, precise) { 1594 var doc = cm.doc, display = cm.display 1595 if (!doc.mode.startState) { return new Context(doc, true, n) } 1596 var start = findStartLine(cm, n, precise) 1597 var saved = start > doc.first && getLine(doc, start - 1).stateAfter 1598 var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start) 1599 1600 doc.iter(start, n, function (line) { 1601 processLine(cm, line.text, context) 1602 var pos = context.line 1603 line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null 1604 context.nextLine() 1605 }) 1606 if (precise) { doc.modeFrontier = context.line } 1607 return context 1608 } 1609 1610 // Lightweight form of highlight -- proceed over this line and 1611 // update state, but don't save a style array. Used for lines that 1612 // aren't currently visible. 1613 function processLine(cm, text, context, startAt) { 1614 var mode = cm.doc.mode 1615 var stream = new StringStream(text, cm.options.tabSize, context) 1616 stream.start = stream.pos = startAt || 0 1617 if (text == "") { callBlankLine(mode, context.state) } 1618 while (!stream.eol()) { 1619 readToken(mode, stream, context.state) 1620 stream.start = stream.pos 1621 } 1622 } 1623 1624 function callBlankLine(mode, state) { 1625 if (mode.blankLine) { return mode.blankLine(state) } 1626 if (!mode.innerMode) { return } 1627 var inner = innerMode(mode, state) 1628 if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) } 1629 } 1630 1631 function readToken(mode, stream, state, inner) { 1632 for (var i = 0; i < 10; i++) { 1633 if (inner) { inner[0] = innerMode(mode, state).mode } 1634 var style = mode.token(stream, state) 1635 if (stream.pos > stream.start) { return style } 1636 } 1637 throw new Error("Mode " + mode.name + " failed to advance stream.") 1638 } 1639 1640 var Token = function(stream, type, state) { 1641 this.start = stream.start; this.end = stream.pos 1642 this.string = stream.current() 1643 this.type = type || null 1644 this.state = state 1645 }; 1646 1647 // Utility for getTokenAt and getLineTokens 1648 function takeToken(cm, pos, precise, asArray) { 1649 var doc = cm.doc, mode = doc.mode, style 1650 pos = clipPos(doc, pos) 1651 var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise) 1652 var stream = new StringStream(line.text, cm.options.tabSize, context), tokens 1653 if (asArray) { tokens = [] } 1654 while ((asArray || stream.pos < pos.ch) && !stream.eol()) { 1655 stream.start = stream.pos 1656 style = readToken(mode, stream, context.state) 1657 if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))) } 1658 } 1659 return asArray ? tokens : new Token(stream, style, context.state) 1660 } 1661 1662 function extractLineClasses(type, output) { 1663 if (type) { for (;;) { 1664 var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/) 1665 if (!lineClass) { break } 1666 type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length) 1667 var prop = lineClass[1] ? "bgClass" : "textClass" 1668 if (output[prop] == null) 1669 { output[prop] = lineClass[2] } 1670 else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) 1671 { output[prop] += " " + lineClass[2] } 1672 } } 1673 return type 1674 } 1675 1676 // Run the given mode's parser over a line, calling f for each token. 1677 function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { 1678 var flattenSpans = mode.flattenSpans 1679 if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans } 1680 var curStart = 0, curStyle = null 1681 var stream = new StringStream(text, cm.options.tabSize, context), style 1682 var inner = cm.options.addModeClass && [null] 1683 if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses) } 1684 while (!stream.eol()) { 1685 if (stream.pos > cm.options.maxHighlightLength) { 1686 flattenSpans = false 1687 if (forceToEnd) { processLine(cm, text, context, stream.pos) } 1688 stream.pos = text.length 1689 style = null 1690 } else { 1691 style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses) 1692 } 1693 if (inner) { 1694 var mName = inner[0].name 1695 if (mName) { style = "m-" + (style ? mName + " " + style : mName) } 1696 } 1697 if (!flattenSpans || curStyle != style) { 1698 while (curStart < stream.start) { 1699 curStart = Math.min(stream.start, curStart + 5000) 1700 f(curStart, curStyle) 1701 } 1702 curStyle = style 1703 } 1704 stream.start = stream.pos 1705 } 1706 while (curStart < stream.pos) { 1707 // Webkit seems to refuse to render text nodes longer than 57444 1708 // characters, and returns inaccurate measurements in nodes 1709 // starting around 5000 chars. 1710 var pos = Math.min(stream.pos, curStart + 5000) 1711 f(pos, curStyle) 1712 curStart = pos 1713 } 1714 } 1715 1716 // Finds the line to start with when starting a parse. Tries to 1717 // find a line with a stateAfter, so that it can start with a 1718 // valid state. If that fails, it returns the line with the 1719 // smallest indentation, which tends to need the least context to 1720 // parse correctly. 1721 function findStartLine(cm, n, precise) { 1722 var minindent, minline, doc = cm.doc 1723 var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100) 1724 for (var search = n; search > lim; --search) { 1725 if (search <= doc.first) { return doc.first } 1726 var line = getLine(doc, search - 1), after = line.stateAfter 1727 if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) 1728 { return search } 1729 var indented = countColumn(line.text, null, cm.options.tabSize) 1730 if (minline == null || minindent > indented) { 1731 minline = search - 1 1732 minindent = indented 1733 } 1734 } 1735 return minline 1736 } 1737 1738 function retreatFrontier(doc, n) { 1739 doc.modeFrontier = Math.min(doc.modeFrontier, n) 1740 if (doc.highlightFrontier < n - 10) { return } 1741 var start = doc.first 1742 for (var line = n - 1; line > start; line--) { 1743 var saved = getLine(doc, line).stateAfter 1744 // change is on 3 1745 // state on line 1 looked ahead 2 -- so saw 3 1746 // test 1 + 2 < 3 should cover this 1747 if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { 1748 start = line + 1 1749 break 1750 } 1751 } 1752 doc.highlightFrontier = Math.min(doc.highlightFrontier, start) 1753 } 1754 1755 // LINE DATA STRUCTURE 1756 1757 // Line objects. These hold state related to a line, including 1758 // highlighting info (the styles array). 1759 var Line = function(text, markedSpans, estimateHeight) { 1760 this.text = text 1761 attachMarkedSpans(this, markedSpans) 1762 this.height = estimateHeight ? estimateHeight(this) : 1 1763 }; 1764 1765 Line.prototype.lineNo = function () { return lineNo(this) }; 1766 eventMixin(Line) 1767 1768 // Change the content (text, markers) of a line. Automatically 1769 // invalidates cached information and tries to re-estimate the 1770 // line's height. 1771 function updateLine(line, text, markedSpans, estimateHeight) { 1772 line.text = text 1773 if (line.stateAfter) { line.stateAfter = null } 1774 if (line.styles) { line.styles = null } 1775 if (line.order != null) { line.order = null } 1776 detachMarkedSpans(line) 1777 attachMarkedSpans(line, markedSpans) 1778 var estHeight = estimateHeight ? estimateHeight(line) : 1 1779 if (estHeight != line.height) { updateLineHeight(line, estHeight) } 1780 } 1781 1782 // Detach a line from the document tree and its markers. 1783 function cleanUpLine(line) { 1784 line.parent = null 1785 detachMarkedSpans(line) 1786 } 1787 1788 // Convert a style as returned by a mode (either null, or a string 1789 // containing one or more styles) to a CSS style. This is cached, 1790 // and also looks for line-wide styles. 1791 var styleToClassCache = {}; 1792 var styleToClassCacheWithMode = {}; 1793 function interpretTokenStyle(style, options) { 1794 if (!style || /^\s*$/.test(style)) { return null } 1795 var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache 1796 return cache[style] || 1797 (cache[style] = style.replace(/\S+/g, "cm-$&")) 1798 } 1799 1800 // Render the DOM representation of the text of a line. Also builds 1801 // up a 'line map', which points at the DOM nodes that represent 1802 // specific stretches of text, and is used by the measuring code. 1803 // The returned object contains the DOM node, this map, and 1804 // information about line-wide styles that were set by the mode. 1805 function buildLineContent(cm, lineView) { 1806 // The padding-right forces the element to have a 'border', which 1807 // is needed on Webkit to be able to get line-level bounding 1808 // rectangles for it (in measureChar). 1809 var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null) 1810 var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, 1811 col: 0, pos: 0, cm: cm, 1812 trailingSpace: false, 1813 splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")} 1814 lineView.measure = {} 1815 1816 // Iterate over the logical lines that make up this visual line. 1817 for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { 1818 var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0) 1819 builder.pos = 0 1820 builder.addToken = buildToken 1821 // Optionally wire in some hacks into the token-rendering 1822 // algorithm, to deal with browser quirks. 1823 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) 1824 { builder.addToken = buildTokenBadBidi(builder.addToken, order) } 1825 builder.map = [] 1826 var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line) 1827 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)) 1828 if (line.styleClasses) { 1829 if (line.styleClasses.bgClass) 1830 { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "") } 1831 if (line.styleClasses.textClass) 1832 { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "") } 1833 } 1834 1835 // Ensure at least a single node is present, for measuring. 1836 if (builder.map.length == 0) 1837 { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))) } 1838 1839 // Store the map and a cache object for the current logical line 1840 if (i == 0) { 1841 lineView.measure.map = builder.map 1842 lineView.measure.cache = {} 1843 } else { 1844 ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) 1845 ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}) 1846 } 1847 } 1848 1849 // See issue #2901 1850 if (webkit) { 1851 var last = builder.content.lastChild 1852 if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) 1853 { builder.content.className = "cm-tab-wrap-hack" } 1854 } 1855 1856 signal(cm, "renderLine", cm, lineView.line, builder.pre) 1857 if (builder.pre.className) 1858 { builder.textClass = joinClasses(builder.pre.className, builder.textClass || "") } 1859 1860 return builder 1861 } 1862 1863 function defaultSpecialCharPlaceholder(ch) { 1864 var token = elt("span", "\u2022", "cm-invalidchar") 1865 token.title = "\\u" + ch.charCodeAt(0).toString(16) 1866 token.setAttribute("aria-label", token.title) 1867 return token 1868 } 1869 1870 // Build up the DOM representation for a single token, and add it to 1871 // the line map. Takes care to render special characters separately. 1872 function buildToken(builder, text, style, startStyle, endStyle, title, css) { 1873 if (!text) { return } 1874 var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text 1875 var special = builder.cm.state.specialChars, mustWrap = false 1876 var content 1877 if (!special.test(text)) { 1878 builder.col += text.length 1879 content = document.createTextNode(displayText) 1880 builder.map.push(builder.pos, builder.pos + text.length, content) 1881 if (ie && ie_version < 9) { mustWrap = true } 1882 builder.pos += text.length 1883 } else { 1884 content = document.createDocumentFragment() 1885 var pos = 0 1886 while (true) { 1887 special.lastIndex = pos 1888 var m = special.exec(text) 1889 var skipped = m ? m.index - pos : text.length - pos 1890 if (skipped) { 1891 var txt = document.createTextNode(displayText.slice(pos, pos + skipped)) 1892 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])) } 1893 else { content.appendChild(txt) } 1894 builder.map.push(builder.pos, builder.pos + skipped, txt) 1895 builder.col += skipped 1896 builder.pos += skipped 1897 } 1898 if (!m) { break } 1899 pos += skipped + 1 1900 var txt$1 = (void 0) 1901 if (m[0] == "\t") { 1902 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize 1903 txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")) 1904 txt$1.setAttribute("role", "presentation") 1905 txt$1.setAttribute("cm-text", "\t") 1906 builder.col += tabWidth 1907 } else if (m[0] == "\r" || m[0] == "\n") { 1908 txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")) 1909 txt$1.setAttribute("cm-text", m[0]) 1910 builder.col += 1 1911 } else { 1912 txt$1 = builder.cm.options.specialCharPlaceholder(m[0]) 1913 txt$1.setAttribute("cm-text", m[0]) 1914 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])) } 1915 else { content.appendChild(txt$1) } 1916 builder.col += 1 1917 } 1918 builder.map.push(builder.pos, builder.pos + 1, txt$1) 1919 builder.pos++ 1920 } 1921 } 1922 builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 1923 if (style || startStyle || endStyle || mustWrap || css) { 1924 var fullStyle = style || "" 1925 if (startStyle) { fullStyle += startStyle } 1926 if (endStyle) { fullStyle += endStyle } 1927 var token = elt("span", [content], fullStyle, css) 1928 if (title) { token.title = title } 1929 return builder.content.appendChild(token) 1930 } 1931 builder.content.appendChild(content) 1932 } 1933 1934 function splitSpaces(text, trailingBefore) { 1935 if (text.length > 1 && !/ /.test(text)) { return text } 1936 var spaceBefore = trailingBefore, result = "" 1937 for (var i = 0; i < text.length; i++) { 1938 var ch = text.charAt(i) 1939 if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) 1940 { ch = "\u00a0" } 1941 result += ch 1942 spaceBefore = ch == " " 1943 } 1944 return result 1945 } 1946 1947 // Work around nonsense dimensions being reported for stretches of 1948 // right-to-left text. 1949 function buildTokenBadBidi(inner, order) { 1950 return function (builder, text, style, startStyle, endStyle, title, css) { 1951 style = style ? style + " cm-force-border" : "cm-force-border" 1952 var start = builder.pos, end = start + text.length 1953 for (;;) { 1954 // Find the part that overlaps with the start of this text 1955 var part = (void 0) 1956 for (var i = 0; i < order.length; i++) { 1957 part = order[i] 1958 if (part.to > start && part.from <= start) { break } 1959 } 1960 if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, title, css) } 1961 inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css) 1962 startStyle = null 1963 text = text.slice(part.to - start) 1964 start = part.to 1965 } 1966 } 1967 } 1968 1969 function buildCollapsedSpan(builder, size, marker, ignoreWidget) { 1970 var widget = !ignoreWidget && marker.widgetNode 1971 if (widget) { builder.map.push(builder.pos, builder.pos + size, widget) } 1972 if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { 1973 if (!widget) 1974 { widget = builder.content.appendChild(document.createElement("span")) } 1975 widget.setAttribute("cm-marker", marker.id) 1976 } 1977 if (widget) { 1978 builder.cm.display.input.setUneditable(widget) 1979 builder.content.appendChild(widget) 1980 } 1981 builder.pos += size 1982 builder.trailingSpace = false 1983 } 1984 1985 // Outputs a number of spans to make up a line, taking highlighting 1986 // and marked text into account. 1987 function insertLineContent(line, builder, styles) { 1988 var spans = line.markedSpans, allText = line.text, at = 0 1989 if (!spans) { 1990 for (var i$1 = 1; i$1 < styles.length; i$1+=2) 1991 { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)) } 1992 return 1993 } 1994 1995 var len = allText.length, pos = 0, i = 1, text = "", style, css 1996 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed 1997 for (;;) { 1998 if (nextChange == pos) { // Update current marker set 1999 spanStyle = spanEndStyle = spanStartStyle = title = css = "" 2000 collapsed = null; nextChange = Infinity 2001 var foundBookmarks = [], endStyles = (void 0) 2002 for (var j = 0; j < spans.length; ++j) { 2003 var sp = spans[j], m = sp.marker 2004 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { 2005 foundBookmarks.push(m) 2006 } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { 2007 if (sp.to != null && sp.to != pos && nextChange > sp.to) { 2008 nextChange = sp.to 2009 spanEndStyle = "" 2010 } 2011 if (m.className) { spanStyle += " " + m.className } 2012 if (m.css) { css = (css ? css + ";" : "") + m.css } 2013 if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle } 2014 if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to) } 2015 if (m.title && !title) { title = m.title } 2016 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) 2017 { collapsed = sp } 2018 } else if (sp.from > pos && nextChange > sp.from) { 2019 nextChange = sp.from 2020 } 2021 } 2022 if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) 2023 { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1] } } } 2024 2025 if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2) 2026 { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]) } } 2027 if (collapsed && (collapsed.from || 0) == pos) { 2028 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, 2029 collapsed.marker, collapsed.from == null) 2030 if (collapsed.to == null) { return } 2031 if (collapsed.to == pos) { collapsed = false } 2032 } 2033 } 2034 if (pos >= len) { break } 2035 2036 var upto = Math.min(len, nextChange) 2037 while (true) { 2038 if (text) { 2039 var end = pos + text.length 2040 if (!collapsed) { 2041 var tokenText = end > upto ? text.slice(0, upto - pos) : text 2042 builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, 2043 spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css) 2044 } 2045 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} 2046 pos = end 2047 spanStartStyle = "" 2048 } 2049 text = allText.slice(at, at = styles[i++]) 2050 style = interpretTokenStyle(styles[i++], builder.cm.options) 2051 } 2052 } 2053 } 2054 2055 2056 // These objects are used to represent the visible (currently drawn) 2057 // part of the document. A LineView may correspond to multiple 2058 // logical lines, if those are connected by collapsed ranges. 2059 function LineView(doc, line, lineN) { 2060 // The starting line 2061 this.line = line 2062 // Continuing lines, if any 2063 this.rest = visualLineContinued(line) 2064 // Number of logical lines in this visual line 2065 this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1 2066 this.node = this.text = null 2067 this.hidden = lineIsHidden(doc, line) 2068 } 2069 2070 // Create a range of LineView objects for the given lines. 2071 function buildViewArray(cm, from, to) { 2072 var array = [], nextPos 2073 for (var pos = from; pos < to; pos = nextPos) { 2074 var view = new LineView(cm.doc, getLine(cm.doc, pos), pos) 2075 nextPos = pos + view.size 2076 array.push(view) 2077 } 2078 return array 2079 } 2080 2081 var operationGroup = null 2082 2083 function pushOperation(op) { 2084 if (operationGroup) { 2085 operationGroup.ops.push(op) 2086 } else { 2087 op.ownsGroup = operationGroup = { 2088 ops: [op], 2089 delayedCallbacks: [] 2090 } 2091 } 2092 } 2093 2094 function fireCallbacksForOps(group) { 2095 // Calls delayed callbacks and cursorActivity handlers until no 2096 // new ones appear 2097 var callbacks = group.delayedCallbacks, i = 0 2098 do { 2099 for (; i < callbacks.length; i++) 2100 { callbacks[i].call(null) } 2101 for (var j = 0; j < group.ops.length; j++) { 2102 var op = group.ops[j] 2103 if (op.cursorActivityHandlers) 2104 { while (op.cursorActivityCalled < op.cursorActivityHandlers.length) 2105 { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm) } } 2106 } 2107 } while (i < callbacks.length) 2108 } 2109 2110 function finishOperation(op, endCb) { 2111 var group = op.ownsGroup 2112 if (!group) { return } 2113 2114 try { fireCallbacksForOps(group) } 2115 finally { 2116 operationGroup = null 2117 endCb(group) 2118 } 2119 } 2120 2121 var orphanDelayedCallbacks = null 2122 2123 // Often, we want to signal events at a point where we are in the 2124 // middle of some work, but don't want the handler to start calling 2125 // other methods on the editor, which might be in an inconsistent 2126 // state or simply not expect any other events to happen. 2127 // signalLater looks whether there are any handlers, and schedules 2128 // them to be executed when the last operation ends, or, if no 2129 // operation is active, when a timeout fires. 2130 function signalLater(emitter, type /*, values...*/) { 2131 var arr = getHandlers(emitter, type) 2132 if (!arr.length) { return } 2133 var args = Array.prototype.slice.call(arguments, 2), list 2134 if (operationGroup) { 2135 list = operationGroup.delayedCallbacks 2136 } else if (orphanDelayedCallbacks) { 2137 list = orphanDelayedCallbacks 2138 } else { 2139 list = orphanDelayedCallbacks = [] 2140 setTimeout(fireOrphanDelayed, 0) 2141 } 2142 var loop = function ( i ) { 2143 list.push(function () { return arr[i].apply(null, args); }) 2144 }; 2145 2146 for (var i = 0; i < arr.length; ++i) 2147 loop( i ); 2148 } 2149 2150 function fireOrphanDelayed() { 2151 var delayed = orphanDelayedCallbacks 2152 orphanDelayedCallbacks = null 2153 for (var i = 0; i < delayed.length; ++i) { delayed[i]() } 2154 } 2155 2156 // When an aspect of a line changes, a string is added to 2157 // lineView.changes. This updates the relevant part of the line's 2158 // DOM structure. 2159 function updateLineForChanges(cm, lineView, lineN, dims) { 2160 for (var j = 0; j < lineView.changes.length; j++) { 2161 var type = lineView.changes[j] 2162 if (type == "text") { updateLineText(cm, lineView) } 2163 else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims) } 2164 else if (type == "class") { updateLineClasses(cm, lineView) } 2165 else if (type == "widget") { updateLineWidgets(cm, lineView, dims) } 2166 } 2167 lineView.changes = null 2168 } 2169 2170 // Lines with gutter elements, widgets or a background class need to 2171 // be wrapped, and have the extra elements added to the wrapper div 2172 function ensureLineWrapped(lineView) { 2173 if (lineView.node == lineView.text) { 2174 lineView.node = elt("div", null, null, "position: relative") 2175 if (lineView.text.parentNode) 2176 { lineView.text.parentNode.replaceChild(lineView.node, lineView.text) } 2177 lineView.node.appendChild(lineView.text) 2178 if (ie && ie_version < 8) { lineView.node.style.zIndex = 2 } 2179 } 2180 return lineView.node 2181 } 2182 2183 function updateLineBackground(cm, lineView) { 2184 var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass 2185 if (cls) { cls += " CodeMirror-linebackground" } 2186 if (lineView.background) { 2187 if (cls) { lineView.background.className = cls } 2188 else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null } 2189 } else if (cls) { 2190 var wrap = ensureLineWrapped(lineView) 2191 lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild) 2192 cm.display.input.setUneditable(lineView.background) 2193 } 2194 } 2195 2196 // Wrapper around buildLineContent which will reuse the structure 2197 // in display.externalMeasured when possible. 2198 function getLineContent(cm, lineView) { 2199 var ext = cm.display.externalMeasured 2200 if (ext && ext.line == lineView.line) { 2201 cm.display.externalMeasured = null 2202 lineView.measure = ext.measure 2203 return ext.built 2204 } 2205 return buildLineContent(cm, lineView) 2206 } 2207 2208 // Redraw the line's text. Interacts with the background and text 2209 // classes because the mode may output tokens that influence these 2210 // classes. 2211 function updateLineText(cm, lineView) { 2212 var cls = lineView.text.className 2213 var built = getLineContent(cm, lineView) 2214 if (lineView.text == lineView.node) { lineView.node = built.pre } 2215 lineView.text.parentNode.replaceChild(built.pre, lineView.text) 2216 lineView.text = built.pre 2217 if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { 2218 lineView.bgClass = built.bgClass 2219 lineView.textClass = built.textClass 2220 updateLineClasses(cm, lineView) 2221 } else if (cls) { 2222 lineView.text.className = cls 2223 } 2224 } 2225 2226 function updateLineClasses(cm, lineView) { 2227 updateLineBackground(cm, lineView) 2228 if (lineView.line.wrapClass) 2229 { ensureLineWrapped(lineView).className = lineView.line.wrapClass } 2230 else if (lineView.node != lineView.text) 2231 { lineView.node.className = "" } 2232 var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass 2233 lineView.text.className = textClass || "" 2234 } 2235 2236 function updateLineGutter(cm, lineView, lineN, dims) { 2237 if (lineView.gutter) { 2238 lineView.node.removeChild(lineView.gutter) 2239 lineView.gutter = null 2240 } 2241 if (lineView.gutterBackground) { 2242 lineView.node.removeChild(lineView.gutterBackground) 2243 lineView.gutterBackground = null 2244 } 2245 if (lineView.line.gutterClass) { 2246 var wrap = ensureLineWrapped(lineView) 2247 lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, 2248 ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px")) 2249 cm.display.input.setUneditable(lineView.gutterBackground) 2250 wrap.insertBefore(lineView.gutterBackground, lineView.text) 2251 } 2252 var markers = lineView.line.gutterMarkers 2253 if (cm.options.lineNumbers || markers) { 2254 var wrap$1 = ensureLineWrapped(lineView) 2255 var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")) 2256 cm.display.input.setUneditable(gutterWrap) 2257 wrap$1.insertBefore(gutterWrap, lineView.text) 2258 if (lineView.line.gutterClass) 2259 { gutterWrap.className += " " + lineView.line.gutterClass } 2260 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) 2261 { lineView.lineNumber = gutterWrap.appendChild( 2262 elt("div", lineNumberFor(cm.options, lineN), 2263 "CodeMirror-linenumber CodeMirror-gutter-elt", 2264 ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))) } 2265 if (markers) { for (var k = 0; k < cm.options.gutters.length; ++k) { 2266 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id] 2267 if (found) 2268 { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", 2269 ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))) } 2270 } } 2271 } 2272 } 2273 2274 function updateLineWidgets(cm, lineView, dims) { 2275 if (lineView.alignable) { lineView.alignable = null } 2276 for (var node = lineView.node.firstChild, next = (void 0); node; node = next) { 2277 next = node.nextSibling 2278 if (node.className == "CodeMirror-linewidget") 2279 { lineView.node.removeChild(node) } 2280 } 2281 insertLineWidgets(cm, lineView, dims) 2282 } 2283 2284 // Build a line's DOM representation from scratch 2285 function buildLineElement(cm, lineView, lineN, dims) { 2286 var built = getLineContent(cm, lineView) 2287 lineView.text = lineView.node = built.pre 2288 if (built.bgClass) { lineView.bgClass = built.bgClass } 2289 if (built.textClass) { lineView.textClass = built.textClass } 2290 2291 updateLineClasses(cm, lineView) 2292 updateLineGutter(cm, lineView, lineN, dims) 2293 insertLineWidgets(cm, lineView, dims) 2294 return lineView.node 2295 } 2296 2297 // A lineView may contain multiple logical lines (when merged by 2298 // collapsed spans). The widgets for all of them need to be drawn. 2299 function insertLineWidgets(cm, lineView, dims) { 2300 insertLineWidgetsFor(cm, lineView.line, lineView, dims, true) 2301 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) 2302 { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false) } } 2303 } 2304 2305 function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { 2306 if (!line.widgets) { return } 2307 var wrap = ensureLineWrapped(lineView) 2308 for (var i = 0, ws = line.widgets; i < ws.length; ++i) { 2309 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget") 2310 if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true") } 2311 positionLineWidget(widget, node, lineView, dims) 2312 cm.display.input.setUneditable(node) 2313 if (allowAbove && widget.above) 2314 { wrap.insertBefore(node, lineView.gutter || lineView.text) } 2315 else 2316 { wrap.appendChild(node) } 2317 signalLater(widget, "redraw") 2318 } 2319 } 2320 2321 function positionLineWidget(widget, node, lineView, dims) { 2322 if (widget.noHScroll) { 2323 ;(lineView.alignable || (lineView.alignable = [])).push(node) 2324 var width = dims.wrapperWidth 2325 node.style.left = dims.fixedPos + "px" 2326 if (!widget.coverGutter) { 2327 width -= dims.gutterTotalWidth 2328 node.style.paddingLeft = dims.gutterTotalWidth + "px" 2329 } 2330 node.style.width = width + "px" 2331 } 2332 if (widget.coverGutter) { 2333 node.style.zIndex = 5 2334 node.style.position = "relative" 2335 if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px" } 2336 } 2337 } 2338 2339 function widgetHeight(widget) { 2340 if (widget.height != null) { return widget.height } 2341 var cm = widget.doc.cm 2342 if (!cm) { return 0 } 2343 if (!contains(document.body, widget.node)) { 2344 var parentStyle = "position: relative;" 2345 if (widget.coverGutter) 2346 { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;" } 2347 if (widget.noHScroll) 2348 { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;" } 2349 removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)) 2350 } 2351 return widget.height = widget.node.parentNode.offsetHeight 2352 } 2353 2354 // Return true when the given mouse event happened in a widget 2355 function eventInWidget(display, e) { 2356 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { 2357 if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || 2358 (n.parentNode == display.sizer && n != display.mover)) 2359 { return true } 2360 } 2361 } 2362 2363 // POSITION MEASUREMENT 2364 2365 function paddingTop(display) {return display.lineSpace.offsetTop} 2366 function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} 2367 function paddingH(display) { 2368 if (display.cachedPaddingH) { return display.cachedPaddingH } 2369 var e = removeChildrenAndAdd(display.measure, elt("pre", "x")) 2370 var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle 2371 var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)} 2372 if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data } 2373 return data 2374 } 2375 2376 function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } 2377 function displayWidth(cm) { 2378 return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth 2379 } 2380 function displayHeight(cm) { 2381 return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight 2382 } 2383 2384 // Ensure the lineView.wrapping.heights array is populated. This is 2385 // an array of bottom offsets for the lines that make up a drawn 2386 // line. When lineWrapping is on, there might be more than one 2387 // height. 2388 function ensureLineHeights(cm, lineView, rect) { 2389 var wrapping = cm.options.lineWrapping 2390 var curWidth = wrapping && displayWidth(cm) 2391 if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { 2392 var heights = lineView.measure.heights = [] 2393 if (wrapping) { 2394 lineView.measure.width = curWidth 2395 var rects = lineView.text.firstChild.getClientRects() 2396 for (var i = 0; i < rects.length - 1; i++) { 2397 var cur = rects[i], next = rects[i + 1] 2398 if (Math.abs(cur.bottom - next.bottom) > 2) 2399 { heights.push((cur.bottom + next.top) / 2 - rect.top) } 2400 } 2401 } 2402 heights.push(rect.bottom - rect.top) 2403 } 2404 } 2405 2406 // Find a line map (mapping character offsets to text nodes) and a 2407 // measurement cache for the given line number. (A line view might 2408 // contain multiple lines when collapsed ranges are present.) 2409 function mapFromLineView(lineView, line, lineN) { 2410 if (lineView.line == line) 2411 { return {map: lineView.measure.map, cache: lineView.measure.cache} } 2412 for (var i = 0; i < lineView.rest.length; i++) 2413 { if (lineView.rest[i] == line) 2414 { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } } 2415 for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) 2416 { if (lineNo(lineView.rest[i$1]) > lineN) 2417 { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } } 2418 } 2419 2420 // Render a line into the hidden node display.externalMeasured. Used 2421 // when measurement is needed for a line that's not in the viewport. 2422 function updateExternalMeasurement(cm, line) { 2423 line = visualLine(line) 2424 var lineN = lineNo(line) 2425 var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN) 2426 view.lineN = lineN 2427 var built = view.built = buildLineContent(cm, view) 2428 view.text = built.pre 2429 removeChildrenAndAdd(cm.display.lineMeasure, built.pre) 2430 return view 2431 } 2432 2433 // Get a {top, bottom, left, right} box (in line-local coordinates) 2434 // for a given character. 2435 function measureChar(cm, line, ch, bias) { 2436 return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) 2437 } 2438 2439 // Find a line view that corresponds to the given line number. 2440 function findViewForLine(cm, lineN) { 2441 if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) 2442 { return cm.display.view[findViewIndex(cm, lineN)] } 2443 var ext = cm.display.externalMeasured 2444 if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) 2445 { return ext } 2446 } 2447 2448 // Measurement can be split in two steps, the set-up work that 2449 // applies to the whole line, and the measurement of the actual 2450 // character. Functions like coordsChar, that need to do a lot of 2451 // measurements in a row, can thus ensure that the set-up work is 2452 // only done once. 2453 function prepareMeasureForLine(cm, line) { 2454 var lineN = lineNo(line) 2455 var view = findViewForLine(cm, lineN) 2456 if (view && !view.text) { 2457 view = null 2458 } else if (view && view.changes) { 2459 updateLineForChanges(cm, view, lineN, getDimensions(cm)) 2460 cm.curOp.forceUpdate = true 2461 } 2462 if (!view) 2463 { view = updateExternalMeasurement(cm, line) } 2464 2465 var info = mapFromLineView(view, line, lineN) 2466 return { 2467 line: line, view: view, rect: null, 2468 map: info.map, cache: info.cache, before: info.before, 2469 hasHeights: false 2470 } 2471 } 2472 2473 // Given a prepared measurement object, measures the position of an 2474 // actual character (or fetches it from the cache). 2475 function measureCharPrepared(cm, prepared, ch, bias, varHeight) { 2476 if (prepared.before) { ch = -1 } 2477 var key = ch + (bias || ""), found 2478 if (prepared.cache.hasOwnProperty(key)) { 2479 found = prepared.cache[key] 2480 } else { 2481 if (!prepared.rect) 2482 { prepared.rect = prepared.view.text.getBoundingClientRect() } 2483 if (!prepared.hasHeights) { 2484 ensureLineHeights(cm, prepared.view, prepared.rect) 2485 prepared.hasHeights = true 2486 } 2487 found = measureCharInner(cm, prepared, ch, bias) 2488 if (!found.bogus) { prepared.cache[key] = found } 2489 } 2490 return {left: found.left, right: found.right, 2491 top: varHeight ? found.rtop : found.top, 2492 bottom: varHeight ? found.rbottom : found.bottom} 2493 } 2494 2495 var nullRect = {left: 0, right: 0, top: 0, bottom: 0} 2496 2497 function nodeAndOffsetInLineMap(map, ch, bias) { 2498 var node, start, end, collapse, mStart, mEnd 2499 // First, search the line map for the text node corresponding to, 2500 // or closest to, the target character. 2501 for (var i = 0; i < map.length; i += 3) { 2502 mStart = map[i] 2503 mEnd = map[i + 1] 2504 if (ch < mStart) { 2505 start = 0; end = 1 2506 collapse = "left" 2507 } else if (ch < mEnd) { 2508 start = ch - mStart 2509 end = start + 1 2510 } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { 2511 end = mEnd - mStart 2512 start = end - 1 2513 if (ch >= mEnd) { collapse = "right" } 2514 } 2515 if (start != null) { 2516 node = map[i + 2] 2517 if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) 2518 { collapse = bias } 2519 if (bias == "left" && start == 0) 2520 { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { 2521 node = map[(i -= 3) + 2] 2522 collapse = "left" 2523 } } 2524 if (bias == "right" && start == mEnd - mStart) 2525 { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { 2526 node = map[(i += 3) + 2] 2527 collapse = "right" 2528 } } 2529 break 2530 } 2531 } 2532 return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} 2533 } 2534 2535 function getUsefulRect(rects, bias) { 2536 var rect = nullRect 2537 if (bias == "left") { for (var i = 0; i < rects.length; i++) { 2538 if ((rect = rects[i]).left != rect.right) { break } 2539 } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { 2540 if ((rect = rects[i$1]).left != rect.right) { break } 2541 } } 2542 return rect 2543 } 2544 2545 function measureCharInner(cm, prepared, ch, bias) { 2546 var place = nodeAndOffsetInLineMap(prepared.map, ch, bias) 2547 var node = place.node, start = place.start, end = place.end, collapse = place.collapse 2548 2549 var rect 2550 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. 2551 for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned 2552 while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start } 2553 while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end } 2554 if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) 2555 { rect = node.parentNode.getBoundingClientRect() } 2556 else 2557 { rect = getUsefulRect(range(node, start, end).getClientRects(), bias) } 2558 if (rect.left || rect.right || start == 0) { break } 2559 end = start 2560 start = start - 1 2561 collapse = "right" 2562 } 2563 if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect) } 2564 } else { // If it is a widget, simply get the box for the whole widget. 2565 if (start > 0) { collapse = bias = "right" } 2566 var rects 2567 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) 2568 { rect = rects[bias == "right" ? rects.length - 1 : 0] } 2569 else 2570 { rect = node.getBoundingClientRect() } 2571 } 2572 if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { 2573 var rSpan = node.parentNode.getClientRects()[0] 2574 if (rSpan) 2575 { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom} } 2576 else 2577 { rect = nullRect } 2578 } 2579 2580 var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top 2581 var mid = (rtop + rbot) / 2 2582 var heights = prepared.view.measure.heights 2583 var i = 0 2584 for (; i < heights.length - 1; i++) 2585 { if (mid < heights[i]) { break } } 2586 var top = i ? heights[i - 1] : 0, bot = heights[i] 2587 var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, 2588 right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, 2589 top: top, bottom: bot} 2590 if (!rect.left && !rect.right) { result.bogus = true } 2591 if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot } 2592 2593 return result 2594 } 2595 2596 // Work around problem with bounding client rects on ranges being 2597 // returned incorrectly when zoomed on IE10 and below. 2598 function maybeUpdateRectForZooming(measure, rect) { 2599 if (!window.screen || screen.logicalXDPI == null || 2600 screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) 2601 { return rect } 2602 var scaleX = screen.logicalXDPI / screen.deviceXDPI 2603 var scaleY = screen.logicalYDPI / screen.deviceYDPI 2604 return {left: rect.left * scaleX, right: rect.right * scaleX, 2605 top: rect.top * scaleY, bottom: rect.bottom * scaleY} 2606 } 2607 2608 function clearLineMeasurementCacheFor(lineView) { 2609 if (lineView.measure) { 2610 lineView.measure.cache = {} 2611 lineView.measure.heights = null 2612 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) 2613 { lineView.measure.caches[i] = {} } } 2614 } 2615 } 2616 2617 function clearLineMeasurementCache(cm) { 2618 cm.display.externalMeasure = null 2619 removeChildren(cm.display.lineMeasure) 2620 for (var i = 0; i < cm.display.view.length; i++) 2621 { clearLineMeasurementCacheFor(cm.display.view[i]) } 2622 } 2623 2624 function clearCaches(cm) { 2625 clearLineMeasurementCache(cm) 2626 cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null 2627 if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true } 2628 cm.display.lineNumChars = null 2629 } 2630 2631 function pageScrollX() { 2632 // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 2633 // which causes page_Offset and bounding client rects to use 2634 // different reference viewports and invalidate our calculations. 2635 if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) } 2636 return window.pageXOffset || (document.documentElement || document.body).scrollLeft 2637 } 2638 function pageScrollY() { 2639 if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) } 2640 return window.pageYOffset || (document.documentElement || document.body).scrollTop 2641 } 2642 2643 function widgetTopHeight(lineObj) { 2644 var height = 0 2645 if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above) 2646 { height += widgetHeight(lineObj.widgets[i]) } } } 2647 return height 2648 } 2649 2650 // Converts a {top, bottom, left, right} box from line-local 2651 // coordinates into another coordinate system. Context may be one of 2652 // "line", "div" (display.lineDiv), "local"./null (editor), "window", 2653 // or "page". 2654 function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { 2655 if (!includeWidgets) { 2656 var height = widgetTopHeight(lineObj) 2657 rect.top += height; rect.bottom += height 2658 } 2659 if (context == "line") { return rect } 2660 if (!context) { context = "local" } 2661 var yOff = heightAtLine(lineObj) 2662 if (context == "local") { yOff += paddingTop(cm.display) } 2663 else { yOff -= cm.display.viewOffset } 2664 if (context == "page" || context == "window") { 2665 var lOff = cm.display.lineSpace.getBoundingClientRect() 2666 yOff += lOff.top + (context == "window" ? 0 : pageScrollY()) 2667 var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()) 2668 rect.left += xOff; rect.right += xOff 2669 } 2670 rect.top += yOff; rect.bottom += yOff 2671 return rect 2672 } 2673 2674 // Coverts a box from "div" coords to another coordinate system. 2675 // Context may be "window", "page", "div", or "local"./null. 2676 function fromCoordSystem(cm, coords, context) { 2677 if (context == "div") { return coords } 2678 var left = coords.left, top = coords.top 2679 // First move into "page" coordinate system 2680 if (context == "page") { 2681 left -= pageScrollX() 2682 top -= pageScrollY() 2683 } else if (context == "local" || !context) { 2684 var localBox = cm.display.sizer.getBoundingClientRect() 2685 left += localBox.left 2686 top += localBox.top 2687 } 2688 2689 var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect() 2690 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} 2691 } 2692 2693 function charCoords(cm, pos, context, lineObj, bias) { 2694 if (!lineObj) { lineObj = getLine(cm.doc, pos.line) } 2695 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) 2696 } 2697 2698 // Returns a box for a given cursor position, which may have an 2699 // 'other' property containing the position of the secondary cursor 2700 // on a bidi boundary. 2701 // A cursor Pos(line, char, "before") is on the same visual line as `char - 1` 2702 // and after `char - 1` in writing order of `char - 1` 2703 // A cursor Pos(line, char, "after") is on the same visual line as `char` 2704 // and before `char` in writing order of `char` 2705 // Examples (upper-case letters are RTL, lower-case are LTR): 2706 // Pos(0, 1, ...) 2707 // before after 2708 // ab a|b a|b 2709 // aB a|B aB| 2710 // Ab |Ab A|b 2711 // AB B|A B|A 2712 // Every position after the last character on a line is considered to stick 2713 // to the last character on the line. 2714 function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { 2715 lineObj = lineObj || getLine(cm.doc, pos.line) 2716 if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) } 2717 function get(ch, right) { 2718 var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight) 2719 if (right) { m.left = m.right; } else { m.right = m.left } 2720 return intoCoordSystem(cm, lineObj, m, context) 2721 } 2722 var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky 2723 if (ch >= lineObj.text.length) { 2724 ch = lineObj.text.length 2725 sticky = "before" 2726 } else if (ch <= 0) { 2727 ch = 0 2728 sticky = "after" 2729 } 2730 if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") } 2731 2732 function getBidi(ch, partPos, invert) { 2733 var part = order[partPos], right = part.level == 1 2734 return get(invert ? ch - 1 : ch, right != invert) 2735 } 2736 var partPos = getBidiPartAt(order, ch, sticky) 2737 var other = bidiOther 2738 var val = getBidi(ch, partPos, sticky == "before") 2739 if (other != null) { val.other = getBidi(ch, other, sticky != "before") } 2740 return val 2741 } 2742 2743 // Used to cheaply estimate the coordinates for a position. Used for 2744 // intermediate scroll updates. 2745 function estimateCoords(cm, pos) { 2746 var left = 0 2747 pos = clipPos(cm.doc, pos) 2748 if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch } 2749 var lineObj = getLine(cm.doc, pos.line) 2750 var top = heightAtLine(lineObj) + paddingTop(cm.display) 2751 return {left: left, right: left, top: top, bottom: top + lineObj.height} 2752 } 2753 2754 // Positions returned by coordsChar contain some extra information. 2755 // xRel is the relative x position of the input coordinates compared 2756 // to the found position (so xRel > 0 means the coordinates are to 2757 // the right of the character position, for example). When outside 2758 // is true, that means the coordinates lie outside the line's 2759 // vertical range. 2760 function PosWithInfo(line, ch, sticky, outside, xRel) { 2761 var pos = Pos(line, ch, sticky) 2762 pos.xRel = xRel 2763 if (outside) { pos.outside = true } 2764 return pos 2765 } 2766 2767 // Compute the character position closest to the given coordinates. 2768 // Input must be lineSpace-local ("div" coordinate system). 2769 function coordsChar(cm, x, y) { 2770 var doc = cm.doc 2771 y += cm.display.viewOffset 2772 if (y < 0) { return PosWithInfo(doc.first, 0, null, true, -1) } 2773 var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1 2774 if (lineN > last) 2775 { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1) } 2776 if (x < 0) { x = 0 } 2777 2778 var lineObj = getLine(doc, lineN) 2779 for (;;) { 2780 var found = coordsCharInner(cm, lineObj, lineN, x, y) 2781 var merged = collapsedSpanAtEnd(lineObj) 2782 var mergedPos = merged && merged.find(0, true) 2783 if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) 2784 { lineN = lineNo(lineObj = mergedPos.to.line) } 2785 else 2786 { return found } 2787 } 2788 } 2789 2790 function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { 2791 y -= widgetTopHeight(lineObj) 2792 var end = lineObj.text.length 2793 var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0) 2794 end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end) 2795 return {begin: begin, end: end} 2796 } 2797 2798 function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { 2799 if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) } 2800 var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top 2801 return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) 2802 } 2803 2804 // Returns true if the given side of a box is after the given 2805 // coordinates, in top-to-bottom, left-to-right order. 2806 function boxIsAfter(box, x, y, left) { 2807 return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x 2808 } 2809 2810 function coordsCharInner(cm, lineObj, lineNo, x, y) { 2811 // Move y into line-local coordinate space 2812 y -= heightAtLine(lineObj) 2813 var preparedMeasure = prepareMeasureForLine(cm, lineObj) 2814 // When directly calling `measureCharPrepared`, we have to adjust 2815 // for the widgets at this line. 2816 var widgetHeight = widgetTopHeight(lineObj) 2817 var begin = 0, end = lineObj.text.length, ltr = true 2818 2819 var order = getOrder(lineObj, cm.doc.direction) 2820 // If the line isn't plain left-to-right text, first figure out 2821 // which bidi section the coordinates fall into. 2822 if (order) { 2823 var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) 2824 (cm, lineObj, lineNo, preparedMeasure, order, x, y) 2825 ltr = part.level != 1 2826 // The awkward -1 offsets are needed because findFirst (called 2827 // on these below) will treat its first bound as inclusive, 2828 // second as exclusive, but we want to actually address the 2829 // characters in the part's range 2830 begin = ltr ? part.from : part.to - 1 2831 end = ltr ? part.to : part.from - 1 2832 } 2833 2834 // A binary search to find the first character whose bounding box 2835 // starts after the coordinates. If we run across any whose box wrap 2836 // the coordinates, store that. 2837 var chAround = null, boxAround = null 2838 var ch = findFirst(function (ch) { 2839 var box = measureCharPrepared(cm, preparedMeasure, ch) 2840 box.top += widgetHeight; box.bottom += widgetHeight 2841 if (!boxIsAfter(box, x, y, false)) { return false } 2842 if (box.top <= y && box.left <= x) { 2843 chAround = ch 2844 boxAround = box 2845 } 2846 return true 2847 }, begin, end) 2848 2849 var baseX, sticky, outside = false 2850 // If a box around the coordinates was found, use that 2851 if (boxAround) { 2852 // Distinguish coordinates nearer to the left or right side of the box 2853 var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr 2854 ch = chAround + (atStart ? 0 : 1) 2855 sticky = atStart ? "after" : "before" 2856 baseX = atLeft ? boxAround.left : boxAround.right 2857 } else { 2858 // (Adjust for extended bound, if necessary.) 2859 if (!ltr && (ch == end || ch == begin)) { ch++ } 2860 // To determine which side to associate with, get the box to the 2861 // left of the character and compare it's vertical position to the 2862 // coordinates 2863 sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : 2864 (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ? 2865 "after" : "before" 2866 // Now get accurate coordinates for this place, in order to get a 2867 // base X position 2868 var coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure) 2869 baseX = coords.left 2870 outside = y < coords.top || y >= coords.bottom 2871 } 2872 2873 ch = skipExtendingChars(lineObj.text, ch, 1) 2874 return PosWithInfo(lineNo, ch, sticky, outside, x - baseX) 2875 } 2876 2877 function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) { 2878 // Bidi parts are sorted left-to-right, and in a non-line-wrapping 2879 // situation, we can take this ordering to correspond to the visual 2880 // ordering. This finds the first part whose end is after the given 2881 // coordinates. 2882 var index = findFirst(function (i) { 2883 var part = order[i], ltr = part.level != 1 2884 return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"), 2885 "line", lineObj, preparedMeasure), x, y, true) 2886 }, 0, order.length - 1) 2887 var part = order[index] 2888 // If this isn't the first part, the part's start is also after 2889 // the coordinates, and the coordinates aren't on the same line as 2890 // that start, move one part back. 2891 if (index > 0) { 2892 var ltr = part.level != 1 2893 var start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"), 2894 "line", lineObj, preparedMeasure) 2895 if (boxIsAfter(start, x, y, true) && start.top > y) 2896 { part = order[index - 1] } 2897 } 2898 return part 2899 } 2900 2901 function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { 2902 // In a wrapped line, rtl text on wrapping boundaries can do things 2903 // that don't correspond to the ordering in our `order` array at 2904 // all, so a binary search doesn't work, and we want to return a 2905 // part that only spans one line so that the binary search in 2906 // coordsCharInner is safe. As such, we first find the extent of the 2907 // wrapped line, and then do a flat search in which we discard any 2908 // spans that aren't on the line. 2909 var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y); 2910 var begin = ref.begin; 2911 var end = ref.end; 2912 if (/\s/.test(lineObj.text.charAt(end - 1))) { end-- } 2913 var part = null, closestDist = null 2914 for (var i = 0; i < order.length; i++) { 2915 var p = order[i] 2916 if (p.from >= end || p.to <= begin) { continue } 2917 var ltr = p.level != 1 2918 var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right 2919 // Weigh against spans ending before this, so that they are only 2920 // picked if nothing ends after 2921 var dist = endX < x ? x - endX + 1e9 : endX - x 2922 if (!part || closestDist > dist) { 2923 part = p 2924 closestDist = dist 2925 } 2926 } 2927 if (!part) { part = order[order.length - 1] } 2928 // Clip the part to the wrapped line. 2929 if (part.from < begin) { part = {from: begin, to: part.to, level: part.level} } 2930 if (part.to > end) { part = {from: part.from, to: end, level: part.level} } 2931 return part 2932 } 2933 2934 var measureText 2935 // Compute the default text height. 2936 function textHeight(display) { 2937 if (display.cachedTextHeight != null) { return display.cachedTextHeight } 2938 if (measureText == null) { 2939 measureText = elt("pre") 2940 // Measure a bunch of lines, for browsers that compute 2941 // fractional heights. 2942 for (var i = 0; i < 49; ++i) { 2943 measureText.appendChild(document.createTextNode("x")) 2944 measureText.appendChild(elt("br")) 2945 } 2946 measureText.appendChild(document.createTextNode("x")) 2947 } 2948 removeChildrenAndAdd(display.measure, measureText) 2949 var height = measureText.offsetHeight / 50 2950 if (height > 3) { display.cachedTextHeight = height } 2951 removeChildren(display.measure) 2952 return height || 1 2953 } 2954 2955 // Compute the default character width. 2956 function charWidth(display) { 2957 if (display.cachedCharWidth != null) { return display.cachedCharWidth } 2958 var anchor = elt("span", "xxxxxxxxxx") 2959 var pre = elt("pre", [anchor]) 2960 removeChildrenAndAdd(display.measure, pre) 2961 var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10 2962 if (width > 2) { display.cachedCharWidth = width } 2963 return width || 10 2964 } 2965 2966 // Do a bulk-read of the DOM positions and sizes needed to draw the 2967 // view, so that we don't interleave reading and writing to the DOM. 2968 function getDimensions(cm) { 2969 var d = cm.display, left = {}, width = {} 2970 var gutterLeft = d.gutters.clientLeft 2971 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { 2972 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft 2973 width[cm.options.gutters[i]] = n.clientWidth 2974 } 2975 return {fixedPos: compensateForHScroll(d), 2976 gutterTotalWidth: d.gutters.offsetWidth, 2977 gutterLeft: left, 2978 gutterWidth: width, 2979 wrapperWidth: d.wrapper.clientWidth} 2980 } 2981 2982 // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, 2983 // but using getBoundingClientRect to get a sub-pixel-accurate 2984 // result. 2985 function compensateForHScroll(display) { 2986 return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left 2987 } 2988 2989 // Returns a function that estimates the height of a line, to use as 2990 // first approximation until the line becomes visible (and is thus 2991 // properly measurable). 2992 function estimateHeight(cm) { 2993 var th = textHeight(cm.display), wrapping = cm.options.lineWrapping 2994 var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3) 2995 return function (line) { 2996 if (lineIsHidden(cm.doc, line)) { return 0 } 2997 2998 var widgetsHeight = 0 2999 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { 3000 if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height } 3001 } } 3002 3003 if (wrapping) 3004 { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th } 3005 else 3006 { return widgetsHeight + th } 3007 } 3008 } 3009 3010 function estimateLineHeights(cm) { 3011 var doc = cm.doc, est = estimateHeight(cm) 3012 doc.iter(function (line) { 3013 var estHeight = est(line) 3014 if (estHeight != line.height) { updateLineHeight(line, estHeight) } 3015 }) 3016 } 3017 3018 // Given a mouse event, find the corresponding position. If liberal 3019 // is false, it checks whether a gutter or scrollbar was clicked, 3020 // and returns null if it was. forRect is used by rectangular 3021 // selections, and tries to estimate a character position even for 3022 // coordinates beyond the right of the text. 3023 function posFromMouse(cm, e, liberal, forRect) { 3024 var display = cm.display 3025 if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null } 3026 3027 var x, y, space = display.lineSpace.getBoundingClientRect() 3028 // Fails unpredictably on IE[67] when mouse is dragged around quickly. 3029 try { x = e.clientX - space.left; y = e.clientY - space.top } 3030 catch (e) { return null } 3031 var coords = coordsChar(cm, x, y), line 3032 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { 3033 var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length 3034 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)) 3035 } 3036 return coords 3037 } 3038 3039 // Find the view element corresponding to a given line. Return null 3040 // when the line isn't visible. 3041 function findViewIndex(cm, n) { 3042 if (n >= cm.display.viewTo) { return null } 3043 n -= cm.display.viewFrom 3044 if (n < 0) { return null } 3045 var view = cm.display.view 3046 for (var i = 0; i < view.length; i++) { 3047 n -= view[i].size 3048 if (n < 0) { return i } 3049 } 3050 } 3051 3052 function updateSelection(cm) { 3053 cm.display.input.showSelection(cm.display.input.prepareSelection()) 3054 } 3055 3056 function prepareSelection(cm, primary) { 3057 if ( primary === void 0 ) primary = true; 3058 3059 var doc = cm.doc, result = {} 3060 var curFragment = result.cursors = document.createDocumentFragment() 3061 var selFragment = result.selection = document.createDocumentFragment() 3062 3063 for (var i = 0; i < doc.sel.ranges.length; i++) { 3064 if (!primary && i == doc.sel.primIndex) { continue } 3065 var range = doc.sel.ranges[i] 3066 if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue } 3067 var collapsed = range.empty() 3068 if (collapsed || cm.options.showCursorWhenSelecting) 3069 { drawSelectionCursor(cm, range.head, curFragment) } 3070 if (!collapsed) 3071 { drawSelectionRange(cm, range, selFragment) } 3072 } 3073 return result 3074 } 3075 3076 // Draws a cursor for the given range 3077 function drawSelectionCursor(cm, head, output) { 3078 var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine) 3079 3080 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")) 3081 cursor.style.left = pos.left + "px" 3082 cursor.style.top = pos.top + "px" 3083 cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px" 3084 3085 if (pos.other) { 3086 // Secondary cursor, shown when on a 'jump' in bi-directional text 3087 var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")) 3088 otherCursor.style.display = "" 3089 otherCursor.style.left = pos.other.left + "px" 3090 otherCursor.style.top = pos.other.top + "px" 3091 otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px" 3092 } 3093 } 3094 3095 function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } 3096 3097 // Draws the given range as a highlighted selection 3098 function drawSelectionRange(cm, range, output) { 3099 var display = cm.display, doc = cm.doc 3100 var fragment = document.createDocumentFragment() 3101 var padding = paddingH(cm.display), leftSide = padding.left 3102 var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right 3103 var docLTR = doc.direction == "ltr" 3104 3105 function add(left, top, width, bottom) { 3106 if (top < 0) { top = 0 } 3107 top = Math.round(top) 3108 bottom = Math.round(bottom) 3109 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"))) 3110 } 3111 3112 function drawForLine(line, fromArg, toArg) { 3113 var lineObj = getLine(doc, line) 3114 var lineLen = lineObj.text.length 3115 var start, end 3116 function coords(ch, bias) { 3117 return charCoords(cm, Pos(line, ch), "div", lineObj, bias) 3118 } 3119 3120 function wrapX(pos, dir, side) { 3121 var extent = wrappedLineExtentChar(cm, lineObj, null, pos) 3122 var prop = (dir == "ltr") == (side == "after") ? "left" : "right" 3123 var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1) 3124 return coords(ch, prop)[prop] 3125 } 3126 3127 var order = getOrder(lineObj, doc.direction) 3128 iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) { 3129 var ltr = dir == "ltr" 3130 var fromPos = coords(from, ltr ? "left" : "right") 3131 var toPos = coords(to - 1, ltr ? "right" : "left") 3132 3133 var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen 3134 var first = i == 0, last = !order || i == order.length - 1 3135 if (toPos.top - fromPos.top <= 3) { // Single line 3136 var openLeft = (docLTR ? openStart : openEnd) && first 3137 var openRight = (docLTR ? openEnd : openStart) && last 3138 var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left 3139 var right = openRight ? rightSide : (ltr ? toPos : fromPos).right 3140 add(left, fromPos.top, right - left, fromPos.bottom) 3141 } else { // Multiple lines 3142 var topLeft, topRight, botLeft, botRight 3143 if (ltr) { 3144 topLeft = docLTR && openStart && first ? leftSide : fromPos.left 3145 topRight = docLTR ? rightSide : wrapX(from, dir, "before") 3146 botLeft = docLTR ? leftSide : wrapX(to, dir, "after") 3147 botRight = docLTR && openEnd && last ? rightSide : toPos.right 3148 } else { 3149 topLeft = !docLTR ? leftSide : wrapX(from, dir, "before") 3150 topRight = !docLTR && openStart && first ? rightSide : fromPos.right 3151 botLeft = !docLTR && openEnd && last ? leftSide : toPos.left 3152 botRight = !docLTR ? rightSide : wrapX(to, dir, "after") 3153 } 3154 add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom) 3155 if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top) } 3156 add(botLeft, toPos.top, botRight - botLeft, toPos.bottom) 3157 } 3158 3159 if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos } 3160 if (cmpCoords(toPos, start) < 0) { start = toPos } 3161 if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos } 3162 if (cmpCoords(toPos, end) < 0) { end = toPos } 3163 }) 3164 return {start: start, end: end} 3165 } 3166 3167 var sFrom = range.from(), sTo = range.to() 3168 if (sFrom.line == sTo.line) { 3169 drawForLine(sFrom.line, sFrom.ch, sTo.ch) 3170 } else { 3171 var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line) 3172 var singleVLine = visualLine(fromLine) == visualLine(toLine) 3173 var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end 3174 var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start 3175 if (singleVLine) { 3176 if (leftEnd.top < rightStart.top - 2) { 3177 add(leftEnd.right, leftEnd.top, null, leftEnd.bottom) 3178 add(leftSide, rightStart.top, rightStart.left, rightStart.bottom) 3179 } else { 3180 add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom) 3181 } 3182 } 3183 if (leftEnd.bottom < rightStart.top) 3184 { add(leftSide, leftEnd.bottom, null, rightStart.top) } 3185 } 3186 3187 output.appendChild(fragment) 3188 } 3189 3190 // Cursor-blinking 3191 function restartBlink(cm) { 3192 if (!cm.state.focused) { return } 3193 var display = cm.display 3194 clearInterval(display.blinker) 3195 var on = true 3196 display.cursorDiv.style.visibility = "" 3197 if (cm.options.cursorBlinkRate > 0) 3198 { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; }, 3199 cm.options.cursorBlinkRate) } 3200 else if (cm.options.cursorBlinkRate < 0) 3201 { display.cursorDiv.style.visibility = "hidden" } 3202 } 3203 3204 function ensureFocus(cm) { 3205 if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm) } 3206 } 3207 3208 function delayBlurEvent(cm) { 3209 cm.state.delayingBlurEvent = true 3210 setTimeout(function () { if (cm.state.delayingBlurEvent) { 3211 cm.state.delayingBlurEvent = false 3212 onBlur(cm) 3213 } }, 100) 3214 } 3215 3216 function onFocus(cm, e) { 3217 if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false } 3218 3219 if (cm.options.readOnly == "nocursor") { return } 3220 if (!cm.state.focused) { 3221 signal(cm, "focus", cm, e) 3222 cm.state.focused = true 3223 addClass(cm.display.wrapper, "CodeMirror-focused") 3224 // This test prevents this from firing when a context 3225 // menu is closed (since the input reset would kill the 3226 // select-all detection hack) 3227 if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { 3228 cm.display.input.reset() 3229 if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20) } // Issue #1730 3230 } 3231 cm.display.input.receivedFocus() 3232 } 3233 restartBlink(cm) 3234 } 3235 function onBlur(cm, e) { 3236 if (cm.state.delayingBlurEvent) { return } 3237 3238 if (cm.state.focused) { 3239 signal(cm, "blur", cm, e) 3240 cm.state.focused = false 3241 rmClass(cm.display.wrapper, "CodeMirror-focused") 3242 } 3243 clearInterval(cm.display.blinker) 3244 setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false } }, 150) 3245 } 3246 3247 // Read the actual heights of the rendered lines, and update their 3248 // stored heights to match. 3249 function updateHeightsInViewport(cm) { 3250 var display = cm.display 3251 var prevBottom = display.lineDiv.offsetTop 3252 for (var i = 0; i < display.view.length; i++) { 3253 var cur = display.view[i], height = (void 0) 3254 if (cur.hidden) { continue } 3255 if (ie && ie_version < 8) { 3256 var bot = cur.node.offsetTop + cur.node.offsetHeight 3257 height = bot - prevBottom 3258 prevBottom = bot 3259 } else { 3260 var box = cur.node.getBoundingClientRect() 3261 height = box.bottom - box.top 3262 } 3263 var diff = cur.line.height - height 3264 if (height < 2) { height = textHeight(display) } 3265 if (diff > .005 || diff < -.005) { 3266 updateLineHeight(cur.line, height) 3267 updateWidgetHeight(cur.line) 3268 if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) 3269 { updateWidgetHeight(cur.rest[j]) } } 3270 } 3271 } 3272 } 3273 3274 // Read and store the height of line widgets associated with the 3275 // given line. 3276 function updateWidgetHeight(line) { 3277 if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) { 3278 var w = line.widgets[i], parent = w.node.parentNode 3279 if (parent) { w.height = parent.offsetHeight } 3280 } } 3281 } 3282 3283 // Compute the lines that are visible in a given viewport (defaults 3284 // the the current scroll position). viewport may contain top, 3285 // height, and ensure (see op.scrollToPos) properties. 3286 function visibleLines(display, doc, viewport) { 3287 var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop 3288 top = Math.floor(top - paddingTop(display)) 3289 var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight 3290 3291 var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom) 3292 // Ensure is a {from: {line, ch}, to: {line, ch}} object, and 3293 // forces those lines into the viewport (if possible). 3294 if (viewport && viewport.ensure) { 3295 var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line 3296 if (ensureFrom < from) { 3297 from = ensureFrom 3298 to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight) 3299 } else if (Math.min(ensureTo, doc.lastLine()) >= to) { 3300 from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight) 3301 to = ensureTo 3302 } 3303 } 3304 return {from: from, to: Math.max(to, from + 1)} 3305 } 3306 3307 // Re-align line numbers and gutter marks to compensate for 3308 // horizontal scrolling. 3309 function alignHorizontally(cm) { 3310 var display = cm.display, view = display.view 3311 if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return } 3312 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft 3313 var gutterW = display.gutters.offsetWidth, left = comp + "px" 3314 for (var i = 0; i < view.length; i++) { if (!view[i].hidden) { 3315 if (cm.options.fixedGutter) { 3316 if (view[i].gutter) 3317 { view[i].gutter.style.left = left } 3318 if (view[i].gutterBackground) 3319 { view[i].gutterBackground.style.left = left } 3320 } 3321 var align = view[i].alignable 3322 if (align) { for (var j = 0; j < align.length; j++) 3323 { align[j].style.left = left } } 3324 } } 3325 if (cm.options.fixedGutter) 3326 { display.gutters.style.left = (comp + gutterW) + "px" } 3327 } 3328 3329 // Used to ensure that the line number gutter is still the right 3330 // size for the current document size. Returns true when an update 3331 // is needed. 3332 function maybeUpdateLineNumberWidth(cm) { 3333 if (!cm.options.lineNumbers) { return false } 3334 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display 3335 if (last.length != display.lineNumChars) { 3336 var test = display.measure.appendChild(elt("div", [elt("div", last)], 3337 "CodeMirror-linenumber CodeMirror-gutter-elt")) 3338 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW 3339 display.lineGutter.style.width = "" 3340 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1 3341 display.lineNumWidth = display.lineNumInnerWidth + padding 3342 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1 3343 display.lineGutter.style.width = display.lineNumWidth + "px" 3344 updateGutterSpace(cm) 3345 return true 3346 } 3347 return false 3348 } 3349 3350 // SCROLLING THINGS INTO VIEW 3351 3352 // If an editor sits on the top or bottom of the window, partially 3353 // scrolled out of view, this ensures that the cursor is visible. 3354 function maybeScrollWindow(cm, rect) { 3355 if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } 3356 3357 var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null 3358 if (rect.top + box.top < 0) { doScroll = true } 3359 else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false } 3360 if (doScroll != null && !phantom) { 3361 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;")) 3362 cm.display.lineSpace.appendChild(scrollNode) 3363 scrollNode.scrollIntoView(doScroll) 3364 cm.display.lineSpace.removeChild(scrollNode) 3365 } 3366 } 3367 3368 // Scroll a given position into view (immediately), verifying that 3369 // it actually became visible (as line heights are accurately 3370 // measured, the position of something may 'drift' during drawing). 3371 function scrollPosIntoView(cm, pos, end, margin) { 3372 if (margin == null) { margin = 0 } 3373 var rect 3374 if (!cm.options.lineWrapping && pos == end) { 3375 // Set pos and end to the cursor positions around the character pos sticks to 3376 // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch 3377 // If pos == Pos(_, 0, "before"), pos and end are unchanged 3378 pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos 3379 end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos 3380 } 3381 for (var limit = 0; limit < 5; limit++) { 3382 var changed = false 3383 var coords = cursorCoords(cm, pos) 3384 var endCoords = !end || end == pos ? coords : cursorCoords(cm, end) 3385 rect = {left: Math.min(coords.left, endCoords.left), 3386 top: Math.min(coords.top, endCoords.top) - margin, 3387 right: Math.max(coords.left, endCoords.left), 3388 bottom: Math.max(coords.bottom, endCoords.bottom) + margin} 3389 var scrollPos = calculateScrollPos(cm, rect) 3390 var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft 3391 if (scrollPos.scrollTop != null) { 3392 updateScrollTop(cm, scrollPos.scrollTop) 3393 if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true } 3394 } 3395 if (scrollPos.scrollLeft != null) { 3396 setScrollLeft(cm, scrollPos.scrollLeft) 3397 if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true } 3398 } 3399 if (!changed) { break } 3400 } 3401 return rect 3402 } 3403 3404 // Scroll a given set of coordinates into view (immediately). 3405 function scrollIntoView(cm, rect) { 3406 var scrollPos = calculateScrollPos(cm, rect) 3407 if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop) } 3408 if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft) } 3409 } 3410 3411 // Calculate a new scroll position needed to scroll the given 3412 // rectangle into view. Returns an object with scrollTop and 3413 // scrollLeft properties. When these are undefined, the 3414 // vertical/horizontal position does not need to be adjusted. 3415 function calculateScrollPos(cm, rect) { 3416 var display = cm.display, snapMargin = textHeight(cm.display) 3417 if (rect.top < 0) { rect.top = 0 } 3418 var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop 3419 var screen = displayHeight(cm), result = {} 3420 if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen } 3421 var docBottom = cm.doc.height + paddingVert(display) 3422 var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin 3423 if (rect.top < screentop) { 3424 result.scrollTop = atTop ? 0 : rect.top 3425 } else if (rect.bottom > screentop + screen) { 3426 var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen) 3427 if (newTop != screentop) { result.scrollTop = newTop } 3428 } 3429 3430 var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft 3431 var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0) 3432 var tooWide = rect.right - rect.left > screenw 3433 if (tooWide) { rect.right = rect.left + screenw } 3434 if (rect.left < 10) 3435 { result.scrollLeft = 0 } 3436 else if (rect.left < screenleft) 3437 { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)) } 3438 else if (rect.right > screenw + screenleft - 3) 3439 { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw } 3440 return result 3441 } 3442 3443 // Store a relative adjustment to the scroll position in the current 3444 // operation (to be applied when the operation finishes). 3445 function addToScrollTop(cm, top) { 3446 if (top == null) { return } 3447 resolveScrollToPos(cm) 3448 cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top 3449 } 3450 3451 // Make sure that at the end of the operation the current cursor is 3452 // shown. 3453 function ensureCursorVisible(cm) { 3454 resolveScrollToPos(cm) 3455 var cur = cm.getCursor() 3456 cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin} 3457 } 3458 3459 function scrollToCoords(cm, x, y) { 3460 if (x != null || y != null) { resolveScrollToPos(cm) } 3461 if (x != null) { cm.curOp.scrollLeft = x } 3462 if (y != null) { cm.curOp.scrollTop = y } 3463 } 3464 3465 function scrollToRange(cm, range) { 3466 resolveScrollToPos(cm) 3467 cm.curOp.scrollToPos = range 3468 } 3469 3470 // When an operation has its scrollToPos property set, and another 3471 // scroll action is applied before the end of the operation, this 3472 // 'simulates' scrolling that position into view in a cheap way, so 3473 // that the effect of intermediate scroll commands is not ignored. 3474 function resolveScrollToPos(cm) { 3475 var range = cm.curOp.scrollToPos 3476 if (range) { 3477 cm.curOp.scrollToPos = null 3478 var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to) 3479 scrollToCoordsRange(cm, from, to, range.margin) 3480 } 3481 } 3482 3483 function scrollToCoordsRange(cm, from, to, margin) { 3484 var sPos = calculateScrollPos(cm, { 3485 left: Math.min(from.left, to.left), 3486 top: Math.min(from.top, to.top) - margin, 3487 right: Math.max(from.right, to.right), 3488 bottom: Math.max(from.bottom, to.bottom) + margin 3489 }) 3490 scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop) 3491 } 3492 3493 // Sync the scrollable area and scrollbars, ensure the viewport 3494 // covers the visible area. 3495 function updateScrollTop(cm, val) { 3496 if (Math.abs(cm.doc.scrollTop - val) < 2) { return } 3497 if (!gecko) { updateDisplaySimple(cm, {top: val}) } 3498 setScrollTop(cm, val, true) 3499 if (gecko) { updateDisplaySimple(cm) } 3500 startWorker(cm, 100) 3501 } 3502 3503 function setScrollTop(cm, val, forceScroll) { 3504 val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val) 3505 if (cm.display.scroller.scrollTop == val && !forceScroll) { return } 3506 cm.doc.scrollTop = val 3507 cm.display.scrollbars.setScrollTop(val) 3508 if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val } 3509 } 3510 3511 // Sync scroller and scrollbar, ensure the gutter elements are 3512 // aligned. 3513 function setScrollLeft(cm, val, isScroller, forceScroll) { 3514 val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth) 3515 if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return } 3516 cm.doc.scrollLeft = val 3517 alignHorizontally(cm) 3518 if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val } 3519 cm.display.scrollbars.setScrollLeft(val) 3520 } 3521 3522 // SCROLLBARS 3523 3524 // Prepare DOM reads needed to update the scrollbars. Done in one 3525 // shot to minimize update/measure roundtrips. 3526 function measureForScrollbars(cm) { 3527 var d = cm.display, gutterW = d.gutters.offsetWidth 3528 var docH = Math.round(cm.doc.height + paddingVert(cm.display)) 3529 return { 3530 clientHeight: d.scroller.clientHeight, 3531 viewHeight: d.wrapper.clientHeight, 3532 scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, 3533 viewWidth: d.wrapper.clientWidth, 3534 barLeft: cm.options.fixedGutter ? gutterW : 0, 3535 docHeight: docH, 3536 scrollHeight: docH + scrollGap(cm) + d.barHeight, 3537 nativeBarWidth: d.nativeBarWidth, 3538 gutterWidth: gutterW 3539 } 3540 } 3541 3542 var NativeScrollbars = function(place, scroll, cm) { 3543 this.cm = cm 3544 var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar") 3545 var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar") 3546 place(vert); place(horiz) 3547 3548 on(vert, "scroll", function () { 3549 if (vert.clientHeight) { scroll(vert.scrollTop, "vertical") } 3550 }) 3551 on(horiz, "scroll", function () { 3552 if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal") } 3553 }) 3554 3555 this.checkedZeroWidth = false 3556 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). 3557 if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px" } 3558 }; 3559 3560 NativeScrollbars.prototype.update = function (measure) { 3561 var needsH = measure.scrollWidth > measure.clientWidth + 1 3562 var needsV = measure.scrollHeight > measure.clientHeight + 1 3563 var sWidth = measure.nativeBarWidth 3564 3565 if (needsV) { 3566 this.vert.style.display = "block" 3567 this.vert.style.bottom = needsH ? sWidth + "px" : "0" 3568 var totalHeight = measure.viewHeight - (needsH ? sWidth : 0) 3569 // A bug in IE8 can cause this value to be negative, so guard it. 3570 this.vert.firstChild.style.height = 3571 Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px" 3572 } else { 3573 this.vert.style.display = "" 3574 this.vert.firstChild.style.height = "0" 3575 } 3576 3577 if (needsH) { 3578 this.horiz.style.display = "block" 3579 this.horiz.style.right = needsV ? sWidth + "px" : "0" 3580 this.horiz.style.left = measure.barLeft + "px" 3581 var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0) 3582 this.horiz.firstChild.style.width = 3583 Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px" 3584 } else { 3585 this.horiz.style.display = "" 3586 this.horiz.firstChild.style.width = "0" 3587 } 3588 3589 if (!this.checkedZeroWidth && measure.clientHeight > 0) { 3590 if (sWidth == 0) { this.zeroWidthHack() } 3591 this.checkedZeroWidth = true 3592 } 3593 3594 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} 3595 }; 3596 3597 NativeScrollbars.prototype.setScrollLeft = function (pos) { 3598 if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos } 3599 if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz") } 3600 }; 3601 3602 NativeScrollbars.prototype.setScrollTop = function (pos) { 3603 if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos } 3604 if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert") } 3605 }; 3606 3607 NativeScrollbars.prototype.zeroWidthHack = function () { 3608 var w = mac && !mac_geMountainLion ? "12px" : "18px" 3609 this.horiz.style.height = this.vert.style.width = w 3610 this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none" 3611 this.disableHoriz = new Delayed 3612 this.disableVert = new Delayed 3613 }; 3614 3615 NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { 3616 bar.style.pointerEvents = "auto" 3617 function maybeDisable() { 3618 // To find out whether the scrollbar is still visible, we 3619 // check whether the element under the pixel in the bottom 3620 // right corner of the scrollbar box is the scrollbar box 3621 // itself (when the bar is still visible) or its filler child 3622 // (when the bar is hidden). If it is still visible, we keep 3623 // it enabled, if it's hidden, we disable pointer events. 3624 var box = bar.getBoundingClientRect() 3625 var elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) 3626 : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1) 3627 if (elt != bar) { bar.style.pointerEvents = "none" } 3628 else { delay.set(1000, maybeDisable) } 3629 } 3630 delay.set(1000, maybeDisable) 3631 }; 3632 3633 NativeScrollbars.prototype.clear = function () { 3634 var parent = this.horiz.parentNode 3635 parent.removeChild(this.horiz) 3636 parent.removeChild(this.vert) 3637 }; 3638 3639 var NullScrollbars = function () {}; 3640 3641 NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} }; 3642 NullScrollbars.prototype.setScrollLeft = function () {}; 3643 NullScrollbars.prototype.setScrollTop = function () {}; 3644 NullScrollbars.prototype.clear = function () {}; 3645 3646 function updateScrollbars(cm, measure) { 3647 if (!measure) { measure = measureForScrollbars(cm) } 3648 var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight 3649 updateScrollbarsInner(cm, measure) 3650 for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { 3651 if (startWidth != cm.display.barWidth && cm.options.lineWrapping) 3652 { updateHeightsInViewport(cm) } 3653 updateScrollbarsInner(cm, measureForScrollbars(cm)) 3654 startWidth = cm.display.barWidth; startHeight = cm.display.barHeight 3655 } 3656 } 3657 3658 // Re-synchronize the fake scrollbars with the actual size of the 3659 // content. 3660 function updateScrollbarsInner(cm, measure) { 3661 var d = cm.display 3662 var sizes = d.scrollbars.update(measure) 3663 3664 d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px" 3665 d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px" 3666 d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" 3667 3668 if (sizes.right && sizes.bottom) { 3669 d.scrollbarFiller.style.display = "block" 3670 d.scrollbarFiller.style.height = sizes.bottom + "px" 3671 d.scrollbarFiller.style.width = sizes.right + "px" 3672 } else { d.scrollbarFiller.style.display = "" } 3673 if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { 3674 d.gutterFiller.style.display = "block" 3675 d.gutterFiller.style.height = sizes.bottom + "px" 3676 d.gutterFiller.style.width = measure.gutterWidth + "px" 3677 } else { d.gutterFiller.style.display = "" } 3678 } 3679 3680 var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars} 3681 3682 function initScrollbars(cm) { 3683 if (cm.display.scrollbars) { 3684 cm.display.scrollbars.clear() 3685 if (cm.display.scrollbars.addClass) 3686 { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass) } 3687 } 3688 3689 cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) { 3690 cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller) 3691 // Prevent clicks in the scrollbars from killing focus 3692 on(node, "mousedown", function () { 3693 if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0) } 3694 }) 3695 node.setAttribute("cm-not-content", "true") 3696 }, function (pos, axis) { 3697 if (axis == "horizontal") { setScrollLeft(cm, pos) } 3698 else { updateScrollTop(cm, pos) } 3699 }, cm) 3700 if (cm.display.scrollbars.addClass) 3701 { addClass(cm.display.wrapper, cm.display.scrollbars.addClass) } 3702 } 3703 3704 // Operations are used to wrap a series of changes to the editor 3705 // state in such a way that each change won't have to update the 3706 // cursor and display (which would be awkward, slow, and 3707 // error-prone). Instead, display updates are batched and then all 3708 // combined and executed at once. 3709 3710 var nextOpId = 0 3711 // Start a new operation. 3712 function startOperation(cm) { 3713 cm.curOp = { 3714 cm: cm, 3715 viewChanged: false, // Flag that indicates that lines might need to be redrawn 3716 startHeight: cm.doc.height, // Used to detect need to update scrollbar 3717 forceUpdate: false, // Used to force a redraw 3718 updateInput: null, // Whether to reset the input textarea 3719 typing: false, // Whether this reset should be careful to leave existing text (for compositing) 3720 changeObjs: null, // Accumulated changes, for firing change events 3721 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on 3722 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already 3723 selectionChanged: false, // Whether the selection needs to be redrawn 3724 updateMaxLine: false, // Set when the widest line needs to be determined anew 3725 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet 3726 scrollToPos: null, // Used to scroll to a specific position 3727 focus: false, 3728 id: ++nextOpId // Unique ID 3729 } 3730 pushOperation(cm.curOp) 3731 } 3732 3733 // Finish an operation, updating the display and signalling delayed events 3734 function endOperation(cm) { 3735 var op = cm.curOp 3736 finishOperation(op, function (group) { 3737 for (var i = 0; i < group.ops.length; i++) 3738 { group.ops[i].cm.curOp = null } 3739 endOperations(group) 3740 }) 3741 } 3742 3743 // The DOM updates done when an operation finishes are batched so 3744 // that the minimum number of relayouts are required. 3745 function endOperations(group) { 3746 var ops = group.ops 3747 for (var i = 0; i < ops.length; i++) // Read DOM 3748 { endOperation_R1(ops[i]) } 3749 for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe) 3750 { endOperation_W1(ops[i$1]) } 3751 for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM 3752 { endOperation_R2(ops[i$2]) } 3753 for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe) 3754 { endOperation_W2(ops[i$3]) } 3755 for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM 3756 { endOperation_finish(ops[i$4]) } 3757 } 3758 3759 function endOperation_R1(op) { 3760 var cm = op.cm, display = cm.display 3761 maybeClipScrollbars(cm) 3762 if (op.updateMaxLine) { findMaxLine(cm) } 3763 3764 op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || 3765 op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || 3766 op.scrollToPos.to.line >= display.viewTo) || 3767 display.maxLineChanged && cm.options.lineWrapping 3768 op.update = op.mustUpdate && 3769 new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate) 3770 } 3771 3772 function endOperation_W1(op) { 3773 op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update) 3774 } 3775 3776 function endOperation_R2(op) { 3777 var cm = op.cm, display = cm.display 3778 if (op.updatedDisplay) { updateHeightsInViewport(cm) } 3779 3780 op.barMeasure = measureForScrollbars(cm) 3781 3782 // If the max line changed since it was last measured, measure it, 3783 // and ensure the document's width matches it. 3784 // updateDisplay_W2 will use these properties to do the actual resizing 3785 if (display.maxLineChanged && !cm.options.lineWrapping) { 3786 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3 3787 cm.display.sizerWidth = op.adjustWidthTo 3788 op.barMeasure.scrollWidth = 3789 Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth) 3790 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)) 3791 } 3792 3793 if (op.updatedDisplay || op.selectionChanged) 3794 { op.preparedSelection = display.input.prepareSelection() } 3795 } 3796 3797 function endOperation_W2(op) { 3798 var cm = op.cm 3799 3800 if (op.adjustWidthTo != null) { 3801 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px" 3802 if (op.maxScrollLeft < cm.doc.scrollLeft) 3803 { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true) } 3804 cm.display.maxLineChanged = false 3805 } 3806 3807 var takeFocus = op.focus && op.focus == activeElt() 3808 if (op.preparedSelection) 3809 { cm.display.input.showSelection(op.preparedSelection, takeFocus) } 3810 if (op.updatedDisplay || op.startHeight != cm.doc.height) 3811 { updateScrollbars(cm, op.barMeasure) } 3812 if (op.updatedDisplay) 3813 { setDocumentHeight(cm, op.barMeasure) } 3814 3815 if (op.selectionChanged) { restartBlink(cm) } 3816 3817 if (cm.state.focused && op.updateInput) 3818 { cm.display.input.reset(op.typing) } 3819 if (takeFocus) { ensureFocus(op.cm) } 3820 } 3821 3822 function endOperation_finish(op) { 3823 var cm = op.cm, display = cm.display, doc = cm.doc 3824 3825 if (op.updatedDisplay) { postUpdateDisplay(cm, op.update) } 3826 3827 // Abort mouse wheel delta measurement, when scrolling explicitly 3828 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) 3829 { display.wheelStartX = display.wheelStartY = null } 3830 3831 // Propagate the scroll position to the actual DOM scroller 3832 if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll) } 3833 3834 if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true) } 3835 // If we need to scroll a specific position into view, do so. 3836 if (op.scrollToPos) { 3837 var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), 3838 clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin) 3839 maybeScrollWindow(cm, rect) 3840 } 3841 3842 // Fire events for markers that are hidden/unidden by editing or 3843 // undoing 3844 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers 3845 if (hidden) { for (var i = 0; i < hidden.length; ++i) 3846 { if (!hidden[i].lines.length) { signal(hidden[i], "hide") } } } 3847 if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1) 3848 { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide") } } } 3849 3850 if (display.wrapper.offsetHeight) 3851 { doc.scrollTop = cm.display.scroller.scrollTop } 3852 3853 // Fire change events, and delayed event handlers 3854 if (op.changeObjs) 3855 { signal(cm, "changes", cm, op.changeObjs) } 3856 if (op.update) 3857 { op.update.finish() } 3858 } 3859 3860 // Run the given function in an operation 3861 function runInOp(cm, f) { 3862 if (cm.curOp) { return f() } 3863 startOperation(cm) 3864 try { return f() } 3865 finally { endOperation(cm) } 3866 } 3867 // Wraps a function in an operation. Returns the wrapped function. 3868 function operation(cm, f) { 3869 return function() { 3870 if (cm.curOp) { return f.apply(cm, arguments) } 3871 startOperation(cm) 3872 try { return f.apply(cm, arguments) } 3873 finally { endOperation(cm) } 3874 } 3875 } 3876 // Used to add methods to editor and doc instances, wrapping them in 3877 // operations. 3878 function methodOp(f) { 3879 return function() { 3880 if (this.curOp) { return f.apply(this, arguments) } 3881 startOperation(this) 3882 try { return f.apply(this, arguments) } 3883 finally { endOperation(this) } 3884 } 3885 } 3886 function docMethodOp(f) { 3887 return function() { 3888 var cm = this.cm 3889 if (!cm || cm.curOp) { return f.apply(this, arguments) } 3890 startOperation(cm) 3891 try { return f.apply(this, arguments) } 3892 finally { endOperation(cm) } 3893 } 3894 } 3895 3896 // Updates the display.view data structure for a given change to the 3897 // document. From and to are in pre-change coordinates. Lendiff is 3898 // the amount of lines added or subtracted by the change. This is 3899 // used for changes that span multiple lines, or change the way 3900 // lines are divided into visual lines. regLineChange (below) 3901 // registers single-line changes. 3902 function regChange(cm, from, to, lendiff) { 3903 if (from == null) { from = cm.doc.first } 3904 if (to == null) { to = cm.doc.first + cm.doc.size } 3905 if (!lendiff) { lendiff = 0 } 3906 3907 var display = cm.display 3908 if (lendiff && to < display.viewTo && 3909 (display.updateLineNumbers == null || display.updateLineNumbers > from)) 3910 { display.updateLineNumbers = from } 3911 3912 cm.curOp.viewChanged = true 3913 3914 if (from >= display.viewTo) { // Change after 3915 if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) 3916 { resetView(cm) } 3917 } else if (to <= display.viewFrom) { // Change before 3918 if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { 3919 resetView(cm) 3920 } else { 3921 display.viewFrom += lendiff 3922 display.viewTo += lendiff 3923 } 3924 } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap 3925 resetView(cm) 3926 } else if (from <= display.viewFrom) { // Top overlap 3927 var cut = viewCuttingPoint(cm, to, to + lendiff, 1) 3928 if (cut) { 3929 display.view = display.view.slice(cut.index) 3930 display.viewFrom = cut.lineN 3931 display.viewTo += lendiff 3932 } else { 3933 resetView(cm) 3934 } 3935 } else if (to >= display.viewTo) { // Bottom overlap 3936 var cut$1 = viewCuttingPoint(cm, from, from, -1) 3937 if (cut$1) { 3938 display.view = display.view.slice(0, cut$1.index) 3939 display.viewTo = cut$1.lineN 3940 } else { 3941 resetView(cm) 3942 } 3943 } else { // Gap in the middle 3944 var cutTop = viewCuttingPoint(cm, from, from, -1) 3945 var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1) 3946 if (cutTop && cutBot) { 3947 display.view = display.view.slice(0, cutTop.index) 3948 .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) 3949 .concat(display.view.slice(cutBot.index)) 3950 display.viewTo += lendiff 3951 } else { 3952 resetView(cm) 3953 } 3954 } 3955 3956 var ext = display.externalMeasured 3957 if (ext) { 3958 if (to < ext.lineN) 3959 { ext.lineN += lendiff } 3960 else if (from < ext.lineN + ext.size) 3961 { display.externalMeasured = null } 3962 } 3963 } 3964 3965 // Register a change to a single line. Type must be one of "text", 3966 // "gutter", "class", "widget" 3967 function regLineChange(cm, line, type) { 3968 cm.curOp.viewChanged = true 3969 var display = cm.display, ext = cm.display.externalMeasured 3970 if (ext && line >= ext.lineN && line < ext.lineN + ext.size) 3971 { display.externalMeasured = null } 3972 3973 if (line < display.viewFrom || line >= display.viewTo) { return } 3974 var lineView = display.view[findViewIndex(cm, line)] 3975 if (lineView.node == null) { return } 3976 var arr = lineView.changes || (lineView.changes = []) 3977 if (indexOf(arr, type) == -1) { arr.push(type) } 3978 } 3979 3980 // Clear the view. 3981 function resetView(cm) { 3982 cm.display.viewFrom = cm.display.viewTo = cm.doc.first 3983 cm.display.view = [] 3984 cm.display.viewOffset = 0 3985 } 3986 3987 function viewCuttingPoint(cm, oldN, newN, dir) { 3988 var index = findViewIndex(cm, oldN), diff, view = cm.display.view 3989 if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) 3990 { return {index: index, lineN: newN} } 3991 var n = cm.display.viewFrom 3992 for (var i = 0; i < index; i++) 3993 { n += view[i].size } 3994 if (n != oldN) { 3995 if (dir > 0) { 3996 if (index == view.length - 1) { return null } 3997 diff = (n + view[index].size) - oldN 3998 index++ 3999 } else { 4000 diff = n - oldN 4001 } 4002 oldN += diff; newN += diff 4003 } 4004 while (visualLineNo(cm.doc, newN) != newN) { 4005 if (index == (dir < 0 ? 0 : view.length - 1)) { return null } 4006 newN += dir * view[index - (dir < 0 ? 1 : 0)].size 4007 index += dir 4008 } 4009 return {index: index, lineN: newN} 4010 } 4011 4012 // Force the view to cover a given range, adding empty view element 4013 // or clipping off existing ones as needed. 4014 function adjustView(cm, from, to) { 4015 var display = cm.display, view = display.view 4016 if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { 4017 display.view = buildViewArray(cm, from, to) 4018 display.viewFrom = from 4019 } else { 4020 if (display.viewFrom > from) 4021 { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view) } 4022 else if (display.viewFrom < from) 4023 { display.view = display.view.slice(findViewIndex(cm, from)) } 4024 display.viewFrom = from 4025 if (display.viewTo < to) 4026 { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)) } 4027 else if (display.viewTo > to) 4028 { display.view = display.view.slice(0, findViewIndex(cm, to)) } 4029 } 4030 display.viewTo = to 4031 } 4032 4033 // Count the number of lines in the view whose DOM representation is 4034 // out of date (or nonexistent). 4035 function countDirtyView(cm) { 4036 var view = cm.display.view, dirty = 0 4037 for (var i = 0; i < view.length; i++) { 4038 var lineView = view[i] 4039 if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty } 4040 } 4041 return dirty 4042 } 4043 4044 // HIGHLIGHT WORKER 4045 4046 function startWorker(cm, time) { 4047 if (cm.doc.highlightFrontier < cm.display.viewTo) 4048 { cm.state.highlight.set(time, bind(highlightWorker, cm)) } 4049 } 4050 4051 function highlightWorker(cm) { 4052 var doc = cm.doc 4053 if (doc.highlightFrontier >= cm.display.viewTo) { return } 4054 var end = +new Date + cm.options.workTime 4055 var context = getContextBefore(cm, doc.highlightFrontier) 4056 var changedLines = [] 4057 4058 doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { 4059 if (context.line >= cm.display.viewFrom) { // Visible 4060 var oldStyles = line.styles 4061 var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null 4062 var highlighted = highlightLine(cm, line, context, true) 4063 if (resetState) { context.state = resetState } 4064 line.styles = highlighted.styles 4065 var oldCls = line.styleClasses, newCls = highlighted.classes 4066 if (newCls) { line.styleClasses = newCls } 4067 else if (oldCls) { line.styleClasses = null } 4068 var ischange = !oldStyles || oldStyles.length != line.styles.length || 4069 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass) 4070 for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i] } 4071 if (ischange) { changedLines.push(context.line) } 4072 line.stateAfter = context.save() 4073 context.nextLine() 4074 } else { 4075 if (line.text.length <= cm.options.maxHighlightLength) 4076 { processLine(cm, line.text, context) } 4077 line.stateAfter = context.line % 5 == 0 ? context.save() : null 4078 context.nextLine() 4079 } 4080 if (+new Date > end) { 4081 startWorker(cm, cm.options.workDelay) 4082 return true 4083 } 4084 }) 4085 doc.highlightFrontier = context.line 4086 doc.modeFrontier = Math.max(doc.modeFrontier, context.line) 4087 if (changedLines.length) { runInOp(cm, function () { 4088 for (var i = 0; i < changedLines.length; i++) 4089 { regLineChange(cm, changedLines[i], "text") } 4090 }) } 4091 } 4092 4093 // DISPLAY DRAWING 4094 4095 var DisplayUpdate = function(cm, viewport, force) { 4096 var display = cm.display 4097 4098 this.viewport = viewport 4099 // Store some values that we'll need later (but don't want to force a relayout for) 4100 this.visible = visibleLines(display, cm.doc, viewport) 4101 this.editorIsHidden = !display.wrapper.offsetWidth 4102 this.wrapperHeight = display.wrapper.clientHeight 4103 this.wrapperWidth = display.wrapper.clientWidth 4104 this.oldDisplayWidth = displayWidth(cm) 4105 this.force = force 4106 this.dims = getDimensions(cm) 4107 this.events = [] 4108 }; 4109 4110 DisplayUpdate.prototype.signal = function (emitter, type) { 4111 if (hasHandler(emitter, type)) 4112 { this.events.push(arguments) } 4113 }; 4114 DisplayUpdate.prototype.finish = function () { 4115 var this$1 = this; 4116 4117 for (var i = 0; i < this.events.length; i++) 4118 { signal.apply(null, this$1.events[i]) } 4119 }; 4120 4121 function maybeClipScrollbars(cm) { 4122 var display = cm.display 4123 if (!display.scrollbarsClipped && display.scroller.offsetWidth) { 4124 display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth 4125 display.heightForcer.style.height = scrollGap(cm) + "px" 4126 display.sizer.style.marginBottom = -display.nativeBarWidth + "px" 4127 display.sizer.style.borderRightWidth = scrollGap(cm) + "px" 4128 display.scrollbarsClipped = true 4129 } 4130 } 4131 4132 function selectionSnapshot(cm) { 4133 if (cm.hasFocus()) { return null } 4134 var active = activeElt() 4135 if (!active || !contains(cm.display.lineDiv, active)) { return null } 4136 var result = {activeElt: active} 4137 if (window.getSelection) { 4138 var sel = window.getSelection() 4139 if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { 4140 result.anchorNode = sel.anchorNode 4141 result.anchorOffset = sel.anchorOffset 4142 result.focusNode = sel.focusNode 4143 result.focusOffset = sel.focusOffset 4144 } 4145 } 4146 return result 4147 } 4148 4149 function restoreSelection(snapshot) { 4150 if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return } 4151 snapshot.activeElt.focus() 4152 if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { 4153 var sel = window.getSelection(), range = document.createRange() 4154 range.setEnd(snapshot.anchorNode, snapshot.anchorOffset) 4155 range.collapse(false) 4156 sel.removeAllRanges() 4157 sel.addRange(range) 4158 sel.extend(snapshot.focusNode, snapshot.focusOffset) 4159 } 4160 } 4161 4162 // Does the actual updating of the line display. Bails out 4163 // (returning false) when there is nothing to be done and forced is 4164 // false. 4165 function updateDisplayIfNeeded(cm, update) { 4166 var display = cm.display, doc = cm.doc 4167 4168 if (update.editorIsHidden) { 4169 resetView(cm) 4170 return false 4171 } 4172 4173 // Bail out if the visible area is already rendered and nothing changed. 4174 if (!update.force && 4175 update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && 4176 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && 4177 display.renderedView == display.view && countDirtyView(cm) == 0) 4178 { return false } 4179 4180 if (maybeUpdateLineNumberWidth(cm)) { 4181 resetView(cm) 4182 update.dims = getDimensions(cm) 4183 } 4184 4185 // Compute a suitable new viewport (from & to) 4186 var end = doc.first + doc.size 4187 var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first) 4188 var to = Math.min(end, update.visible.to + cm.options.viewportMargin) 4189 if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom) } 4190 if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo) } 4191 if (sawCollapsedSpans) { 4192 from = visualLineNo(cm.doc, from) 4193 to = visualLineEndNo(cm.doc, to) 4194 } 4195 4196 var different = from != display.viewFrom || to != display.viewTo || 4197 display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth 4198 adjustView(cm, from, to) 4199 4200 display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)) 4201 // Position the mover div to align with the current scroll position 4202 cm.display.mover.style.top = display.viewOffset + "px" 4203 4204 var toUpdate = countDirtyView(cm) 4205 if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && 4206 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) 4207 { return false } 4208 4209 // For big changes, we hide the enclosing element during the 4210 // update, since that speeds up the operations on most browsers. 4211 var selSnapshot = selectionSnapshot(cm) 4212 if (toUpdate > 4) { display.lineDiv.style.display = "none" } 4213 patchDisplay(cm, display.updateLineNumbers, update.dims) 4214 if (toUpdate > 4) { display.lineDiv.style.display = "" } 4215 display.renderedView = display.view 4216 // There might have been a widget with a focused element that got 4217 // hidden or updated, if so re-focus it. 4218 restoreSelection(selSnapshot) 4219 4220 // Prevent selection and cursors from interfering with the scroll 4221 // width and height. 4222 removeChildren(display.cursorDiv) 4223 removeChildren(display.selectionDiv) 4224 display.gutters.style.height = display.sizer.style.minHeight = 0 4225 4226 if (different) { 4227 display.lastWrapHeight = update.wrapperHeight 4228 display.lastWrapWidth = update.wrapperWidth 4229 startWorker(cm, 400) 4230 } 4231 4232 display.updateLineNumbers = null 4233 4234 return true 4235 } 4236 4237 function postUpdateDisplay(cm, update) { 4238 var viewport = update.viewport 4239 4240 for (var first = true;; first = false) { 4241 if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { 4242 // Clip forced viewport to actual scrollable area. 4243 if (viewport && viewport.top != null) 4244 { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)} } 4245 // Updated line heights might result in the drawn area not 4246 // actually covering the viewport. Keep looping until it does. 4247 update.visible = visibleLines(cm.display, cm.doc, viewport) 4248 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) 4249 { break } 4250 } 4251 if (!updateDisplayIfNeeded(cm, update)) { break } 4252 updateHeightsInViewport(cm) 4253 var barMeasure = measureForScrollbars(cm) 4254 updateSelection(cm) 4255 updateScrollbars(cm, barMeasure) 4256 setDocumentHeight(cm, barMeasure) 4257 update.force = false 4258 } 4259 4260 update.signal(cm, "update", cm) 4261 if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { 4262 update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo) 4263 cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo 4264 } 4265 } 4266 4267 function updateDisplaySimple(cm, viewport) { 4268 var update = new DisplayUpdate(cm, viewport) 4269 if (updateDisplayIfNeeded(cm, update)) { 4270 updateHeightsInViewport(cm) 4271 postUpdateDisplay(cm, update) 4272 var barMeasure = measureForScrollbars(cm) 4273 updateSelection(cm) 4274 updateScrollbars(cm, barMeasure) 4275 setDocumentHeight(cm, barMeasure) 4276 update.finish() 4277 } 4278 } 4279 4280 // Sync the actual display DOM structure with display.view, removing 4281 // nodes for lines that are no longer in view, and creating the ones 4282 // that are not there yet, and updating the ones that are out of 4283 // date. 4284 function patchDisplay(cm, updateNumbersFrom, dims) { 4285 var display = cm.display, lineNumbers = cm.options.lineNumbers 4286 var container = display.lineDiv, cur = container.firstChild 4287 4288 function rm(node) { 4289 var next = node.nextSibling 4290 // Works around a throw-scroll bug in OS X Webkit 4291 if (webkit && mac && cm.display.currentWheelTarget == node) 4292 { node.style.display = "none" } 4293 else 4294 { node.parentNode.removeChild(node) } 4295 return next 4296 } 4297 4298 var view = display.view, lineN = display.viewFrom 4299 // Loop over the elements in the view, syncing cur (the DOM nodes 4300 // in display.lineDiv) with the view as we go. 4301 for (var i = 0; i < view.length; i++) { 4302 var lineView = view[i] 4303 if (lineView.hidden) { 4304 } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet 4305 var node = buildLineElement(cm, lineView, lineN, dims) 4306 container.insertBefore(node, cur) 4307 } else { // Already drawn 4308 while (cur != lineView.node) { cur = rm(cur) } 4309 var updateNumber = lineNumbers && updateNumbersFrom != null && 4310 updateNumbersFrom <= lineN && lineView.lineNumber 4311 if (lineView.changes) { 4312 if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false } 4313 updateLineForChanges(cm, lineView, lineN, dims) 4314 } 4315 if (updateNumber) { 4316 removeChildren(lineView.lineNumber) 4317 lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))) 4318 } 4319 cur = lineView.node.nextSibling 4320 } 4321 lineN += lineView.size 4322 } 4323 while (cur) { cur = rm(cur) } 4324 } 4325 4326 function updateGutterSpace(cm) { 4327 var width = cm.display.gutters.offsetWidth 4328 cm.display.sizer.style.marginLeft = width + "px" 4329 } 4330 4331 function setDocumentHeight(cm, measure) { 4332 cm.display.sizer.style.minHeight = measure.docHeight + "px" 4333 cm.display.heightForcer.style.top = measure.docHeight + "px" 4334 cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px" 4335 } 4336 4337 // Rebuild the gutter elements, ensure the margin to the left of the 4338 // code matches their width. 4339 function updateGutters(cm) { 4340 var gutters = cm.display.gutters, specs = cm.options.gutters 4341 removeChildren(gutters) 4342 var i = 0 4343 for (; i < specs.length; ++i) { 4344 var gutterClass = specs[i] 4345 var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)) 4346 if (gutterClass == "CodeMirror-linenumbers") { 4347 cm.display.lineGutter = gElt 4348 gElt.style.width = (cm.display.lineNumWidth || 1) + "px" 4349 } 4350 } 4351 gutters.style.display = i ? "" : "none" 4352 updateGutterSpace(cm) 4353 } 4354 4355 // Make sure the gutters options contains the element 4356 // "CodeMirror-linenumbers" when the lineNumbers option is true. 4357 function setGuttersForLineNumbers(options) { 4358 var found = indexOf(options.gutters, "CodeMirror-linenumbers") 4359 if (found == -1 && options.lineNumbers) { 4360 options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]) 4361 } else if (found > -1 && !options.lineNumbers) { 4362 options.gutters = options.gutters.slice(0) 4363 options.gutters.splice(found, 1) 4364 } 4365 } 4366 4367 var wheelSamples = 0; 4368 var wheelPixelsPerUnit = null; 4369 // Fill in a browser-detected starting value on browsers where we 4370 // know one. These don't have to be accurate -- the result of them 4371 // being wrong would just be a slight flicker on the first wheel 4372 // scroll (if it is large enough). 4373 if (ie) { wheelPixelsPerUnit = -.53 } 4374 else if (gecko) { wheelPixelsPerUnit = 15 } 4375 else if (chrome) { wheelPixelsPerUnit = -.7 } 4376 else if (safari) { wheelPixelsPerUnit = -1/3 } 4377 4378 function wheelEventDelta(e) { 4379 var dx = e.wheelDeltaX, dy = e.wheelDeltaY 4380 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail } 4381 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail } 4382 else if (dy == null) { dy = e.wheelDelta } 4383 return {x: dx, y: dy} 4384 } 4385 function wheelEventPixels(e) { 4386 var delta = wheelEventDelta(e) 4387 delta.x *= wheelPixelsPerUnit 4388 delta.y *= wheelPixelsPerUnit 4389 return delta 4390 } 4391 4392 function onScrollWheel(cm, e) { 4393 var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y 4394 4395 var display = cm.display, scroll = display.scroller 4396 // Quit if there's nothing to scroll here 4397 var canScrollX = scroll.scrollWidth > scroll.clientWidth 4398 var canScrollY = scroll.scrollHeight > scroll.clientHeight 4399 if (!(dx && canScrollX || dy && canScrollY)) { return } 4400 4401 // Webkit browsers on OS X abort momentum scrolls when the target 4402 // of the scroll event is removed from the scrollable element. 4403 // This hack (see related code in patchDisplay) makes sure the 4404 // element is kept around. 4405 if (dy && mac && webkit) { 4406 outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { 4407 for (var i = 0; i < view.length; i++) { 4408 if (view[i].node == cur) { 4409 cm.display.currentWheelTarget = cur 4410 break outer 4411 } 4412 } 4413 } 4414 } 4415 4416 // On some browsers, horizontal scrolling will cause redraws to 4417 // happen before the gutter has been realigned, causing it to 4418 // wriggle around in a most unseemly way. When we have an 4419 // estimated pixels/delta value, we just handle horizontal 4420 // scrolling entirely here. It'll be slightly off from native, but 4421 // better than glitching out. 4422 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { 4423 if (dy && canScrollY) 4424 { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)) } 4425 setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit)) 4426 // Only prevent default scrolling if vertical scrolling is 4427 // actually possible. Otherwise, it causes vertical scroll 4428 // jitter on OSX trackpads when deltaX is small and deltaY 4429 // is large (issue #3579) 4430 if (!dy || (dy && canScrollY)) 4431 { e_preventDefault(e) } 4432 display.wheelStartX = null // Abort measurement, if in progress 4433 return 4434 } 4435 4436 // 'Project' the visible viewport to cover the area that is being 4437 // scrolled into view (if we know enough to estimate it). 4438 if (dy && wheelPixelsPerUnit != null) { 4439 var pixels = dy * wheelPixelsPerUnit 4440 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight 4441 if (pixels < 0) { top = Math.max(0, top + pixels - 50) } 4442 else { bot = Math.min(cm.doc.height, bot + pixels + 50) } 4443 updateDisplaySimple(cm, {top: top, bottom: bot}) 4444 } 4445 4446 if (wheelSamples < 20) { 4447 if (display.wheelStartX == null) { 4448 display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop 4449 display.wheelDX = dx; display.wheelDY = dy 4450 setTimeout(function () { 4451 if (display.wheelStartX == null) { return } 4452 var movedX = scroll.scrollLeft - display.wheelStartX 4453 var movedY = scroll.scrollTop - display.wheelStartY 4454 var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || 4455 (movedX && display.wheelDX && movedX / display.wheelDX) 4456 display.wheelStartX = display.wheelStartY = null 4457 if (!sample) { return } 4458 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1) 4459 ++wheelSamples 4460 }, 200) 4461 } else { 4462 display.wheelDX += dx; display.wheelDY += dy 4463 } 4464 } 4465 } 4466 4467 // Selection objects are immutable. A new one is created every time 4468 // the selection changes. A selection is one or more non-overlapping 4469 // (and non-touching) ranges, sorted, and an integer that indicates 4470 // which one is the primary selection (the one that's scrolled into 4471 // view, that getCursor returns, etc). 4472 var Selection = function(ranges, primIndex) { 4473 this.ranges = ranges 4474 this.primIndex = primIndex 4475 }; 4476 4477 Selection.prototype.primary = function () { return this.ranges[this.primIndex] }; 4478 4479 Selection.prototype.equals = function (other) { 4480 var this$1 = this; 4481 4482 if (other == this) { return true } 4483 if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false } 4484 for (var i = 0; i < this.ranges.length; i++) { 4485 var here = this$1.ranges[i], there = other.ranges[i] 4486 if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false } 4487 } 4488 return true 4489 }; 4490 4491 Selection.prototype.deepCopy = function () { 4492 var this$1 = this; 4493 4494 var out = [] 4495 for (var i = 0; i < this.ranges.length; i++) 4496 { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)) } 4497 return new Selection(out, this.primIndex) 4498 }; 4499 4500 Selection.prototype.somethingSelected = function () { 4501 var this$1 = this; 4502 4503 for (var i = 0; i < this.ranges.length; i++) 4504 { if (!this$1.ranges[i].empty()) { return true } } 4505 return false 4506 }; 4507 4508 Selection.prototype.contains = function (pos, end) { 4509 var this$1 = this; 4510 4511 if (!end) { end = pos } 4512 for (var i = 0; i < this.ranges.length; i++) { 4513 var range = this$1.ranges[i] 4514 if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) 4515 { return i } 4516 } 4517 return -1 4518 }; 4519 4520 var Range = function(anchor, head) { 4521 this.anchor = anchor; this.head = head 4522 }; 4523 4524 Range.prototype.from = function () { return minPos(this.anchor, this.head) }; 4525 Range.prototype.to = function () { return maxPos(this.anchor, this.head) }; 4526 Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch }; 4527 4528 // Take an unsorted, potentially overlapping set of ranges, and 4529 // build a selection out of it. 'Consumes' ranges array (modifying 4530 // it). 4531 function normalizeSelection(ranges, primIndex) { 4532 var prim = ranges[primIndex] 4533 ranges.sort(function (a, b) { return cmp(a.from(), b.from()); }) 4534 primIndex = indexOf(ranges, prim) 4535 for (var i = 1; i < ranges.length; i++) { 4536 var cur = ranges[i], prev = ranges[i - 1] 4537 if (cmp(prev.to(), cur.from()) >= 0) { 4538 var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()) 4539 var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head 4540 if (i <= primIndex) { --primIndex } 4541 ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)) 4542 } 4543 } 4544 return new Selection(ranges, primIndex) 4545 } 4546 4547 function simpleSelection(anchor, head) { 4548 return new Selection([new Range(anchor, head || anchor)], 0) 4549 } 4550 4551 // Compute the position of the end of a change (its 'to' property 4552 // refers to the pre-change end). 4553 function changeEnd(change) { 4554 if (!change.text) { return change.to } 4555 return Pos(change.from.line + change.text.length - 1, 4556 lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) 4557 } 4558 4559 // Adjust a position to refer to the post-change position of the 4560 // same text, or the end of the change if the change covers it. 4561 function adjustForChange(pos, change) { 4562 if (cmp(pos, change.from) < 0) { return pos } 4563 if (cmp(pos, change.to) <= 0) { return changeEnd(change) } 4564 4565 var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch 4566 if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch } 4567 return Pos(line, ch) 4568 } 4569 4570 function computeSelAfterChange(doc, change) { 4571 var out = [] 4572 for (var i = 0; i < doc.sel.ranges.length; i++) { 4573 var range = doc.sel.ranges[i] 4574 out.push(new Range(adjustForChange(range.anchor, change), 4575 adjustForChange(range.head, change))) 4576 } 4577 return normalizeSelection(out, doc.sel.primIndex) 4578 } 4579 4580 function offsetPos(pos, old, nw) { 4581 if (pos.line == old.line) 4582 { return Pos(nw.line, pos.ch - old.ch + nw.ch) } 4583 else 4584 { return Pos(nw.line + (pos.line - old.line), pos.ch) } 4585 } 4586 4587 // Used by replaceSelections to allow moving the selection to the 4588 // start or around the replaced test. Hint may be "start" or "around". 4589 function computeReplacedSel(doc, changes, hint) { 4590 var out = [] 4591 var oldPrev = Pos(doc.first, 0), newPrev = oldPrev 4592 for (var i = 0; i < changes.length; i++) { 4593 var change = changes[i] 4594 var from = offsetPos(change.from, oldPrev, newPrev) 4595 var to = offsetPos(changeEnd(change), oldPrev, newPrev) 4596 oldPrev = change.to 4597 newPrev = to 4598 if (hint == "around") { 4599 var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0 4600 out[i] = new Range(inv ? to : from, inv ? from : to) 4601 } else { 4602 out[i] = new Range(from, from) 4603 } 4604 } 4605 return new Selection(out, doc.sel.primIndex) 4606 } 4607 4608 // Used to get the editor into a consistent state again when options change. 4609 4610 function loadMode(cm) { 4611 cm.doc.mode = getMode(cm.options, cm.doc.modeOption) 4612 resetModeState(cm) 4613 } 4614 4615 function resetModeState(cm) { 4616 cm.doc.iter(function (line) { 4617 if (line.stateAfter) { line.stateAfter = null } 4618 if (line.styles) { line.styles = null } 4619 }) 4620 cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first 4621 startWorker(cm, 100) 4622 cm.state.modeGen++ 4623 if (cm.curOp) { regChange(cm) } 4624 } 4625 4626 // DOCUMENT DATA STRUCTURE 4627 4628 // By default, updates that start and end at the beginning of a line 4629 // are treated specially, in order to make the association of line 4630 // widgets and marker elements with the text behave more intuitive. 4631 function isWholeLineUpdate(doc, change) { 4632 return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && 4633 (!doc.cm || doc.cm.options.wholeLineUpdateBefore) 4634 } 4635 4636 // Perform a change on the document data structure. 4637 function updateDoc(doc, change, markedSpans, estimateHeight) { 4638 function spansFor(n) {return markedSpans ? markedSpans[n] : null} 4639 function update(line, text, spans) { 4640 updateLine(line, text, spans, estimateHeight) 4641 signalLater(line, "change", line, change) 4642 } 4643 function linesFor(start, end) { 4644 var result = [] 4645 for (var i = start; i < end; ++i) 4646 { result.push(new Line(text[i], spansFor(i), estimateHeight)) } 4647 return result 4648 } 4649 4650 var from = change.from, to = change.to, text = change.text 4651 var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line) 4652 var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line 4653 4654 // Adjust the line structure 4655 if (change.full) { 4656 doc.insert(0, linesFor(0, text.length)) 4657 doc.remove(text.length, doc.size - text.length) 4658 } else if (isWholeLineUpdate(doc, change)) { 4659 // This is a whole-line replace. Treated specially to make 4660 // sure line objects move the way they are supposed to. 4661 var added = linesFor(0, text.length - 1) 4662 update(lastLine, lastLine.text, lastSpans) 4663 if (nlines) { doc.remove(from.line, nlines) } 4664 if (added.length) { doc.insert(from.line, added) } 4665 } else if (firstLine == lastLine) { 4666 if (text.length == 1) { 4667 update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans) 4668 } else { 4669 var added$1 = linesFor(1, text.length - 1) 4670 added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)) 4671 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) 4672 doc.insert(from.line + 1, added$1) 4673 } 4674 } else if (text.length == 1) { 4675 update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)) 4676 doc.remove(from.line + 1, nlines) 4677 } else { 4678 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) 4679 update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans) 4680 var added$2 = linesFor(1, text.length - 1) 4681 if (nlines > 1) { doc.remove(from.line + 1, nlines - 1) } 4682 doc.insert(from.line + 1, added$2) 4683 } 4684 4685 signalLater(doc, "change", doc, change) 4686 } 4687 4688 // Call f for all linked documents. 4689 function linkedDocs(doc, f, sharedHistOnly) { 4690 function propagate(doc, skip, sharedHist) { 4691 if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) { 4692 var rel = doc.linked[i] 4693 if (rel.doc == skip) { continue } 4694 var shared = sharedHist && rel.sharedHist 4695 if (sharedHistOnly && !shared) { continue } 4696 f(rel.doc, shared) 4697 propagate(rel.doc, doc, shared) 4698 } } 4699 } 4700 propagate(doc, null, true) 4701 } 4702 4703 // Attach a document to an editor. 4704 function attachDoc(cm, doc) { 4705 if (doc.cm) { throw new Error("This document is already in use.") } 4706 cm.doc = doc 4707 doc.cm = cm 4708 estimateLineHeights(cm) 4709 loadMode(cm) 4710 setDirectionClass(cm) 4711 if (!cm.options.lineWrapping) { findMaxLine(cm) } 4712 cm.options.mode = doc.modeOption 4713 regChange(cm) 4714 } 4715 4716 function setDirectionClass(cm) { 4717 ;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl") 4718 } 4719 4720 function directionChanged(cm) { 4721 runInOp(cm, function () { 4722 setDirectionClass(cm) 4723 regChange(cm) 4724 }) 4725 } 4726 4727 function History(startGen) { 4728 // Arrays of change events and selections. Doing something adds an 4729 // event to done and clears undo. Undoing moves events from done 4730 // to undone, redoing moves them in the other direction. 4731 this.done = []; this.undone = [] 4732 this.undoDepth = Infinity 4733 // Used to track when changes can be merged into a single undo 4734 // event 4735 this.lastModTime = this.lastSelTime = 0 4736 this.lastOp = this.lastSelOp = null 4737 this.lastOrigin = this.lastSelOrigin = null 4738 // Used by the isClean() method 4739 this.generation = this.maxGeneration = startGen || 1 4740 } 4741 4742 // Create a history change event from an updateDoc-style change 4743 // object. 4744 function historyChangeFromChange(doc, change) { 4745 var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)} 4746 attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1) 4747 linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true) 4748 return histChange 4749 } 4750 4751 // Pop all selection events off the end of a history array. Stop at 4752 // a change event. 4753 function clearSelectionEvents(array) { 4754 while (array.length) { 4755 var last = lst(array) 4756 if (last.ranges) { array.pop() } 4757 else { break } 4758 } 4759 } 4760 4761 // Find the top change event in the history. Pop off selection 4762 // events that are in the way. 4763 function lastChangeEvent(hist, force) { 4764 if (force) { 4765 clearSelectionEvents(hist.done) 4766 return lst(hist.done) 4767 } else if (hist.done.length && !lst(hist.done).ranges) { 4768 return lst(hist.done) 4769 } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { 4770 hist.done.pop() 4771 return lst(hist.done) 4772 } 4773 } 4774 4775 // Register a change in the history. Merges changes that are within 4776 // a single operation, or are close together with an origin that 4777 // allows merging (starting with "+") into a single event. 4778 function addChangeToHistory(doc, change, selAfter, opId) { 4779 var hist = doc.history 4780 hist.undone.length = 0 4781 var time = +new Date, cur 4782 var last 4783 4784 if ((hist.lastOp == opId || 4785 hist.lastOrigin == change.origin && change.origin && 4786 ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || 4787 change.origin.charAt(0) == "*")) && 4788 (cur = lastChangeEvent(hist, hist.lastOp == opId))) { 4789 // Merge this change into the last event 4790 last = lst(cur.changes) 4791 if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { 4792 // Optimized case for simple insertion -- don't want to add 4793 // new changesets for every character typed 4794 last.to = changeEnd(change) 4795 } else { 4796 // Add new sub-event 4797 cur.changes.push(historyChangeFromChange(doc, change)) 4798 } 4799 } else { 4800 // Can not be merged, start a new event. 4801 var before = lst(hist.done) 4802 if (!before || !before.ranges) 4803 { pushSelectionToHistory(doc.sel, hist.done) } 4804 cur = {changes: [historyChangeFromChange(doc, change)], 4805 generation: hist.generation} 4806 hist.done.push(cur) 4807 while (hist.done.length > hist.undoDepth) { 4808 hist.done.shift() 4809 if (!hist.done[0].ranges) { hist.done.shift() } 4810 } 4811 } 4812 hist.done.push(selAfter) 4813 hist.generation = ++hist.maxGeneration 4814 hist.lastModTime = hist.lastSelTime = time 4815 hist.lastOp = hist.lastSelOp = opId 4816 hist.lastOrigin = hist.lastSelOrigin = change.origin 4817 4818 if (!last) { signal(doc, "historyAdded") } 4819 } 4820 4821 function selectionEventCanBeMerged(doc, origin, prev, sel) { 4822 var ch = origin.charAt(0) 4823 return ch == "*" || 4824 ch == "+" && 4825 prev.ranges.length == sel.ranges.length && 4826 prev.somethingSelected() == sel.somethingSelected() && 4827 new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) 4828 } 4829 4830 // Called whenever the selection changes, sets the new selection as 4831 // the pending selection in the history, and pushes the old pending 4832 // selection into the 'done' array when it was significantly 4833 // different (in number of selected ranges, emptiness, or time). 4834 function addSelectionToHistory(doc, sel, opId, options) { 4835 var hist = doc.history, origin = options && options.origin 4836 4837 // A new event is started when the previous origin does not match 4838 // the current, or the origins don't allow matching. Origins 4839 // starting with * are always merged, those starting with + are 4840 // merged when similar and close together in time. 4841 if (opId == hist.lastSelOp || 4842 (origin && hist.lastSelOrigin == origin && 4843 (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || 4844 selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) 4845 { hist.done[hist.done.length - 1] = sel } 4846 else 4847 { pushSelectionToHistory(sel, hist.done) } 4848 4849 hist.lastSelTime = +new Date 4850 hist.lastSelOrigin = origin 4851 hist.lastSelOp = opId 4852 if (options && options.clearRedo !== false) 4853 { clearSelectionEvents(hist.undone) } 4854 } 4855 4856 function pushSelectionToHistory(sel, dest) { 4857 var top = lst(dest) 4858 if (!(top && top.ranges && top.equals(sel))) 4859 { dest.push(sel) } 4860 } 4861 4862 // Used to store marked span information in the history. 4863 function attachLocalSpans(doc, change, from, to) { 4864 var existing = change["spans_" + doc.id], n = 0 4865 doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) { 4866 if (line.markedSpans) 4867 { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans } 4868 ++n 4869 }) 4870 } 4871 4872 // When un/re-doing restores text containing marked spans, those 4873 // that have been explicitly cleared should not be restored. 4874 function removeClearedSpans(spans) { 4875 if (!spans) { return null } 4876 var out 4877 for (var i = 0; i < spans.length; ++i) { 4878 if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i) } } 4879 else if (out) { out.push(spans[i]) } 4880 } 4881 return !out ? spans : out.length ? out : null 4882 } 4883 4884 // Retrieve and filter the old marked spans stored in a change event. 4885 function getOldSpans(doc, change) { 4886 var found = change["spans_" + doc.id] 4887 if (!found) { return null } 4888 var nw = [] 4889 for (var i = 0; i < change.text.length; ++i) 4890 { nw.push(removeClearedSpans(found[i])) } 4891 return nw 4892 } 4893 4894 // Used for un/re-doing changes from the history. Combines the 4895 // result of computing the existing spans with the set of spans that 4896 // existed in the history (so that deleting around a span and then 4897 // undoing brings back the span). 4898 function mergeOldSpans(doc, change) { 4899 var old = getOldSpans(doc, change) 4900 var stretched = stretchSpansOverChange(doc, change) 4901 if (!old) { return stretched } 4902 if (!stretched) { return old } 4903 4904 for (var i = 0; i < old.length; ++i) { 4905 var oldCur = old[i], stretchCur = stretched[i] 4906 if (oldCur && stretchCur) { 4907 spans: for (var j = 0; j < stretchCur.length; ++j) { 4908 var span = stretchCur[j] 4909 for (var k = 0; k < oldCur.length; ++k) 4910 { if (oldCur[k].marker == span.marker) { continue spans } } 4911 oldCur.push(span) 4912 } 4913 } else if (stretchCur) { 4914 old[i] = stretchCur 4915 } 4916 } 4917 return old 4918 } 4919 4920 // Used both to provide a JSON-safe object in .getHistory, and, when 4921 // detaching a document, to split the history in two 4922 function copyHistoryArray(events, newGroup, instantiateSel) { 4923 var copy = [] 4924 for (var i = 0; i < events.length; ++i) { 4925 var event = events[i] 4926 if (event.ranges) { 4927 copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event) 4928 continue 4929 } 4930 var changes = event.changes, newChanges = [] 4931 copy.push({changes: newChanges}) 4932 for (var j = 0; j < changes.length; ++j) { 4933 var change = changes[j], m = (void 0) 4934 newChanges.push({from: change.from, to: change.to, text: change.text}) 4935 if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) { 4936 if (indexOf(newGroup, Number(m[1])) > -1) { 4937 lst(newChanges)[prop] = change[prop] 4938 delete change[prop] 4939 } 4940 } } } 4941 } 4942 } 4943 return copy 4944 } 4945 4946 // The 'scroll' parameter given to many of these indicated whether 4947 // the new cursor position should be scrolled into view after 4948 // modifying the selection. 4949 4950 // If shift is held or the extend flag is set, extends a range to 4951 // include a given position (and optionally a second position). 4952 // Otherwise, simply returns the range between the given positions. 4953 // Used for cursor motion and such. 4954 function extendRange(range, head, other, extend) { 4955 if (extend) { 4956 var anchor = range.anchor 4957 if (other) { 4958 var posBefore = cmp(head, anchor) < 0 4959 if (posBefore != (cmp(other, anchor) < 0)) { 4960 anchor = head 4961 head = other 4962 } else if (posBefore != (cmp(head, other) < 0)) { 4963 head = other 4964 } 4965 } 4966 return new Range(anchor, head) 4967 } else { 4968 return new Range(other || head, head) 4969 } 4970 } 4971 4972 // Extend the primary selection range, discard the rest. 4973 function extendSelection(doc, head, other, options, extend) { 4974 if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend) } 4975 setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options) 4976 } 4977 4978 // Extend all selections (pos is an array of selections with length 4979 // equal the number of selections) 4980 function extendSelections(doc, heads, options) { 4981 var out = [] 4982 var extend = doc.cm && (doc.cm.display.shift || doc.extend) 4983 for (var i = 0; i < doc.sel.ranges.length; i++) 4984 { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend) } 4985 var newSel = normalizeSelection(out, doc.sel.primIndex) 4986 setSelection(doc, newSel, options) 4987 } 4988 4989 // Updates a single range in the selection. 4990 function replaceOneSelection(doc, i, range, options) { 4991 var ranges = doc.sel.ranges.slice(0) 4992 ranges[i] = range 4993 setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options) 4994 } 4995 4996 // Reset the selection to a single range. 4997 function setSimpleSelection(doc, anchor, head, options) { 4998 setSelection(doc, simpleSelection(anchor, head), options) 4999 } 5000 5001 // Give beforeSelectionChange handlers a change to influence a 5002 // selection update. 5003 function filterSelectionChange(doc, sel, options) { 5004 var obj = { 5005 ranges: sel.ranges, 5006 update: function(ranges) { 5007 var this$1 = this; 5008 5009 this.ranges = [] 5010 for (var i = 0; i < ranges.length; i++) 5011 { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), 5012 clipPos(doc, ranges[i].head)) } 5013 }, 5014 origin: options && options.origin 5015 } 5016 signal(doc, "beforeSelectionChange", doc, obj) 5017 if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj) } 5018 if (obj.ranges != sel.ranges) { return normalizeSelection(obj.ranges, obj.ranges.length - 1) } 5019 else { return sel } 5020 } 5021 5022 function setSelectionReplaceHistory(doc, sel, options) { 5023 var done = doc.history.done, last = lst(done) 5024 if (last && last.ranges) { 5025 done[done.length - 1] = sel 5026 setSelectionNoUndo(doc, sel, options) 5027 } else { 5028 setSelection(doc, sel, options) 5029 } 5030 } 5031 5032 // Set a new selection. 5033 function setSelection(doc, sel, options) { 5034 setSelectionNoUndo(doc, sel, options) 5035 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options) 5036 } 5037 5038 function setSelectionNoUndo(doc, sel, options) { 5039 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) 5040 { sel = filterSelectionChange(doc, sel, options) } 5041 5042 var bias = options && options.bias || 5043 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1) 5044 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)) 5045 5046 if (!(options && options.scroll === false) && doc.cm) 5047 { ensureCursorVisible(doc.cm) } 5048 } 5049 5050 function setSelectionInner(doc, sel) { 5051 if (sel.equals(doc.sel)) { return } 5052 5053 doc.sel = sel 5054 5055 if (doc.cm) { 5056 doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true 5057 signalCursorActivity(doc.cm) 5058 } 5059 signalLater(doc, "cursorActivity", doc) 5060 } 5061 5062 // Verify that the selection does not partially select any atomic 5063 // marked ranges. 5064 function reCheckSelection(doc) { 5065 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)) 5066 } 5067 5068 // Return a selection that does not partially select any atomic 5069 // ranges. 5070 function skipAtomicInSelection(doc, sel, bias, mayClear) { 5071 var out 5072 for (var i = 0; i < sel.ranges.length; i++) { 5073 var range = sel.ranges[i] 5074 var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i] 5075 var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear) 5076 var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear) 5077 if (out || newAnchor != range.anchor || newHead != range.head) { 5078 if (!out) { out = sel.ranges.slice(0, i) } 5079 out[i] = new Range(newAnchor, newHead) 5080 } 5081 } 5082 return out ? normalizeSelection(out, sel.primIndex) : sel 5083 } 5084 5085 function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { 5086 var line = getLine(doc, pos.line) 5087 if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { 5088 var sp = line.markedSpans[i], m = sp.marker 5089 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && 5090 (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { 5091 if (mayClear) { 5092 signal(m, "beforeCursorEnter") 5093 if (m.explicitlyCleared) { 5094 if (!line.markedSpans) { break } 5095 else {--i; continue} 5096 } 5097 } 5098 if (!m.atomic) { continue } 5099 5100 if (oldPos) { 5101 var near = m.find(dir < 0 ? 1 : -1), diff = (void 0) 5102 if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) 5103 { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null) } 5104 if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) 5105 { return skipAtomicInner(doc, near, pos, dir, mayClear) } 5106 } 5107 5108 var far = m.find(dir < 0 ? -1 : 1) 5109 if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) 5110 { far = movePos(doc, far, dir, far.line == pos.line ? line : null) } 5111 return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null 5112 } 5113 } } 5114 return pos 5115 } 5116 5117 // Ensure a given position is not inside an atomic range. 5118 function skipAtomic(doc, pos, oldPos, bias, mayClear) { 5119 var dir = bias || 1 5120 var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || 5121 (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || 5122 skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || 5123 (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)) 5124 if (!found) { 5125 doc.cantEdit = true 5126 return Pos(doc.first, 0) 5127 } 5128 return found 5129 } 5130 5131 function movePos(doc, pos, dir, line) { 5132 if (dir < 0 && pos.ch == 0) { 5133 if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) } 5134 else { return null } 5135 } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { 5136 if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) } 5137 else { return null } 5138 } else { 5139 return new Pos(pos.line, pos.ch + dir) 5140 } 5141 } 5142 5143 function selectAll(cm) { 5144 cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll) 5145 } 5146 5147 // UPDATING 5148 5149 // Allow "beforeChange" event handlers to influence a change 5150 function filterChange(doc, change, update) { 5151 var obj = { 5152 canceled: false, 5153 from: change.from, 5154 to: change.to, 5155 text: change.text, 5156 origin: change.origin, 5157 cancel: function () { return obj.canceled = true; } 5158 } 5159 if (update) { obj.update = function (from, to, text, origin) { 5160 if (from) { obj.from = clipPos(doc, from) } 5161 if (to) { obj.to = clipPos(doc, to) } 5162 if (text) { obj.text = text } 5163 if (origin !== undefined) { obj.origin = origin } 5164 } } 5165 signal(doc, "beforeChange", doc, obj) 5166 if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj) } 5167 5168 if (obj.canceled) { return null } 5169 return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} 5170 } 5171 5172 // Apply a change to a document, and add it to the document's 5173 // history, and propagating it to all linked documents. 5174 function makeChange(doc, change, ignoreReadOnly) { 5175 if (doc.cm) { 5176 if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) } 5177 if (doc.cm.state.suppressEdits) { return } 5178 } 5179 5180 if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { 5181 change = filterChange(doc, change, true) 5182 if (!change) { return } 5183 } 5184 5185 // Possibly split or suppress the update based on the presence 5186 // of read-only spans in its range. 5187 var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to) 5188 if (split) { 5189 for (var i = split.length - 1; i >= 0; --i) 5190 { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}) } 5191 } else { 5192 makeChangeInner(doc, change) 5193 } 5194 } 5195 5196 function makeChangeInner(doc, change) { 5197 if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return } 5198 var selAfter = computeSelAfterChange(doc, change) 5199 addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN) 5200 5201 makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)) 5202 var rebased = [] 5203 5204 linkedDocs(doc, function (doc, sharedHist) { 5205 if (!sharedHist && indexOf(rebased, doc.history) == -1) { 5206 rebaseHist(doc.history, change) 5207 rebased.push(doc.history) 5208 } 5209 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)) 5210 }) 5211 } 5212 5213 // Revert a change stored in a document's history. 5214 function makeChangeFromHistory(doc, type, allowSelectionOnly) { 5215 if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) { return } 5216 5217 var hist = doc.history, event, selAfter = doc.sel 5218 var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done 5219 5220 // Verify that there is a useable event (so that ctrl-z won't 5221 // needlessly clear selection events) 5222 var i = 0 5223 for (; i < source.length; i++) { 5224 event = source[i] 5225 if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) 5226 { break } 5227 } 5228 if (i == source.length) { return } 5229 hist.lastOrigin = hist.lastSelOrigin = null 5230 5231 for (;;) { 5232 event = source.pop() 5233 if (event.ranges) { 5234 pushSelectionToHistory(event, dest) 5235 if (allowSelectionOnly && !event.equals(doc.sel)) { 5236 setSelection(doc, event, {clearRedo: false}) 5237 return 5238 } 5239 selAfter = event 5240 } 5241 else { break } 5242 } 5243 5244 // Build up a reverse change object to add to the opposite history 5245 // stack (redo when undoing, and vice versa). 5246 var antiChanges = [] 5247 pushSelectionToHistory(selAfter, dest) 5248 dest.push({changes: antiChanges, generation: hist.generation}) 5249 hist.generation = event.generation || ++hist.maxGeneration 5250 5251 var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange") 5252 5253 var loop = function ( i ) { 5254 var change = event.changes[i] 5255 change.origin = type 5256 if (filter && !filterChange(doc, change, false)) { 5257 source.length = 0 5258 return {} 5259 } 5260 5261 antiChanges.push(historyChangeFromChange(doc, change)) 5262 5263 var after = i ? computeSelAfterChange(doc, change) : lst(source) 5264 makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)) 5265 if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}) } 5266 var rebased = [] 5267 5268 // Propagate to the linked documents 5269 linkedDocs(doc, function (doc, sharedHist) { 5270 if (!sharedHist && indexOf(rebased, doc.history) == -1) { 5271 rebaseHist(doc.history, change) 5272 rebased.push(doc.history) 5273 } 5274 makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)) 5275 }) 5276 }; 5277 5278 for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { 5279 var returned = loop( i$1 ); 5280 5281 if ( returned ) return returned.v; 5282 } 5283 } 5284 5285 // Sub-views need their line numbers shifted when text is added 5286 // above or below them in the parent document. 5287 function shiftDoc(doc, distance) { 5288 if (distance == 0) { return } 5289 doc.first += distance 5290 doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range( 5291 Pos(range.anchor.line + distance, range.anchor.ch), 5292 Pos(range.head.line + distance, range.head.ch) 5293 ); }), doc.sel.primIndex) 5294 if (doc.cm) { 5295 regChange(doc.cm, doc.first, doc.first - distance, distance) 5296 for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) 5297 { regLineChange(doc.cm, l, "gutter") } 5298 } 5299 } 5300 5301 // More lower-level change function, handling only a single document 5302 // (not linked ones). 5303 function makeChangeSingleDoc(doc, change, selAfter, spans) { 5304 if (doc.cm && !doc.cm.curOp) 5305 { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) } 5306 5307 if (change.to.line < doc.first) { 5308 shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)) 5309 return 5310 } 5311 if (change.from.line > doc.lastLine()) { return } 5312 5313 // Clip the change to the size of this doc 5314 if (change.from.line < doc.first) { 5315 var shift = change.text.length - 1 - (doc.first - change.from.line) 5316 shiftDoc(doc, shift) 5317 change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), 5318 text: [lst(change.text)], origin: change.origin} 5319 } 5320 var last = doc.lastLine() 5321 if (change.to.line > last) { 5322 change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), 5323 text: [change.text[0]], origin: change.origin} 5324 } 5325 5326 change.removed = getBetween(doc, change.from, change.to) 5327 5328 if (!selAfter) { selAfter = computeSelAfterChange(doc, change) } 5329 if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans) } 5330 else { updateDoc(doc, change, spans) } 5331 setSelectionNoUndo(doc, selAfter, sel_dontScroll) 5332 } 5333 5334 // Handle the interaction of a change to a document with the editor 5335 // that this document is part of. 5336 function makeChangeSingleDocInEditor(cm, change, spans) { 5337 var doc = cm.doc, display = cm.display, from = change.from, to = change.to 5338 5339 var recomputeMaxLength = false, checkWidthStart = from.line 5340 if (!cm.options.lineWrapping) { 5341 checkWidthStart = lineNo(visualLine(getLine(doc, from.line))) 5342 doc.iter(checkWidthStart, to.line + 1, function (line) { 5343 if (line == display.maxLine) { 5344 recomputeMaxLength = true 5345 return true 5346 } 5347 }) 5348 } 5349 5350 if (doc.sel.contains(change.from, change.to) > -1) 5351 { signalCursorActivity(cm) } 5352 5353 updateDoc(doc, change, spans, estimateHeight(cm)) 5354 5355 if (!cm.options.lineWrapping) { 5356 doc.iter(checkWidthStart, from.line + change.text.length, function (line) { 5357 var len = lineLength(line) 5358 if (len > display.maxLineLength) { 5359 display.maxLine = line 5360 display.maxLineLength = len 5361 display.maxLineChanged = true 5362 recomputeMaxLength = false 5363 } 5364 }) 5365 if (recomputeMaxLength) { cm.curOp.updateMaxLine = true } 5366 } 5367 5368 retreatFrontier(doc, from.line) 5369 startWorker(cm, 400) 5370 5371 var lendiff = change.text.length - (to.line - from.line) - 1 5372 // Remember that these lines changed, for updating the display 5373 if (change.full) 5374 { regChange(cm) } 5375 else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) 5376 { regLineChange(cm, from.line, "text") } 5377 else 5378 { regChange(cm, from.line, to.line + 1, lendiff) } 5379 5380 var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change") 5381 if (changeHandler || changesHandler) { 5382 var obj = { 5383 from: from, to: to, 5384 text: change.text, 5385 removed: change.removed, 5386 origin: change.origin 5387 } 5388 if (changeHandler) { signalLater(cm, "change", cm, obj) } 5389 if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj) } 5390 } 5391 cm.display.selForContextMenu = null 5392 } 5393 5394 function replaceRange(doc, code, from, to, origin) { 5395 if (!to) { to = from } 5396 if (cmp(to, from) < 0) { var assign; 5397 (assign = [to, from], from = assign[0], to = assign[1], assign) } 5398 if (typeof code == "string") { code = doc.splitLines(code) } 5399 makeChange(doc, {from: from, to: to, text: code, origin: origin}) 5400 } 5401 5402 // Rebasing/resetting history to deal with externally-sourced changes 5403 5404 function rebaseHistSelSingle(pos, from, to, diff) { 5405 if (to < pos.line) { 5406 pos.line += diff 5407 } else if (from < pos.line) { 5408 pos.line = from 5409 pos.ch = 0 5410 } 5411 } 5412 5413 // Tries to rebase an array of history events given a change in the 5414 // document. If the change touches the same lines as the event, the 5415 // event, and everything 'behind' it, is discarded. If the change is 5416 // before the event, the event's positions are updated. Uses a 5417 // copy-on-write scheme for the positions, to avoid having to 5418 // reallocate them all on every rebase, but also avoid problems with 5419 // shared position objects being unsafely updated. 5420 function rebaseHistArray(array, from, to, diff) { 5421 for (var i = 0; i < array.length; ++i) { 5422 var sub = array[i], ok = true 5423 if (sub.ranges) { 5424 if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true } 5425 for (var j = 0; j < sub.ranges.length; j++) { 5426 rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff) 5427 rebaseHistSelSingle(sub.ranges[j].head, from, to, diff) 5428 } 5429 continue 5430 } 5431 for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { 5432 var cur = sub.changes[j$1] 5433 if (to < cur.from.line) { 5434 cur.from = Pos(cur.from.line + diff, cur.from.ch) 5435 cur.to = Pos(cur.to.line + diff, cur.to.ch) 5436 } else if (from <= cur.to.line) { 5437 ok = false 5438 break 5439 } 5440 } 5441 if (!ok) { 5442 array.splice(0, i + 1) 5443 i = 0 5444 } 5445 } 5446 } 5447 5448 function rebaseHist(hist, change) { 5449 var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1 5450 rebaseHistArray(hist.done, from, to, diff) 5451 rebaseHistArray(hist.undone, from, to, diff) 5452 } 5453 5454 // Utility for applying a change to a line by handle or number, 5455 // returning the number and optionally registering the line as 5456 // changed. 5457 function changeLine(doc, handle, changeType, op) { 5458 var no = handle, line = handle 5459 if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)) } 5460 else { no = lineNo(handle) } 5461 if (no == null) { return null } 5462 if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType) } 5463 return line 5464 } 5465 5466 // The document is represented as a BTree consisting of leaves, with 5467 // chunk of lines in them, and branches, with up to ten leaves or 5468 // other branch nodes below them. The top node is always a branch 5469 // node, and is the document object itself (meaning it has 5470 // additional methods and properties). 5471 // 5472 // All nodes have parent links. The tree is used both to go from 5473 // line numbers to line objects, and to go from objects to numbers. 5474 // It also indexes by height, and is used to convert between height 5475 // and line object, and to find the total height of the document. 5476 // 5477 // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html 5478 5479 function LeafChunk(lines) { 5480 var this$1 = this; 5481 5482 this.lines = lines 5483 this.parent = null 5484 var height = 0 5485 for (var i = 0; i < lines.length; ++i) { 5486 lines[i].parent = this$1 5487 height += lines[i].height 5488 } 5489 this.height = height 5490 } 5491 5492 LeafChunk.prototype = { 5493 chunkSize: function chunkSize() { return this.lines.length }, 5494 5495 // Remove the n lines at offset 'at'. 5496 removeInner: function removeInner(at, n) { 5497 var this$1 = this; 5498 5499 for (var i = at, e = at + n; i < e; ++i) { 5500 var line = this$1.lines[i] 5501 this$1.height -= line.height 5502 cleanUpLine(line) 5503 signalLater(line, "delete") 5504 } 5505 this.lines.splice(at, n) 5506 }, 5507 5508 // Helper used to collapse a small branch into a single leaf. 5509 collapse: function collapse(lines) { 5510 lines.push.apply(lines, this.lines) 5511 }, 5512 5513 // Insert the given array of lines at offset 'at', count them as 5514 // having the given height. 5515 insertInner: function insertInner(at, lines, height) { 5516 var this$1 = this; 5517 5518 this.height += height 5519 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)) 5520 for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1 } 5521 }, 5522 5523 // Used to iterate over a part of the tree. 5524 iterN: function iterN(at, n, op) { 5525 var this$1 = this; 5526 5527 for (var e = at + n; at < e; ++at) 5528 { if (op(this$1.lines[at])) { return true } } 5529 } 5530 } 5531 5532 function BranchChunk(children) { 5533 var this$1 = this; 5534 5535 this.children = children 5536 var size = 0, height = 0 5537 for (var i = 0; i < children.length; ++i) { 5538 var ch = children[i] 5539 size += ch.chunkSize(); height += ch.height 5540 ch.parent = this$1 5541 } 5542 this.size = size 5543 this.height = height 5544 this.parent = null 5545 } 5546 5547 BranchChunk.prototype = { 5548 chunkSize: function chunkSize() { return this.size }, 5549 5550 removeInner: function removeInner(at, n) { 5551 var this$1 = this; 5552 5553 this.size -= n 5554 for (var i = 0; i < this.children.length; ++i) { 5555 var child = this$1.children[i], sz = child.chunkSize() 5556 if (at < sz) { 5557 var rm = Math.min(n, sz - at), oldHeight = child.height 5558 child.removeInner(at, rm) 5559 this$1.height -= oldHeight - child.height 5560 if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null } 5561 if ((n -= rm) == 0) { break } 5562 at = 0 5563 } else { at -= sz } 5564 } 5565 // If the result is smaller than 25 lines, ensure that it is a 5566 // single leaf node. 5567 if (this.size - n < 25 && 5568 (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { 5569 var lines = [] 5570 this.collapse(lines) 5571 this.children = [new LeafChunk(lines)] 5572 this.children[0].parent = this 5573 } 5574 }, 5575 5576 collapse: function collapse(lines) { 5577 var this$1 = this; 5578 5579 for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines) } 5580 }, 5581 5582 insertInner: function insertInner(at, lines, height) { 5583 var this$1 = this; 5584 5585 this.size += lines.length 5586 this.height += height 5587 for (var i = 0; i < this.children.length; ++i) { 5588 var child = this$1.children[i], sz = child.chunkSize() 5589 if (at <= sz) { 5590 child.insertInner(at, lines, height) 5591 if (child.lines && child.lines.length > 50) { 5592 // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. 5593 // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. 5594 var remaining = child.lines.length % 25 + 25 5595 for (var pos = remaining; pos < child.lines.length;) { 5596 var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) 5597 child.height -= leaf.height 5598 this$1.children.splice(++i, 0, leaf) 5599 leaf.parent = this$1 5600 } 5601 child.lines = child.lines.slice(0, remaining) 5602 this$1.maybeSpill() 5603 } 5604 break 5605 } 5606 at -= sz 5607 } 5608 }, 5609 5610 // When a node has grown, check whether it should be split. 5611 maybeSpill: function maybeSpill() { 5612 if (this.children.length <= 10) { return } 5613 var me = this 5614 do { 5615 var spilled = me.children.splice(me.children.length - 5, 5) 5616 var sibling = new BranchChunk(spilled) 5617 if (!me.parent) { // Become the parent node 5618 var copy = new BranchChunk(me.children) 5619 copy.parent = me 5620 me.children = [copy, sibling] 5621 me = copy 5622 } else { 5623 me.size -= sibling.size 5624 me.height -= sibling.height 5625 var myIndex = indexOf(me.parent.children, me) 5626 me.parent.children.splice(myIndex + 1, 0, sibling) 5627 } 5628 sibling.parent = me.parent 5629 } while (me.children.length > 10) 5630 me.parent.maybeSpill() 5631 }, 5632 5633 iterN: function iterN(at, n, op) { 5634 var this$1 = this; 5635 5636 for (var i = 0; i < this.children.length; ++i) { 5637 var child = this$1.children[i], sz = child.chunkSize() 5638 if (at < sz) { 5639 var used = Math.min(n, sz - at) 5640 if (child.iterN(at, used, op)) { return true } 5641 if ((n -= used) == 0) { break } 5642 at = 0 5643 } else { at -= sz } 5644 } 5645 } 5646 } 5647 5648 // Line widgets are block elements displayed above or below a line. 5649 5650 var LineWidget = function(doc, node, options) { 5651 var this$1 = this; 5652 5653 if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) 5654 { this$1[opt] = options[opt] } } } 5655 this.doc = doc 5656 this.node = node 5657 }; 5658 5659 LineWidget.prototype.clear = function () { 5660 var this$1 = this; 5661 5662 var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line) 5663 if (no == null || !ws) { return } 5664 for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1) } } 5665 if (!ws.length) { line.widgets = null } 5666 var height = widgetHeight(this) 5667 updateLineHeight(line, Math.max(0, line.height - height)) 5668 if (cm) { 5669 runInOp(cm, function () { 5670 adjustScrollWhenAboveVisible(cm, line, -height) 5671 regLineChange(cm, no, "widget") 5672 }) 5673 signalLater(cm, "lineWidgetCleared", cm, this, no) 5674 } 5675 }; 5676 5677 LineWidget.prototype.changed = function () { 5678 var this$1 = this; 5679 5680 var oldH = this.height, cm = this.doc.cm, line = this.line 5681 this.height = null 5682 var diff = widgetHeight(this) - oldH 5683 if (!diff) { return } 5684 updateLineHeight(line, line.height + diff) 5685 if (cm) { 5686 runInOp(cm, function () { 5687 cm.curOp.forceUpdate = true 5688 adjustScrollWhenAboveVisible(cm, line, diff) 5689 signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line)) 5690 }) 5691 } 5692 }; 5693 eventMixin(LineWidget) 5694 5695 function adjustScrollWhenAboveVisible(cm, line, diff) { 5696 if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) 5697 { addToScrollTop(cm, diff) } 5698 } 5699 5700 function addLineWidget(doc, handle, node, options) { 5701 var widget = new LineWidget(doc, node, options) 5702 var cm = doc.cm 5703 if (cm && widget.noHScroll) { cm.display.alignWidgets = true } 5704 changeLine(doc, handle, "widget", function (line) { 5705 var widgets = line.widgets || (line.widgets = []) 5706 if (widget.insertAt == null) { widgets.push(widget) } 5707 else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget) } 5708 widget.line = line 5709 if (cm && !lineIsHidden(doc, line)) { 5710 var aboveVisible = heightAtLine(line) < doc.scrollTop 5711 updateLineHeight(line, line.height + widgetHeight(widget)) 5712 if (aboveVisible) { addToScrollTop(cm, widget.height) } 5713 cm.curOp.forceUpdate = true 5714 } 5715 return true 5716 }) 5717 signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)) 5718 return widget 5719 } 5720 5721 // TEXTMARKERS 5722 5723 // Created with markText and setBookmark methods. A TextMarker is a 5724 // handle that can be used to clear or find a marked position in the 5725 // document. Line objects hold arrays (markedSpans) containing 5726 // {from, to, marker} object pointing to such marker objects, and 5727 // indicating that such a marker is present on that line. Multiple 5728 // lines may point to the same marker when it spans across lines. 5729 // The spans will have null for their from/to properties when the 5730 // marker continues beyond the start/end of the line. Markers have 5731 // links back to the lines they currently touch. 5732 5733 // Collapsed markers have unique ids, in order to be able to order 5734 // them, which is needed for uniquely determining an outer marker 5735 // when they overlap (they may nest, but not partially overlap). 5736 var nextMarkerId = 0 5737 5738 var TextMarker = function(doc, type) { 5739 this.lines = [] 5740 this.type = type 5741 this.doc = doc 5742 this.id = ++nextMarkerId 5743 }; 5744 5745 // Clear the marker. 5746 TextMarker.prototype.clear = function () { 5747 var this$1 = this; 5748 5749 if (this.explicitlyCleared) { return } 5750 var cm = this.doc.cm, withOp = cm && !cm.curOp 5751 if (withOp) { startOperation(cm) } 5752 if (hasHandler(this, "clear")) { 5753 var found = this.find() 5754 if (found) { signalLater(this, "clear", found.from, found.to) } 5755 } 5756 var min = null, max = null 5757 for (var i = 0; i < this.lines.length; ++i) { 5758 var line = this$1.lines[i] 5759 var span = getMarkedSpanFor(line.markedSpans, this$1) 5760 if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text") } 5761 else if (cm) { 5762 if (span.to != null) { max = lineNo(line) } 5763 if (span.from != null) { min = lineNo(line) } 5764 } 5765 line.markedSpans = removeMarkedSpan(line.markedSpans, span) 5766 if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm) 5767 { updateLineHeight(line, textHeight(cm.display)) } 5768 } 5769 if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) { 5770 var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual) 5771 if (len > cm.display.maxLineLength) { 5772 cm.display.maxLine = visual 5773 cm.display.maxLineLength = len 5774 cm.display.maxLineChanged = true 5775 } 5776 } } 5777 5778 if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1) } 5779 this.lines.length = 0 5780 this.explicitlyCleared = true 5781 if (this.atomic && this.doc.cantEdit) { 5782 this.doc.cantEdit = false 5783 if (cm) { reCheckSelection(cm.doc) } 5784 } 5785 if (cm) { signalLater(cm, "markerCleared", cm, this, min, max) } 5786 if (withOp) { endOperation(cm) } 5787 if (this.parent) { this.parent.clear() } 5788 }; 5789 5790 // Find the position of the marker in the document. Returns a {from, 5791 // to} object by default. Side can be passed to get a specific side 5792 // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the 5793 // Pos objects returned contain a line object, rather than a line 5794 // number (used to prevent looking up the same line twice). 5795 TextMarker.prototype.find = function (side, lineObj) { 5796 var this$1 = this; 5797 5798 if (side == null && this.type == "bookmark") { side = 1 } 5799 var from, to 5800 for (var i = 0; i < this.lines.length; ++i) { 5801 var line = this$1.lines[i] 5802 var span = getMarkedSpanFor(line.markedSpans, this$1) 5803 if (span.from != null) { 5804 from = Pos(lineObj ? line : lineNo(line), span.from) 5805 if (side == -1) { return from } 5806 } 5807 if (span.to != null) { 5808 to = Pos(lineObj ? line : lineNo(line), span.to) 5809 if (side == 1) { return to } 5810 } 5811 } 5812 return from && {from: from, to: to} 5813 }; 5814 5815 // Signals that the marker's widget changed, and surrounding layout 5816 // should be recomputed. 5817 TextMarker.prototype.changed = function () { 5818 var this$1 = this; 5819 5820 var pos = this.find(-1, true), widget = this, cm = this.doc.cm 5821 if (!pos || !cm) { return } 5822 runInOp(cm, function () { 5823 var line = pos.line, lineN = lineNo(pos.line) 5824 var view = findViewForLine(cm, lineN) 5825 if (view) { 5826 clearLineMeasurementCacheFor(view) 5827 cm.curOp.selectionChanged = cm.curOp.forceUpdate = true 5828 } 5829 cm.curOp.updateMaxLine = true 5830 if (!lineIsHidden(widget.doc, line) && widget.height != null) { 5831 var oldHeight = widget.height 5832 widget.height = null 5833 var dHeight = widgetHeight(widget) - oldHeight 5834 if (dHeight) 5835 { updateLineHeight(line, line.height + dHeight) } 5836 } 5837 signalLater(cm, "markerChanged", cm, this$1) 5838 }) 5839 }; 5840 5841 TextMarker.prototype.attachLine = function (line) { 5842 if (!this.lines.length && this.doc.cm) { 5843 var op = this.doc.cm.curOp 5844 if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) 5845 { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) } 5846 } 5847 this.lines.push(line) 5848 }; 5849 5850 TextMarker.prototype.detachLine = function (line) { 5851 this.lines.splice(indexOf(this.lines, line), 1) 5852 if (!this.lines.length && this.doc.cm) { 5853 var op = this.doc.cm.curOp 5854 ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this) 5855 } 5856 }; 5857 eventMixin(TextMarker) 5858 5859 // Create a marker, wire it up to the right lines, and 5860 function markText(doc, from, to, options, type) { 5861 // Shared markers (across linked documents) are handled separately 5862 // (markTextShared will call out to this again, once per 5863 // document). 5864 if (options && options.shared) { return markTextShared(doc, from, to, options, type) } 5865 // Ensure we are in an operation. 5866 if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) } 5867 5868 var marker = new TextMarker(doc, type), diff = cmp(from, to) 5869 if (options) { copyObj(options, marker, false) } 5870 // Don't connect empty markers unless clearWhenEmpty is false 5871 if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) 5872 { return marker } 5873 if (marker.replacedWith) { 5874 // Showing up as a widget implies collapsed (widget replaces text) 5875 marker.collapsed = true 5876 marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget") 5877 if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true") } 5878 if (options.insertLeft) { marker.widgetNode.insertLeft = true } 5879 } 5880 if (marker.collapsed) { 5881 if (conflictingCollapsedRange(doc, from.line, from, to, marker) || 5882 from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) 5883 { throw new Error("Inserting collapsed marker partially overlapping an existing one") } 5884 seeCollapsedSpans() 5885 } 5886 5887 if (marker.addToHistory) 5888 { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN) } 5889 5890 var curLine = from.line, cm = doc.cm, updateMaxLine 5891 doc.iter(curLine, to.line + 1, function (line) { 5892 if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) 5893 { updateMaxLine = true } 5894 if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0) } 5895 addMarkedSpan(line, new MarkedSpan(marker, 5896 curLine == from.line ? from.ch : null, 5897 curLine == to.line ? to.ch : null)) 5898 ++curLine 5899 }) 5900 // lineIsHidden depends on the presence of the spans, so needs a second pass 5901 if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) { 5902 if (lineIsHidden(doc, line)) { updateLineHeight(line, 0) } 5903 }) } 5904 5905 if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }) } 5906 5907 if (marker.readOnly) { 5908 seeReadOnlySpans() 5909 if (doc.history.done.length || doc.history.undone.length) 5910 { doc.clearHistory() } 5911 } 5912 if (marker.collapsed) { 5913 marker.id = ++nextMarkerId 5914 marker.atomic = true 5915 } 5916 if (cm) { 5917 // Sync editor state 5918 if (updateMaxLine) { cm.curOp.updateMaxLine = true } 5919 if (marker.collapsed) 5920 { regChange(cm, from.line, to.line + 1) } 5921 else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) 5922 { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text") } } 5923 if (marker.atomic) { reCheckSelection(cm.doc) } 5924 signalLater(cm, "markerAdded", cm, marker) 5925 } 5926 return marker 5927 } 5928 5929 // SHARED TEXTMARKERS 5930 5931 // A shared marker spans multiple linked documents. It is 5932 // implemented as a meta-marker-object controlling multiple normal 5933 // markers. 5934 var SharedTextMarker = function(markers, primary) { 5935 var this$1 = this; 5936 5937 this.markers = markers 5938 this.primary = primary 5939 for (var i = 0; i < markers.length; ++i) 5940 { markers[i].parent = this$1 } 5941 }; 5942 5943 SharedTextMarker.prototype.clear = function () { 5944 var this$1 = this; 5945 5946 if (this.explicitlyCleared) { return } 5947 this.explicitlyCleared = true 5948 for (var i = 0; i < this.markers.length; ++i) 5949 { this$1.markers[i].clear() } 5950 signalLater(this, "clear") 5951 }; 5952 5953 SharedTextMarker.prototype.find = function (side, lineObj) { 5954 return this.primary.find(side, lineObj) 5955 }; 5956 eventMixin(SharedTextMarker) 5957 5958 function markTextShared(doc, from, to, options, type) { 5959 options = copyObj(options) 5960 options.shared = false 5961 var markers = [markText(doc, from, to, options, type)], primary = markers[0] 5962 var widget = options.widgetNode 5963 linkedDocs(doc, function (doc) { 5964 if (widget) { options.widgetNode = widget.cloneNode(true) } 5965 markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)) 5966 for (var i = 0; i < doc.linked.length; ++i) 5967 { if (doc.linked[i].isParent) { return } } 5968 primary = lst(markers) 5969 }) 5970 return new SharedTextMarker(markers, primary) 5971 } 5972 5973 function findSharedMarkers(doc) { 5974 return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; }) 5975 } 5976 5977 function copySharedMarkers(doc, markers) { 5978 for (var i = 0; i < markers.length; i++) { 5979 var marker = markers[i], pos = marker.find() 5980 var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to) 5981 if (cmp(mFrom, mTo)) { 5982 var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type) 5983 marker.markers.push(subMark) 5984 subMark.parent = marker 5985 } 5986 } 5987 } 5988 5989 function detachSharedMarkers(markers) { 5990 var loop = function ( i ) { 5991 var marker = markers[i], linked = [marker.primary.doc] 5992 linkedDocs(marker.primary.doc, function (d) { return linked.push(d); }) 5993 for (var j = 0; j < marker.markers.length; j++) { 5994 var subMarker = marker.markers[j] 5995 if (indexOf(linked, subMarker.doc) == -1) { 5996 subMarker.parent = null 5997 marker.markers.splice(j--, 1) 5998 } 5999 } 6000 }; 6001 6002 for (var i = 0; i < markers.length; i++) loop( i ); 6003 } 6004 6005 var nextDocId = 0 6006 var Doc = function(text, mode, firstLine, lineSep, direction) { 6007 if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) } 6008 if (firstLine == null) { firstLine = 0 } 6009 6010 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]) 6011 this.first = firstLine 6012 this.scrollTop = this.scrollLeft = 0 6013 this.cantEdit = false 6014 this.cleanGeneration = 1 6015 this.modeFrontier = this.highlightFrontier = firstLine 6016 var start = Pos(firstLine, 0) 6017 this.sel = simpleSelection(start) 6018 this.history = new History(null) 6019 this.id = ++nextDocId 6020 this.modeOption = mode 6021 this.lineSep = lineSep 6022 this.direction = (direction == "rtl") ? "rtl" : "ltr" 6023 this.extend = false 6024 6025 if (typeof text == "string") { text = this.splitLines(text) } 6026 updateDoc(this, {from: start, to: start, text: text}) 6027 setSelection(this, simpleSelection(start), sel_dontScroll) 6028 } 6029 6030 Doc.prototype = createObj(BranchChunk.prototype, { 6031 constructor: Doc, 6032 // Iterate over the document. Supports two forms -- with only one 6033 // argument, it calls that for each line in the document. With 6034 // three, it iterates over the range given by the first two (with 6035 // the second being non-inclusive). 6036 iter: function(from, to, op) { 6037 if (op) { this.iterN(from - this.first, to - from, op) } 6038 else { this.iterN(this.first, this.first + this.size, from) } 6039 }, 6040 6041 // Non-public interface for adding and removing lines. 6042 insert: function(at, lines) { 6043 var height = 0 6044 for (var i = 0; i < lines.length; ++i) { height += lines[i].height } 6045 this.insertInner(at - this.first, lines, height) 6046 }, 6047 remove: function(at, n) { this.removeInner(at - this.first, n) }, 6048 6049 // From here, the methods are part of the public interface. Most 6050 // are also available from CodeMirror (editor) instances. 6051 6052 getValue: function(lineSep) { 6053 var lines = getLines(this, this.first, this.first + this.size) 6054 if (lineSep === false) { return lines } 6055 return lines.join(lineSep || this.lineSeparator()) 6056 }, 6057 setValue: docMethodOp(function(code) { 6058 var top = Pos(this.first, 0), last = this.first + this.size - 1 6059 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), 6060 text: this.splitLines(code), origin: "setValue", full: true}, true) 6061 if (this.cm) { scrollToCoords(this.cm, 0, 0) } 6062 setSelection(this, simpleSelection(top), sel_dontScroll) 6063 }), 6064 replaceRange: function(code, from, to, origin) { 6065 from = clipPos(this, from) 6066 to = to ? clipPos(this, to) : from 6067 replaceRange(this, code, from, to, origin) 6068 }, 6069 getRange: function(from, to, lineSep) { 6070 var lines = getBetween(this, clipPos(this, from), clipPos(this, to)) 6071 if (lineSep === false) { return lines } 6072 return lines.join(lineSep || this.lineSeparator()) 6073 }, 6074 6075 getLine: function(line) {var l = this.getLineHandle(line); return l && l.text}, 6076 6077 getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }}, 6078 getLineNumber: function(line) {return lineNo(line)}, 6079 6080 getLineHandleVisualStart: function(line) { 6081 if (typeof line == "number") { line = getLine(this, line) } 6082 return visualLine(line) 6083 }, 6084 6085 lineCount: function() {return this.size}, 6086 firstLine: function() {return this.first}, 6087 lastLine: function() {return this.first + this.size - 1}, 6088 6089 clipPos: function(pos) {return clipPos(this, pos)}, 6090 6091 getCursor: function(start) { 6092 var range = this.sel.primary(), pos 6093 if (start == null || start == "head") { pos = range.head } 6094 else if (start == "anchor") { pos = range.anchor } 6095 else if (start == "end" || start == "to" || start === false) { pos = range.to() } 6096 else { pos = range.from() } 6097 return pos 6098 }, 6099 listSelections: function() { return this.sel.ranges }, 6100 somethingSelected: function() {return this.sel.somethingSelected()}, 6101 6102 setCursor: docMethodOp(function(line, ch, options) { 6103 setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options) 6104 }), 6105 setSelection: docMethodOp(function(anchor, head, options) { 6106 setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options) 6107 }), 6108 extendSelection: docMethodOp(function(head, other, options) { 6109 extendSelection(this, clipPos(this, head), other && clipPos(this, other), options) 6110 }), 6111 extendSelections: docMethodOp(function(heads, options) { 6112 extendSelections(this, clipPosArray(this, heads), options) 6113 }), 6114 extendSelectionsBy: docMethodOp(function(f, options) { 6115 var heads = map(this.sel.ranges, f) 6116 extendSelections(this, clipPosArray(this, heads), options) 6117 }), 6118 setSelections: docMethodOp(function(ranges, primary, options) { 6119 var this$1 = this; 6120 6121 if (!ranges.length) { return } 6122 var out = [] 6123 for (var i = 0; i < ranges.length; i++) 6124 { out[i] = new Range(clipPos(this$1, ranges[i].anchor), 6125 clipPos(this$1, ranges[i].head)) } 6126 if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex) } 6127 setSelection(this, normalizeSelection(out, primary), options) 6128 }), 6129 addSelection: docMethodOp(function(anchor, head, options) { 6130 var ranges = this.sel.ranges.slice(0) 6131 ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))) 6132 setSelection(this, normalizeSelection(ranges, ranges.length - 1), options) 6133 }), 6134 6135 getSelection: function(lineSep) { 6136 var this$1 = this; 6137 6138 var ranges = this.sel.ranges, lines 6139 for (var i = 0; i < ranges.length; i++) { 6140 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) 6141 lines = lines ? lines.concat(sel) : sel 6142 } 6143 if (lineSep === false) { return lines } 6144 else { return lines.join(lineSep || this.lineSeparator()) } 6145 }, 6146 getSelections: function(lineSep) { 6147 var this$1 = this; 6148 6149 var parts = [], ranges = this.sel.ranges 6150 for (var i = 0; i < ranges.length; i++) { 6151 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) 6152 if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()) } 6153 parts[i] = sel 6154 } 6155 return parts 6156 }, 6157 replaceSelection: function(code, collapse, origin) { 6158 var dup = [] 6159 for (var i = 0; i < this.sel.ranges.length; i++) 6160 { dup[i] = code } 6161 this.replaceSelections(dup, collapse, origin || "+input") 6162 }, 6163 replaceSelections: docMethodOp(function(code, collapse, origin) { 6164 var this$1 = this; 6165 6166 var changes = [], sel = this.sel 6167 for (var i = 0; i < sel.ranges.length; i++) { 6168 var range = sel.ranges[i] 6169 changes[i] = {from: range.from(), to: range.to(), text: this$1.splitLines(code[i]), origin: origin} 6170 } 6171 var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse) 6172 for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) 6173 { makeChange(this$1, changes[i$1]) } 6174 if (newSel) { setSelectionReplaceHistory(this, newSel) } 6175 else if (this.cm) { ensureCursorVisible(this.cm) } 6176 }), 6177 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}), 6178 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}), 6179 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true)}), 6180 redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true)}), 6181 6182 setExtending: function(val) {this.extend = val}, 6183 getExtending: function() {return this.extend}, 6184 6185 historySize: function() { 6186 var hist = this.history, done = 0, undone = 0 6187 for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done } } 6188 for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone } } 6189 return {undo: done, redo: undone} 6190 }, 6191 clearHistory: function() {this.history = new History(this.history.maxGeneration)}, 6192 6193 markClean: function() { 6194 this.cleanGeneration = this.changeGeneration(true) 6195 }, 6196 changeGeneration: function(forceSplit) { 6197 if (forceSplit) 6198 { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null } 6199 return this.history.generation 6200 }, 6201 isClean: function (gen) { 6202 return this.history.generation == (gen || this.cleanGeneration) 6203 }, 6204 6205 getHistory: function() { 6206 return {done: copyHistoryArray(this.history.done), 6207 undone: copyHistoryArray(this.history.undone)} 6208 }, 6209 setHistory: function(histData) { 6210 var hist = this.history = new History(this.history.maxGeneration) 6211 hist.done = copyHistoryArray(histData.done.slice(0), null, true) 6212 hist.undone = copyHistoryArray(histData.undone.slice(0), null, true) 6213 }, 6214 6215 setGutterMarker: docMethodOp(function(line, gutterID, value) { 6216 return changeLine(this, line, "gutter", function (line) { 6217 var markers = line.gutterMarkers || (line.gutterMarkers = {}) 6218 markers[gutterID] = value 6219 if (!value && isEmpty(markers)) { line.gutterMarkers = null } 6220 return true 6221 }) 6222 }), 6223 6224 clearGutter: docMethodOp(function(gutterID) { 6225 var this$1 = this; 6226 6227 this.iter(function (line) { 6228 if (line.gutterMarkers && line.gutterMarkers[gutterID]) { 6229 changeLine(this$1, line, "gutter", function () { 6230 line.gutterMarkers[gutterID] = null 6231 if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null } 6232 return true 6233 }) 6234 } 6235 }) 6236 }), 6237 6238 lineInfo: function(line) { 6239 var n 6240 if (typeof line == "number") { 6241 if (!isLine(this, line)) { return null } 6242 n = line 6243 line = getLine(this, line) 6244 if (!line) { return null } 6245 } else { 6246 n = lineNo(line) 6247 if (n == null) { return null } 6248 } 6249 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, 6250 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, 6251 widgets: line.widgets} 6252 }, 6253 6254 addLineClass: docMethodOp(function(handle, where, cls) { 6255 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { 6256 var prop = where == "text" ? "textClass" 6257 : where == "background" ? "bgClass" 6258 : where == "gutter" ? "gutterClass" : "wrapClass" 6259 if (!line[prop]) { line[prop] = cls } 6260 else if (classTest(cls).test(line[prop])) { return false } 6261 else { line[prop] += " " + cls } 6262 return true 6263 }) 6264 }), 6265 removeLineClass: docMethodOp(function(handle, where, cls) { 6266 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { 6267 var prop = where == "text" ? "textClass" 6268 : where == "background" ? "bgClass" 6269 : where == "gutter" ? "gutterClass" : "wrapClass" 6270 var cur = line[prop] 6271 if (!cur) { return false } 6272 else if (cls == null) { line[prop] = null } 6273 else { 6274 var found = cur.match(classTest(cls)) 6275 if (!found) { return false } 6276 var end = found.index + found[0].length 6277 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null 6278 } 6279 return true 6280 }) 6281 }), 6282 6283 addLineWidget: docMethodOp(function(handle, node, options) { 6284 return addLineWidget(this, handle, node, options) 6285 }), 6286 removeLineWidget: function(widget) { widget.clear() }, 6287 6288 markText: function(from, to, options) { 6289 return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") 6290 }, 6291 setBookmark: function(pos, options) { 6292 var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), 6293 insertLeft: options && options.insertLeft, 6294 clearWhenEmpty: false, shared: options && options.shared, 6295 handleMouseEvents: options && options.handleMouseEvents} 6296 pos = clipPos(this, pos) 6297 return markText(this, pos, pos, realOpts, "bookmark") 6298 }, 6299 findMarksAt: function(pos) { 6300 pos = clipPos(this, pos) 6301 var markers = [], spans = getLine(this, pos.line).markedSpans 6302 if (spans) { for (var i = 0; i < spans.length; ++i) { 6303 var span = spans[i] 6304 if ((span.from == null || span.from <= pos.ch) && 6305 (span.to == null || span.to >= pos.ch)) 6306 { markers.push(span.marker.parent || span.marker) } 6307 } } 6308 return markers 6309 }, 6310 findMarks: function(from, to, filter) { 6311 from = clipPos(this, from); to = clipPos(this, to) 6312 var found = [], lineNo = from.line 6313 this.iter(from.line, to.line + 1, function (line) { 6314 var spans = line.markedSpans 6315 if (spans) { for (var i = 0; i < spans.length; i++) { 6316 var span = spans[i] 6317 if (!(span.to != null && lineNo == from.line && from.ch >= span.to || 6318 span.from == null && lineNo != from.line || 6319 span.from != null && lineNo == to.line && span.from >= to.ch) && 6320 (!filter || filter(span.marker))) 6321 { found.push(span.marker.parent || span.marker) } 6322 } } 6323 ++lineNo 6324 }) 6325 return found 6326 }, 6327 getAllMarks: function() { 6328 var markers = [] 6329 this.iter(function (line) { 6330 var sps = line.markedSpans 6331 if (sps) { for (var i = 0; i < sps.length; ++i) 6332 { if (sps[i].from != null) { markers.push(sps[i].marker) } } } 6333 }) 6334 return markers 6335 }, 6336 6337 posFromIndex: function(off) { 6338 var ch, lineNo = this.first, sepSize = this.lineSeparator().length 6339 this.iter(function (line) { 6340 var sz = line.text.length + sepSize 6341 if (sz > off) { ch = off; return true } 6342 off -= sz 6343 ++lineNo 6344 }) 6345 return clipPos(this, Pos(lineNo, ch)) 6346 }, 6347 indexFromPos: function (coords) { 6348 coords = clipPos(this, coords) 6349 var index = coords.ch 6350 if (coords.line < this.first || coords.ch < 0) { return 0 } 6351 var sepSize = this.lineSeparator().length 6352 this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value 6353 index += line.text.length + sepSize 6354 }) 6355 return index 6356 }, 6357 6358 copy: function(copyHistory) { 6359 var doc = new Doc(getLines(this, this.first, this.first + this.size), 6360 this.modeOption, this.first, this.lineSep, this.direction) 6361 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft 6362 doc.sel = this.sel 6363 doc.extend = false 6364 if (copyHistory) { 6365 doc.history.undoDepth = this.history.undoDepth 6366 doc.setHistory(this.getHistory()) 6367 } 6368 return doc 6369 }, 6370 6371 linkedDoc: function(options) { 6372 if (!options) { options = {} } 6373 var from = this.first, to = this.first + this.size 6374 if (options.from != null && options.from > from) { from = options.from } 6375 if (options.to != null && options.to < to) { to = options.to } 6376 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction) 6377 if (options.sharedHist) { copy.history = this.history 6378 ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}) 6379 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}] 6380 copySharedMarkers(copy, findSharedMarkers(this)) 6381 return copy 6382 }, 6383 unlinkDoc: function(other) { 6384 var this$1 = this; 6385 6386 if (other instanceof CodeMirror) { other = other.doc } 6387 if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { 6388 var link = this$1.linked[i] 6389 if (link.doc != other) { continue } 6390 this$1.linked.splice(i, 1) 6391 other.unlinkDoc(this$1) 6392 detachSharedMarkers(findSharedMarkers(this$1)) 6393 break 6394 } } 6395 // If the histories were shared, split them again 6396 if (other.history == this.history) { 6397 var splitIds = [other.id] 6398 linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true) 6399 other.history = new History(null) 6400 other.history.done = copyHistoryArray(this.history.done, splitIds) 6401 other.history.undone = copyHistoryArray(this.history.undone, splitIds) 6402 } 6403 }, 6404 iterLinkedDocs: function(f) {linkedDocs(this, f)}, 6405 6406 getMode: function() {return this.mode}, 6407 getEditor: function() {return this.cm}, 6408 6409 splitLines: function(str) { 6410 if (this.lineSep) { return str.split(this.lineSep) } 6411 return splitLinesAuto(str) 6412 }, 6413 lineSeparator: function() { return this.lineSep || "\n" }, 6414 6415 setDirection: docMethodOp(function (dir) { 6416 if (dir != "rtl") { dir = "ltr" } 6417 if (dir == this.direction) { return } 6418 this.direction = dir 6419 this.iter(function (line) { return line.order = null; }) 6420 if (this.cm) { directionChanged(this.cm) } 6421 }) 6422 }) 6423 6424 // Public alias. 6425 Doc.prototype.eachLine = Doc.prototype.iter 6426 6427 // Kludge to work around strange IE behavior where it'll sometimes 6428 // re-fire a series of drag-related events right after the drop (#1551) 6429 var lastDrop = 0 6430 6431 function onDrop(e) { 6432 var cm = this 6433 clearDragCursor(cm) 6434 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) 6435 { return } 6436 e_preventDefault(e) 6437 if (ie) { lastDrop = +new Date } 6438 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files 6439 if (!pos || cm.isReadOnly()) { return } 6440 // Might be a file drop, in which case we simply extract the text 6441 // and insert it. 6442 if (files && files.length && window.FileReader && window.File) { 6443 var n = files.length, text = Array(n), read = 0 6444 var loadFile = function (file, i) { 6445 if (cm.options.allowDropFileTypes && 6446 indexOf(cm.options.allowDropFileTypes, file.type) == -1) 6447 { return } 6448 6449 var reader = new FileReader 6450 reader.onload = operation(cm, function () { 6451 var content = reader.result 6452 if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = "" } 6453 text[i] = content 6454 if (++read == n) { 6455 pos = clipPos(cm.doc, pos) 6456 var change = {from: pos, to: pos, 6457 text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), 6458 origin: "paste"} 6459 makeChange(cm.doc, change) 6460 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))) 6461 } 6462 }) 6463 reader.readAsText(file) 6464 } 6465 for (var i = 0; i < n; ++i) { loadFile(files[i], i) } 6466 } else { // Normal drop 6467 // Don't do a replace if the drop happened inside of the selected text. 6468 if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { 6469 cm.state.draggingText(e) 6470 // Ensure the editor is re-focused 6471 setTimeout(function () { return cm.display.input.focus(); }, 20) 6472 return 6473 } 6474 try { 6475 var text$1 = e.dataTransfer.getData("Text") 6476 if (text$1) { 6477 var selected 6478 if (cm.state.draggingText && !cm.state.draggingText.copy) 6479 { selected = cm.listSelections() } 6480 setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)) 6481 if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1) 6482 { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag") } } 6483 cm.replaceSelection(text$1, "around", "paste") 6484 cm.display.input.focus() 6485 } 6486 } 6487 catch(e){} 6488 } 6489 } 6490 6491 function onDragStart(cm, e) { 6492 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } 6493 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } 6494 6495 e.dataTransfer.setData("Text", cm.getSelection()) 6496 e.dataTransfer.effectAllowed = "copyMove" 6497 6498 // Use dummy image instead of default browsers image. 6499 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. 6500 if (e.dataTransfer.setDragImage && !safari) { 6501 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;") 6502 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" 6503 if (presto) { 6504 img.width = img.height = 1 6505 cm.display.wrapper.appendChild(img) 6506 // Force a relayout, or Opera won't use our image for some obscure reason 6507 img._top = img.offsetTop 6508 } 6509 e.dataTransfer.setDragImage(img, 0, 0) 6510 if (presto) { img.parentNode.removeChild(img) } 6511 } 6512 } 6513 6514 function onDragOver(cm, e) { 6515 var pos = posFromMouse(cm, e) 6516 if (!pos) { return } 6517 var frag = document.createDocumentFragment() 6518 drawSelectionCursor(cm, pos, frag) 6519 if (!cm.display.dragCursor) { 6520 cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors") 6521 cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv) 6522 } 6523 removeChildrenAndAdd(cm.display.dragCursor, frag) 6524 } 6525 6526 function clearDragCursor(cm) { 6527 if (cm.display.dragCursor) { 6528 cm.display.lineSpace.removeChild(cm.display.dragCursor) 6529 cm.display.dragCursor = null 6530 } 6531 } 6532 6533 // These must be handled carefully, because naively registering a 6534 // handler for each editor will cause the editors to never be 6535 // garbage collected. 6536 6537 function forEachCodeMirror(f) { 6538 if (!document.getElementsByClassName) { return } 6539 var byClass = document.getElementsByClassName("CodeMirror") 6540 for (var i = 0; i < byClass.length; i++) { 6541 var cm = byClass[i].CodeMirror 6542 if (cm) { f(cm) } 6543 } 6544 } 6545 6546 var globalsRegistered = false 6547 function ensureGlobalHandlers() { 6548 if (globalsRegistered) { return } 6549 registerGlobalHandlers() 6550 globalsRegistered = true 6551 } 6552 function registerGlobalHandlers() { 6553 // When the window resizes, we need to refresh active editors. 6554 var resizeTimer 6555 on(window, "resize", function () { 6556 if (resizeTimer == null) { resizeTimer = setTimeout(function () { 6557 resizeTimer = null 6558 forEachCodeMirror(onResize) 6559 }, 100) } 6560 }) 6561 // When the window loses focus, we want to show the editor as blurred 6562 on(window, "blur", function () { return forEachCodeMirror(onBlur); }) 6563 } 6564 // Called when the window resizes 6565 function onResize(cm) { 6566 var d = cm.display 6567 if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) 6568 { return } 6569 // Might be a text scaling operation, clear size caches. 6570 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null 6571 d.scrollbarsClipped = false 6572 cm.setSize() 6573 } 6574 6575 var keyNames = { 6576 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 6577 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 6578 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", 6579 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 6580 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", 6581 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 6582 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", 6583 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" 6584 } 6585 6586 // Number keys 6587 for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i) } 6588 // Alphabetic keys 6589 for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1) } 6590 // Function keys 6591 for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2 } 6592 6593 var keyMap = {} 6594 6595 keyMap.basic = { 6596 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", 6597 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", 6598 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", 6599 "Tab": "defaultTab", "Shift-Tab": "indentAuto", 6600 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", 6601 "Esc": "singleSelection" 6602 } 6603 // Note that the save and find-related commands aren't defined by 6604 // default. User code or addons can define them. Unknown commands 6605 // are simply ignored. 6606 keyMap.pcDefault = { 6607 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", 6608 "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", 6609 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", 6610 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", 6611 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", 6612 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", 6613 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", 6614 fallthrough: "basic" 6615 } 6616 // Very basic readline/emacs-style bindings, which are standard on Mac. 6617 keyMap.emacsy = { 6618 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", 6619 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", 6620 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", 6621 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", 6622 "Ctrl-O": "openLine" 6623 } 6624 keyMap.macDefault = { 6625 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", 6626 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", 6627 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", 6628 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", 6629 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", 6630 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", 6631 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", 6632 fallthrough: ["basic", "emacsy"] 6633 } 6634 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault 6635 6636 // KEYMAP DISPATCH 6637 6638 function normalizeKeyName(name) { 6639 var parts = name.split(/-(?!$)/) 6640 name = parts[parts.length - 1] 6641 var alt, ctrl, shift, cmd 6642 for (var i = 0; i < parts.length - 1; i++) { 6643 var mod = parts[i] 6644 if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true } 6645 else if (/^a(lt)?$/i.test(mod)) { alt = true } 6646 else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true } 6647 else if (/^s(hift)?$/i.test(mod)) { shift = true } 6648 else { throw new Error("Unrecognized modifier name: " + mod) } 6649 } 6650 if (alt) { name = "Alt-" + name } 6651 if (ctrl) { name = "Ctrl-" + name } 6652 if (cmd) { name = "Cmd-" + name } 6653 if (shift) { name = "Shift-" + name } 6654 return name 6655 } 6656 6657 // This is a kludge to keep keymaps mostly working as raw objects 6658 // (backwards compatibility) while at the same time support features 6659 // like normalization and multi-stroke key bindings. It compiles a 6660 // new normalized keymap, and then updates the old object to reflect 6661 // this. 6662 function normalizeKeyMap(keymap) { 6663 var copy = {} 6664 for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) { 6665 var value = keymap[keyname] 6666 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue } 6667 if (value == "...") { delete keymap[keyname]; continue } 6668 6669 var keys = map(keyname.split(" "), normalizeKeyName) 6670 for (var i = 0; i < keys.length; i++) { 6671 var val = (void 0), name = (void 0) 6672 if (i == keys.length - 1) { 6673 name = keys.join(" ") 6674 val = value 6675 } else { 6676 name = keys.slice(0, i + 1).join(" ") 6677 val = "..." 6678 } 6679 var prev = copy[name] 6680 if (!prev) { copy[name] = val } 6681 else if (prev != val) { throw new Error("Inconsistent bindings for " + name) } 6682 } 6683 delete keymap[keyname] 6684 } } 6685 for (var prop in copy) { keymap[prop] = copy[prop] } 6686 return keymap 6687 } 6688 6689 function lookupKey(key, map, handle, context) { 6690 map = getKeyMap(map) 6691 var found = map.call ? map.call(key, context) : map[key] 6692 if (found === false) { return "nothing" } 6693 if (found === "...") { return "multi" } 6694 if (found != null && handle(found)) { return "handled" } 6695 6696 if (map.fallthrough) { 6697 if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") 6698 { return lookupKey(key, map.fallthrough, handle, context) } 6699 for (var i = 0; i < map.fallthrough.length; i++) { 6700 var result = lookupKey(key, map.fallthrough[i], handle, context) 6701 if (result) { return result } 6702 } 6703 } 6704 } 6705 6706 // Modifier key presses don't count as 'real' key presses for the 6707 // purpose of keymap fallthrough. 6708 function isModifierKey(value) { 6709 var name = typeof value == "string" ? value : keyNames[value.keyCode] 6710 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" 6711 } 6712 6713 function addModifierNames(name, event, noShift) { 6714 var base = name 6715 if (event.altKey && base != "Alt") { name = "Alt-" + name } 6716 if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name } 6717 if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name } 6718 if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name } 6719 return name 6720 } 6721 6722 // Look up the name of a key as indicated by an event object. 6723 function keyName(event, noShift) { 6724 if (presto && event.keyCode == 34 && event["char"]) { return false } 6725 var name = keyNames[event.keyCode] 6726 if (name == null || event.altGraphKey) { return false } 6727 return addModifierNames(name, event, noShift) 6728 } 6729 6730 function getKeyMap(val) { 6731 return typeof val == "string" ? keyMap[val] : val 6732 } 6733 6734 // Helper for deleting text near the selection(s), used to implement 6735 // backspace, delete, and similar functionality. 6736 function deleteNearSelection(cm, compute) { 6737 var ranges = cm.doc.sel.ranges, kill = [] 6738 // Build up a set of ranges to kill first, merging overlapping 6739 // ranges. 6740 for (var i = 0; i < ranges.length; i++) { 6741 var toKill = compute(ranges[i]) 6742 while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { 6743 var replaced = kill.pop() 6744 if (cmp(replaced.from, toKill.from) < 0) { 6745 toKill.from = replaced.from 6746 break 6747 } 6748 } 6749 kill.push(toKill) 6750 } 6751 // Next, remove those actual ranges. 6752 runInOp(cm, function () { 6753 for (var i = kill.length - 1; i >= 0; i--) 6754 { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete") } 6755 ensureCursorVisible(cm) 6756 }) 6757 } 6758 6759 function moveCharLogically(line, ch, dir) { 6760 var target = skipExtendingChars(line.text, ch + dir, dir) 6761 return target < 0 || target > line.text.length ? null : target 6762 } 6763 6764 function moveLogically(line, start, dir) { 6765 var ch = moveCharLogically(line, start.ch, dir) 6766 return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") 6767 } 6768 6769 function endOfLine(visually, cm, lineObj, lineNo, dir) { 6770 if (visually) { 6771 var order = getOrder(lineObj, cm.doc.direction) 6772 if (order) { 6773 var part = dir < 0 ? lst(order) : order[0] 6774 var moveInStorageOrder = (dir < 0) == (part.level == 1) 6775 var sticky = moveInStorageOrder ? "after" : "before" 6776 var ch 6777 // With a wrapped rtl chunk (possibly spanning multiple bidi parts), 6778 // it could be that the last bidi part is not on the last visual line, 6779 // since visual lines contain content order-consecutive chunks. 6780 // Thus, in rtl, we are looking for the first (content-order) character 6781 // in the rtl chunk that is on the last line (that is, the same line 6782 // as the last (content-order) character). 6783 if (part.level > 0 || cm.doc.direction == "rtl") { 6784 var prep = prepareMeasureForLine(cm, lineObj) 6785 ch = dir < 0 ? lineObj.text.length - 1 : 0 6786 var targetTop = measureCharPrepared(cm, prep, ch).top 6787 ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch) 6788 if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1) } 6789 } else { ch = dir < 0 ? part.to : part.from } 6790 return new Pos(lineNo, ch, sticky) 6791 } 6792 } 6793 return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") 6794 } 6795 6796 function moveVisually(cm, line, start, dir) { 6797 var bidi = getOrder(line, cm.doc.direction) 6798 if (!bidi) { return moveLogically(line, start, dir) } 6799 if (start.ch >= line.text.length) { 6800 start.ch = line.text.length 6801 start.sticky = "before" 6802 } else if (start.ch <= 0) { 6803 start.ch = 0 6804 start.sticky = "after" 6805 } 6806 var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos] 6807 if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { 6808 // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, 6809 // nothing interesting happens. 6810 return moveLogically(line, start, dir) 6811 } 6812 6813 var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); } 6814 var prep 6815 var getWrappedLineExtent = function (ch) { 6816 if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } 6817 prep = prep || prepareMeasureForLine(cm, line) 6818 return wrappedLineExtentChar(cm, line, prep, ch) 6819 } 6820 var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch) 6821 6822 if (cm.doc.direction == "rtl" || part.level == 1) { 6823 var moveInStorageOrder = (part.level == 1) == (dir < 0) 6824 var ch = mv(start, moveInStorageOrder ? 1 : -1) 6825 if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { 6826 // Case 2: We move within an rtl part or in an rtl editor on the same visual line 6827 var sticky = moveInStorageOrder ? "before" : "after" 6828 return new Pos(start.line, ch, sticky) 6829 } 6830 } 6831 6832 // Case 3: Could not move within this bidi part in this visual line, so leave 6833 // the current bidi part 6834 6835 var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { 6836 var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder 6837 ? new Pos(start.line, mv(ch, 1), "before") 6838 : new Pos(start.line, ch, "after"); } 6839 6840 for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { 6841 var part = bidi[partPos] 6842 var moveInStorageOrder = (dir > 0) == (part.level != 1) 6843 var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1) 6844 if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) } 6845 ch = moveInStorageOrder ? part.from : mv(part.to, -1) 6846 if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } 6847 } 6848 } 6849 6850 // Case 3a: Look for other bidi parts on the same visual line 6851 var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent) 6852 if (res) { return res } 6853 6854 // Case 3b: Look for other bidi parts on the next visual line 6855 var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1) 6856 if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { 6857 res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)) 6858 if (res) { return res } 6859 } 6860 6861 // Case 4: Nowhere to move 6862 return null 6863 } 6864 6865 // Commands are parameter-less actions that can be performed on an 6866 // editor, mostly used for keybindings. 6867 var commands = { 6868 selectAll: selectAll, 6869 singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); }, 6870 killLine: function (cm) { return deleteNearSelection(cm, function (range) { 6871 if (range.empty()) { 6872 var len = getLine(cm.doc, range.head.line).text.length 6873 if (range.head.ch == len && range.head.line < cm.lastLine()) 6874 { return {from: range.head, to: Pos(range.head.line + 1, 0)} } 6875 else 6876 { return {from: range.head, to: Pos(range.head.line, len)} } 6877 } else { 6878 return {from: range.from(), to: range.to()} 6879 } 6880 }); }, 6881 deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({ 6882 from: Pos(range.from().line, 0), 6883 to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) 6884 }); }); }, 6885 delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({ 6886 from: Pos(range.from().line, 0), to: range.from() 6887 }); }); }, 6888 delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { 6889 var top = cm.charCoords(range.head, "div").top + 5 6890 var leftPos = cm.coordsChar({left: 0, top: top}, "div") 6891 return {from: leftPos, to: range.from()} 6892 }); }, 6893 delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) { 6894 var top = cm.charCoords(range.head, "div").top + 5 6895 var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") 6896 return {from: range.from(), to: rightPos } 6897 }); }, 6898 undo: function (cm) { return cm.undo(); }, 6899 redo: function (cm) { return cm.redo(); }, 6900 undoSelection: function (cm) { return cm.undoSelection(); }, 6901 redoSelection: function (cm) { return cm.redoSelection(); }, 6902 goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); }, 6903 goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); }, 6904 goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); }, 6905 {origin: "+move", bias: 1} 6906 ); }, 6907 goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); }, 6908 {origin: "+move", bias: 1} 6909 ); }, 6910 goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); }, 6911 {origin: "+move", bias: -1} 6912 ); }, 6913 goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { 6914 var top = cm.cursorCoords(range.head, "div").top + 5 6915 return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") 6916 }, sel_move); }, 6917 goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { 6918 var top = cm.cursorCoords(range.head, "div").top + 5 6919 return cm.coordsChar({left: 0, top: top}, "div") 6920 }, sel_move); }, 6921 goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) { 6922 var top = cm.cursorCoords(range.head, "div").top + 5 6923 var pos = cm.coordsChar({left: 0, top: top}, "div") 6924 if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) } 6925 return pos 6926 }, sel_move); }, 6927 goLineUp: function (cm) { return cm.moveV(-1, "line"); }, 6928 goLineDown: function (cm) { return cm.moveV(1, "line"); }, 6929 goPageUp: function (cm) { return cm.moveV(-1, "page"); }, 6930 goPageDown: function (cm) { return cm.moveV(1, "page"); }, 6931 goCharLeft: function (cm) { return cm.moveH(-1, "char"); }, 6932 goCharRight: function (cm) { return cm.moveH(1, "char"); }, 6933 goColumnLeft: function (cm) { return cm.moveH(-1, "column"); }, 6934 goColumnRight: function (cm) { return cm.moveH(1, "column"); }, 6935 goWordLeft: function (cm) { return cm.moveH(-1, "word"); }, 6936 goGroupRight: function (cm) { return cm.moveH(1, "group"); }, 6937 goGroupLeft: function (cm) { return cm.moveH(-1, "group"); }, 6938 goWordRight: function (cm) { return cm.moveH(1, "word"); }, 6939 delCharBefore: function (cm) { return cm.deleteH(-1, "char"); }, 6940 delCharAfter: function (cm) { return cm.deleteH(1, "char"); }, 6941 delWordBefore: function (cm) { return cm.deleteH(-1, "word"); }, 6942 delWordAfter: function (cm) { return cm.deleteH(1, "word"); }, 6943 delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); }, 6944 delGroupAfter: function (cm) { return cm.deleteH(1, "group"); }, 6945 indentAuto: function (cm) { return cm.indentSelection("smart"); }, 6946 indentMore: function (cm) { return cm.indentSelection("add"); }, 6947 indentLess: function (cm) { return cm.indentSelection("subtract"); }, 6948 insertTab: function (cm) { return cm.replaceSelection("\t"); }, 6949 insertSoftTab: function (cm) { 6950 var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize 6951 for (var i = 0; i < ranges.length; i++) { 6952 var pos = ranges[i].from() 6953 var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize) 6954 spaces.push(spaceStr(tabSize - col % tabSize)) 6955 } 6956 cm.replaceSelections(spaces) 6957 }, 6958 defaultTab: function (cm) { 6959 if (cm.somethingSelected()) { cm.indentSelection("add") } 6960 else { cm.execCommand("insertTab") } 6961 }, 6962 // Swap the two chars left and right of each selection's head. 6963 // Move cursor behind the two swapped characters afterwards. 6964 // 6965 // Doesn't consider line feeds a character. 6966 // Doesn't scan more than one line above to find a character. 6967 // Doesn't do anything on an empty line. 6968 // Doesn't do anything with non-empty selections. 6969 transposeChars: function (cm) { return runInOp(cm, function () { 6970 var ranges = cm.listSelections(), newSel = [] 6971 for (var i = 0; i < ranges.length; i++) { 6972 if (!ranges[i].empty()) { continue } 6973 var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text 6974 if (line) { 6975 if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1) } 6976 if (cur.ch > 0) { 6977 cur = new Pos(cur.line, cur.ch + 1) 6978 cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), 6979 Pos(cur.line, cur.ch - 2), cur, "+transpose") 6980 } else if (cur.line > cm.doc.first) { 6981 var prev = getLine(cm.doc, cur.line - 1).text 6982 if (prev) { 6983 cur = new Pos(cur.line, 1) 6984 cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + 6985 prev.charAt(prev.length - 1), 6986 Pos(cur.line - 1, prev.length - 1), cur, "+transpose") 6987 } 6988 } 6989 } 6990 newSel.push(new Range(cur, cur)) 6991 } 6992 cm.setSelections(newSel) 6993 }); }, 6994 newlineAndIndent: function (cm) { return runInOp(cm, function () { 6995 var sels = cm.listSelections() 6996 for (var i = sels.length - 1; i >= 0; i--) 6997 { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input") } 6998 sels = cm.listSelections() 6999 for (var i$1 = 0; i$1 < sels.length; i$1++) 7000 { cm.indentLine(sels[i$1].from().line, null, true) } 7001 ensureCursorVisible(cm) 7002 }); }, 7003 openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, 7004 toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } 7005 } 7006 7007 7008 function lineStart(cm, lineN) { 7009 var line = getLine(cm.doc, lineN) 7010 var visual = visualLine(line) 7011 if (visual != line) { lineN = lineNo(visual) } 7012 return endOfLine(true, cm, visual, lineN, 1) 7013 } 7014 function lineEnd(cm, lineN) { 7015 var line = getLine(cm.doc, lineN) 7016 var visual = visualLineEnd(line) 7017 if (visual != line) { lineN = lineNo(visual) } 7018 return endOfLine(true, cm, line, lineN, -1) 7019 } 7020 function lineStartSmart(cm, pos) { 7021 var start = lineStart(cm, pos.line) 7022 var line = getLine(cm.doc, start.line) 7023 var order = getOrder(line, cm.doc.direction) 7024 if (!order || order[0].level == 0) { 7025 var firstNonWS = Math.max(0, line.text.search(/\S/)) 7026 var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch 7027 return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) 7028 } 7029 return start 7030 } 7031 7032 // Run a handler that was bound to a key. 7033 function doHandleBinding(cm, bound, dropShift) { 7034 if (typeof bound == "string") { 7035 bound = commands[bound] 7036 if (!bound) { return false } 7037 } 7038 // Ensure previous input has been read, so that the handler sees a 7039 // consistent view of the document 7040 cm.display.input.ensurePolled() 7041 var prevShift = cm.display.shift, done = false 7042 try { 7043 if (cm.isReadOnly()) { cm.state.suppressEdits = true } 7044 if (dropShift) { cm.display.shift = false } 7045 done = bound(cm) != Pass 7046 } finally { 7047 cm.display.shift = prevShift 7048 cm.state.suppressEdits = false 7049 } 7050 return done 7051 } 7052 7053 function lookupKeyForEditor(cm, name, handle) { 7054 for (var i = 0; i < cm.state.keyMaps.length; i++) { 7055 var result = lookupKey(name, cm.state.keyMaps[i], handle, cm) 7056 if (result) { return result } 7057 } 7058 return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) 7059 || lookupKey(name, cm.options.keyMap, handle, cm) 7060 } 7061 7062 // Note that, despite the name, this function is also used to check 7063 // for bound mouse clicks. 7064 7065 var stopSeq = new Delayed 7066 7067 function dispatchKey(cm, name, e, handle) { 7068 var seq = cm.state.keySeq 7069 if (seq) { 7070 if (isModifierKey(name)) { return "handled" } 7071 if (/\'$/.test(name)) 7072 { cm.state.keySeq = null } 7073 else 7074 { stopSeq.set(50, function () { 7075 if (cm.state.keySeq == seq) { 7076 cm.state.keySeq = null 7077 cm.display.input.reset() 7078 } 7079 }) } 7080 if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true } 7081 } 7082 return dispatchKeyInner(cm, name, e, handle) 7083 } 7084 7085 function dispatchKeyInner(cm, name, e, handle) { 7086 var result = lookupKeyForEditor(cm, name, handle) 7087 7088 if (result == "multi") 7089 { cm.state.keySeq = name } 7090 if (result == "handled") 7091 { signalLater(cm, "keyHandled", cm, name, e) } 7092 7093 if (result == "handled" || result == "multi") { 7094 e_preventDefault(e) 7095 restartBlink(cm) 7096 } 7097 7098 return !!result 7099 } 7100 7101 // Handle a key from the keydown event. 7102 function handleKeyBinding(cm, e) { 7103 var name = keyName(e, true) 7104 if (!name) { return false } 7105 7106 if (e.shiftKey && !cm.state.keySeq) { 7107 // First try to resolve full name (including 'Shift-'). Failing 7108 // that, see if there is a cursor-motion command (starting with 7109 // 'go') bound to the keyname without 'Shift-'. 7110 return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); }) 7111 || dispatchKey(cm, name, e, function (b) { 7112 if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) 7113 { return doHandleBinding(cm, b) } 7114 }) 7115 } else { 7116 return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); }) 7117 } 7118 } 7119 7120 // Handle a key from the keypress event 7121 function handleCharBinding(cm, e, ch) { 7122 return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); }) 7123 } 7124 7125 var lastStoppedKey = null 7126 function onKeyDown(e) { 7127 var cm = this 7128 cm.curOp.focus = activeElt() 7129 if (signalDOMEvent(cm, e)) { return } 7130 // IE does strange things with escape. 7131 if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false } 7132 var code = e.keyCode 7133 cm.display.shift = code == 16 || e.shiftKey 7134 var handled = handleKeyBinding(cm, e) 7135 if (presto) { 7136 lastStoppedKey = handled ? code : null 7137 // Opera has no cut event... we try to at least catch the key combo 7138 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) 7139 { cm.replaceSelection("", null, "cut") } 7140 } 7141 7142 // Turn mouse into crosshair when Alt is held on Mac. 7143 if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) 7144 { showCrossHair(cm) } 7145 } 7146 7147 function showCrossHair(cm) { 7148 var lineDiv = cm.display.lineDiv 7149 addClass(lineDiv, "CodeMirror-crosshair") 7150 7151 function up(e) { 7152 if (e.keyCode == 18 || !e.altKey) { 7153 rmClass(lineDiv, "CodeMirror-crosshair") 7154 off(document, "keyup", up) 7155 off(document, "mouseover", up) 7156 } 7157 } 7158 on(document, "keyup", up) 7159 on(document, "mouseover", up) 7160 } 7161 7162 function onKeyUp(e) { 7163 if (e.keyCode == 16) { this.doc.sel.shift = false } 7164 signalDOMEvent(this, e) 7165 } 7166 7167 function onKeyPress(e) { 7168 var cm = this 7169 if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return } 7170 var keyCode = e.keyCode, charCode = e.charCode 7171 if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} 7172 if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return } 7173 var ch = String.fromCharCode(charCode == null ? keyCode : charCode) 7174 // Some browsers fire keypress events for backspace 7175 if (ch == "\x08") { return } 7176 if (handleCharBinding(cm, e, ch)) { return } 7177 cm.display.input.onKeyPress(e) 7178 } 7179 7180 var DOUBLECLICK_DELAY = 400 7181 7182 var PastClick = function(time, pos, button) { 7183 this.time = time 7184 this.pos = pos 7185 this.button = button 7186 }; 7187 7188 PastClick.prototype.compare = function (time, pos, button) { 7189 return this.time + DOUBLECLICK_DELAY > time && 7190 cmp(pos, this.pos) == 0 && button == this.button 7191 }; 7192 7193 var lastClick; 7194 var lastDoubleClick; 7195 function clickRepeat(pos, button) { 7196 var now = +new Date 7197 if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { 7198 lastClick = lastDoubleClick = null 7199 return "triple" 7200 } else if (lastClick && lastClick.compare(now, pos, button)) { 7201 lastDoubleClick = new PastClick(now, pos, button) 7202 lastClick = null 7203 return "double" 7204 } else { 7205 lastClick = new PastClick(now, pos, button) 7206 lastDoubleClick = null 7207 return "single" 7208 } 7209 } 7210 7211 // A mouse down can be a single click, double click, triple click, 7212 // start of selection drag, start of text drag, new cursor 7213 // (ctrl-click), rectangle drag (alt-drag), or xwin 7214 // middle-click-paste. Or it might be a click on something we should 7215 // not interfere with, such as a scrollbar or widget. 7216 function onMouseDown(e) { 7217 var cm = this, display = cm.display 7218 if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return } 7219 display.input.ensurePolled() 7220 display.shift = e.shiftKey 7221 7222 if (eventInWidget(display, e)) { 7223 if (!webkit) { 7224 // Briefly turn off draggability, to allow widgets to do 7225 // normal dragging things. 7226 display.scroller.draggable = false 7227 setTimeout(function () { return display.scroller.draggable = true; }, 100) 7228 } 7229 return 7230 } 7231 if (clickInGutter(cm, e)) { return } 7232 var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single" 7233 window.focus() 7234 7235 // #3261: make sure, that we're not starting a second selection 7236 if (button == 1 && cm.state.selectingText) 7237 { cm.state.selectingText(e) } 7238 7239 if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return } 7240 7241 if (button == 1) { 7242 if (pos) { leftButtonDown(cm, pos, repeat, e) } 7243 else if (e_target(e) == display.scroller) { e_preventDefault(e) } 7244 } else if (button == 2) { 7245 if (pos) { extendSelection(cm.doc, pos) } 7246 setTimeout(function () { return display.input.focus(); }, 20) 7247 } else if (button == 3) { 7248 if (captureRightClick) { onContextMenu(cm, e) } 7249 else { delayBlurEvent(cm) } 7250 } 7251 } 7252 7253 function handleMappedButton(cm, button, pos, repeat, event) { 7254 var name = "Click" 7255 if (repeat == "double") { name = "Double" + name } 7256 else if (repeat == "triple") { name = "Triple" + name } 7257 name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name 7258 7259 return dispatchKey(cm, addModifierNames(name, event), event, function (bound) { 7260 if (typeof bound == "string") { bound = commands[bound] } 7261 if (!bound) { return false } 7262 var done = false 7263 try { 7264 if (cm.isReadOnly()) { cm.state.suppressEdits = true } 7265 done = bound(cm, pos) != Pass 7266 } finally { 7267 cm.state.suppressEdits = false 7268 } 7269 return done 7270 }) 7271 } 7272 7273 function configureMouse(cm, repeat, event) { 7274 var option = cm.getOption("configureMouse") 7275 var value = option ? option(cm, repeat, event) : {} 7276 if (value.unit == null) { 7277 var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey 7278 value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line" 7279 } 7280 if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey } 7281 if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey } 7282 if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey) } 7283 return value 7284 } 7285 7286 function leftButtonDown(cm, pos, repeat, event) { 7287 if (ie) { setTimeout(bind(ensureFocus, cm), 0) } 7288 else { cm.curOp.focus = activeElt() } 7289 7290 var behavior = configureMouse(cm, repeat, event) 7291 7292 var sel = cm.doc.sel, contained 7293 if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && 7294 repeat == "single" && (contained = sel.contains(pos)) > -1 && 7295 (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && 7296 (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) 7297 { leftButtonStartDrag(cm, event, pos, behavior) } 7298 else 7299 { leftButtonSelect(cm, event, pos, behavior) } 7300 } 7301 7302 // Start a text drag. When it ends, see if any dragging actually 7303 // happen, and treat as a click if it didn't. 7304 function leftButtonStartDrag(cm, event, pos, behavior) { 7305 var display = cm.display, moved = false 7306 var dragEnd = operation(cm, function (e) { 7307 if (webkit) { display.scroller.draggable = false } 7308 cm.state.draggingText = false 7309 off(document, "mouseup", dragEnd) 7310 off(document, "mousemove", mouseMove) 7311 off(display.scroller, "dragstart", dragStart) 7312 off(display.scroller, "drop", dragEnd) 7313 if (!moved) { 7314 e_preventDefault(e) 7315 if (!behavior.addNew) 7316 { extendSelection(cm.doc, pos, null, null, behavior.extend) } 7317 // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) 7318 if (webkit || ie && ie_version == 9) 7319 { setTimeout(function () {document.body.focus(); display.input.focus()}, 20) } 7320 else 7321 { display.input.focus() } 7322 } 7323 }) 7324 var mouseMove = function(e2) { 7325 moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10 7326 } 7327 var dragStart = function () { return moved = true; } 7328 // Let the drag handler handle this. 7329 if (webkit) { display.scroller.draggable = true } 7330 cm.state.draggingText = dragEnd 7331 dragEnd.copy = !behavior.moveOnDrag 7332 // IE's approach to draggable 7333 if (display.scroller.dragDrop) { display.scroller.dragDrop() } 7334 on(document, "mouseup", dragEnd) 7335 on(document, "mousemove", mouseMove) 7336 on(display.scroller, "dragstart", dragStart) 7337 on(display.scroller, "drop", dragEnd) 7338 7339 delayBlurEvent(cm) 7340 setTimeout(function () { return display.input.focus(); }, 20) 7341 } 7342 7343 function rangeForUnit(cm, pos, unit) { 7344 if (unit == "char") { return new Range(pos, pos) } 7345 if (unit == "word") { return cm.findWordAt(pos) } 7346 if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } 7347 var result = unit(cm, pos) 7348 return new Range(result.from, result.to) 7349 } 7350 7351 // Normal selection, as opposed to text dragging. 7352 function leftButtonSelect(cm, event, start, behavior) { 7353 var display = cm.display, doc = cm.doc 7354 e_preventDefault(event) 7355 7356 var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges 7357 if (behavior.addNew && !behavior.extend) { 7358 ourIndex = doc.sel.contains(start) 7359 if (ourIndex > -1) 7360 { ourRange = ranges[ourIndex] } 7361 else 7362 { ourRange = new Range(start, start) } 7363 } else { 7364 ourRange = doc.sel.primary() 7365 ourIndex = doc.sel.primIndex 7366 } 7367 7368 if (behavior.unit == "rectangle") { 7369 if (!behavior.addNew) { ourRange = new Range(start, start) } 7370 start = posFromMouse(cm, event, true, true) 7371 ourIndex = -1 7372 } else { 7373 var range = rangeForUnit(cm, start, behavior.unit) 7374 if (behavior.extend) 7375 { ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend) } 7376 else 7377 { ourRange = range } 7378 } 7379 7380 if (!behavior.addNew) { 7381 ourIndex = 0 7382 setSelection(doc, new Selection([ourRange], 0), sel_mouse) 7383 startSel = doc.sel 7384 } else if (ourIndex == -1) { 7385 ourIndex = ranges.length 7386 setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), 7387 {scroll: false, origin: "*mouse"}) 7388 } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { 7389 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), 7390 {scroll: false, origin: "*mouse"}) 7391 startSel = doc.sel 7392 } else { 7393 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse) 7394 } 7395 7396 var lastPos = start 7397 function extendTo(pos) { 7398 if (cmp(lastPos, pos) == 0) { return } 7399 lastPos = pos 7400 7401 if (behavior.unit == "rectangle") { 7402 var ranges = [], tabSize = cm.options.tabSize 7403 var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize) 7404 var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize) 7405 var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol) 7406 for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); 7407 line <= end; line++) { 7408 var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize) 7409 if (left == right) 7410 { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))) } 7411 else if (text.length > leftPos) 7412 { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))) } 7413 } 7414 if (!ranges.length) { ranges.push(new Range(start, start)) } 7415 setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), 7416 {origin: "*mouse", scroll: false}) 7417 cm.scrollIntoView(pos) 7418 } else { 7419 var oldRange = ourRange 7420 var range = rangeForUnit(cm, pos, behavior.unit) 7421 var anchor = oldRange.anchor, head 7422 if (cmp(range.anchor, anchor) > 0) { 7423 head = range.head 7424 anchor = minPos(oldRange.from(), range.anchor) 7425 } else { 7426 head = range.anchor 7427 anchor = maxPos(oldRange.to(), range.head) 7428 } 7429 var ranges$1 = startSel.ranges.slice(0) 7430 ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head)) 7431 setSelection(doc, normalizeSelection(ranges$1, ourIndex), sel_mouse) 7432 } 7433 } 7434 7435 var editorSize = display.wrapper.getBoundingClientRect() 7436 // Used to ensure timeout re-tries don't fire when another extend 7437 // happened in the meantime (clearTimeout isn't reliable -- at 7438 // least on Chrome, the timeouts still happen even when cleared, 7439 // if the clear happens after their scheduled firing time). 7440 var counter = 0 7441 7442 function extend(e) { 7443 var curCount = ++counter 7444 var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle") 7445 if (!cur) { return } 7446 if (cmp(cur, lastPos) != 0) { 7447 cm.curOp.focus = activeElt() 7448 extendTo(cur) 7449 var visible = visibleLines(display, doc) 7450 if (cur.line >= visible.to || cur.line < visible.from) 7451 { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e) }}), 150) } 7452 } else { 7453 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0 7454 if (outside) { setTimeout(operation(cm, function () { 7455 if (counter != curCount) { return } 7456 display.scroller.scrollTop += outside 7457 extend(e) 7458 }), 50) } 7459 } 7460 } 7461 7462 function done(e) { 7463 cm.state.selectingText = false 7464 counter = Infinity 7465 e_preventDefault(e) 7466 display.input.focus() 7467 off(document, "mousemove", move) 7468 off(document, "mouseup", up) 7469 doc.history.lastSelOrigin = null 7470 } 7471 7472 var move = operation(cm, function (e) { 7473 if (!e_button(e)) { done(e) } 7474 else { extend(e) } 7475 }) 7476 var up = operation(cm, done) 7477 cm.state.selectingText = up 7478 on(document, "mousemove", move) 7479 on(document, "mouseup", up) 7480 } 7481 7482 // Used when mouse-selecting to adjust the anchor to the proper side 7483 // of a bidi jump depending on the visual position of the head. 7484 function bidiSimplify(cm, range) { 7485 var anchor = range.anchor; 7486 var head = range.head; 7487 var anchorLine = getLine(cm.doc, anchor.line) 7488 if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range } 7489 var order = getOrder(anchorLine) 7490 if (!order) { return range } 7491 var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index] 7492 if (part.from != anchor.ch && part.to != anchor.ch) { return range } 7493 var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1) 7494 if (boundary == 0 || boundary == order.length) { return range } 7495 7496 // Compute the relative visual position of the head compared to the 7497 // anchor (<0 is to the left, >0 to the right) 7498 var leftSide 7499 if (head.line != anchor.line) { 7500 leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0 7501 } else { 7502 var headIndex = getBidiPartAt(order, head.ch, head.sticky) 7503 var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1) 7504 if (headIndex == boundary - 1 || headIndex == boundary) 7505 { leftSide = dir < 0 } 7506 else 7507 { leftSide = dir > 0 } 7508 } 7509 7510 var usePart = order[boundary + (leftSide ? -1 : 0)] 7511 var from = leftSide == (usePart.level == 1) 7512 var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before" 7513 return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head) 7514 } 7515 7516 7517 // Determines whether an event happened in the gutter, and fires the 7518 // handlers for the corresponding event. 7519 function gutterEvent(cm, e, type, prevent) { 7520 var mX, mY 7521 if (e.touches) { 7522 mX = e.touches[0].clientX 7523 mY = e.touches[0].clientY 7524 } else { 7525 try { mX = e.clientX; mY = e.clientY } 7526 catch(e) { return false } 7527 } 7528 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false } 7529 if (prevent) { e_preventDefault(e) } 7530 7531 var display = cm.display 7532 var lineBox = display.lineDiv.getBoundingClientRect() 7533 7534 if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) } 7535 mY -= lineBox.top - display.viewOffset 7536 7537 for (var i = 0; i < cm.options.gutters.length; ++i) { 7538 var g = display.gutters.childNodes[i] 7539 if (g && g.getBoundingClientRect().right >= mX) { 7540 var line = lineAtHeight(cm.doc, mY) 7541 var gutter = cm.options.gutters[i] 7542 signal(cm, type, cm, line, gutter, e) 7543 return e_defaultPrevented(e) 7544 } 7545 } 7546 } 7547 7548 function clickInGutter(cm, e) { 7549 return gutterEvent(cm, e, "gutterClick", true) 7550 } 7551 7552 // CONTEXT MENU HANDLING 7553 7554 // To make the context menu work, we need to briefly unhide the 7555 // textarea (making it as unobtrusive as possible) to let the 7556 // right-click take effect on it. 7557 function onContextMenu(cm, e) { 7558 if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } 7559 if (signalDOMEvent(cm, e, "contextmenu")) { return } 7560 cm.display.input.onContextMenu(e) 7561 } 7562 7563 function contextMenuInGutter(cm, e) { 7564 if (!hasHandler(cm, "gutterContextMenu")) { return false } 7565 return gutterEvent(cm, e, "gutterContextMenu", false) 7566 } 7567 7568 function themeChanged(cm) { 7569 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + 7570 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-") 7571 clearCaches(cm) 7572 } 7573 7574 var Init = {toString: function(){return "CodeMirror.Init"}} 7575 7576 var defaults = {} 7577 var optionHandlers = {} 7578 7579 function defineOptions(CodeMirror) { 7580 var optionHandlers = CodeMirror.optionHandlers 7581 7582 function option(name, deflt, handle, notOnInit) { 7583 CodeMirror.defaults[name] = deflt 7584 if (handle) { optionHandlers[name] = 7585 notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old) }} : handle } 7586 } 7587 7588 CodeMirror.defineOption = option 7589 7590 // Passed to option handlers when there is no old value. 7591 CodeMirror.Init = Init 7592 7593 // These two are, on init, called from the constructor because they 7594 // have to be initialized before the editor can start at all. 7595 option("value", "", function (cm, val) { return cm.setValue(val); }, true) 7596 option("mode", null, function (cm, val) { 7597 cm.doc.modeOption = val 7598 loadMode(cm) 7599 }, true) 7600 7601 option("indentUnit", 2, loadMode, true) 7602 option("indentWithTabs", false) 7603 option("smartIndent", true) 7604 option("tabSize", 4, function (cm) { 7605 resetModeState(cm) 7606 clearCaches(cm) 7607 regChange(cm) 7608 }, true) 7609 option("lineSeparator", null, function (cm, val) { 7610 cm.doc.lineSep = val 7611 if (!val) { return } 7612 var newBreaks = [], lineNo = cm.doc.first 7613 cm.doc.iter(function (line) { 7614 for (var pos = 0;;) { 7615 var found = line.text.indexOf(val, pos) 7616 if (found == -1) { break } 7617 pos = found + val.length 7618 newBreaks.push(Pos(lineNo, found)) 7619 } 7620 lineNo++ 7621 }) 7622 for (var i = newBreaks.length - 1; i >= 0; i--) 7623 { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) } 7624 }) 7625 option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) { 7626 cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g") 7627 if (old != Init) { cm.refresh() } 7628 }) 7629 option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true) 7630 option("electricChars", true) 7631 option("inputStyle", mobile ? "contenteditable" : "textarea", function () { 7632 throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME 7633 }, true) 7634 option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true) 7635 option("rtlMoveVisually", !windows) 7636 option("wholeLineUpdateBefore", true) 7637 7638 option("theme", "default", function (cm) { 7639 themeChanged(cm) 7640 guttersChanged(cm) 7641 }, true) 7642 option("keyMap", "default", function (cm, val, old) { 7643 var next = getKeyMap(val) 7644 var prev = old != Init && getKeyMap(old) 7645 if (prev && prev.detach) { prev.detach(cm, next) } 7646 if (next.attach) { next.attach(cm, prev || null) } 7647 }) 7648 option("extraKeys", null) 7649 option("configureMouse", null) 7650 7651 option("lineWrapping", false, wrappingChanged, true) 7652 option("gutters", [], function (cm) { 7653 setGuttersForLineNumbers(cm.options) 7654 guttersChanged(cm) 7655 }, true) 7656 option("fixedGutter", true, function (cm, val) { 7657 cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0" 7658 cm.refresh() 7659 }, true) 7660 option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true) 7661 option("scrollbarStyle", "native", function (cm) { 7662 initScrollbars(cm) 7663 updateScrollbars(cm) 7664 cm.display.scrollbars.setScrollTop(cm.doc.scrollTop) 7665 cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft) 7666 }, true) 7667 option("lineNumbers", false, function (cm) { 7668 setGuttersForLineNumbers(cm.options) 7669 guttersChanged(cm) 7670 }, true) 7671 option("firstLineNumber", 1, guttersChanged, true) 7672 option("lineNumberFormatter", function (integer) { return integer; }, guttersChanged, true) 7673 option("showCursorWhenSelecting", false, updateSelection, true) 7674 7675 option("resetSelectionOnContextMenu", true) 7676 option("lineWiseCopyCut", true) 7677 option("pasteLinesPerSelection", true) 7678 7679 option("readOnly", false, function (cm, val) { 7680 if (val == "nocursor") { 7681 onBlur(cm) 7682 cm.display.input.blur() 7683 } 7684 cm.display.input.readOnlyChanged(val) 7685 }) 7686 option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset() }}, true) 7687 option("dragDrop", true, dragDropChanged) 7688 option("allowDropFileTypes", null) 7689 7690 option("cursorBlinkRate", 530) 7691 option("cursorScrollMargin", 0) 7692 option("cursorHeight", 1, updateSelection, true) 7693 option("singleCursorHeightPerLine", true, updateSelection, true) 7694 option("workTime", 100) 7695 option("workDelay", 100) 7696 option("flattenSpans", true, resetModeState, true) 7697 option("addModeClass", false, resetModeState, true) 7698 option("pollInterval", 100) 7699 option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; }) 7700 option("historyEventDelay", 1250) 7701 option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true) 7702 option("maxHighlightLength", 10000, resetModeState, true) 7703 option("moveInputWithCursor", true, function (cm, val) { 7704 if (!val) { cm.display.input.resetPosition() } 7705 }) 7706 7707 option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; }) 7708 option("autofocus", null) 7709 option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true) 7710 } 7711 7712 function guttersChanged(cm) { 7713 updateGutters(cm) 7714 regChange(cm) 7715 alignHorizontally(cm) 7716 } 7717 7718 function dragDropChanged(cm, value, old) { 7719 var wasOn = old && old != Init 7720 if (!value != !wasOn) { 7721 var funcs = cm.display.dragFunctions 7722 var toggle = value ? on : off 7723 toggle(cm.display.scroller, "dragstart", funcs.start) 7724 toggle(cm.display.scroller, "dragenter", funcs.enter) 7725 toggle(cm.display.scroller, "dragover", funcs.over) 7726 toggle(cm.display.scroller, "dragleave", funcs.leave) 7727 toggle(cm.display.scroller, "drop", funcs.drop) 7728 } 7729 } 7730 7731 function wrappingChanged(cm) { 7732 if (cm.options.lineWrapping) { 7733 addClass(cm.display.wrapper, "CodeMirror-wrap") 7734 cm.display.sizer.style.minWidth = "" 7735 cm.display.sizerWidth = null 7736 } else { 7737 rmClass(cm.display.wrapper, "CodeMirror-wrap") 7738 findMaxLine(cm) 7739 } 7740 estimateLineHeights(cm) 7741 regChange(cm) 7742 clearCaches(cm) 7743 setTimeout(function () { return updateScrollbars(cm); }, 100) 7744 } 7745 7746 // A CodeMirror instance represents an editor. This is the object 7747 // that user code is usually dealing with. 7748 7749 function CodeMirror(place, options) { 7750 var this$1 = this; 7751 7752 if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) } 7753 7754 this.options = options = options ? copyObj(options) : {} 7755 // Determine effective options based on given values and defaults. 7756 copyObj(defaults, options, false) 7757 setGuttersForLineNumbers(options) 7758 7759 var doc = options.value 7760 if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction) } 7761 this.doc = doc 7762 7763 var input = new CodeMirror.inputStyles[options.inputStyle](this) 7764 var display = this.display = new Display(place, doc, input) 7765 display.wrapper.CodeMirror = this 7766 updateGutters(this) 7767 themeChanged(this) 7768 if (options.lineWrapping) 7769 { this.display.wrapper.className += " CodeMirror-wrap" } 7770 initScrollbars(this) 7771 7772 this.state = { 7773 keyMaps: [], // stores maps added by addKeyMap 7774 overlays: [], // highlighting overlays, as added by addOverlay 7775 modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info 7776 overwrite: false, 7777 delayingBlurEvent: false, 7778 focused: false, 7779 suppressEdits: false, // used to disable editing during key handlers when in readOnly mode 7780 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll 7781 selectingText: false, 7782 draggingText: false, 7783 highlight: new Delayed(), // stores highlight worker timeout 7784 keySeq: null, // Unfinished key sequence 7785 specialChars: null 7786 } 7787 7788 if (options.autofocus && !mobile) { display.input.focus() } 7789 7790 // Override magic textarea content restore that IE sometimes does 7791 // on our hidden textarea on reload 7792 if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20) } 7793 7794 registerEventHandlers(this) 7795 ensureGlobalHandlers() 7796 7797 startOperation(this) 7798 this.curOp.forceUpdate = true 7799 attachDoc(this, doc) 7800 7801 if ((options.autofocus && !mobile) || this.hasFocus()) 7802 { setTimeout(bind(onFocus, this), 20) } 7803 else 7804 { onBlur(this) } 7805 7806 for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) 7807 { optionHandlers[opt](this$1, options[opt], Init) } } 7808 maybeUpdateLineNumberWidth(this) 7809 if (options.finishInit) { options.finishInit(this) } 7810 for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1) } 7811 endOperation(this) 7812 // Suppress optimizelegibility in Webkit, since it breaks text 7813 // measuring on line wrapping boundaries. 7814 if (webkit && options.lineWrapping && 7815 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") 7816 { display.lineDiv.style.textRendering = "auto" } 7817 } 7818 7819 // The default configuration options. 7820 CodeMirror.defaults = defaults 7821 // Functions to run when options are changed. 7822 CodeMirror.optionHandlers = optionHandlers 7823 7824 // Attach the necessary event handlers when initializing the editor 7825 function registerEventHandlers(cm) { 7826 var d = cm.display 7827 on(d.scroller, "mousedown", operation(cm, onMouseDown)) 7828 // Older IE's will not fire a second mousedown for a double click 7829 if (ie && ie_version < 11) 7830 { on(d.scroller, "dblclick", operation(cm, function (e) { 7831 if (signalDOMEvent(cm, e)) { return } 7832 var pos = posFromMouse(cm, e) 7833 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return } 7834 e_preventDefault(e) 7835 var word = cm.findWordAt(pos) 7836 extendSelection(cm.doc, word.anchor, word.head) 7837 })) } 7838 else 7839 { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }) } 7840 // Some browsers fire contextmenu *after* opening the menu, at 7841 // which point we can't mess with it anymore. Context menu is 7842 // handled in onMouseDown for these browsers. 7843 if (!captureRightClick) { on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }) } 7844 7845 // Used to suppress mouse event handling when a touch happens 7846 var touchFinished, prevTouch = {end: 0} 7847 function finishTouch() { 7848 if (d.activeTouch) { 7849 touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000) 7850 prevTouch = d.activeTouch 7851 prevTouch.end = +new Date 7852 } 7853 } 7854 function isMouseLikeTouchEvent(e) { 7855 if (e.touches.length != 1) { return false } 7856 var touch = e.touches[0] 7857 return touch.radiusX <= 1 && touch.radiusY <= 1 7858 } 7859 function farAway(touch, other) { 7860 if (other.left == null) { return true } 7861 var dx = other.left - touch.left, dy = other.top - touch.top 7862 return dx * dx + dy * dy > 20 * 20 7863 } 7864 on(d.scroller, "touchstart", function (e) { 7865 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { 7866 d.input.ensurePolled() 7867 clearTimeout(touchFinished) 7868 var now = +new Date 7869 d.activeTouch = {start: now, moved: false, 7870 prev: now - prevTouch.end <= 300 ? prevTouch : null} 7871 if (e.touches.length == 1) { 7872 d.activeTouch.left = e.touches[0].pageX 7873 d.activeTouch.top = e.touches[0].pageY 7874 } 7875 } 7876 }) 7877 on(d.scroller, "touchmove", function () { 7878 if (d.activeTouch) { d.activeTouch.moved = true } 7879 }) 7880 on(d.scroller, "touchend", function (e) { 7881 var touch = d.activeTouch 7882 if (touch && !eventInWidget(d, e) && touch.left != null && 7883 !touch.moved && new Date - touch.start < 300) { 7884 var pos = cm.coordsChar(d.activeTouch, "page"), range 7885 if (!touch.prev || farAway(touch, touch.prev)) // Single tap 7886 { range = new Range(pos, pos) } 7887 else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap 7888 { range = cm.findWordAt(pos) } 7889 else // Triple tap 7890 { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } 7891 cm.setSelection(range.anchor, range.head) 7892 cm.focus() 7893 e_preventDefault(e) 7894 } 7895 finishTouch() 7896 }) 7897 on(d.scroller, "touchcancel", finishTouch) 7898 7899 // Sync scrolling between fake scrollbars and real scrollable 7900 // area, ensure viewport is updated when scrolling. 7901 on(d.scroller, "scroll", function () { 7902 if (d.scroller.clientHeight) { 7903 updateScrollTop(cm, d.scroller.scrollTop) 7904 setScrollLeft(cm, d.scroller.scrollLeft, true) 7905 signal(cm, "scroll", cm) 7906 } 7907 }) 7908 7909 // Listen to wheel events in order to try and update the viewport on time. 7910 on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); }) 7911 on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); }) 7912 7913 // Prevent wrapper from ever scrolling 7914 on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }) 7915 7916 d.dragFunctions = { 7917 enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e) }}, 7918 over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e) }}, 7919 start: function (e) { return onDragStart(cm, e); }, 7920 drop: operation(cm, onDrop), 7921 leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }} 7922 } 7923 7924 var inp = d.input.getField() 7925 on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); }) 7926 on(inp, "keydown", operation(cm, onKeyDown)) 7927 on(inp, "keypress", operation(cm, onKeyPress)) 7928 on(inp, "focus", function (e) { return onFocus(cm, e); }) 7929 on(inp, "blur", function (e) { return onBlur(cm, e); }) 7930 } 7931 7932 var initHooks = [] 7933 CodeMirror.defineInitHook = function (f) { return initHooks.push(f); } 7934 7935 // Indent the given line. The how parameter can be "smart", 7936 // "add"/null, "subtract", or "prev". When aggressive is false 7937 // (typically set to true for forced single-line indents), empty 7938 // lines are not indented, and places where the mode returns Pass 7939 // are left alone. 7940 function indentLine(cm, n, how, aggressive) { 7941 var doc = cm.doc, state 7942 if (how == null) { how = "add" } 7943 if (how == "smart") { 7944 // Fall back to "prev" when the mode doesn't have an indentation 7945 // method. 7946 if (!doc.mode.indent) { how = "prev" } 7947 else { state = getContextBefore(cm, n).state } 7948 } 7949 7950 var tabSize = cm.options.tabSize 7951 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize) 7952 if (line.stateAfter) { line.stateAfter = null } 7953 var curSpaceString = line.text.match(/^\s*/)[0], indentation 7954 if (!aggressive && !/\S/.test(line.text)) { 7955 indentation = 0 7956 how = "not" 7957 } else if (how == "smart") { 7958 indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text) 7959 if (indentation == Pass || indentation > 150) { 7960 if (!aggressive) { return } 7961 how = "prev" 7962 } 7963 } 7964 if (how == "prev") { 7965 if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize) } 7966 else { indentation = 0 } 7967 } else if (how == "add") { 7968 indentation = curSpace + cm.options.indentUnit 7969 } else if (how == "subtract") { 7970 indentation = curSpace - cm.options.indentUnit 7971 } else if (typeof how == "number") { 7972 indentation = curSpace + how 7973 } 7974 indentation = Math.max(0, indentation) 7975 7976 var indentString = "", pos = 0 7977 if (cm.options.indentWithTabs) 7978 { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t"} } 7979 if (pos < indentation) { indentString += spaceStr(indentation - pos) } 7980 7981 if (indentString != curSpaceString) { 7982 replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input") 7983 line.stateAfter = null 7984 return true 7985 } else { 7986 // Ensure that, if the cursor was in the whitespace at the start 7987 // of the line, it is moved to the end of that space. 7988 for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { 7989 var range = doc.sel.ranges[i$1] 7990 if (range.head.line == n && range.head.ch < curSpaceString.length) { 7991 var pos$1 = Pos(n, curSpaceString.length) 7992 replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)) 7993 break 7994 } 7995 } 7996 } 7997 } 7998 7999 // This will be set to a {lineWise: bool, text: [string]} object, so 8000 // that, when pasting, we know what kind of selections the copied 8001 // text was made out of. 8002 var lastCopied = null 8003 8004 function setLastCopied(newLastCopied) { 8005 lastCopied = newLastCopied 8006 } 8007 8008 function applyTextInput(cm, inserted, deleted, sel, origin) { 8009 var doc = cm.doc 8010 cm.display.shift = false 8011 if (!sel) { sel = doc.sel } 8012 8013 var paste = cm.state.pasteIncoming || origin == "paste" 8014 var textLines = splitLinesAuto(inserted), multiPaste = null 8015 // When pasing N lines into N selections, insert one line per selection 8016 if (paste && sel.ranges.length > 1) { 8017 if (lastCopied && lastCopied.text.join("\n") == inserted) { 8018 if (sel.ranges.length % lastCopied.text.length == 0) { 8019 multiPaste = [] 8020 for (var i = 0; i < lastCopied.text.length; i++) 8021 { multiPaste.push(doc.splitLines(lastCopied.text[i])) } 8022 } 8023 } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { 8024 multiPaste = map(textLines, function (l) { return [l]; }) 8025 } 8026 } 8027 8028 var updateInput 8029 // Normal behavior is to insert the new text into every selection 8030 for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { 8031 var range = sel.ranges[i$1] 8032 var from = range.from(), to = range.to() 8033 if (range.empty()) { 8034 if (deleted && deleted > 0) // Handle deletion 8035 { from = Pos(from.line, from.ch - deleted) } 8036 else if (cm.state.overwrite && !paste) // Handle overwrite 8037 { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)) } 8038 else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) 8039 { from = to = Pos(from.line, 0) } 8040 } 8041 updateInput = cm.curOp.updateInput 8042 var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, 8043 origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")} 8044 makeChange(cm.doc, changeEvent) 8045 signalLater(cm, "inputRead", cm, changeEvent) 8046 } 8047 if (inserted && !paste) 8048 { triggerElectric(cm, inserted) } 8049 8050 ensureCursorVisible(cm) 8051 cm.curOp.updateInput = updateInput 8052 cm.curOp.typing = true 8053 cm.state.pasteIncoming = cm.state.cutIncoming = false 8054 } 8055 8056 function handlePaste(e, cm) { 8057 var pasted = e.clipboardData && e.clipboardData.getData("Text") 8058 if (pasted) { 8059 e.preventDefault() 8060 if (!cm.isReadOnly() && !cm.options.disableInput) 8061 { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }) } 8062 return true 8063 } 8064 } 8065 8066 function triggerElectric(cm, inserted) { 8067 // When an 'electric' character is inserted, immediately trigger a reindent 8068 if (!cm.options.electricChars || !cm.options.smartIndent) { return } 8069 var sel = cm.doc.sel 8070 8071 for (var i = sel.ranges.length - 1; i >= 0; i--) { 8072 var range = sel.ranges[i] 8073 if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) { continue } 8074 var mode = cm.getModeAt(range.head) 8075 var indented = false 8076 if (mode.electricChars) { 8077 for (var j = 0; j < mode.electricChars.length; j++) 8078 { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { 8079 indented = indentLine(cm, range.head.line, "smart") 8080 break 8081 } } 8082 } else if (mode.electricInput) { 8083 if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) 8084 { indented = indentLine(cm, range.head.line, "smart") } 8085 } 8086 if (indented) { signalLater(cm, "electricInput", cm, range.head.line) } 8087 } 8088 } 8089 8090 function copyableRanges(cm) { 8091 var text = [], ranges = [] 8092 for (var i = 0; i < cm.doc.sel.ranges.length; i++) { 8093 var line = cm.doc.sel.ranges[i].head.line 8094 var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)} 8095 ranges.push(lineRange) 8096 text.push(cm.getRange(lineRange.anchor, lineRange.head)) 8097 } 8098 return {text: text, ranges: ranges} 8099 } 8100 8101 function disableBrowserMagic(field, spellcheck) { 8102 field.setAttribute("autocorrect", "off") 8103 field.setAttribute("autocapitalize", "off") 8104 field.setAttribute("spellcheck", !!spellcheck) 8105 } 8106 8107 function hiddenTextarea() { 8108 var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none") 8109 var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;") 8110 // The textarea is kept positioned near the cursor to prevent the 8111 // fact that it'll be scrolled into view on input from scrolling 8112 // our fake cursor out of view. On webkit, when wrap=off, paste is 8113 // very slow. So make the area wide instead. 8114 if (webkit) { te.style.width = "1000px" } 8115 else { te.setAttribute("wrap", "off") } 8116 // If border: 0; -- iOS fails to open keyboard (issue #1287) 8117 if (ios) { te.style.border = "1px solid black" } 8118 disableBrowserMagic(te) 8119 return div 8120 } 8121 8122 // The publicly visible API. Note that methodOp(f) means 8123 // 'wrap f in an operation, performed on its `this` parameter'. 8124 8125 // This is not the complete set of editor methods. Most of the 8126 // methods defined on the Doc type are also injected into 8127 // CodeMirror.prototype, for backwards compatibility and 8128 // convenience. 8129 8130 function addEditorMethods(CodeMirror) { 8131 var optionHandlers = CodeMirror.optionHandlers 8132 8133 var helpers = CodeMirror.helpers = {} 8134 8135 CodeMirror.prototype = { 8136 constructor: CodeMirror, 8137 focus: function(){window.focus(); this.display.input.focus()}, 8138 8139 setOption: function(option, value) { 8140 var options = this.options, old = options[option] 8141 if (options[option] == value && option != "mode") { return } 8142 options[option] = value 8143 if (optionHandlers.hasOwnProperty(option)) 8144 { operation(this, optionHandlers[option])(this, value, old) } 8145 signal(this, "optionChange", this, option) 8146 }, 8147 8148 getOption: function(option) {return this.options[option]}, 8149 getDoc: function() {return this.doc}, 8150 8151 addKeyMap: function(map, bottom) { 8152 this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)) 8153 }, 8154 removeKeyMap: function(map) { 8155 var maps = this.state.keyMaps 8156 for (var i = 0; i < maps.length; ++i) 8157 { if (maps[i] == map || maps[i].name == map) { 8158 maps.splice(i, 1) 8159 return true 8160 } } 8161 }, 8162 8163 addOverlay: methodOp(function(spec, options) { 8164 var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec) 8165 if (mode.startState) { throw new Error("Overlays may not be stateful.") } 8166 insertSorted(this.state.overlays, 8167 {mode: mode, modeSpec: spec, opaque: options && options.opaque, 8168 priority: (options && options.priority) || 0}, 8169 function (overlay) { return overlay.priority; }) 8170 this.state.modeGen++ 8171 regChange(this) 8172 }), 8173 removeOverlay: methodOp(function(spec) { 8174 var this$1 = this; 8175 8176 var overlays = this.state.overlays 8177 for (var i = 0; i < overlays.length; ++i) { 8178 var cur = overlays[i].modeSpec 8179 if (cur == spec || typeof spec == "string" && cur.name == spec) { 8180 overlays.splice(i, 1) 8181 this$1.state.modeGen++ 8182 regChange(this$1) 8183 return 8184 } 8185 } 8186 }), 8187 8188 indentLine: methodOp(function(n, dir, aggressive) { 8189 if (typeof dir != "string" && typeof dir != "number") { 8190 if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev" } 8191 else { dir = dir ? "add" : "subtract" } 8192 } 8193 if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive) } 8194 }), 8195 indentSelection: methodOp(function(how) { 8196 var this$1 = this; 8197 8198 var ranges = this.doc.sel.ranges, end = -1 8199 for (var i = 0; i < ranges.length; i++) { 8200 var range = ranges[i] 8201 if (!range.empty()) { 8202 var from = range.from(), to = range.to() 8203 var start = Math.max(end, from.line) 8204 end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1 8205 for (var j = start; j < end; ++j) 8206 { indentLine(this$1, j, how) } 8207 var newRanges = this$1.doc.sel.ranges 8208 if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) 8209 { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll) } 8210 } else if (range.head.line > end) { 8211 indentLine(this$1, range.head.line, how, true) 8212 end = range.head.line 8213 if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1) } 8214 } 8215 } 8216 }), 8217 8218 // Fetch the parser token for a given character. Useful for hacks 8219 // that want to inspect the mode state (say, for completion). 8220 getTokenAt: function(pos, precise) { 8221 return takeToken(this, pos, precise) 8222 }, 8223 8224 getLineTokens: function(line, precise) { 8225 return takeToken(this, Pos(line), precise, true) 8226 }, 8227 8228 getTokenTypeAt: function(pos) { 8229 pos = clipPos(this.doc, pos) 8230 var styles = getLineStyles(this, getLine(this.doc, pos.line)) 8231 var before = 0, after = (styles.length - 1) / 2, ch = pos.ch 8232 var type 8233 if (ch == 0) { type = styles[2] } 8234 else { for (;;) { 8235 var mid = (before + after) >> 1 8236 if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid } 8237 else if (styles[mid * 2 + 1] < ch) { before = mid + 1 } 8238 else { type = styles[mid * 2 + 2]; break } 8239 } } 8240 var cut = type ? type.indexOf("overlay ") : -1 8241 return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) 8242 }, 8243 8244 getModeAt: function(pos) { 8245 var mode = this.doc.mode 8246 if (!mode.innerMode) { return mode } 8247 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode 8248 }, 8249 8250 getHelper: function(pos, type) { 8251 return this.getHelpers(pos, type)[0] 8252 }, 8253 8254 getHelpers: function(pos, type) { 8255 var this$1 = this; 8256 8257 var found = [] 8258 if (!helpers.hasOwnProperty(type)) { return found } 8259 var help = helpers[type], mode = this.getModeAt(pos) 8260 if (typeof mode[type] == "string") { 8261 if (help[mode[type]]) { found.push(help[mode[type]]) } 8262 } else if (mode[type]) { 8263 for (var i = 0; i < mode[type].length; i++) { 8264 var val = help[mode[type][i]] 8265 if (val) { found.push(val) } 8266 } 8267 } else if (mode.helperType && help[mode.helperType]) { 8268 found.push(help[mode.helperType]) 8269 } else if (help[mode.name]) { 8270 found.push(help[mode.name]) 8271 } 8272 for (var i$1 = 0; i$1 < help._global.length; i$1++) { 8273 var cur = help._global[i$1] 8274 if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1) 8275 { found.push(cur.val) } 8276 } 8277 return found 8278 }, 8279 8280 getStateAfter: function(line, precise) { 8281 var doc = this.doc 8282 line = clipLine(doc, line == null ? doc.first + doc.size - 1: line) 8283 return getContextBefore(this, line + 1, precise).state 8284 }, 8285 8286 cursorCoords: function(start, mode) { 8287 var pos, range = this.doc.sel.primary() 8288 if (start == null) { pos = range.head } 8289 else if (typeof start == "object") { pos = clipPos(this.doc, start) } 8290 else { pos = start ? range.from() : range.to() } 8291 return cursorCoords(this, pos, mode || "page") 8292 }, 8293 8294 charCoords: function(pos, mode) { 8295 return charCoords(this, clipPos(this.doc, pos), mode || "page") 8296 }, 8297 8298 coordsChar: function(coords, mode) { 8299 coords = fromCoordSystem(this, coords, mode || "page") 8300 return coordsChar(this, coords.left, coords.top) 8301 }, 8302 8303 lineAtHeight: function(height, mode) { 8304 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top 8305 return lineAtHeight(this.doc, height + this.display.viewOffset) 8306 }, 8307 heightAtLine: function(line, mode, includeWidgets) { 8308 var end = false, lineObj 8309 if (typeof line == "number") { 8310 var last = this.doc.first + this.doc.size - 1 8311 if (line < this.doc.first) { line = this.doc.first } 8312 else if (line > last) { line = last; end = true } 8313 lineObj = getLine(this.doc, line) 8314 } else { 8315 lineObj = line 8316 } 8317 return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + 8318 (end ? this.doc.height - heightAtLine(lineObj) : 0) 8319 }, 8320 8321 defaultTextHeight: function() { return textHeight(this.display) }, 8322 defaultCharWidth: function() { return charWidth(this.display) }, 8323 8324 getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, 8325 8326 addWidget: function(pos, node, scroll, vert, horiz) { 8327 var display = this.display 8328 pos = cursorCoords(this, clipPos(this.doc, pos)) 8329 var top = pos.bottom, left = pos.left 8330 node.style.position = "absolute" 8331 node.setAttribute("cm-ignore-events", "true") 8332 this.display.input.setUneditable(node) 8333 display.sizer.appendChild(node) 8334 if (vert == "over") { 8335 top = pos.top 8336 } else if (vert == "above" || vert == "near") { 8337 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), 8338 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth) 8339 // Default to positioning above (if specified and possible); otherwise default to positioning below 8340 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) 8341 { top = pos.top - node.offsetHeight } 8342 else if (pos.bottom + node.offsetHeight <= vspace) 8343 { top = pos.bottom } 8344 if (left + node.offsetWidth > hspace) 8345 { left = hspace - node.offsetWidth } 8346 } 8347 node.style.top = top + "px" 8348 node.style.left = node.style.right = "" 8349 if (horiz == "right") { 8350 left = display.sizer.clientWidth - node.offsetWidth 8351 node.style.right = "0px" 8352 } else { 8353 if (horiz == "left") { left = 0 } 8354 else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2 } 8355 node.style.left = left + "px" 8356 } 8357 if (scroll) 8358 { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}) } 8359 }, 8360 8361 triggerOnKeyDown: methodOp(onKeyDown), 8362 triggerOnKeyPress: methodOp(onKeyPress), 8363 triggerOnKeyUp: onKeyUp, 8364 triggerOnMouseDown: methodOp(onMouseDown), 8365 8366 execCommand: function(cmd) { 8367 if (commands.hasOwnProperty(cmd)) 8368 { return commands[cmd].call(null, this) } 8369 }, 8370 8371 triggerElectric: methodOp(function(text) { triggerElectric(this, text) }), 8372 8373 findPosH: function(from, amount, unit, visually) { 8374 var this$1 = this; 8375 8376 var dir = 1 8377 if (amount < 0) { dir = -1; amount = -amount } 8378 var cur = clipPos(this.doc, from) 8379 for (var i = 0; i < amount; ++i) { 8380 cur = findPosH(this$1.doc, cur, dir, unit, visually) 8381 if (cur.hitSide) { break } 8382 } 8383 return cur 8384 }, 8385 8386 moveH: methodOp(function(dir, unit) { 8387 var this$1 = this; 8388 8389 this.extendSelectionsBy(function (range) { 8390 if (this$1.display.shift || this$1.doc.extend || range.empty()) 8391 { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rtlMoveVisually) } 8392 else 8393 { return dir < 0 ? range.from() : range.to() } 8394 }, sel_move) 8395 }), 8396 8397 deleteH: methodOp(function(dir, unit) { 8398 var sel = this.doc.sel, doc = this.doc 8399 if (sel.somethingSelected()) 8400 { doc.replaceSelection("", null, "+delete") } 8401 else 8402 { deleteNearSelection(this, function (range) { 8403 var other = findPosH(doc, range.head, dir, unit, false) 8404 return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other} 8405 }) } 8406 }), 8407 8408 findPosV: function(from, amount, unit, goalColumn) { 8409 var this$1 = this; 8410 8411 var dir = 1, x = goalColumn 8412 if (amount < 0) { dir = -1; amount = -amount } 8413 var cur = clipPos(this.doc, from) 8414 for (var i = 0; i < amount; ++i) { 8415 var coords = cursorCoords(this$1, cur, "div") 8416 if (x == null) { x = coords.left } 8417 else { coords.left = x } 8418 cur = findPosV(this$1, coords, dir, unit) 8419 if (cur.hitSide) { break } 8420 } 8421 return cur 8422 }, 8423 8424 moveV: methodOp(function(dir, unit) { 8425 var this$1 = this; 8426 8427 var doc = this.doc, goals = [] 8428 var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected() 8429 doc.extendSelectionsBy(function (range) { 8430 if (collapse) 8431 { return dir < 0 ? range.from() : range.to() } 8432 var headPos = cursorCoords(this$1, range.head, "div") 8433 if (range.goalColumn != null) { headPos.left = range.goalColumn } 8434 goals.push(headPos.left) 8435 var pos = findPosV(this$1, headPos, dir, unit) 8436 if (unit == "page" && range == doc.sel.primary()) 8437 { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top) } 8438 return pos 8439 }, sel_move) 8440 if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) 8441 { doc.sel.ranges[i].goalColumn = goals[i] } } 8442 }), 8443 8444 // Find the word at the given position (as returned by coordsChar). 8445 findWordAt: function(pos) { 8446 var doc = this.doc, line = getLine(doc, pos.line).text 8447 var start = pos.ch, end = pos.ch 8448 if (line) { 8449 var helper = this.getHelper(pos, "wordChars") 8450 if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end } 8451 var startChar = line.charAt(start) 8452 var check = isWordChar(startChar, helper) 8453 ? function (ch) { return isWordChar(ch, helper); } 8454 : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } 8455 : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); } 8456 while (start > 0 && check(line.charAt(start - 1))) { --start } 8457 while (end < line.length && check(line.charAt(end))) { ++end } 8458 } 8459 return new Range(Pos(pos.line, start), Pos(pos.line, end)) 8460 }, 8461 8462 toggleOverwrite: function(value) { 8463 if (value != null && value == this.state.overwrite) { return } 8464 if (this.state.overwrite = !this.state.overwrite) 8465 { addClass(this.display.cursorDiv, "CodeMirror-overwrite") } 8466 else 8467 { rmClass(this.display.cursorDiv, "CodeMirror-overwrite") } 8468 8469 signal(this, "overwriteToggle", this, this.state.overwrite) 8470 }, 8471 hasFocus: function() { return this.display.input.getField() == activeElt() }, 8472 isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, 8473 8474 scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y) }), 8475 getScrollInfo: function() { 8476 var scroller = this.display.scroller 8477 return {left: scroller.scrollLeft, top: scroller.scrollTop, 8478 height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, 8479 width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, 8480 clientHeight: displayHeight(this), clientWidth: displayWidth(this)} 8481 }, 8482 8483 scrollIntoView: methodOp(function(range, margin) { 8484 if (range == null) { 8485 range = {from: this.doc.sel.primary().head, to: null} 8486 if (margin == null) { margin = this.options.cursorScrollMargin } 8487 } else if (typeof range == "number") { 8488 range = {from: Pos(range, 0), to: null} 8489 } else if (range.from == null) { 8490 range = {from: range, to: null} 8491 } 8492 if (!range.to) { range.to = range.from } 8493 range.margin = margin || 0 8494 8495 if (range.from.line != null) { 8496 scrollToRange(this, range) 8497 } else { 8498 scrollToCoordsRange(this, range.from, range.to, range.margin) 8499 } 8500 }), 8501 8502 setSize: methodOp(function(width, height) { 8503 var this$1 = this; 8504 8505 var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; } 8506 if (width != null) { this.display.wrapper.style.width = interpret(width) } 8507 if (height != null) { this.display.wrapper.style.height = interpret(height) } 8508 if (this.options.lineWrapping) { clearLineMeasurementCache(this) } 8509 var lineNo = this.display.viewFrom 8510 this.doc.iter(lineNo, this.display.viewTo, function (line) { 8511 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) 8512 { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widget"); break } } } 8513 ++lineNo 8514 }) 8515 this.curOp.forceUpdate = true 8516 signal(this, "refresh", this) 8517 }), 8518 8519 operation: function(f){return runInOp(this, f)}, 8520 startOperation: function(){return startOperation(this)}, 8521 endOperation: function(){return endOperation(this)}, 8522 8523 refresh: methodOp(function() { 8524 var oldHeight = this.display.cachedTextHeight 8525 regChange(this) 8526 this.curOp.forceUpdate = true 8527 clearCaches(this) 8528 scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop) 8529 updateGutterSpace(this) 8530 if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) 8531 { estimateLineHeights(this) } 8532 signal(this, "refresh", this) 8533 }), 8534 8535 swapDoc: methodOp(function(doc) { 8536 var old = this.doc 8537 old.cm = null 8538 attachDoc(this, doc) 8539 clearCaches(this) 8540 this.display.input.reset() 8541 scrollToCoords(this, doc.scrollLeft, doc.scrollTop) 8542 this.curOp.forceScroll = true 8543 signalLater(this, "swapDoc", this, old) 8544 return old 8545 }), 8546 8547 getInputField: function(){return this.display.input.getField()}, 8548 getWrapperElement: function(){return this.display.wrapper}, 8549 getScrollerElement: function(){return this.display.scroller}, 8550 getGutterElement: function(){return this.display.gutters} 8551 } 8552 eventMixin(CodeMirror) 8553 8554 CodeMirror.registerHelper = function(type, name, value) { 8555 if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []} } 8556 helpers[type][name] = value 8557 } 8558 CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { 8559 CodeMirror.registerHelper(type, name, value) 8560 helpers[type]._global.push({pred: predicate, val: value}) 8561 } 8562 } 8563 8564 // Used for horizontal relative motion. Dir is -1 or 1 (left or 8565 // right), unit can be "char", "column" (like char, but doesn't 8566 // cross line boundaries), "word" (across next word), or "group" (to 8567 // the start of next group of word or non-word-non-whitespace 8568 // chars). The visually param controls whether, in right-to-left 8569 // text, direction 1 means to move towards the next index in the 8570 // string, or towards the character to the right of the current 8571 // position. The resulting position will have a hitSide=true 8572 // property if it reached the end of the document. 8573 function findPosH(doc, pos, dir, unit, visually) { 8574 var oldPos = pos 8575 var origDir = dir 8576 var lineObj = getLine(doc, pos.line) 8577 function findNextLine() { 8578 var l = pos.line + dir 8579 if (l < doc.first || l >= doc.first + doc.size) { return false } 8580 pos = new Pos(l, pos.ch, pos.sticky) 8581 return lineObj = getLine(doc, l) 8582 } 8583 function moveOnce(boundToLine) { 8584 var next 8585 if (visually) { 8586 next = moveVisually(doc.cm, lineObj, pos, dir) 8587 } else { 8588 next = moveLogically(lineObj, pos, dir) 8589 } 8590 if (next == null) { 8591 if (!boundToLine && findNextLine()) 8592 { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir) } 8593 else 8594 { return false } 8595 } else { 8596 pos = next 8597 } 8598 return true 8599 } 8600 8601 if (unit == "char") { 8602 moveOnce() 8603 } else if (unit == "column") { 8604 moveOnce(true) 8605 } else if (unit == "word" || unit == "group") { 8606 var sawType = null, group = unit == "group" 8607 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars") 8608 for (var first = true;; first = false) { 8609 if (dir < 0 && !moveOnce(!first)) { break } 8610 var cur = lineObj.text.charAt(pos.ch) || "\n" 8611 var type = isWordChar(cur, helper) ? "w" 8612 : group && cur == "\n" ? "n" 8613 : !group || /\s/.test(cur) ? null 8614 : "p" 8615 if (group && !first && !type) { type = "s" } 8616 if (sawType && sawType != type) { 8617 if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after"} 8618 break 8619 } 8620 8621 if (type) { sawType = type } 8622 if (dir > 0 && !moveOnce(!first)) { break } 8623 } 8624 } 8625 var result = skipAtomic(doc, pos, oldPos, origDir, true) 8626 if (equalCursorPos(oldPos, result)) { result.hitSide = true } 8627 return result 8628 } 8629 8630 // For relative vertical movement. Dir may be -1 or 1. Unit can be 8631 // "page" or "line". The resulting position will have a hitSide=true 8632 // property if it reached the end of the document. 8633 function findPosV(cm, pos, dir, unit) { 8634 var doc = cm.doc, x = pos.left, y 8635 if (unit == "page") { 8636 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight) 8637 var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3) 8638 y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount 8639 8640 } else if (unit == "line") { 8641 y = dir > 0 ? pos.bottom + 3 : pos.top - 3 8642 } 8643 var target 8644 for (;;) { 8645 target = coordsChar(cm, x, y) 8646 if (!target.outside) { break } 8647 if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } 8648 y += dir * 5 8649 } 8650 return target 8651 } 8652 8653 // CONTENTEDITABLE INPUT STYLE 8654 8655 var ContentEditableInput = function(cm) { 8656 this.cm = cm 8657 this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null 8658 this.polling = new Delayed() 8659 this.composing = null 8660 this.gracePeriod = false 8661 this.readDOMTimeout = null 8662 }; 8663 8664 ContentEditableInput.prototype.init = function (display) { 8665 var this$1 = this; 8666 8667 var input = this, cm = input.cm 8668 var div = input.div = display.lineDiv 8669 disableBrowserMagic(div, cm.options.spellcheck) 8670 8671 on(div, "paste", function (e) { 8672 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } 8673 // IE doesn't fire input events, so we schedule a read for the pasted content in this way 8674 if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20) } 8675 }) 8676 8677 on(div, "compositionstart", function (e) { 8678 this$1.composing = {data: e.data, done: false} 8679 }) 8680 on(div, "compositionupdate", function (e) { 8681 if (!this$1.composing) { this$1.composing = {data: e.data, done: false} } 8682 }) 8683 on(div, "compositionend", function (e) { 8684 if (this$1.composing) { 8685 if (e.data != this$1.composing.data) { this$1.readFromDOMSoon() } 8686 this$1.composing.done = true 8687 } 8688 }) 8689 8690 on(div, "touchstart", function () { return input.forceCompositionEnd(); }) 8691 8692 on(div, "input", function () { 8693 if (!this$1.composing) { this$1.readFromDOMSoon() } 8694 }) 8695 8696 function onCopyCut(e) { 8697 if (signalDOMEvent(cm, e)) { return } 8698 if (cm.somethingSelected()) { 8699 setLastCopied({lineWise: false, text: cm.getSelections()}) 8700 if (e.type == "cut") { cm.replaceSelection("", null, "cut") } 8701 } else if (!cm.options.lineWiseCopyCut) { 8702 return 8703 } else { 8704 var ranges = copyableRanges(cm) 8705 setLastCopied({lineWise: true, text: ranges.text}) 8706 if (e.type == "cut") { 8707 cm.operation(function () { 8708 cm.setSelections(ranges.ranges, 0, sel_dontScroll) 8709 cm.replaceSelection("", null, "cut") 8710 }) 8711 } 8712 } 8713 if (e.clipboardData) { 8714 e.clipboardData.clearData() 8715 var content = lastCopied.text.join("\n") 8716 // iOS exposes the clipboard API, but seems to discard content inserted into it 8717 e.clipboardData.setData("Text", content) 8718 if (e.clipboardData.getData("Text") == content) { 8719 e.preventDefault() 8720 return 8721 } 8722 } 8723 // Old-fashioned briefly-focus-a-textarea hack 8724 var kludge = hiddenTextarea(), te = kludge.firstChild 8725 cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild) 8726 te.value = lastCopied.text.join("\n") 8727 var hadFocus = document.activeElement 8728 selectInput(te) 8729 setTimeout(function () { 8730 cm.display.lineSpace.removeChild(kludge) 8731 hadFocus.focus() 8732 if (hadFocus == div) { input.showPrimarySelection() } 8733 }, 50) 8734 } 8735 on(div, "copy", onCopyCut) 8736 on(div, "cut", onCopyCut) 8737 }; 8738 8739 ContentEditableInput.prototype.prepareSelection = function () { 8740 var result = prepareSelection(this.cm, false) 8741 result.focus = this.cm.state.focused 8742 return result 8743 }; 8744 8745 ContentEditableInput.prototype.showSelection = function (info, takeFocus) { 8746 if (!info || !this.cm.display.view.length) { return } 8747 if (info.focus || takeFocus) { this.showPrimarySelection() } 8748 this.showMultipleSelections(info) 8749 }; 8750 8751 ContentEditableInput.prototype.showPrimarySelection = function () { 8752 var sel = window.getSelection(), cm = this.cm, prim = cm.doc.sel.primary() 8753 var from = prim.from(), to = prim.to() 8754 8755 if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { 8756 sel.removeAllRanges() 8757 return 8758 } 8759 8760 var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) 8761 var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset) 8762 if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && 8763 cmp(minPos(curAnchor, curFocus), from) == 0 && 8764 cmp(maxPos(curAnchor, curFocus), to) == 0) 8765 { return } 8766 8767 var view = cm.display.view 8768 var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || 8769 {node: view[0].measure.map[2], offset: 0} 8770 var end = to.line < cm.display.viewTo && posToDOM(cm, to) 8771 if (!end) { 8772 var measure = view[view.length - 1].measure 8773 var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map 8774 end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]} 8775 } 8776 8777 if (!start || !end) { 8778 sel.removeAllRanges() 8779 return 8780 } 8781 8782 var old = sel.rangeCount && sel.getRangeAt(0), rng 8783 try { rng = range(start.node, start.offset, end.offset, end.node) } 8784 catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible 8785 if (rng) { 8786 if (!gecko && cm.state.focused) { 8787 sel.collapse(start.node, start.offset) 8788 if (!rng.collapsed) { 8789 sel.removeAllRanges() 8790 sel.addRange(rng) 8791 } 8792 } else { 8793 sel.removeAllRanges() 8794 sel.addRange(rng) 8795 } 8796 if (old && sel.anchorNode == null) { sel.addRange(old) } 8797 else if (gecko) { this.startGracePeriod() } 8798 } 8799 this.rememberSelection() 8800 }; 8801 8802 ContentEditableInput.prototype.startGracePeriod = function () { 8803 var this$1 = this; 8804 8805 clearTimeout(this.gracePeriod) 8806 this.gracePeriod = setTimeout(function () { 8807 this$1.gracePeriod = false 8808 if (this$1.selectionChanged()) 8809 { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }) } 8810 }, 20) 8811 }; 8812 8813 ContentEditableInput.prototype.showMultipleSelections = function (info) { 8814 removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors) 8815 removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection) 8816 }; 8817 8818 ContentEditableInput.prototype.rememberSelection = function () { 8819 var sel = window.getSelection() 8820 this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset 8821 this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset 8822 }; 8823 8824 ContentEditableInput.prototype.selectionInEditor = function () { 8825 var sel = window.getSelection() 8826 if (!sel.rangeCount) { return false } 8827 var node = sel.getRangeAt(0).commonAncestorContainer 8828 return contains(this.div, node) 8829 }; 8830 8831 ContentEditableInput.prototype.focus = function () { 8832 if (this.cm.options.readOnly != "nocursor") { 8833 if (!this.selectionInEditor()) 8834 { this.showSelection(this.prepareSelection(), true) } 8835 this.div.focus() 8836 } 8837 }; 8838 ContentEditableInput.prototype.blur = function () { this.div.blur() }; 8839 ContentEditableInput.prototype.getField = function () { return this.div }; 8840 8841 ContentEditableInput.prototype.supportsTouch = function () { return true }; 8842 8843 ContentEditableInput.prototype.receivedFocus = function () { 8844 var input = this 8845 if (this.selectionInEditor()) 8846 { this.pollSelection() } 8847 else 8848 { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }) } 8849 8850 function poll() { 8851 if (input.cm.state.focused) { 8852 input.pollSelection() 8853 input.polling.set(input.cm.options.pollInterval, poll) 8854 } 8855 } 8856 this.polling.set(this.cm.options.pollInterval, poll) 8857 }; 8858 8859 ContentEditableInput.prototype.selectionChanged = function () { 8860 var sel = window.getSelection() 8861 return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || 8862 sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset 8863 }; 8864 8865 ContentEditableInput.prototype.pollSelection = function () { 8866 if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return } 8867 var sel = window.getSelection(), cm = this.cm 8868 // On Android Chrome (version 56, at least), backspacing into an 8869 // uneditable block element will put the cursor in that element, 8870 // and then, because it's not editable, hide the virtual keyboard. 8871 // Because Android doesn't allow us to actually detect backspace 8872 // presses in a sane way, this code checks for when that happens 8873 // and simulates a backspace press in this case. 8874 if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anchorNode)) { 8875 this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}) 8876 this.blur() 8877 this.focus() 8878 return 8879 } 8880 if (this.composing) { return } 8881 this.rememberSelection() 8882 var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) 8883 var head = domToPos(cm, sel.focusNode, sel.focusOffset) 8884 if (anchor && head) { runInOp(cm, function () { 8885 setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll) 8886 if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true } 8887 }) } 8888 }; 8889 8890 ContentEditableInput.prototype.pollContent = function () { 8891 if (this.readDOMTimeout != null) { 8892 clearTimeout(this.readDOMTimeout) 8893 this.readDOMTimeout = null 8894 } 8895 8896 var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary() 8897 var from = sel.from(), to = sel.to() 8898 if (from.ch == 0 && from.line > cm.firstLine()) 8899 { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) } 8900 if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) 8901 { to = Pos(to.line + 1, 0) } 8902 if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false } 8903 8904 var fromIndex, fromLine, fromNode 8905 if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { 8906 fromLine = lineNo(display.view[0].line) 8907 fromNode = display.view[0].node 8908 } else { 8909 fromLine = lineNo(display.view[fromIndex].line) 8910 fromNode = display.view[fromIndex - 1].node.nextSibling 8911 } 8912 var toIndex = findViewIndex(cm, to.line) 8913 var toLine, toNode 8914 if (toIndex == display.view.length - 1) { 8915 toLine = display.viewTo - 1 8916 toNode = display.lineDiv.lastChild 8917 } else { 8918 toLine = lineNo(display.view[toIndex + 1].line) - 1 8919 toNode = display.view[toIndex + 1].node.previousSibling 8920 } 8921 8922 if (!fromNode) { return false } 8923 var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)) 8924 var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)) 8925 while (newText.length > 1 && oldText.length > 1) { 8926 if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- } 8927 else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ } 8928 else { break } 8929 } 8930 8931 var cutFront = 0, cutEnd = 0 8932 var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length) 8933 while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) 8934 { ++cutFront } 8935 var newBot = lst(newText), oldBot = lst(oldText) 8936 var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), 8937 oldBot.length - (oldText.length == 1 ? cutFront : 0)) 8938 while (cutEnd < maxCutEnd && 8939 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) 8940 { ++cutEnd } 8941 // Try to move start of change to start of selection if ambiguous 8942 if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { 8943 while (cutFront && cutFront > from.ch && 8944 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { 8945 cutFront-- 8946 cutEnd++ 8947 } 8948 } 8949 8950 newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "") 8951 newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "") 8952 8953 var chFrom = Pos(fromLine, cutFront) 8954 var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0) 8955 if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { 8956 replaceRange(cm.doc, newText, chFrom, chTo, "+input") 8957 return true 8958 } 8959 }; 8960 8961 ContentEditableInput.prototype.ensurePolled = function () { 8962 this.forceCompositionEnd() 8963 }; 8964 ContentEditableInput.prototype.reset = function () { 8965 this.forceCompositionEnd() 8966 }; 8967 ContentEditableInput.prototype.forceCompositionEnd = function () { 8968 if (!this.composing) { return } 8969 clearTimeout(this.readDOMTimeout) 8970 this.composing = null 8971 this.updateFromDOM() 8972 this.div.blur() 8973 this.div.focus() 8974 }; 8975 ContentEditableInput.prototype.readFromDOMSoon = function () { 8976 var this$1 = this; 8977 8978 if (this.readDOMTimeout != null) { return } 8979 this.readDOMTimeout = setTimeout(function () { 8980 this$1.readDOMTimeout = null 8981 if (this$1.composing) { 8982 if (this$1.composing.done) { this$1.composing = null } 8983 else { return } 8984 } 8985 this$1.updateFromDOM() 8986 }, 80) 8987 }; 8988 8989 ContentEditableInput.prototype.updateFromDOM = function () { 8990 var this$1 = this; 8991 8992 if (this.cm.isReadOnly() || !this.pollContent()) 8993 { runInOp(this.cm, function () { return regChange(this$1.cm); }) } 8994 }; 8995 8996 ContentEditableInput.prototype.setUneditable = function (node) { 8997 node.contentEditable = "false" 8998 }; 8999 9000 ContentEditableInput.prototype.onKeyPress = function (e) { 9001 if (e.charCode == 0) { return } 9002 e.preventDefault() 9003 if (!this.cm.isReadOnly()) 9004 { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0) } 9005 }; 9006 9007 ContentEditableInput.prototype.readOnlyChanged = function (val) { 9008 this.div.contentEditable = String(val != "nocursor") 9009 }; 9010 9011 ContentEditableInput.prototype.onContextMenu = function () {}; 9012 ContentEditableInput.prototype.resetPosition = function () {}; 9013 9014 ContentEditableInput.prototype.needsContentAttribute = true 9015 9016 function posToDOM(cm, pos) { 9017 var view = findViewForLine(cm, pos.line) 9018 if (!view || view.hidden) { return null } 9019 var line = getLine(cm.doc, pos.line) 9020 var info = mapFromLineView(view, line, pos.line) 9021 9022 var order = getOrder(line, cm.doc.direction), side = "left" 9023 if (order) { 9024 var partPos = getBidiPartAt(order, pos.ch) 9025 side = partPos % 2 ? "right" : "left" 9026 } 9027 var result = nodeAndOffsetInLineMap(info.map, pos.ch, side) 9028 result.offset = result.collapse == "right" ? result.end : result.start 9029 return result 9030 } 9031 9032 function isInGutter(node) { 9033 for (var scan = node; scan; scan = scan.parentNode) 9034 { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } } 9035 return false 9036 } 9037 9038 function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } 9039 9040 function domTextBetween(cm, from, to, fromLine, toLine) { 9041 var text = "", closing = false, lineSep = cm.doc.lineSeparator() 9042 function recognizeMarker(id) { return function (marker) { return marker.id == id; } } 9043 function close() { 9044 if (closing) { 9045 text += lineSep 9046 closing = false 9047 } 9048 } 9049 function addText(str) { 9050 if (str) { 9051 close() 9052 text += str 9053 } 9054 } 9055 function walk(node) { 9056 if (node.nodeType == 1) { 9057 var cmText = node.getAttribute("cm-text") 9058 if (cmText != null) { 9059 addText(cmText || node.textContent.replace(/\u200b/g, "")) 9060 return 9061 } 9062 var markerID = node.getAttribute("cm-marker"), range 9063 if (markerID) { 9064 var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)) 9065 if (found.length && (range = found[0].find(0))) 9066 { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)) } 9067 return 9068 } 9069 if (node.getAttribute("contenteditable") == "false") { return } 9070 var isBlock = /^(pre|div|p)$/i.test(node.nodeName) 9071 if (isBlock) { close() } 9072 for (var i = 0; i < node.childNodes.length; i++) 9073 { walk(node.childNodes[i]) } 9074 if (isBlock) { closing = true } 9075 } else if (node.nodeType == 3) { 9076 addText(node.nodeValue) 9077 } 9078 } 9079 for (;;) { 9080 walk(from) 9081 if (from == to) { break } 9082 from = from.nextSibling 9083 } 9084 return text 9085 } 9086 9087 function domToPos(cm, node, offset) { 9088 var lineNode 9089 if (node == cm.display.lineDiv) { 9090 lineNode = cm.display.lineDiv.childNodes[offset] 9091 if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) } 9092 node = null; offset = 0 9093 } else { 9094 for (lineNode = node;; lineNode = lineNode.parentNode) { 9095 if (!lineNode || lineNode == cm.display.lineDiv) { return null } 9096 if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break } 9097 } 9098 } 9099 for (var i = 0; i < cm.display.view.length; i++) { 9100 var lineView = cm.display.view[i] 9101 if (lineView.node == lineNode) 9102 { return locateNodeInLineView(lineView, node, offset) } 9103 } 9104 } 9105 9106 function locateNodeInLineView(lineView, node, offset) { 9107 var wrapper = lineView.text.firstChild, bad = false 9108 if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) } 9109 if (node == wrapper) { 9110 bad = true 9111 node = wrapper.childNodes[offset] 9112 offset = 0 9113 if (!node) { 9114 var line = lineView.rest ? lst(lineView.rest) : lineView.line 9115 return badPos(Pos(lineNo(line), line.text.length), bad) 9116 } 9117 } 9118 9119 var textNode = node.nodeType == 3 ? node : null, topNode = node 9120 if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { 9121 textNode = node.firstChild 9122 if (offset) { offset = textNode.nodeValue.length } 9123 } 9124 while (topNode.parentNode != wrapper) { topNode = topNode.parentNode } 9125 var measure = lineView.measure, maps = measure.maps 9126 9127 function find(textNode, topNode, offset) { 9128 for (var i = -1; i < (maps ? maps.length : 0); i++) { 9129 var map = i < 0 ? measure.map : maps[i] 9130 for (var j = 0; j < map.length; j += 3) { 9131 var curNode = map[j + 2] 9132 if (curNode == textNode || curNode == topNode) { 9133 var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]) 9134 var ch = map[j] + offset 9135 if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0)] } 9136 return Pos(line, ch) 9137 } 9138 } 9139 } 9140 } 9141 var found = find(textNode, topNode, offset) 9142 if (found) { return badPos(found, bad) } 9143 9144 // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems 9145 for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { 9146 found = find(after, after.firstChild, 0) 9147 if (found) 9148 { return badPos(Pos(found.line, found.ch - dist), bad) } 9149 else 9150 { dist += after.textContent.length } 9151 } 9152 for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) { 9153 found = find(before, before.firstChild, -1) 9154 if (found) 9155 { return badPos(Pos(found.line, found.ch + dist$1), bad) } 9156 else 9157 { dist$1 += before.textContent.length } 9158 } 9159 } 9160 9161 // TEXTAREA INPUT STYLE 9162 9163 var TextareaInput = function(cm) { 9164 this.cm = cm 9165 // See input.poll and input.reset 9166 this.prevInput = "" 9167 9168 // Flag that indicates whether we expect input to appear real soon 9169 // now (after some event like 'keypress' or 'input') and are 9170 // polling intensively. 9171 this.pollingFast = false 9172 // Self-resetting timeout for the poller 9173 this.polling = new Delayed() 9174 // Used to work around IE issue with selection being forgotten when focus moves away from textarea 9175 this.hasSelection = false 9176 this.composing = null 9177 }; 9178 9179 TextareaInput.prototype.init = function (display) { 9180 var this$1 = this; 9181 9182 var input = this, cm = this.cm 9183 9184 // Wraps and hides input textarea 9185 var div = this.wrapper = hiddenTextarea() 9186 // The semihidden textarea that is focused when the editor is 9187 // focused, and receives input. 9188 var te = this.textarea = div.firstChild 9189 display.wrapper.insertBefore(div, display.wrapper.firstChild) 9190 9191 // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) 9192 if (ios) { te.style.width = "0px" } 9193 9194 on(te, "input", function () { 9195 if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null } 9196 input.poll() 9197 }) 9198 9199 on(te, "paste", function (e) { 9200 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } 9201 9202 cm.state.pasteIncoming = true 9203 input.fastPoll() 9204 }) 9205 9206 function prepareCopyCut(e) { 9207 if (signalDOMEvent(cm, e)) { return } 9208 if (cm.somethingSelected()) { 9209 setLastCopied({lineWise: false, text: cm.getSelections()}) 9210 } else if (!cm.options.lineWiseCopyCut) { 9211 return 9212 } else { 9213 var ranges = copyableRanges(cm) 9214 setLastCopied({lineWise: true, text: ranges.text}) 9215 if (e.type == "cut") { 9216 cm.setSelections(ranges.ranges, null, sel_dontScroll) 9217 } else { 9218 input.prevInput = "" 9219 te.value = ranges.text.join("\n") 9220 selectInput(te) 9221 } 9222 } 9223 if (e.type == "cut") { cm.state.cutIncoming = true } 9224 } 9225 on(te, "cut", prepareCopyCut) 9226 on(te, "copy", prepareCopyCut) 9227 9228 on(display.scroller, "paste", function (e) { 9229 if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } 9230 cm.state.pasteIncoming = true 9231 input.focus() 9232 }) 9233 9234 // Prevent normal selection in the editor (we handle our own) 9235 on(display.lineSpace, "selectstart", function (e) { 9236 if (!eventInWidget(display, e)) { e_preventDefault(e) } 9237 }) 9238 9239 on(te, "compositionstart", function () { 9240 var start = cm.getCursor("from") 9241 if (input.composing) { input.composing.range.clear() } 9242 input.composing = { 9243 start: start, 9244 range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) 9245 } 9246 }) 9247 on(te, "compositionend", function () { 9248 if (input.composing) { 9249 input.poll() 9250 input.composing.range.clear() 9251 input.composing = null 9252 } 9253 }) 9254 }; 9255 9256 TextareaInput.prototype.prepareSelection = function () { 9257 // Redraw the selection and/or cursor 9258 var cm = this.cm, display = cm.display, doc = cm.doc 9259 var result = prepareSelection(cm) 9260 9261 // Move the hidden textarea near the cursor to prevent scrolling artifacts 9262 if (cm.options.moveInputWithCursor) { 9263 var headPos = cursorCoords(cm, doc.sel.primary().head, "div") 9264 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect() 9265 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, 9266 headPos.top + lineOff.top - wrapOff.top)) 9267 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, 9268 headPos.left + lineOff.left - wrapOff.left)) 9269 } 9270 9271 return result 9272 }; 9273 9274 TextareaInput.prototype.showSelection = function (drawn) { 9275 var cm = this.cm, display = cm.display 9276 removeChildrenAndAdd(display.cursorDiv, drawn.cursors) 9277 removeChildrenAndAdd(display.selectionDiv, drawn.selection) 9278 if (drawn.teTop != null) { 9279 this.wrapper.style.top = drawn.teTop + "px" 9280 this.wrapper.style.left = drawn.teLeft + "px" 9281 } 9282 }; 9283 9284 // Reset the input to correspond to the selection (or to be empty, 9285 // when not typing and nothing is selected) 9286 TextareaInput.prototype.reset = function (typing) { 9287 if (this.contextMenuPending || this.composing) { return } 9288 var cm = this.cm 9289 if (cm.somethingSelected()) { 9290 this.prevInput = "" 9291 var content = cm.getSelection() 9292 this.textarea.value = content 9293 if (cm.state.focused) { selectInput(this.textarea) } 9294 if (ie && ie_version >= 9) { this.hasSelection = content } 9295 } else if (!typing) { 9296 this.prevInput = this.textarea.value = "" 9297 if (ie && ie_version >= 9) { this.hasSelection = null } 9298 } 9299 }; 9300 9301 TextareaInput.prototype.getField = function () { return this.textarea }; 9302 9303 TextareaInput.prototype.supportsTouch = function () { return false }; 9304 9305 TextareaInput.prototype.focus = function () { 9306 if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { 9307 try { this.textarea.focus() } 9308 catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM 9309 } 9310 }; 9311 9312 TextareaInput.prototype.blur = function () { this.textarea.blur() }; 9313 9314 TextareaInput.prototype.resetPosition = function () { 9315 this.wrapper.style.top = this.wrapper.style.left = 0 9316 }; 9317 9318 TextareaInput.prototype.receivedFocus = function () { this.slowPoll() }; 9319 9320 // Poll for input changes, using the normal rate of polling. This 9321 // runs as long as the editor is focused. 9322 TextareaInput.prototype.slowPoll = function () { 9323 var this$1 = this; 9324 9325 if (this.pollingFast) { return } 9326 this.polling.set(this.cm.options.pollInterval, function () { 9327 this$1.poll() 9328 if (this$1.cm.state.focused) { this$1.slowPoll() } 9329 }) 9330 }; 9331 9332 // When an event has just come in that is likely to add or change 9333 // something in the input textarea, we poll faster, to ensure that 9334 // the change appears on the screen quickly. 9335 TextareaInput.prototype.fastPoll = function () { 9336 var missed = false, input = this 9337 input.pollingFast = true 9338 function p() { 9339 var changed = input.poll() 9340 if (!changed && !missed) {missed = true; input.polling.set(60, p)} 9341 else {input.pollingFast = false; input.slowPoll()} 9342 } 9343 input.polling.set(20, p) 9344 }; 9345 9346 // Read input from the textarea, and update the document to match. 9347 // When something is selected, it is present in the textarea, and 9348 // selected (unless it is huge, in which case a placeholder is 9349 // used). When nothing is selected, the cursor sits after previously 9350 // seen text (can be empty), which is stored in prevInput (we must 9351 // not reset the textarea when typing, because that breaks IME). 9352 TextareaInput.prototype.poll = function () { 9353 var this$1 = this; 9354 9355 var cm = this.cm, input = this.textarea, prevInput = this.prevInput 9356 // Since this is called a *lot*, try to bail out as cheaply as 9357 // possible when it is clear that nothing happened. hasSelection 9358 // will be the case when there is a lot of text in the textarea, 9359 // in which case reading its value would be expensive. 9360 if (this.contextMenuPending || !cm.state.focused || 9361 (hasSelection(input) && !prevInput && !this.composing) || 9362 cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) 9363 { return false } 9364 9365 var text = input.value 9366 // If nothing changed, bail. 9367 if (text == prevInput && !cm.somethingSelected()) { return false } 9368 // Work around nonsensical selection resetting in IE9/10, and 9369 // inexplicable appearance of private area unicode characters on 9370 // some key combos in Mac (#2689). 9371 if (ie && ie_version >= 9 && this.hasSelection === text || 9372 mac && /[\uf700-\uf7ff]/.test(text)) { 9373 cm.display.input.reset() 9374 return false 9375 } 9376 9377 if (cm.doc.sel == cm.display.selForContextMenu) { 9378 var first = text.charCodeAt(0) 9379 if (first == 0x200b && !prevInput) { prevInput = "\u200b" } 9380 if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } 9381 } 9382 // Find the part of the input that is actually new 9383 var same = 0, l = Math.min(prevInput.length, text.length) 9384 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same } 9385 9386 runInOp(cm, function () { 9387 applyTextInput(cm, text.slice(same), prevInput.length - same, 9388 null, this$1.composing ? "*compose" : null) 9389 9390 // Don't leave long text in the textarea, since it makes further polling slow 9391 if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = "" } 9392 else { this$1.prevInput = text } 9393 9394 if (this$1.composing) { 9395 this$1.composing.range.clear() 9396 this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"), 9397 {className: "CodeMirror-composing"}) 9398 } 9399 }) 9400 return true 9401 }; 9402 9403 TextareaInput.prototype.ensurePolled = function () { 9404 if (this.pollingFast && this.poll()) { this.pollingFast = false } 9405 }; 9406 9407 TextareaInput.prototype.onKeyPress = function () { 9408 if (ie && ie_version >= 9) { this.hasSelection = null } 9409 this.fastPoll() 9410 }; 9411 9412 TextareaInput.prototype.onContextMenu = function (e) { 9413 var input = this, cm = input.cm, display = cm.display, te = input.textarea 9414 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop 9415 if (!pos || presto) { return } // Opera is difficult. 9416 9417 // Reset the current text selection only if the click is done outside of the selection 9418 // and 'resetSelectionOnContextMenu' option is true. 9419 var reset = cm.options.resetSelectionOnContextMenu 9420 if (reset && cm.doc.sel.contains(pos) == -1) 9421 { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) } 9422 9423 var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText 9424 input.wrapper.style.cssText = "position: absolute" 9425 var wrapperBox = input.wrapper.getBoundingClientRect() 9426 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);" 9427 var oldScrollY 9428 if (webkit) { oldScrollY = window.scrollY } // Work around Chrome issue (#2712) 9429 display.input.focus() 9430 if (webkit) { window.scrollTo(null, oldScrollY) } 9431 display.input.reset() 9432 // Adds "Select all" to context menu in FF 9433 if (!cm.somethingSelected()) { te.value = input.prevInput = " " } 9434 input.contextMenuPending = true 9435 display.selForContextMenu = cm.doc.sel 9436 clearTimeout(display.detectingSelectAll) 9437 9438 // Select-all will be greyed out if there's nothing to select, so 9439 // this adds a zero-width space so that we can later check whether 9440 // it got selected. 9441 function prepareSelectAllHack() { 9442 if (te.selectionStart != null) { 9443 var selected = cm.somethingSelected() 9444 var extval = "\u200b" + (selected ? te.value : "") 9445 te.value = "\u21da" // Used to catch context-menu undo 9446 te.value = extval 9447 input.prevInput = selected ? "" : "\u200b" 9448 te.selectionStart = 1; te.selectionEnd = extval.length 9449 // Re-set this, in case some other handler touched the 9450 // selection in the meantime. 9451 display.selForContextMenu = cm.doc.sel 9452 } 9453 } 9454 function rehide() { 9455 input.contextMenuPending = false 9456 input.wrapper.style.cssText = oldWrapperCSS 9457 te.style.cssText = oldCSS 9458 if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos) } 9459 9460 // Try to detect the user choosing select-all 9461 if (te.selectionStart != null) { 9462 if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack() } 9463 var i = 0, poll = function () { 9464 if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && 9465 te.selectionEnd > 0 && input.prevInput == "\u200b") { 9466 operation(cm, selectAll)(cm) 9467 } else if (i++ < 10) { 9468 display.detectingSelectAll = setTimeout(poll, 500) 9469 } else { 9470 display.selForContextMenu = null 9471 display.input.reset() 9472 } 9473 } 9474 display.detectingSelectAll = setTimeout(poll, 200) 9475 } 9476 } 9477 9478 if (ie && ie_version >= 9) { prepareSelectAllHack() } 9479 if (captureRightClick) { 9480 e_stop(e) 9481 var mouseup = function () { 9482 off(window, "mouseup", mouseup) 9483 setTimeout(rehide, 20) 9484 } 9485 on(window, "mouseup", mouseup) 9486 } else { 9487 setTimeout(rehide, 50) 9488 } 9489 }; 9490 9491 TextareaInput.prototype.readOnlyChanged = function (val) { 9492 if (!val) { this.reset() } 9493 this.textarea.disabled = val == "nocursor" 9494 }; 9495 9496 TextareaInput.prototype.setUneditable = function () {}; 9497 9498 TextareaInput.prototype.needsContentAttribute = false 9499 9500 function fromTextArea(textarea, options) { 9501 options = options ? copyObj(options) : {} 9502 options.value = textarea.value 9503 if (!options.tabindex && textarea.tabIndex) 9504 { options.tabindex = textarea.tabIndex } 9505 if (!options.placeholder && textarea.placeholder) 9506 { options.placeholder = textarea.placeholder } 9507 // Set autofocus to true if this textarea is focused, or if it has 9508 // autofocus and no other element is focused. 9509 if (options.autofocus == null) { 9510 var hasFocus = activeElt() 9511 options.autofocus = hasFocus == textarea || 9512 textarea.getAttribute("autofocus") != null && hasFocus == document.body 9513 } 9514 9515 function save() {textarea.value = cm.getValue()} 9516 9517 var realSubmit 9518 if (textarea.form) { 9519 on(textarea.form, "submit", save) 9520 // Deplorable hack to make the submit method do the right thing. 9521 if (!options.leaveSubmitMethodAlone) { 9522 var form = textarea.form 9523 realSubmit = form.submit 9524 try { 9525 var wrappedSubmit = form.submit = function () { 9526 save() 9527 form.submit = realSubmit 9528 form.submit() 9529 form.submit = wrappedSubmit 9530 } 9531 } catch(e) {} 9532 } 9533 } 9534 9535 options.finishInit = function (cm) { 9536 cm.save = save 9537 cm.getTextArea = function () { return textarea; } 9538 cm.toTextArea = function () { 9539 cm.toTextArea = isNaN // Prevent this from being ran twice 9540 save() 9541 textarea.parentNode.removeChild(cm.getWrapperElement()) 9542 textarea.style.display = "" 9543 if (textarea.form) { 9544 off(textarea.form, "submit", save) 9545 if (typeof textarea.form.submit == "function") 9546 { textarea.form.submit = realSubmit } 9547 } 9548 } 9549 } 9550 9551 textarea.style.display = "none" 9552 var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); }, 9553 options) 9554 return cm 9555 } 9556 9557 function addLegacyProps(CodeMirror) { 9558 CodeMirror.off = off 9559 CodeMirror.on = on 9560 CodeMirror.wheelEventPixels = wheelEventPixels 9561 CodeMirror.Doc = Doc 9562 CodeMirror.splitLines = splitLinesAuto 9563 CodeMirror.countColumn = countColumn 9564 CodeMirror.findColumn = findColumn 9565 CodeMirror.isWordChar = isWordCharBasic 9566 CodeMirror.Pass = Pass 9567 CodeMirror.signal = signal 9568 CodeMirror.Line = Line 9569 CodeMirror.changeEnd = changeEnd 9570 CodeMirror.scrollbarModel = scrollbarModel 9571 CodeMirror.Pos = Pos 9572 CodeMirror.cmpPos = cmp 9573 CodeMirror.modes = modes 9574 CodeMirror.mimeModes = mimeModes 9575 CodeMirror.resolveMode = resolveMode 9576 CodeMirror.getMode = getMode 9577 CodeMirror.modeExtensions = modeExtensions 9578 CodeMirror.extendMode = extendMode 9579 CodeMirror.copyState = copyState 9580 CodeMirror.startState = startState 9581 CodeMirror.innerMode = innerMode 9582 CodeMirror.commands = commands 9583 CodeMirror.keyMap = keyMap 9584 CodeMirror.keyName = keyName 9585 CodeMirror.isModifierKey = isModifierKey 9586 CodeMirror.lookupKey = lookupKey 9587 CodeMirror.normalizeKeyMap = normalizeKeyMap 9588 CodeMirror.StringStream = StringStream 9589 CodeMirror.SharedTextMarker = SharedTextMarker 9590 CodeMirror.TextMarker = TextMarker 9591 CodeMirror.LineWidget = LineWidget 9592 CodeMirror.e_preventDefault = e_preventDefault 9593 CodeMirror.e_stopPropagation = e_stopPropagation 9594 CodeMirror.e_stop = e_stop 9595 CodeMirror.addClass = addClass 9596 CodeMirror.contains = contains 9597 CodeMirror.rmClass = rmClass 9598 CodeMirror.keyNames = keyNames 9599 } 9600 9601 // EDITOR CONSTRUCTOR 9602 9603 defineOptions(CodeMirror) 9604 9605 addEditorMethods(CodeMirror) 9606 9607 // Set up methods on CodeMirror's prototype to redirect to the editor's document. 9608 var dontDelegate = "iter insert remove copy getEditor constructor".split(" ") 9609 for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) 9610 { CodeMirror.prototype[prop] = (function(method) { 9611 return function() {return method.apply(this.doc, arguments)} 9612 })(Doc.prototype[prop]) } } 9613 9614 eventMixin(Doc) 9615 9616 // INPUT HANDLING 9617 9618 CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput} 9619 9620 // MODE DEFINITION AND QUERYING 9621 9622 // Extra arguments are stored as the mode's dependencies, which is 9623 // used by (legacy) mechanisms like loadmode.js to automatically 9624 // load a mode. (Preferred mechanism is the require/define calls.) 9625 CodeMirror.defineMode = function(name/*, mode, …*/) { 9626 if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name } 9627 defineMode.apply(this, arguments) 9628 } 9629 9630 CodeMirror.defineMIME = defineMIME 9631 9632 // Minimal default mode. 9633 CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); }) 9634 CodeMirror.defineMIME("text/plain", "null") 9635 9636 // EXTENSIONS 9637 9638 CodeMirror.defineExtension = function (name, func) { 9639 CodeMirror.prototype[name] = func 9640 } 9641 CodeMirror.defineDocExtension = function (name, func) { 9642 Doc.prototype[name] = func 9643 } 9644 9645 CodeMirror.fromTextArea = fromTextArea 9646 9647 addLegacyProps(CodeMirror) 9648 9649 CodeMirror.version = "5.32.0" 9650 9651 return CodeMirror; 9652 9653 })));
Downloadmodules/editor/codemirror/lib/codemirror.js
History Sun, 17 Dec 2017 01:14:09 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.