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. 18var userAgent = navigator.userAgent 19var platform = navigator.platform 20 21var gecko = /gecko\/\d/i.test(userAgent) 22var ie_upto10 = /MSIE \d/.test(userAgent) 23var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent) 24var edge = /Edge\/(\d+)/.exec(userAgent) 25var ie = ie_upto10 || ie_11up || edge 26var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]) 27var webkit = !edge && /WebKit\//.test(userAgent) 28var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent) 29var chrome = !edge && /Chrome\//.test(userAgent) 30var presto = /Opera\//.test(userAgent) 31var safari = /Apple Computer/.test(navigator.vendor) 32var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent) 33var phantom = /PhantomJS/.test(userAgent) 34 35var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent) 36var android = /Android/.test(userAgent) 37// This is woefully incomplete. Suggestions for alternative methods welcome. 38var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent) 39var mac = ios || /Mac/.test(platform) 40var chromeOS = /\bCrOS\b/.test(userAgent) 41var windows = /win/i.test(platform) 42 43var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/) 44if (presto_version) { presto_version = Number(presto_version[1]) } 45if (presto_version && presto_version >= 15) { presto = false; webkit = true } 46// Some browsers use the wrong event properties to signal cmd/ctrl on OS X 47var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)) 48var captureRightClick = gecko || (ie && ie_version >= 9) 49 50function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } 51 52var 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 61function removeChildren(e) { 62 for (var count = e.childNodes.length; count > 0; --count) 63 { e.removeChild(e.firstChild) } 64 return e 65} 66 67function removeChildrenAndAdd(parent, e) { 68 return removeChildren(parent).appendChild(e) 69} 70 71function 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 80function eltP(tag, content, className, style) { 81 var e = elt(tag, content, className, style) 82 e.setAttribute("role", "presentation") 83 return e 84} 85 86var range 87if (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} } 93else { 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 103function 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 114function 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 129function addClass(node, cls) { 130 var current = node.className
131 if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls } 132} 133function 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 140var selectInput = function(node) { node.select() } 141if (ios) // Mobile Safari apparently has a bug where select() is broken. 142 { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length } } 143else if (ie) // Suppress mysterious IE10 errors 144 { selectInput = function(node) { try { node.select() } catch(_e) {} } } 145 146function bind(f) { 147 var args = Array.prototype.slice.call(arguments, 1) 148 return function(){return f.apply(null, args)} 149} 150 151function 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. 161function 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 176var Delayed = function() {this.id = null}; 177Delayed.prototype.set = function (ms, f) { 178 clearTimeout(this.id) 179 this.id = setTimeout(f, ms) 180}; 181 182function 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 189var scrollerGap = 30 190 191// Returned or thrown by various protocols to signal 'I'm not 192// handling this'. 193var Pass = {toString: function(){return "CodeMirror.Pass"}} 194 195// Reused option objects for setSelection & friends 196var sel_dontScroll = {scroll: false}; 197var sel_mouse = {origin: "*mouse"}; 198var sel_move = {origin: "+move"}; 199// The inverse of countColumn -- find the offset that corresponds to 200// a particular column. 201function 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 215var spaceStrs = [""] 216function spaceStr(n) { 217 while (spaceStrs.length <= n) 218 { spaceStrs.push(lst(spaceStrs) + " ") } 219 return spaceStrs[n] 220} 221 222function lst(arr) { return arr[arr.length-1] } 223 224function 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 230function 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 236function nothing() {} 237 238function 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 250var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/ 251function isWordCharBasic(ch) { 252 return /\w/.test(ch) || ch > "\x80" &&
253 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)) 254} 255function 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 261function 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. 271var 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]/ 272function 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. 275function 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`. 283function 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 300function 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 curs
313ors. 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. 400function 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. 416function 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. 428function 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. 436function 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. 443function 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. 457function 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 477function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} 478 479function lineNumberFor(options, i) { 480 return String(options.lineNumberFormatter(i + options.firstLineNumber)) 481} 482 483// A Pos instance represents a position within the text. 484function 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. 495function cmp(a, b) { return a.line - b.line || a.ch - b.ch } 496 497function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } 498 499function copyPos(x) {return Pos(x.line, x.ch)} 500function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } 501function 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. 505function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} 506function 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} 512function 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} 518function 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. 525var sawReadOnlySpans = false; 526var sawCollapsedSpans = false; 527function seeReadOnlySpans() { 528 sawReadOnlySpans = true 529} 530 531function seeCollapsedSpans() { 532 sawCollapsedSpans = true 533} 534 535// TEXTMARKER SPANS 536 537function 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. 543function 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). 551function 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. 558function 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). 567function 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} 579function 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. 599function 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.
661function 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. 672function 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. 701function 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} 708function 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. 717function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } 718function 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. 723function 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. 736function 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} 746function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } 747function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } 748 749function collapsedSpanAround(line, ch) { 750 var sps = sawCollapsedSpans && line.markedSpans, found 751 if (sps) { for (var i = 0; i < sps.length; ++i) { 752 var sp = sps[i] 753 if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) && 754 (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker } 755 } } 756 return found 757} 758 759// Test whether there exists a collapsed span that partially 760// overlaps (covers the start or end, but not both) of a new span. 761// Such overlap is not allowed. 762function conflictingCollapsedRange(doc, lineNo, from, to, marker) { 763 var line = getLine(doc, lineNo) 764 var sps = sawCollapsedSpans && line.markedSpans 765 if (sps) { for (var i = 0; i < sps.length; ++i) { 766 var sp = sps[i] 767 if (!sp.marker.collapsed) { continue } 768 var found = sp.marker.find(0) 769 var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker) 770 var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker) 771 if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue } 772 if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || 773 fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) 774 { return true } 775 } } 776}
776 777 778// A visual line is a line as drawn on the screen. Folding, for 779// example, can cause multiple logical lines to appear on the same 780// visual line. This finds the start of the visual line that the 781// given line is part of (usually that is the line itself). 782function visualLine(line) { 783 var merged 784 while (merged = collapsedSpanAtStart(line)) 785 { line = merged.find(-1, true).line } 786 return line 787} 788 789function visualLineEnd(line) { 790 var merged 791 while (merged = collapsedSpanAtEnd(line)) 792 { line = merged.find(1, true).line } 793 return line 794} 795 796// Returns an array of logical lines that continue the visual line 797// started by the argument, or undefined if there are no such lines. 798function visualLineContinued(line) { 799 var merged, lines 800 while (merged = collapsedSpanAtEnd(line)) { 801 line = merged.find(1, true).line 802 ;(lines || (lines = [])).push(line) 803 } 804 return lines 805} 806 807// Get the line number of the start of the visual line that the 808// given line number is part of. 809function visualLineNo(doc, lineN) { 810 var line = getLine(doc, lineN), vis = visualLine(line) 811 if (line == vis) { return lineN } 812 return lineNo(vis) 813} 814 815// Get the line number of the start of the next visual line after 816// the given line. 817function visualLineEndNo(doc, lineN) { 818 if (lineN > doc.lastLine()) { return lineN } 819 var line = getLine(doc, lineN), merged 820 if (!lineIsHidden(doc, line)) { return lineN } 821 while (merged = collapsedSpanAtEnd(line)) 822 { line = merged.find(1, true).line } 823 return lineNo(line) + 1 824} 825 826// Compute whether a line is hidden. Lines count as hidden when they 827// are part of a visual line that starts with another line, or when 828// they are entirely covered by collapsed, non-widget span. 829function lineIsHidden(doc, line) { 830 var sps = sawCollapsedSpans && line.markedSpans 831 if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { 832 sp = sps[i] 833 if (!sp.marker.collapsed) { continue } 834 if (sp.from == null) { return true } 835 if (sp.marker.widgetNode) { continue } 836 if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) 837 { return true } 838 } } 839} 840function lineIsHiddenInner(doc, line, span) { 841 if (span.to == null) { 842 var end = span.marker.find(1, true) 843 return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) 844 } 845 if (span.marker.inclusiveRight && span.to == line.text.length) 846 { return true } 847 for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) { 848 sp = line.markedSpans[i] 849 if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && 850 (sp.to == null || sp.to != span.from) && 851 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && 852 lineIsHiddenInner(doc, line, sp)) { return true } 853 } 854} 855 856// Find the height above the given line. 857function heightAtLine(lineObj) { 858 lineObj = visualLine(lineObj) 859 860 var h = 0, chunk = lineObj.parent 861 for (var i = 0; i < chunk.lines.length; ++i) { 862 var line = chunk.lines[i] 863 if (line == lineObj) { break } 864 else { h += line.height } 865 } 866 for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { 867 for (var i$1 = 0; i$1 < p.children.length; ++i$1) { 868 var cur = p.children[i$1] 869 if (cur == chunk) { break } 870 else { h += cur.height } 871 } 872 } 873 return h 874} 875 876// Compute the character length of a line, taking into account 877// collapsed ranges (see markText) that might hide parts, and join 878// other lines onto it. 879function lineLength(line) { 880 if (line.height == 0) { return 0 } 881 var len = line.text.length, merged, cur = line 882 while (merged = collapsedSpanAtStart(cur)) { 883 var found = merged.find(0, true) 884 cur = found.from.line 885 len += found.from.ch - found.to.ch 886 } 887 cur = line 888 while (merged = collapsedSpanAtEnd(cur)) { 889 var found$1 = merged.find(0, true) 890 len -= cur.text.length - found$1.from.ch 891 cur = found$1.to.line 892 len += cur.text.length - found$1.to.ch 893 } 894 return len 895} 896 897// Find the longest line in the document. 898function findMaxLine(cm) { 899 var d = cm.display, doc = cm.doc 900 d.maxLine = getLine(doc, doc.first) 901 d.maxLineLength = lineLength(d.maxLine) 902 d.maxLineChanged = true 903 doc.iter(function (line) { 904 var len = lineLength(line) 905 if (len > d.maxLineLength) { 906 d.maxLineLength = len 907 d.maxLine = line 908 } 909 }) 910} 911 912// BIDI HELPERS 913 914function iterateBidiSections(order, from, to, f) { 915 if (!order) { return f(from, to, "ltr", 0) } 916 var found = false 917 for (var i = 0; i < order.length; ++i) { 918 var part = order[i] 919 if (part.from < to && part.to >
919 from || from == to && part.to == from) { 920 f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr", i) 921 found = true 922 } 923 } 924 if (!found) { f(from, to, "ltr") } 925} 926 927var bidiOther = null 928function getBidiPartAt(order, ch, sticky) { 929 var found 930 bidiOther = null 931 for (var i = 0; i < order.length; ++i) { 932 var cur = order[i] 933 if (cur.from < ch && cur.to > ch) { return i } 934 if (cur.to == ch) { 935 if (cur.from != cur.to && sticky == "before") { found = i } 936 else { bidiOther = i } 937 } 938 if (cur.from == ch) { 939 if (cur.from != cur.to && sticky != "before") { found = i } 940 else { bidiOther = i } 941 } 942 } 943 return found != null ? found : bidiOther 944} 945 946// Bidirectional ordering algorithm 947// See http://unicode.org/reports/tr9/tr9-13.html for the algorithm 948// that this (partially) implements. 949 950// One-char codes used for character types: 951// L (L): Left-to-Right 952// R (R): Right-to-Left 953// r (AL): Right-to-Left Arabic 954// 1 (EN): European Number 955// + (ES): European Number Separator 956// % (ET): European Number Terminator 957// n (AN): Arabic Number 958// , (CS): Common Number Separator 959// m (NSM): Non-Spacing Mark 960// b (BN): Boundary Neutral 961// s (B): Paragraph Separator 962// t (S): Segment Separator 963// w (WS): Whitespace 964// N (ON): Other Neutrals 965 966// Returns null if characters are ordered as they appear 967// (left-to-right), or an array of sections ({from, to, level} 968// objects) in the order in which they occur visually. 969var bidiOrdering = (function() { 970 // Character types for codepoints 0 to 0xff 971 var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN" 972 // Character types for codepoints 0x600 to 0x6f9 973 var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111" 974 function charType(code) { 975 if (code <= 0xf7) { return lowTypes.charAt(code) } 976 else if (0x590 <= code && code <= 0x5f4) { return "R" } 977 else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) } 978 else if (0x6ee <= code && code <= 0x8ac) { return "r" } 979 else if (0x2000 <= code && code <= 0x200b) { return "w" } 980 else if (code == 0x200c) { return "b" } 981 else { return "L" } 982 } 983 984 var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/ 985 var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/ 986 987 function BidiSpan(level, from, to) { 988 this.level = level 989 this.from = from; this.to = to 990 } 991 992 return function(str, direction) { 993 var outerType = direction == "ltr" ? "L" : "R" 994 995 if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return false } 996 var len = str.length, types = [] 997 for (var i = 0; i < len; ++i) 998 { types.push(charType(str.charCodeAt(i))) } 999 1000 // W1. Examine each non-spacing mark (NSM) in the level run, and 1001 // change the type of the NSM to the type of the previous 1002 // character. If the NSM is at the start of the level run, it will 1003 // get the type of sor. 1004 for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) { 1005 var type = types[i$1] 1006 if (type == "m") { types[i$1] = prev } 1007 else { prev = type } 1008 } 1009 1010 // W2. Search backwards from each instance of a European number 1011 // until the first strong type (R, L, AL, or sor) is found. If an 1012 // AL is found, change the type of the European number to Arabic 1013 // number. 1014 // W3. Change all ALs to R. 1015 for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) { 1016 var type$1 = types[i$2] 1017 if (type$1 == "1" && cur == "r") { types[i$2] = "n" } 1018 else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "R" } } 1019 } 1020 1021 // W4. A single European separator between two European numbers 1022 // changes to a European number. A single common separator between 1023 // two numbers of the same type changes to that type. 1024 for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) { 1025 var type$2 = types[i$3] 1026 if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1" } 1027 else if (type$2 == "," && prev$1 == types[i$3+1] && 1028 (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1 } 1029 prev$1 = type$2 1030 } 1031 1032 // W5. A sequence of European terminators adjacent to European 1033 // numbers changes to all European numbers. 1034 // W6. Otherwise, separators and terminators change to Other 1035 // Neutral. 1036 for (var i$4 = 0; i$4 < len; ++i$4) { 1037 var type$3 = types[i$4] 1038 if (type$3 == ",") { types[i$4] = "N" } 1039 else if (type$3 == "%") { 1040 var end = (void 0) 1041 for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {} 1042 var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N" 1043 for (var j = i$4; j < end; ++j) { types[j] = replace } 1044 i$4 = end - 1 1045 } 1046 } 1047 1048 // W7. Search backwards from each instance of a European number 1049 // until the first strong type (R, L, or sor) is found. If an L is 1050 // found, then change the type of the European number to L. 1051 for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) { 1052 var type$4 = types[i$5] 1053 if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L" } 1054 else if (isStrong.test(type$4)) { cur$1 = type$4 } 1055 } 1056 1057 // N1. A sequence of neutrals takes the direction of the 1058 // surrounding strong text if the text on both sides has the same 1059 // direction. European and Arabic numbers act as if they were R in 1060 // terms of their influence on neutrals. Start-of-level-run (sor) 1061 // and end-of-level-run (eor) are used at level run boundaries. 1062 // N2. Any remaining neutrals take the embedding direction. 1063 for (var i$6 = 0; i$6 < len; ++i$6) { 1064 if (isNeutral.test(types[i$6])) { 1065 var end$1 = (void 0) 1066 for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {} 1067 var before = (i$6 ? types[i$6-1] : outerType) == "L" 1068 var after = (end$1 < len ? types[end$1] : outerType) == "L" 1069 var replace$1 = before == after ? (before ? "L" : "R") : outerType 1070 for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1 } 1071 i$6 = end$1 - 1 1072 } 1073 } 1074 1075 // Here we depart from the documented algorithm, in order to avoid 1076 // building up an actual levels array. Since there are only three 1077 // levels (0, 1, 2) in an implementation that doesn't take 1078 // explicit embedding into account, we can build up the order on 1079 // the fly, without following the level-based algorithm. 1080 var order = [], m 1081 for (var i$7 = 0; i$7 < len;) { 1082 if (countsAsLeft.test(types[i$7])) { 1083 var start = i$7 1084 for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {} 1085 order.push(new BidiSpan(0, start, i$7)) 1086 } else { 1087 var pos = i$7, at = order.length 1088 for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {} 1089 for (var j$2 = pos; j$2 < i$7;) { 1090 if (countsAsNum.test(types[j$2])) { 1091 if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)) } 1092 var nstart = j$2 1093 for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {} 1094 order.splice(at, 0, new BidiSpan(2, nstart, j$2)) 1095 pos = j$2 1096 } else { ++j$2 } 1097 } 1098 if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)) } 1099 } 1100 } 1101 if (direction == "ltr") { 1102 if (order[0].level == 1 && (m = str.match(/^\s+/))) { 1103 order[0].from = m[0].length 1104 order.unshift(new BidiSpan(0, 0, m[0].length)) 1105 } 1106 if (lst(order).level == 1 && (m = str.match(/\s+$/))) { 1107 lst(order).to -= m[0].length 1108 order.push(new BidiSpan(0, len - m[0].length, len)) 1109 } 1110 } 1111 1112 return direction == "rtl" ? order.reverse() : order 1113 } 1114})() 1115 1116// Get the bidi ordering for the given line (and cache it). Returns 1117// false for lines that are fully left-to-right, and an array of 1118// BidiSpan objects otherwise. 1119function getOrder(line, direction) { 1120 var order = line.order 1121 if (order == null) { order = line.order = bidiOrdering(line.text, direction) } 1122 return order 1123} 1124 1125// EVENT HANDLING 1126 1127// Lightweight event framework. on/off also work on DOM nodes, 1128// registering native DOM handlers. 1129 1130var noHandlers = [] 1131 1132var on = function(emitter, type, f) { 1133 if (emitter.addEventListener) { 1134 emitter.addEventListener(type, f, false) 1135 } else if (emitter.attachEvent) { 1136 emitter.attachEvent("on" + type, f) 1137 } else { 1138 var map = emitter._handlers || (emitter._handlers = {})
1139 map[type] = (map[type] || noHandlers).concat(f) 1140 } 1141} 1142 1143function getHandlers(emitter, type) { 1144 return emitter._handlers && emitter._handlers[type] || noHandlers 1145} 1146 1147function off(emitter, type, f) { 1148 if (emitter.removeEventListener) { 1149 emitter.removeEventListener(type, f, false) 1150 } else if (emitter.detachEvent) { 1151 emitter.detachEvent("on" + type, f) 1152 } else { 1153 var map = emitter._handlers, arr = map && map[type] 1154 if (arr) { 1155 var index = indexOf(arr, f) 1156 if (index > -1) 1157 { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)) } 1158 } 1159 } 1160} 1161 1162function signal(emitter, type /*, values...*/) { 1163 var handlers = getHandlers(emitter, type) 1164 if (!handlers.length) { return } 1165 var args = Array.prototype.slice.call(arguments, 2) 1166 for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args) } 1167} 1168 1169// The DOM events that CodeMirror handles can be overridden by 1170// registering a (non-DOM) handler on the editor for the event name, 1171// and preventDefault-ing the event in that handler. 1172function signalDOMEvent(cm, e, override) { 1173 if (typeof e == "string") 1174 { e = {type: e, preventDefault: function() { this.defaultPrevented = true }} } 1175 signal(cm, override || e.type, cm, e) 1176 return e_defaultPrevented(e) || e.codemirrorIgnore 1177} 1178 1179function signalCursorActivity(cm) { 1180 var arr = cm._handlers && cm._handlers.cursorActivity 1181 if (!arr) { return } 1182 var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []) 1183 for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) 1184 { set.push(arr[i]) } } 1185} 1186 1187function hasHandler(emitter, type) { 1188 return getHandlers(emitter, type).length > 0 1189} 1190 1191// Add on and off methods to a constructor's prototype, to make 1192// registering events on such objects more convenient. 1193function eventMixin(ctor) { 1194 ctor.prototype.on = function(type, f) {on(this, type, f)} 1195 ctor.prototype.off = function(type, f) {off(this, type, f)} 1196} 1197 1198// Due to the fact that we still support jurassic IE versions, some 1199// compatibility wrappers are needed. 1200 1201function e_preventDefault(e) { 1202 if (e.preventDefault) { e.preventDefault() } 1203 else { e.returnValue = false } 1204} 1205function e_stopPropagation(e) { 1206 if (e.stopPropagation) { e.stopPropagation() } 1207 else { e.cancelBubble = true } 1208} 1209function e_defaultPrevented(e) { 1210 return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false 1211} 1212function e_stop(e) {e_preventDefault(e); e_stopPropagation(e)} 1213 1214function e_target(e) {return e.target || e.srcElement} 1215function e_button(e) { 1216 var b = e.which 1217 if (b == null) { 1218 if (e.button & 1) { b = 1 } 1219 else if (e.button & 2) { b = 3 } 1220 else if (e.button & 4) { b = 2 } 1221 } 1222 if (mac && e.ctrlKey && b == 1) { b = 3 } 1223 return b 1224} 1225 1226// Detect drag-and-drop 1227var dragAndDrop = function() { 1228 // There is *some* kind of drag-and-drop support in IE6-8, but I 1229 // couldn't get it to work yet. 1230 if (ie && ie_version < 9) { return false } 1231 var div = elt('div') 1232 return "draggable" in div || "dragDrop" in div 1233}() 1234 1235var zwspSupported 1236function zeroWidthElement(measure) { 1237 if (zwspSupported == null) { 1238 var test = elt("span", "\u200b") 1239 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])) 1240 if (measure.firstChild.offsetHeight != 0) 1241 { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8) } 1242 } 1243 var node = zwspSupported ? elt("span", "\u200b") : 1244 elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px") 1245 node.setAttribute("cm-text", "") 1246 return node 1247} 1248 1249// Feature-detect IE's crummy client rect reporting for bidi text 1250var badBidiRects 1251function hasBadBidiRects(measure) { 1252 if (badBidiRects != null) { return badBidiRects } 1253 var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")) 1254 var r0 = range(txt, 0, 1).getBoundingClientRect() 1255 var r1 = range(txt, 1, 2).getBoundingClientRect() 1256 removeChildren(measure) 1257 if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780) 1258 return badBidiRects = (r1.right - r0.right < 3) 1259} 1260 1261// See if "".split is the broken IE version, if so, provide an 1262// alternative way to split lines. 1263var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { 1264 var pos = 0, result = [], l = string.length 1265 while (pos <= l) { 1266 var nl = string.indexOf("\n", pos) 1267 if (nl == -1) { nl = string.length } 1268 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl) 1269 var rt = line.indexOf("\r") 1270 if (rt != -1) { 1271 result.push(line.slice(0, rt)) 1272 pos += rt + 1 1273 } else { 1274 result.push(line) 1275 pos = nl + 1 1276 } 1277 } 1278 return result 1279} : function (string) { return string.split(/\r\n?|\n/); } 1280 1281var hasSelection = window.getSelection ? function (te) {
1282 try { return te.selectionStart != te.selectionEnd } 1283 catch(e) { return false } 1284} : function (te) { 1285 var range 1286 try {range = te.ownerDocument.selection.createRange()} 1287 catch(e) {} 1288 if (!range || range.parentElement() != te) { return false } 1289 return range.compareEndPoints("StartToEnd", range) != 0 1290} 1291 1292var hasCopyEvent = (function () { 1293 var e = elt("div") 1294 if ("oncopy" in e) { return true } 1295 e.setAttribute("oncopy", "return;") 1296 return typeof e.oncopy == "function" 1297})() 1298 1299var badZoomedRects = null 1300function hasBadZoomedRects(measure) { 1301 if (badZoomedRects != null) { return badZoomedRects } 1302 var node = removeChildrenAndAdd(measure, elt("span", "x")) 1303 var normal = node.getBoundingClientRect() 1304 var fromRange = range(node, 0, 1).getBoundingClientRect() 1305 return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 1306} 1307 1308var modes = {}; 1309var mimeModes = {}; 1310// Extra arguments are stored as the mode's dependencies, which is 1311// used by (legacy) mechanisms like loadmode.js to automatically 1312// load a mode. (Preferred mechanism is the require/define calls.) 1313function defineMode(name, mode) { 1314 if (arguments.length > 2) 1315 { mode.dependencies = Array.prototype.slice.call(arguments, 2) } 1316 modes[name] = mode 1317} 1318 1319function defineMIME(mime, spec) { 1320 mimeModes[mime] = spec 1321} 1322 1323// Given a MIME type, a {name, ...options} config object, or a name 1324// string, return a mode config object. 1325function resolveMode(spec) { 1326 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { 1327 spec = mimeModes[spec] 1328 } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { 1329 var found = mimeModes[spec.name] 1330 if (typeof found == "string") { found = {name: found} } 1331 spec = createObj(found, spec) 1332 spec.name = found.name 1333 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { 1334 return resolveMode("application/xml") 1335 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { 1336 return resolveMode("application/json") 1337 } 1338 if (typeof spec == "string") { return {name: spec} } 1339 else { return spec || {name: "null"} } 1340} 1341 1342// Given a mode spec (anything that resolveMode accepts), find and 1343// initialize an actual mode object. 1344function getMode(options, spec) { 1345 spec = resolveMode(spec) 1346 var mfactory = modes[spec.name] 1347 if (!mfactory) { return getMode(options, "text/plain") } 1348 var modeObj = mfactory(options, spec) 1349 if (modeExtensions.hasOwnProperty(spec.name)) { 1350 var exts = modeExtensions[spec.name] 1351 for (var prop in exts) { 1352 if (!exts.hasOwnProperty(prop)) { continue } 1353 if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop] } 1354 modeObj[prop] = exts[prop] 1355 } 1356 } 1357 modeObj.name = spec.name 1358 if (spec.helperType) { modeObj.helperType = spec.helperType } 1359 if (spec.modeProps) { for (var prop$1 in spec.modeProps) 1360 { modeObj[prop$1] = spec.modeProps[prop$1] } } 1361 1362 return modeObj 1363} 1364 1365// This can be used to attach properties to mode objects from 1366// outside the actual mode definition. 1367var modeExtensions = {} 1368function extendMode(mode, properties) { 1369 var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}) 1370 copyObj(properties, exts) 1371} 1372 1373function copyState(mode, state) { 1374 if (state === true) { return state } 1375 if (mode.copyState) { return mode.copyState(state) } 1376 var nstate = {} 1377 for (var n in state) { 1378 var val = state[n] 1379 if (val instanceof Array) { val = val.concat([]) } 1380 nstate[n] = val 1381 } 1382 return nstate 1383} 1384 1385// Given a mode and a state (for that mode), find the inner mode and 1386// state at the position that the state refers to. 1387function innerMode(mode, state) { 1388 var info 1389 while (mode.innerMode) { 1390 info = mode.innerMode(state) 1391 if (!info || info.mode == mode) { break } 1392 state = info.state 1393 mode = info.mode 1394 } 1395 return info || {mode: mode, state: state} 1396} 1397 1398function startState(mode, a1, a2) { 1399 return mode.startState ? mode.startState(a1, a2) : true 1400} 1401 1402// STRING STREAM 1403 1404// Fed to the mode parsers, provides helper functions to make 1405// parsers more succinct. 1406 1407var StringStream = function(string, tabSize, lineOracle) { 1408 this.pos = this.start = 0 1409 this.string = string 1410 this.tabSize = tabSize || 8 1411 this.lastColumnPos = this.lastColumnValue = 0 1412 this.lineStart = 0 1413 this.lineOracle = lineOracle 1414}; 1415 1416StringStream.prototype.eol = function () {return this.pos >= this.string.length}; 1417StringStream.prototype.sol = function () {return this.pos == this.lineStart}; 1418StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined}; 1419StringStream.prototype.next = function () { 1420 if (this.pos < this.string.length) 1421 { return this.string.charAt(this.pos++) } 1422}; 1423StringStream.prototype.eat = function (match) { 1424 var ch = this.string.charAt(this.pos) 1425 var ok 1426 if (typeof match == "string") { ok = ch == match } 1427 else { ok = ch && (match.test ? match.test(ch) : match(ch)) } 1428 if (ok) {++this.pos; return ch} 1429}; 1430StringStream.prototype.eatWhile = function (match) { 1431 var start = this.pos 1432 while (this.eat(match)){} 1433 return this.pos > start 1434}; 1435StringStream.prototype.eatSpace = function () { 1436 var this$1 = this; 1437 1438 var start = this.pos 1439 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos } 1440 return this.pos > start 1441}; 1442StringStream.prototype.skipToEnd = function () {this.pos = this.string.length}; 1443StringStream.prototype.skipTo = function (ch) { 1444 var found = this.string.indexOf(ch, this.pos) 1445 if (found > -1) {this.pos = found; return true} 1446}; 1447StringStream.prototype.backUp = function (n) {this.pos -= n}; 1448StringStream.prototype.column = function () { 1449 if (this.lastColumnPos < this.start) { 1450 this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue) 1451 this.lastColumnPos = this.start 1452 } 1453 return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) 1454}; 1455StringStream.prototype.indentation = function () { 1456 return countColumn(this.string, null, this.tabSize) - 1457 (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) 1458}; 1459StringStream.prototype.match = function (pattern, consume, caseInsensitive) { 1460 if (typeof pattern == "string") { 1461 var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; } 1462 var substr = this.string.substr(this.pos, pattern.length) 1463 if (cased(substr) == cased(pattern)) { 1464 if (consume !== false) { this.pos += pattern.length } 1465 return true 1466 } 1467 } else { 1468 var match = this.string.slice(this.pos).match(pattern) 1469 if (match && match.index > 0) { return null } 1470 if (match && consume !== false) { this.pos += match[0].length } 1471 return match 1472 } 1473}; 1474StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)}; 1475StringStream.prototype.hideFirstChars = function (n, inner) { 1476 this.lineStart += n 1477 try { return inner() } 1478 finally { this.lineStart -= n } 1479}; 1480StringStream.prototype.lookAhead = function (n) { 1481 var oracle = this.lineOracle 1482 return oracle && oracle.lookAhead(n) 1483}; 1484StringStream.prototype.baseToken = function () { 1485 var oracle = this.lineOracle 1486 return oracle && oracle.baseToken(this.pos) 1487}; 1488 1489var SavedContext = function(state, lookAhead) { 1490 this.state = state 1491 this.lookAhead = lookAhead 1492}; 1493 1494var Context = function(doc, state, line, lookAhead) { 1495 this.state = state 1496 this.doc = doc 1497 this.line = line 1498 this.maxLookAhead = lookAhead || 0 1499 this.baseTokens = null 1500 this.baseTokenPos = 1 1501}; 1502 1503Context.prototype.lookAhead = function (n) { 1504 var line = this.doc.getLine(this.line + n) 1505 if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n } 1506 return line 1507}; 1508 1509Context.prototype.baseToken = function (n) { 1510 var this$1 = this; 1511 1512 if (!this.baseTokens) { return null } 1513 while (this.baseTokens[this.baseTokenPos] <= n) 1514 { this$1.baseTokenPos += 2 } 1515 var type = this.baseTokens[this.baseTokenPos + 1] 1516 return {type: type && type.replace(/( |^)overlay .*/, ""), 1517 size: this.baseTokens[this.baseTokenPos] - n} 1518}; 1519 1520Context.prototype.nextLine = function () { 1521 this.line++ 1522 if (this.maxLookAhead > 0) { this.maxLookAhead-- } 1523}; 1524 1525Context.fromSaved = function (doc, saved, line) { 1526 if (saved instanceof SavedContext) 1527 { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) } 1528 else 1529 { return new Context(doc, copyState(doc.mode, saved), line) } 1530}
1530; 1531 1532Context.prototype.save = function (copy) { 1533 var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state 1534 return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state 1535}; 1536 1537 1538// Compute a style array (an array starting with a mode generation 1539// -- for invalidation -- followed by pairs of end positions and 1540// style strings), which is used to highlight the tokens on the 1541// line. 1542function highlightLine(cm, line, context, forceToEnd) { 1543 // A styles array always starts with a number identifying the 1544 // mode/overlays that it is based on (for easy invalidation). 1545 var st = [cm.state.modeGen], lineClasses = {} 1546 // Compute the base array of styles 1547 runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); }, 1548 lineClasses, forceToEnd) 1549 var state = context.state 1550 1551 // Run overlays, adjust style array. 1552 var loop = function ( o ) { 1553 context.baseTokens = st 1554 var overlay = cm.state.overlays[o], i = 1, at = 0 1555 context.state = true 1556 runMode(cm, line.text, overlay.mode, context, function (end, style) { 1557 var start = i 1558 // Ensure there's a token end at the current position, and that i points at it 1559 while (at < end) { 1560 var i_end = st[i] 1561 if (i_end > end) 1562 { st.splice(i, 1, end, st[i+1], i_end) } 1563 i += 2 1564 at = Math.min(end, i_end) 1565 } 1566 if (!style) { return } 1567 if (overlay.opaque) { 1568 st.splice(start, i - start, end, "overlay " + style) 1569 i = start + 2 1570 } else { 1571 for (; start < i; start += 2) { 1572 var cur = st[start+1] 1573 st[start+1] = (cur ? cur + " " : "") + "overlay " + style 1574 } 1575 } 1576 }, lineClasses) 1577 context.state = state 1578 context.baseTokens = null 1579 context.baseTokenPos = 1 1580 }; 1581 1582 for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); 1583 1584 return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} 1585} 1586 1587function getLineStyles(cm, line, updateFrontier) { 1588 if (!line.styles || line.styles[0] != cm.state.modeGen) { 1589 var context = getContextBefore(cm, lineNo(line)) 1590 var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state) 1591 var result = highlightLine(cm, line, context) 1592 if (resetState) { context.state = resetState } 1593 line.stateAfter = context.save(!resetState) 1594 line.styles = result.styles 1595 if (result.classes) { line.styleClasses = result.classes } 1596 else if (line.styleClasses) { line.styleClasses = null } 1597 if (updateFrontier === cm.doc.highlightFrontier) 1598 { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier) } 1599 } 1600 return line.styles 1601} 1602 1603function getContextBefore(cm, n, precise) { 1604 var doc = cm.doc, display = cm.display 1605 if (!doc.mode.startState) { return new Context(doc, true, n) } 1606 var start = findStartLine(cm, n, precise) 1607 var saved = start > doc.first && getLine(doc, start - 1).stateAfter 1608 var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start) 1609 1610 doc.iter(start, n, function (line) { 1611 processLine(cm, line.text, context) 1612 var pos = context.line 1613 line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null 1614 context.nextLine() 1615 }) 1616 if (precise) { doc.modeFrontier = context.line } 1617 return context 1618} 1619 1620// Lightweight form of highlight -- proceed over this line and 1621// update state, but don't save a style array. Used for lines that 1622// aren't currently visible. 1623function processLine(cm, text, context, startAt) { 1624 var mode = cm.doc.mode 1625 var stream = new StringStream(text, cm.options.tabSize, context) 1626 stream.start = stream.pos = startAt || 0 1627 if (text == "") { callBlankLine(mode, context.state) } 1628 while (!stream.eol()) { 1629 readToken(mode, stream, context.state) 1630 stream.start = stream.pos 1631 } 1632} 1633 1634function callBlankLine(mode, state) { 1635 if (mode.blankLine) { return mode.blankLine(state) } 1636 if (!mode.innerMode) { return } 1637 var inner = innerMode(mode, state) 1638 if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) } 1639} 1640 1641function readToken(mode, stream, state, inner) { 1642 for (var i = 0; i < 10; i++) { 1643 if (inner) { inner[0] = innerMode(mode, state).mode } 1644 var style = mode.token(stream, state) 1645 if (stream.pos > stream.start) { return style } 1646 } 1647 throw new Error("Mode " + mode.name + " failed to advance stream.") 1648} 1649 1650var Token = function(stream, type, state) { 1651 this.start = stream.start; this.end = stream.pos 1652 this.string = stream.current() 1653 this.type = type || null 1654 this.state = state 1655}; 1656 1657// Utility for getTokenAt and getLineTokens 1658function takeToken(cm, pos, precise, asArray) { 1659 var doc = cm.doc, mode = doc.mode, style 1660 pos = clipPos(doc, pos) 1661 var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise) 1662 var stream = new StringStream(line.text, cm.options.tabSize, context), tokens 1663 if (asArray) { tokens = [] } 1664 while ((asArray || stream.pos < pos.ch) && !stream.eol()) { 1665 stream.start = stream.pos 1666 style = readToken(mode, stream, context.state) 1667 if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))) } 1668 } 1669 return asArray ? tokens : new Token(stream, style, context.state) 1670} 1671 1672function extractLineClasses(type, output) { 1673 if (type) { for (;;) { 1674 var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/) 1675 if (!lineClass) { break } 1676 type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length) 1677 var prop = lineClass[1] ? "bgClass" : "textClass" 1678 if (output[prop] == null) 1679 { output[prop] = lineClass[2] } 1680 else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) 1681 { output[prop] += " " + lineClass[2] } 1682 } } 1683 return type 1684} 1685 1686// Run the given mode's parser over a line, calling f for each token. 1687function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { 1688 var flattenSpans = mode.flattenSpans 1689 if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans } 1690 var curStart = 0, curStyle = null 1691 var stream = new StringStream(text, cm.options.tabSize, context), style 1692 var inner = cm.options.addModeClass && [null] 1693 if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses) } 1694 while (!stream.eol()) { 1695 if (stream.pos > cm.options.maxHighlightLength) { 1696 flattenSpans = false 1697 if (forceToEnd) { processLine(cm, text, context, stream.pos) } 1698 stream.pos = text.length 1699 style = null 1700 } else { 1701 style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses) 1702 } 1703 if (inner) { 1704 var mName = inner[0].name 1705 if (mName) { style = "m-" + (style ? mName + " " + style : mName) } 1706 } 1707 if (!flattenSpans || curStyle != style) { 1708 while (curStart < stream.start) { 1709 curStart = Math.min(stream.start, curStart + 5000) 1710 f(curStart, curStyle) 1711 } 1712 curStyle = style 1713 } 1714 stream.start = stream.pos 1715 } 1716 while (curStart < stream.pos) { 1717 // Webkit seems to refuse to render text nodes longer than 57444 1718 // characters, and returns inaccurate measurements in nodes 1719 // starting around 5000 chars. 1720 var pos = Math.min(stream.pos, curStart + 5000) 1721 f(pos, curStyle) 1722 curStart = pos 1723 } 1724} 1725 1726// Finds the line to start with when starting a parse. Tries to 1727// find a line with a stateAfter, so that it can start with a 1728// valid state. If that fails, it returns the line with the 1729// smallest indentation, which tends to need the least context to 1730// parse correctly. 1731function findStartLine(cm, n, precise) { 1732 var minindent, minline, doc = cm.doc 1733 var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100) 1734 for (var search = n; search > lim; --search) { 1735 if (search <= doc.first) { return doc.first } 1736 var line = getLine(doc, search - 1), after = line.stateAfter 1737 if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) 1738 { return search } 1739 var indented = countColumn(line.text, null, cm.options.tabSize) 1740 if (minline == null || minindent > indented) { 1741 minline = search - 1 1742 minindent = indented 1743 } 1744 } 1745 return minline 1746} 1747 1748function retreatFrontier(doc, n) { 1749 doc.modeFrontier = Math.min(doc.modeFrontier, n) 1750 if (doc.highlightFrontier < n - 10) { return } 1751 var start = doc.first 1752 for (var line = n - 1; line > start; line--) { 1753 var saved = getLine(doc, line).stateAfter 1754 // change is on 3 1755 // state on line 1 looked ahead 2 -- so saw 3 1756 // test 1 + 2 < 3 should cover this 1757 if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { 1758 start = line + 1 1759 break 1760 } 1761 } 1762 doc.highlightFrontier = Math.min(doc.highlightFrontier, start) 1763} 1764 1765// LINE DATA STRUCTURE 1766 1767// Line objects. These hold state related to a line, including 1768// highlighting info (the styles array). 1769var Line = function(text, markedSpans, estimateHeight) { 1770 this.text = text 1771 attachMarkedSpans(this, markedSpans) 1772 this.height = estimateHeight ? estimateHeight(this) : 1 1773}; 1774 1775Line.prototype.lineNo = function () { return lineNo(this) }; 1776eventMixin(Line) 1777 1778// Change the content (text, markers) of a line. Automatically 1779// invalidates cached information and tries to re-estimate the 1780// line's height. 1781function updateLine(line, text, markedSpans, estimateHeight) { 1782 line.text = text 1783 if (line.stateAfter) { line.stateAfter = null } 1784 if (line.styles) { line.styles = null } 1785 if (line.order != null) { line.order = null } 1786 detachMarkedSpans(line) 1787 attachMarkedSpans(line, markedSpans) 1788 var estHeight = estimateHeight ? estimateHeight(line) : 1
1789 if (estHeight != line.height) { updateLineHeight(line, estHeight) } 1790} 1791 1792// Detach a line from the document tree and its markers. 1793function cleanUpLine(line) { 1794 line.parent = null 1795 detachMarkedSpans(line) 1796} 1797 1798// Convert a style as returned by a mode (either null, or a string 1799// containing one or more styles) to a CSS style. This is cached, 1800// and also looks for line-wide styles. 1801var styleToClassCache = {}; 1802var styleToClassCacheWithMode = {}; 1803function interpretTokenStyle(style, options) { 1804 if (!style || /^\s*$/.test(style)) { return null } 1805 var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache 1806 return cache[style] || 1807 (cache[style] = style.replace(/\S+/g, "cm-$&")) 1808} 1809 1810// Render the DOM representation of the text of a line. Also builds 1811// up a 'line map', which points at the DOM nodes that represent 1812// specific stretches of text, and is used by the measuring code. 1813// The returned object contains the DOM node, this map, and 1814// information about line-wide styles that were set by the mode. 1815function buildLineContent(cm, lineView) { 1816 // The padding-right forces the element to have a 'border', which 1817 // is needed on Webkit to be able to get line-level bounding 1818 // rectangles for it (in measureChar). 1819 var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null) 1820 var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, 1821 col: 0, pos: 0, cm: cm, 1822 trailingSpace: false, 1823 splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")} 1824 lineView.measure = {} 1825 1826 // Iterate over the logical lines that make up this visual line. 1827 for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { 1828 var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0) 1829 builder.pos = 0 1830 builder.addToken = buildToken 1831 // Optionally wire in some hacks into the token-rendering 1832 // algorithm, to deal with browser quirks. 1833 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) 1834 { builder.addToken = buildTokenBadBidi(builder.addToken, order) } 1835 builder.map = [] 1836 var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line) 1837 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)) 1838 if (line.styleClasses) { 1839 if (line.styleClasses.bgClass)
1840 { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "") } 1841 if (line.styleClasses.textClass) 1842 { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "") } 1843 } 1844 1845 // Ensure at least a single node is present, for measuring. 1846 if (builder.map.length == 0) 1847 { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))) } 1848 1849 // Store the map and a cache object for the current logical line 1850 if (i == 0) { 1851 lineView.measure.map = builder.map 1852 lineView.measure.cache = {} 1853 } else { 1854 ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) 1855 ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}) 1856 } 1857 } 1858 1859 // See issue #2901 1860 if (webkit) { 1861 var last = builder.content.lastChild 1862 if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) 1863 { builder.content.className = "cm-tab-wrap-hack" } 1864 } 1865 1866 signal(cm, "renderLine", cm, lineView.line, builder.pre) 1867 if (builder.pre.className)
1868 { builder.textClass = joinClasses(builder.pre.className, builder.textClass || "") } 1869 1870 return builder 1871} 1872 1873function defaultSpecialCharPlaceholder(ch) { 1874 var token = elt("span", "\u2022", "cm-invalidchar") 1875 token.title = "\\u" + ch.charCodeAt(0).toString(16) 1876 token.setAttribute("aria-label", token.title) 1877 return token 1878} 1879 1880// Build up the DOM representation for a single token, and add it to 1881// the line map. Takes care to render special characters separately. 1882function buildToken(builder, text, style, startStyle, endStyle, title, css) { 1883 if (!text) { return } 1884 var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text 1885 var special = builder.cm.state.specialChars, mustWrap = false 1886 var content 1887 if (!special.test(text)) { 1888 builder.col += text.length 1889 content = document.createTextNode(displayText) 1890 builder.map.push(builder.pos, builder.pos + text.length, content) 1891 if (ie && ie_version < 9) { mustWrap = true } 1892 builder.pos += text.length 1893 } else { 1894 content = document.createDocumentFragment() 1895 var pos = 0 1896 while (true) { 1897 special.lastIndex = pos 1898 var m = special.exec(text) 1899 var skipped = m ? m.index - pos : text.length - pos 1900 if (skipped) { 1901 var txt = document.createTextNode(displayText.slice(pos, pos + skipped)) 1902 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])) } 1903 else { content.appendChild(txt) } 1904 builder.map.push(builder.pos, builder.pos + skipped, txt) 1905 builder.col += skipped 1906 builder.pos += skipped 1907 } 1908 if (!m) { break } 1909 pos += skipped + 1 1910 var txt$1 = (void 0) 1911 if (m[0] == "\t") { 1912 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize 1913 txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")) 1914 txt$1.setAttribute("role", "presentation") 1915 txt$1.setAttribute("cm-text", "\t") 1916 builder.col += tabWidth 1917 } else if (m[0] == "\r" || m[0] == "\n") { 1918 txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")) 1919 txt$1.setAttribute("cm-text", m[0]) 1920 builder.col += 1 1921 } else { 1922 txt$1 = builder.cm.options.specialCharPlaceholder(m[0]) 1923 txt$1.setAttribute("cm-text", m[0]) 1924 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])) } 1925 else { content.appendChild(txt$1) } 1926 builder.col += 1 1927 } 1928 builder.map.push(builder.pos, builder.pos + 1, txt$1) 1929 builder.pos++ 1930 } 1931 } 1932 builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 1933 if (style || startStyle || endStyle || mustWrap || css) { 1934 var fullStyle = style || "" 1935 if (startStyle) { fullStyle += startStyle } 1936 if (endStyle) { fullStyle += endStyle } 1937 var token = elt("span", [content], fullStyle, css) 1938 if (title) { token.title = title } 1939 return builder.content.appendChild(token) 1940 } 1941 builder.content.appendChild(content) 1942} 1943 1944function splitSpaces(text, trailingBefore) { 1945 if (text.length > 1 && !/ /.test(text)) { return text } 1946 var spaceBefore = trailingBefore, result = "" 1947 for (var i = 0; i < text.length; i++) { 1948 var ch = text.charAt(i) 1949 if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) 1950 { ch = "\u00a0" } 1951 result += ch 1952 spaceBefore = ch == " " 1953 } 1954 return result 1955} 1956 1957// Work around nonsense dimensions being reported for stretches of 1958// right-to-left text. 1959function buildTokenBadBidi(inner, order) { 1960 return function (builder, text, style, startStyle, endStyle, title, css) { 1961 style = style ? style + " cm-force-border" : "cm-force-border" 1962 var start = builder.pos, end = start + text.length 1963 for (;;) { 1964 // Find the part that overlaps with the start of this text 1965 var part = (void 0) 1966 for (var i = 0; i < order.length; i++) { 1967 part = order[i] 1968 if (part.to > start && part.from <= start) { break } 1969 } 1970 if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, title, css) } 1971 inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css) 1972 startStyle = null 1973 text = text.slice(part.to - start) 1974 start = part.to 1975 } 1976 } 1977} 1978 1979function buildCollapsedSpan(builder, size, marker, ignoreWidget) { 1980 var widget = !ignoreWidget && marker.widgetNode 1981 if (widget) { builder.map.push(builder.pos, builder.pos + size, widget) } 1982 if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { 1983 if (!widget) 1984 { widget = builder.content.appendChild(document.createElement("span")) } 1985 widget.setAttribute("cm-marker", marker.id) 1986 } 1987 if (widget) { 1988 builder.cm.display.input.setUneditable(widget) 1989 builder.content.appendChild(widget) 1990 } 1991 builder.pos += size 1992 builder.trailingSpace = false 1993} 1994 1995// Outputs a number of spans to make up a line, taking highlighting 1996// and marked text into account. 1997function insertLineContent(line, builder, styles) { 1998 var spans = line.markedSpans, allText = line.text, at = 0 1999 if (!spans) { 2000 for (var i$1 = 1; i$1 < styles.length; i$1+=2) 2001 { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)) } 2002 return 2003 } 2004 2005 var len = allText.length, pos = 0, i = 1, text = "", style, css 2006 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed 2007 for (;;) { 2008 if (nextChange == pos) { // Update current marker set 2009 spanStyle = spanEndStyle = spanStartStyle = title = css = "" 2010 collapsed = null; nextChange = Infinity 2011 var foundBookmarks = [], endStyles = (void 0) 2012 for (var j = 0; j < spans.length; ++j) { 2013 var sp = spans[j], m = sp.marker 2014 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { 2015 foundBookmarks.push(m) 2016 } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { 2017 if (sp.to != null && sp.to != pos && nextChange > sp.to) { 2018 nextChange = sp.to 2019 spanEndStyle = "" 2020 } 2021 if (m.className) { spanStyle += " " + m.className } 2022 if (m.css) { css = (css ? css + ";" : "") + m.css } 2023 if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle } 2024 if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to) } 2025 if (m.title && !title) { title = m.title } 2026 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) 2027 { collapsed = sp } 2028 } else if (sp.from > pos && nextChange > sp.from) { 2029 nextChange = sp.from 2030 } 2031 } 2032 if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) 2033 { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1] } } } 2034 2035 if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2) 2036 { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]) } } 2037 if (collapsed && (collapsed.from || 0) == pos) { 2038 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, 2039 collapsed.marker, collapsed.from == null) 2040 if (collapsed.to == null) { return } 2041 if (collapsed.to == pos) { collapsed = false } 2042 } 2043 } 2044 if (pos >= len) { break } 2045 2046 var upto = Math.min(len, nextChange) 2047 while (true) { 2048 if (text) { 2049 var end = pos + text.length 2050 if (!collapsed) { 2051 var tokenText = end > upto ? text.slice(0, upto - pos) : text 2052 builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, 2053 spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css) 2054 } 2055 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} 2056 pos = end 2057 spanStartStyle = "" 2058 } 2059 text = allText.slice(at, at = styles[i++]) 2060 style = interpretTokenStyle(styles[i++], builder.cm.options) 2061 } 2062 } 2063} 2064 2065 2066// These objects are used to represent the visible (currently drawn) 2067// part of the document. A LineView may correspond to multiple 2068// logical lines, if those are connected by collapsed ranges. 2069function LineView(doc, line, lineN) { 2070 // The starting line 2071 this.line = line 2072 // Continuing lines, if any
2073 this.rest = visualLineContinued(line) 2074 // Number of logical lines in this visual line 2075 this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1 2076 this.node = this.text = null 2077 this.hidden = lineIsHidden(doc, line) 2078} 2079 2080// Create a range of LineView objects for the given lines. 2081function buildViewArray(cm, from, to) { 2082 var array = [], nextPos 2083 for (var pos = from; pos < to; pos = nextPos) { 2084 var view = new LineView(cm.doc, getLine(cm.doc, pos), pos) 2085 nextPos = pos + view.size 2086 array.push(view) 2087 } 2088 return array 2089} 2090 2091var operationGroup = null 2092 2093function pushOperation(op) { 2094 if (operationGroup) { 2095 operationGroup.ops.push(op) 2096 } else { 2097 op.ownsGroup = operationGroup = { 2098 ops: [op], 2099 delayedCallbacks: [] 2100 } 2101 } 2102} 2103 2104function fireCallbacksForOps(group) { 2105 // Calls delayed callbacks and cursorActivity handlers until no 2106 // new ones appear 2107 var callbacks = group.delayedCallbacks, i = 0 2108 do { 2109 for (; i < callbacks.length; i++) 2110 { callbacks[i].call(null) } 2111 for (var j = 0; j < group.ops.length; j++) { 2112 var op = group.ops[j] 2113 if (op.cursorActivityHandlers) 2114 { while (op.cursorActivityCalled < op.cursorActivityHandlers.length) 2115 { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm) } } 2116 } 2117 } while (i < callbacks.length) 2118} 2119 2120function finishOperation(op, endCb) { 2121 var group = op.ownsGroup 2122 if (!group) { return } 2123 2124 try { fireCallbacksForOps(group) } 2125 finally { 2126 operationGroup = null 2127 endCb(group) 2128 } 2129} 2130 2131var orphanDelayedCallbacks = null 2132 2133// Often, we want to signal events at a point where we are in the 2134// middle of some work, but don't want the handler to start calling 2135// other methods on the editor, which might be in an inconsistent 2136// state or simply not expect any other events to happen. 2137// signalLater looks whether there are any handlers, and schedules 2138// them to be executed when the last operation ends, or, if no 2139// operation is active, when a timeout fires. 2140function signalLater(emitter, type /*, values...*/) { 2141 var arr = getHandlers(emitter, type) 2142 if (!arr.length) { return } 2143 var args = Array.prototype.slice.call(arguments, 2), list 2144 if (operationGroup) { 2145 list = operationGroup.delayedCallbacks 2146 } else if (orphanDelayedCallbacks) { 2147 list = orphanDelayedCallbacks 2148 } else { 2149 list = orphanDelayedCallbacks = [] 2150 setTimeout(fireOrphanDelayed, 0) 2151 } 2152 var loop = function ( i ) { 2153 list.push(function () { return arr[i].apply(null, args); }) 2154 }; 2155 2156 for (var i = 0; i < arr.length; ++i) 2157 loop( i ); 2158} 2159 2160function fireOrphanDelayed() { 2161 var delayed = orphanDelayedCallbacks 2162 orphanDelayedCallbacks = null 2163 for (var i = 0; i < delayed.length; ++i) { delayed[i]() } 2164} 2165 2166// When an aspect of a line changes, a string is added to 2167// lineView.changes. This updates the relevant part of the line's 2168// DOM structure. 2169function updateLineForChanges(cm, lineView, lineN, dims) { 2170 for (var j = 0; j < lineView.changes.length; j++) { 2171 var type = lineView.changes[j] 2172 if (type == "text") { updateLineText(cm, lineView) } 2173 else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims) } 2174 else if (type == "class") { updateLineClasses(cm, lineView) } 2175 else if (type == "widget") { updateLineWidgets(cm, lineView, dims) } 2176 } 2177 lineView.changes = null 2178} 2179 2180// Lines with gutter elements, widgets or a background class need to 2181// be wrapped, and have the extra elements added to the wrapper div 2182function ensureLineWrapped(lineView) { 2183 if (lineView.node == lineView.text) { 2184 lineView.node = elt("div", null, null, "position: relative") 2185 if (lineView.text.parentNode) 2186 { lineView.text.parentNode.replaceChild(lineView.node, lineView.text) } 2187 lineView.node.appendChild(lineView.text) 2188 if (ie && ie_version < 8) { lineView.node.style.zIndex = 2 } 2189 } 2190 return lineView.node 2191} 2192 2193function updateLineBackground(cm, lineView) { 2194 var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass 2195 if (cls) { cls += " CodeMirror-linebackground" } 2196 if (lineView.background) { 2197 if (cls) { lineView.background.className = cls } 2198 else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null } 2199 } else if (cls) { 2200 var wrap = ensureLineWrapped(lineView) 2201 lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild) 2202 cm.display.input.setUneditable(lineView.background) 2203 } 2204} 2205 2206// Wrapper around buildLineContent which will reuse the structure 2207// in display.externalMeasured when possible. 2208function getLineContent(cm, lineView) { 2209 var ext = cm.display.externalMeasured 2210 if (ext && ext.line == lineView.line) { 2211 cm.display.externalMeasured = null 2212 lineView.measure = ext.measure 2213 return ext.built 2214 } 2215 return buildLineContent(cm, lineView) 2216} 2217 2218// Redraw the line's text. Interacts with the background and text 2219// classes because the mode may output tokens that influence these 2220// classes. 2221function updateLineText(cm, lineView) { 2222 var cls = lineView.text.className 2223 var built = getLineContent(cm, lineView) 2224 if (lineView.text == lineView.node) { lineView.node = built.pre } 2225 lineView.text.parentNode.replaceChild(built.pre, lineView.text) 2226 lineView.text = built.pre 2227 if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { 2228 lineView.bgClass = built.bgClass 2229 lineView.textClass = built.textClass 2230 updateLineClasses(cm, lineView) 2231 } else if (cls) { 2232 lineView.text.className = cls 2233 } 2234} 2235 2236function updateLineClasses(cm, lineView) {
2237 updateLineBackground(cm, lineView) 2238 if (lineView.line.wrapClass) 2239 { ensureLineWrapped(lineView).className = lineView.line.wrapClass } 2240 else if (lineView.node != lineView.text) 2241 { lineView.node.className = "" } 2242 var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass 2243 lineView.text.className = textClass || "" 2244} 2245 2246function updateLineGutter(cm, lineView, lineN, dims) { 2247 if (lineView.gutter) { 2248 lineView.node.removeChild(lineView.gutter) 2249 lineView.gutter = null 2250 } 2251 if (lineView.gutterBackground) { 2252 lineView.node.removeChild(lineView.gutterBackground) 2253 lineView.gutterBackground = null 2254 } 2255 if (lineView.line.gutterClass) { 2256 var wrap = ensureLineWrapped(lineView) 2257 lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, 2258 ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px")) 2259 cm.display.input.setUneditable(lineView.gutterBackground) 2260 wrap.insertBefore(lineView.gutterBackground, lineView.text) 2261 } 2262 var markers = lineView.line.gutterMarkers 2263 if (cm.options.lineNumbers || markers) { 2264 var wrap$1 = ensureLineWrapped(lineView) 2265 var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")) 2266 cm.display.input.setUneditable(gutterWrap) 2267 wrap$1.insertBefore(gutterWrap, lineView.text) 2268 if (lineView.line.gutterClass) 2269 { gutterWrap.className += " " + lineView.line.gutterClass } 2270 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) 2271 { lineView.lineNumber = gutterWrap.appendChild( 2272 elt("div", lineNumberFor(cm.options, lineN), 2273 "CodeMirror-linenumber CodeMirror-gutter-elt", 2274 ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))) } 2275 if (markers) { for (var k = 0; k < cm.options.gutters.length; ++k) { 2276 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id] 2277 if (found) 2278 { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", 2279 ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))) } 2280 } } 2281 } 2282} 2283 2284function updateLineWidgets(cm, lineView, dims) { 2285 if (lineView.alignable) { lineView.alignable = null } 2286 for (var node = lineView.node.firstChild, next = (void 0); node; node = next) { 2287 next = node.nextSibling 2288 if (node.className == "CodeMirror-linewidget") 2289 { lineView.node.removeChild(node) } 2290 } 2291 insertLineWidgets(cm, lineView, dims) 2292} 2293 2294// Build a line's DOM representation from scratch 2295function buildLineElement(cm, lineView, lineN, dims) { 2296 var built = getLineContent(cm, lineView) 2297 lineView.text = lineView.node = built.pre 2298 if (built.bgClass) { lineView.bgClass = built.bgClass } 2299 if (built.textClass) { lineView.textClass = built.textClass } 2300 2301 updateLineClasses(cm, lineView) 2302 updateLineGutter(cm, lineView, lineN, dims) 2303 insertLineWidgets(cm, lineView, dims) 2304 return lineView.node 2305} 2306 2307// A lineView may contain multiple logical lines (when merged by
2308// collapsed spans). The widgets for all of them need to be drawn. 2309function insertLineWidgets(cm, lineView, dims) { 2310 insertLineWidgetsFor(cm, lineView.line, lineView, dims, true) 2311 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) 2312 { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false) } } 2313} 2314 2315function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { 2316 if (!line.widgets) { return } 2317 var wrap = ensureLineWrapped(lineView) 2318 for (var i = 0, ws = line.widgets; i < ws.length; ++i) { 2319 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget") 2320 if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true") } 2321 positionLineWidget(widget, node, lineView, dims) 2322 cm.display.input.setUneditable(node) 2323 if (allowAbove && widget.above) 2324 { wrap.insertBefore(node, lineView.gutter || lineView.text) } 2325 else 2326 { wrap.appendChild(node) } 2327 signalLater(widget, "redraw") 2328 } 2329} 2330 2331function positionLineWidget(widget, node, lineView, dims) { 2332 if (widget.noHScroll) { 2333 ;(lineView.alignable || (lineView.alignable = [])).push(node) 2334 var width = dims.wrapperWidth 2335 node.style.left = dims.fixedPos + "px" 2336 if (!widget.coverGutter) { 2337 width -= dims.gutterTotalWidth 2338 node.style.paddingLeft = dims.gutterTotalWidth + "px" 2339 } 2340 node.style.width = width + "px" 2341 } 2342 if (widget.coverGutter) { 2343 node.style.zIndex = 5 2344 node.style.position = "relative" 2345 if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px" } 2346 } 2347} 2348 2349function widgetHeight(widget) { 2350 if (widget.height != null) { return widget.height } 2351 var cm = widget.doc.cm 2352 if (!cm) { return 0 } 2353 if (!contains(document.body, widget.node)) { 2354 var parentStyle = "position: relative;" 2355 if (widget.coverGutter) 2356 { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;" } 2357 if (widget.noHScroll) 2358 { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;" } 2359 removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)) 2360 } 2361 return widget.height = widget.node.parentNode.offsetHeight 2362} 2363 2364// Return true when the given mouse event happened in a widget 2365function eventInWidget(display, e) { 2366 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { 2367 if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || 2368 (n.parentNode == display.sizer && n != display.mover)) 2369 { return true } 2370 } 2371} 2372 2373// POSITION MEASUREMENT 2374 2375function paddingTop(display) {return display.lineSpace.offsetTop} 2376function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} 2377function paddingH(display) { 2378 if (display.cachedPaddingH) { return display.cachedPaddingH } 2379 var e = removeChildrenAndAdd(display.measure, elt("pre", "x")) 2380 var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle 2381 var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)} 2382 if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data } 2383 return data 2384} 2385 2386function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } 2387function displayWidth(cm) { 2388 return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth 2389} 2390function displayHeight(cm) { 2391 return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight 2392} 2393 2394// Ensure the lineView.wrapping.heights array is populated. This is 2395// an array of bottom offsets for the lines that make up a drawn 2396// line. When lineWrapping is on, there might be more than one 2397// height. 2398function ensureLineHeights(cm, lineView, rect) { 2399 var wrapping = cm.options.lineWrapping 2400 var curWidth = wrapping && displayWidth(cm) 2401 if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { 2402 var heights = lineView.measure.heights = [] 2403 if (wrapping) { 2404 lineView.measure.width = curWidth 2405 var rects = lineView.text.firstChild.getClientRects() 2406 for (var i = 0; i < rects.length - 1; i++) { 2407 var cur = rects[i], next = rects[i + 1] 2408 if (Math.abs(cur.bottom - next.bottom) > 2) 2409 { heights.push((cur.bottom + next.top) / 2 - rect.top) } 2410 } 2411 } 2412 heights.push(rect.bottom - rect.top) 2413 } 2414} 2415 2416// Find a line map (mapping character offsets to text nodes) and a 2417// measurement cache for the given line number. (A line view might 2418// contain multiple lines when collapsed ranges are present.) 2419function mapFromLineView(lineView, line, lineN) { 2420 if (lineView.line == line) 2421 { return {map: lineView.measure.map, cache: lineView.measure.cache} } 2422 for (var i = 0; i < lineView.rest.length; i++) 2423 { if (lineView.rest[i] == line) 2424 { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } } 2425 for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) 2426 { if (lineNo(lineView.rest[i$1]) > lineN) 2427 { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } } 2428} 2429 2430// Render a line into the hidden node display.externalMeasured. Used 2431// when measurement is needed for a line that's not in the viewport.
2432function updateExternalMeasurement(cm, line) { 2433 line = visualLine(line) 2434 var lineN = lineNo(line) 2435 var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN) 2436 view.lineN = lineN 2437 var built = view.built = buildLineContent(cm, view) 2438 view.text = built.pre 2439 removeChildrenAndAdd(cm.display.lineMeasure, built.pre) 2440 return view 2441} 2442 2443// Get a {top, bottom, left, right} box (in line-local coordinates) 2444// for a given character. 2445function measureChar(cm, line, ch, bias) { 2446 return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) 2447} 2448 2449// Find a line view that corresponds to the given line number. 2450function findViewForLine(cm, lineN) { 2451 if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) 2452 { return cm.display.view[findViewIndex(cm, lineN)] } 2453 var ext = cm.display.externalMeasured 2454 if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) 2455 { return ext } 2456} 2457 2458// Measurement can be split in two steps, the set-up work that 2459// applies to the whole line, and the measurement of the actual 2460// character. Functions like coordsChar, that need to do a lot of 2461// measurements in a row, can thus ensure that the set-up work is 2462// only done once. 2463function prepareMeasureForLine(cm, line) { 2464 var lineN = lineNo(line) 2465 var view = findViewForLine(cm, lineN) 2466 if (view && !view.text) { 2467 view = null 2468 } else if (view && view.changes) { 2469 updateLineForChanges(cm, view, lineN, getDimensions(cm)) 2470 cm.curOp.forceUpdate = true 2471 } 2472 if (!view) 2473 { view = updateExternalMeasurement(cm, line) } 2474 2475 var info = mapFromLineView(view, line, lineN) 2476 return { 2477 line: line, view: view, rect: null, 2478 map: info.map, cache: info.cache, before: info.before, 2479 hasHeights: false 2480 } 2481} 2482 2483// Given a prepared measurement object, measures the position of an 2484// actual character (or fetches it from the cache). 2485function measureCharPrepared(cm, prepared, ch, bias, varHeight) { 2486 if (prepared.before) { ch = -1 } 2487 var key = ch + (bias || ""), found 2488 if (prepared.cache.hasOwnProperty(key)) { 2489 found = prepared.cache[key] 2490 } else { 2491 if (!prepared.rect) 2492 { prepared.rect = prepared.view.text.getBoundingClientRect() } 2493 if (!prepared.hasHeights) { 2494 ensureLineHeights(cm, prepared.view, prepared.rect) 2495 prepared.hasHeights = true 2496 } 2497 found = measureCharInner(cm, prepared, ch, bias) 2498 if (!found.bogus) { prepared.cache[key] = found } 2499 } 2500 return {left: found.left, right: found.right, 2501 top: varHeight ? found.rtop : found.top, 2502 bottom: varHeight ? found.rbottom : found.bottom} 2503} 2504 2505var nullRect = {left: 0, right: 0, top: 0, bottom: 0} 2506 2507function nodeAndOffsetInLineMap(map, ch, bias) { 2508 var node, start, end, collapse, mStart, mEnd 2509 // First, search the line map for the text node corresponding to, 2510 // or closest to, the target character. 2511 for (var i = 0; i < map.length; i += 3) { 2512 mStart = map[i] 2513 mEnd = map[i + 1] 2514 if (ch < mStart) { 2515 start = 0; end = 1 2516 collapse = "left" 2517 } else if (ch < mEnd) { 2518 start = ch - mStart 2519 end = start + 1 2520 } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { 2521 end = mEnd - mStart 2522 start = end - 1 2523 if (ch >= mEnd) { collapse = "right" } 2524 } 2525 if (start != null) { 2526 node = map[i + 2] 2527 if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) 2528 { collapse = bias } 2529 if (bias == "left" && start == 0) 2530 { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { 2531 node = map[(i -= 3) + 2] 2532 collapse = "left" 2533 } } 2534 if (bias == "right" && start == mEnd - mStart) 2535 { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { 2536 node = map[(i += 3) + 2] 2537 collapse = "right" 2538 } } 2539 break 2540 } 2541 } 2542 return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} 2543} 2544 2545function getUsefulRect(rects, bias) { 2546 var rect = nullRect 2547 if (bias == "left") { for (var i = 0; i < rects.length; i++) { 2548 if ((rect = rects[i]).left != rect.right) { break } 2549 } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { 2550 if ((rect = rects[i$1]).left != rect.right) { break } 2551 } } 2552 return rect 2553} 2554 2555function measureCharInner(cm, prepared, ch, bias) { 2556 var place = nodeAndOffsetInLineMap(prepared.map, ch, bias) 2557 var node = place.node, start = place.start, end = place.end, collapse = place.collapse 2558 2559 var rect 2560 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. 2561 for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned 2562 while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start }
2563 while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end } 2564 if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) 2565 { rect = node.parentNode.getBoundingClientRect() } 2566 else 2567 { rect = getUsefulRect(range(node, start, end).getClientRects(), bias) } 2568 if (rect.left || rect.right || start == 0) { break } 2569 end = start 2570 start = start - 1 2571 collapse = "right" 2572 } 2573 if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect) } 2574 } else { // If it is a widget, simply get the box for the whole widget. 2575 if (start > 0) { collapse = bias = "right" } 2576 var rects 2577 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) 2578 { rect = rects[bias == "right" ? rects.length - 1 : 0] } 2579 else 2580 { rect = node.getBoundingClientRect() } 2581 } 2582 if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { 2583 var rSpan = node.parentNode.getClientRects()[0] 2584 if (rSpan) 2585 { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom} } 2586 else 2587 { rect = nullRect } 2588 } 2589 2590 var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top 2591 var mid = (rtop + rbot) / 2 2592 var heights = prepared.view.measure.heights 2593 var i = 0 2594 for (; i < heights.length - 1; i++) 2595 { if (mid < heights[i]) { break } } 2596 var top = i ? heights[i - 1] : 0, bot = heights[i] 2597 var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, 2598 right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, 2599 top: top, bottom: bot} 2600 if (!rect.left && !rect.right) { result.bogus = true } 2601 if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot } 2602 2603 return result 2604} 2605 2606// Work around problem with bounding client rects on ranges being 2607// returned incorrectly when zoomed on IE10 and below. 2608function maybeUpdateRectForZooming(measure, rect) { 2609 if (!window.screen || screen.logicalXDPI == null || 2610 screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) 2611 { return rect } 2612 var scaleX = screen.logicalXDPI / screen.deviceXDPI 2613 var scaleY = screen.logicalYDPI / screen.deviceYDPI 2614 return {left: rect.left * scaleX, right: rect.right * scaleX, 2615 top: rect.top * scaleY, bottom: rect.bottom * scaleY} 2616} 2617 2618function clearLineMeasurementCacheFor(lineView) { 2619 if (lineView.measure) { 2620 lineView.measure.cache = {} 2621 lineView.measure.heights = null 2622 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) 2623 { lineView.measure.caches[i] = {} } } 2624 } 2625} 2626 2627function clearLineMeasurementCache(cm) { 2628 cm.display.externalMeasure = null 2629 removeChildren(cm.display.lineMeasure) 2630 for (var i = 0; i < cm.display.view.length; i++) 2631 { clearLineMeasurementCacheFor(cm.display.view[i]) } 2632} 2633 2634function clearCaches(cm) { 2635 clearLineMeasurementCache(cm) 2636 cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null 2637 if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true } 2638 cm.display.lineNumChars = null 2639} 2640 2641function pageScrollX() { 2642 // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 2643 // which causes page_Offset and bounding client rects to use 2644 // different reference viewports and invalidate our calculations. 2645 if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) } 2646 return window.pageXOffset || (document.documentElement || document.body).scrollLeft 2647} 2648function pageScrollY() { 2649 if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) } 2650 return window.pageYOffset || (document.documentElement || document.body).scrollTop 2651} 2652 2653function widgetTopHeight(lineObj) { 2654 var height = 0 2655 if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above) 2656 { height += widgetHeight(lineObj.widgets[i]) } } } 2657 return height 2658} 2659 2660// Converts a {top, bottom, left, right} box from line-local 2661// coordinates into another coordinate system. Context may be one of 2662// "line", "div" (display.lineDiv), "local"./null (editor), "window", 2663// or "page". 2664function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { 2665 if (!includeWidgets) { 2666 var height = widgetTopHeight(lineObj) 2667 rect.top += height; rect.bottom += height 2668 } 2669 if (context == "line") { return rect } 2670 if (!context) { context = "local" } 2671 var yOff = heightAtLine(lineObj) 2672 if (context == "local") { yOff += paddingTop(cm.display) }
2673 else { yOff -= cm.display.viewOffset } 2674 if (context == "page" || context == "window") { 2675 var lOff = cm.display.lineSpace.getBoundingClientRect() 2676 yOff += lOff.top + (context == "window" ? 0 : pageScrollY()) 2677 var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()) 2678 rect.left += xOff; rect.right += xOff 2679 } 2680 rect.top += yOff; rect.bottom += yOff 2681 return rect 2682} 2683 2684// Coverts a box from "div" coords to another coordinate system. 2685// Context may be "window", "page", "div", or "local"./null. 2686function fromCoordSystem(cm, coords, context) { 2687 if (context == "div") { return coords } 2688 var left = coords.left, top = coords.top 2689 // First move into "page" coordinate system 2690 if (context == "page") { 2691 left -= pageScrollX() 2692 top -= pageScrollY() 2693 } else if (context == "local" || !context) { 2694 var localBox = cm.display.sizer.getBoundingClientRect() 2695 left += localBox.left 2696 top += localBox.top 2697 } 2698 2699 var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect() 2700 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} 2701} 2702 2703function charCoords(cm, pos, context, lineObj, bias) { 2704 if (!lineObj) { lineObj = getLine(cm.doc, pos.line) } 2705 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) 2706} 2707 2708// Returns a box for a given cursor position, which may have an 2709// 'other' property containing the position of the secondary cursor 2710// on a bidi boundary. 2711// A cursor Pos(line, char, "before") is on the same visual line as `char - 1` 2712// and after `char - 1` in writing order of `char - 1` 2713// A cursor Pos(line, char, "after") is on the same visual line as `char` 2714// and before `char` in writing order of `char` 2715// Examples (upper-case letters are RTL, lower-case are LTR): 2716// Pos(0, 1, ...) 2717// before after 2718// ab a|b a|b 2719// aB a|B aB| 2720// Ab |Ab A|b 2721// AB B|A B|A 2722// Every position after the last character on a line is considered to stick 2723// to the last character on the line. 2724function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { 2725 lineObj = lineObj || getLine(cm.doc, pos.line) 2726 if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) } 2727 function get(ch, right) { 2728 var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight) 2729 if (right) { m.left = m.right; } else { m.right = m.left } 2730 return intoCoordSystem(cm, lineObj, m, context) 2731 } 2732 var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky 2733 if (ch >= lineObj.text.length) { 2734 ch = lineObj.text.length 2735 sticky = "before" 2736 } else if (ch <= 0) { 2737 ch = 0 2738 sticky = "after" 2739 } 2740 if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") } 2741 2742 function getBidi(ch, partPos, invert) { 2743 var part = order[partPos], right = part.level == 1 2744 return get(invert ? ch - 1 : ch, right != invert) 2745 } 2746 var partPos = getBidiPartAt(order, ch, sticky) 2747 var other = bidiOther 2748 var val = getBidi(ch, partPos, sticky == "before") 2749 if (other != null) { val.other = getBidi(ch, other, sticky != "before") } 2750 return val 2751} 2752 2753// Used to cheaply estimate the coordinates for a position. Used for 2754// intermediate scroll updates. 2755function estimateCoords(cm, pos) { 2756 var left = 0 2757 pos = clipPos(cm.doc, pos) 2758 if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch } 2759 var lineObj = getLine(cm.doc, pos.line) 2760 var top = heightAtLine(lineObj) + paddingTop(cm.display) 2761 return {left: left, right: left, top: top, bottom: top + lineObj.height} 2762} 2763 2764// Positions returned by coordsChar contain some extra information. 2765// xRel is the relative x position of the input coordinates compared 2766// to the found position (so xRel > 0 means the coordinates are to 2767// the right of the character position, for example). When outside 2768// is true, that means the coordinates lie outside the line's 2769// vertical range. 2770function PosWithInfo(line, ch, sticky, outside, xRel) { 2771 var pos = Pos(line, ch, sticky) 2772 pos.xRel = xRel 2773 if (outside) { pos.outside = true } 2774 return pos 2775} 2776 2777// Compute the character position closest to the given coordinates. 2778// Input must be lineSpace-local ("div" coordinate system). 2779function coordsChar(cm, x, y) { 2780 var doc = cm.doc
2781 y += cm.display.viewOffset 2782 if (y < 0) { return PosWithInfo(doc.first, 0, null, true, -1) } 2783 var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1 2784 if (lineN > last) 2785 { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1) } 2786 if (x < 0) { x = 0 } 2787 2788 var lineObj = getLine(doc, lineN) 2789 for (;;) { 2790 var found = coordsCharInner(cm, lineObj, lineN, x, y) 2791 var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 ? 1 : 0)) 2792 if (!collapsed) { return found } 2793 var rangeEnd = collapsed.find(1) 2794 if (rangeEnd.line == lineN) { return rangeEnd } 2795 lineObj = getLine(doc, lineN = rangeEnd.line) 2796 } 2797} 2798 2799function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { 2800 y -= widgetTopHeight(lineObj) 2801 var end = lineObj.text.length 2802 var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0) 2803 end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end) 2804 return {begin: begin, end: end} 2805} 2806 2807function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { 2808 if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) } 2809 var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top 2810 return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) 2811} 2812 2813// Returns true if the given side of a box is after the given 2814// coordinates, in top-to-bottom, left-to-right order. 2815function boxIsAfter(box, x, y, left) { 2816 return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x 2817} 2818 2819function coordsCharInner(cm, lineObj, lineNo, x, y) { 2820 // Move y into line-local coordinate space 2821 y -= heightAtLine(lineObj) 2822 var preparedMeasure = prepareMeasureForLine(cm, lineObj) 2823 // When directly calling `measureCharPrepared`, we have to adjust 2824 // for the widgets at this line. 2825 var widgetHeight = widgetTopHeight(lineObj) 2826 var begin = 0, end = lineObj.text.length, ltr = true 2827 2828 var order = getOrder(lineObj, cm.doc.direction) 2829 // If the line isn't plain left-to-right text, first figure out 2830 // which bidi section the coordinates fall into. 2831 if (order) { 2832 var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) 2833 (cm, lineObj, lineNo, preparedMeasure, order, x, y) 2834 ltr = part.level != 1 2835 // The awkward -1 offsets are needed because findFirst (called 2836 // on these below) will treat its first bound as inclusive, 2837 // second as exclusive, but we want to actually address the 2838 // characters in the part's range 2839 begin = ltr ? part.from : part.to - 1 2840 end = ltr ? part.to : part.from - 1 2841 } 2842 2843 // A binary search to find the first character whose bounding box 2844 // starts after the coordinates. If we run across any whose box wrap 2845 // the coordinates, store that. 2846 var chAround = null, boxAround = null 2847 var ch = findFirst(function (ch) { 2848 var box = measureCharPrepared(cm, preparedMeasure, ch) 2849 box.top += widgetHeight; box.bottom += widgetHeight 2850 if (!boxIsAfter(box, x, y, false)) { return false } 2851 if (box.top <= y && box.left <= x) { 2852 chAround = ch 2853 boxAround = box 2854 } 2855 return true 2856 }, begin, end) 2857 2858 var baseX, sticky, outside = false 2859 // If a box around the coordinates was found, use that 2860 if (boxAround) { 2861 // Distinguish coordinates nearer to the left or right side of the box 2862 var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr 2863 ch = chAround + (atStart ? 0 : 1) 2864 sticky = atStart ? "after" : "before" 2865 baseX = atLeft ? boxAround.left : boxAround.right 2866 } else {
2867 // (Adjust for extended bound, if necessary.) 2868 if (!ltr && (ch == end || ch == begin)) { ch++ } 2869 // To determine which side to associate with, get the box to the 2870 // left of the character and compare it's vertical position to the 2871 // coordinates 2872 sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : 2873 (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ? 2874 "after" : "before" 2875 // Now get accurate coordinates for this place, in order to get a 2876 // base X position 2877 var coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure) 2878 baseX = coords.left 2879 outside = y < coords.top || y >= coords.bottom 2880 } 2881 2882 ch = skipExtendingChars(lineObj.text, ch, 1) 2883 return PosWithInfo(lineNo, ch, sticky, outside, x - baseX) 2884} 2885 2886function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) { 2887 // Bidi parts are sorted left-to-right, and in a non-line-wrapping 2888 // situation, we can take this ordering to correspond to the visual 2889 // ordering. This finds the first part whose end is after the given 2890 // coordinates. 2891 var index = findFirst(function (i) { 2892 var part = order[i], ltr = part.level != 1 2893 return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"), 2894 "line", lineObj, preparedMeasure), x, y, true) 2895 }, 0, order.length - 1) 2896 var part = order[index] 2897 // If this isn't the first part, the part's start is also after 2898 // the coordinates, and the coordinates aren't on the same line as 2899 // that start, move one part back. 2900 if (index > 0) { 2901 var ltr = part.level != 1 2902 var start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"), 2903 "line", lineObj, preparedMeasure) 2904 if (boxIsAfter(start, x, y, true) && start.top > y) 2905 { part = order[index - 1] } 2906 } 2907 return part 2908} 2909 2910function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { 2911 // In a wrapped line, rtl text on wrapping boundaries can do things 2912 // that don't correspond to the ordering in our `order` array at 2913 // all, so a binary search doesn't work, and we want to return a 2914 // part that only spans one line so that the binary search in 2915 // coordsCharInner is safe. As such, we first find the extent of the 2916 // wrapped line, and then do a flat search in which we discard any 2917 // spans that aren't on the line. 2918 var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y); 2919 var begin = ref.begin; 2920 var end = ref.end; 2921 if (/\s/.test(lineObj.text.charAt(end - 1))) { end-- } 2922 var part = null, closestDist = null 2923 for (var i = 0; i < order.length; i++) { 2924 var p = order[i] 2925 if (p.from >= end || p.to <= begin) { continue } 2926 var ltr = p.level != 1 2927 var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right 2928 // Weigh against spans ending before this, so that they are only 2929 // picked if nothing ends after 2930 var dist = endX < x ? x - endX + 1e9 : endX - x 2931 if (!part || closestDist > dist) { 2932 part = p 2933 closestDist = dist 2934 } 2935 } 2936 if (!part) { part = order[order.length - 1] } 2937 // Clip the part to the wrapped line. 2938 if (part.from < begin) { part = {from: begin, to: part.to, level: part.level} } 2939 if (part.to > end) { part = {from: part.from, to: end, level: part.level} } 2940 return part 2941} 2942 2943var measureText 2944// Compute the default text height. 2945function textHeight(display) { 2946 if (display.cachedTextHeight != null) { return display.cachedTextHeight } 2947 if (measureText == null) { 2948 measureText = elt("pre") 2949 // Measure a bunch of lines, for browsers that compute 2950 // fractional heights. 2951 for (var i = 0; i < 49; ++i) { 2952 measureText.appendChild(document.createTextNode("x")) 2953 measureText.appendChild(elt("br")) 2954 } 2955 measureText.appendChild(document.createTextNode("x")) 2956 } 2957 removeChildrenAndAdd(display.measure, measureText) 2958 var height = measureText.offsetHeight / 50 2959 if (height > 3) { display.cachedTextHeight = height } 2960 removeChildren(display.measure) 2961 return height || 1 2962} 2963 2964// Compute the default character width. 2965function charWidth(display) {
2966 if (display.cachedCharWidth != null) { return display.cachedCharWidth } 2967 var anchor = elt("span", "xxxxxxxxxx") 2968 var pre = elt("pre", [anchor]) 2969 removeChildrenAndAdd(display.measure, pre) 2970 var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10 2971 if (width > 2) { display.cachedCharWidth = width } 2972 return width || 10 2973} 2974 2975// Do a bulk-read of the DOM positions and sizes needed to draw the 2976// view, so that we don't interleave reading and writing to the DOM. 2977function getDimensions(cm) { 2978 var d = cm.display, left = {}, width = {} 2979 var gutterLeft = d.gutters.clientLeft 2980 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { 2981 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft 2982 width[cm.options.gutters[i]] = n.clientWidth 2983 } 2984 return {fixedPos: compensateForHScroll(d), 2985 gutterTotalWidth: d.gutters.offsetWidth, 2986 gutterLeft: left, 2987 gutterWidth: width, 2988 wrapperWidth: d.wrapper.clientWidth} 2989} 2990 2991// Computes display.scroller.scrollLeft + display.gutters.offsetWidth, 2992// but using getBoundingClientRect to get a sub-pixel-accurate 2993// result. 2994function compensateForHScroll(display) {
2995 return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left 2996} 2997 2998// Returns a function that estimates the height of a line, to use as 2999// first approximation until the line becomes visible (and is thus 3000// properly measurable). 3001function estimateHeight(cm) { 3002 var th = textHeight(cm.display), wrapping = cm.options.lineWrapping 3003 var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3) 3004 return function (line) { 3005 if (lineIsHidden(cm.doc, line)) { return 0 } 3006 3007 var widgetsHeight = 0 3008 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { 3009 if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height } 3010 } } 3011 3012 if (wrapping) 3013 { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th } 3014 else 3015 { return widgetsHeight + th } 3016 } 3017} 3018 3019function estimateLineHeights(cm) { 3020 var doc = cm.doc, est = estimateHeight(cm) 3021 doc.iter(function (line) { 3022 var estHeight = est(line) 3023 if (estHeight != line.height) { updateLineHeight(line, estHeight) } 3024 }) 3025} 3026 3027// Given a mouse event, find the corresponding position. If liberal 3028// is false, it checks whether a gutter or scrollbar was clicked, 3029// and returns null if it was. forRect is used by rectangular 3030// selections, and tries to estimate a character position even for 3031// coordinates beyond the right of the text. 3032function posFromMouse(cm, e, liberal, forRect) { 3033 var display = cm.display 3034 if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null } 3035 3036 var x, y, space = display.lineSpace.getBoundingClientRect() 3037 // Fails unpredictably on IE[67] when mouse is dragged around quickly. 3038 try { x = e.clientX - space.left; y = e.clientY - space.top } 3039 catch (e) { return null } 3040 var coords = coordsChar(cm, x, y), line 3041 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { 3042 var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length 3043 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)) 3044 } 3045 return coords 3046} 3047 3048// Find the view element corresponding to a given line. Return null 3049// when the line isn't visible. 3050function findViewIndex(cm, n) { 3051 if (n >
3051= cm.display.viewTo) { return null } 3052 n -= cm.display.viewFrom 3053 if (n < 0) { return null } 3054 var view = cm.display.view 3055 for (var i = 0; i < view.length; i++) { 3056 n -= view[i].size 3057 if (n < 0) { return i } 3058 } 3059} 3060 3061function updateSelection(cm) { 3062 cm.display.input.showSelection(cm.display.input.prepareSelection()) 3063} 3064 3065function prepareSelection(cm, primary) { 3066 if ( primary === void 0 ) primary = true; 3067 3068 var doc = cm.doc, result = {} 3069 var curFragment = result.cursors = document.createDocumentFragment() 3070 var selFragment = result.selection = document.createDocumentFragment() 3071 3072 for (var i = 0; i < doc.sel.ranges.length; i++) { 3073 if (!primary && i == doc.sel.primIndex) { continue } 3074 var range = doc.sel.ranges[i] 3075 if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue } 3076 var collapsed = range.empty() 3077 if (collapsed || cm.options.showCursorWhenSelecting) 3078 { drawSelectionCursor(cm, range.head, curFragment) } 3079 if (!collapsed) 3080 { drawSelectionRange(cm, range, selFragment) } 3081 } 3082 return result 3083} 3084 3085// Draws a cursor for the given range 3086function drawSelectionCursor(cm, head, output) { 3087 var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine) 3088 3089 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")) 3090 cursor.style.left = pos.left + "px" 3091 cursor.style.top = pos.top + "px" 3092 cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px" 3093 3094 if (pos.other) { 3095 // Secondary cursor, shown when on a 'jump' in bi-directional text 3096 var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")) 3097 otherCursor.style.display = "" 3098 otherCursor.style.left = pos.other.left + "px" 3099 otherCursor.style.top = pos.other.top + "px" 3100 otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px" 3101 } 3102} 3103 3104function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } 3105 3106// Draws the given range as a highlighted selection 3107function drawSelectionRange(cm, range, output) { 3108 var display = cm.display, doc = cm.doc 3109 var fragment = document.createDocumentFragment() 3110 var padding = paddingH(cm.display), leftSide = padding.left 3111 var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right 3112 var docLTR = doc.direction == "ltr" 3113 3114 function add(left, top, width, bottom) { 3115 if (top < 0) { top = 0 } 3116 top = Math.round(top) 3117 bottom = Math.round(bottom) 3118 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"))) 3119 } 3120 3121 function drawForLine(line, fromArg, toArg) { 3122 var lineObj = getLine(doc, line) 3123 var lineLen = lineObj.text.length 3124 var start, end 3125 function coords(ch, bias) { 3126 return charCoords(cm, Pos(line, ch), "div", lineObj, bias) 3127 } 3128 3129 function wrapX(pos, dir, side) { 3130 var extent = wrappedLineExtentChar(cm, lineObj, null, pos) 3131 var prop = (dir == "ltr") == (side == "after") ? "left" : "right" 3132 var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1) 3133 return coords(ch, prop)[prop] 3134 } 3135 3136 var order = getOrder(lineObj, doc.direction) 3137 iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) { 3138 var ltr = dir == "ltr" 3139 var fromPos = coords(from, ltr ? "left" : "right") 3140 var toPos = coords(to - 1, ltr ? "right" : "left") 3141 3142 var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen 3143 var first = i == 0, last = !order || i == order.length - 1 3144 if (toPos.top - fromPos.top <= 3) { // Single line 3145 var openLeft = (docLTR ? openStart : openEnd) && first 3146 var openRight = (docLTR ? openEnd : openStart) && last 3147 var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left 3148 var right = openRight ? rightSide : (ltr ? toPos : fromPos).right 3149 add(left, fromPos.top, right - left, fromPos.bottom) 3150 } else { // Multiple lines
3151 var topLeft, topRight, botLeft, botRight 3152 if (ltr) { 3153 topLeft = docLTR && openStart && first ? leftSide : fromPos.left 3154 topRight = docLTR ? rightSide : wrapX(from, dir, "before") 3155 botLeft = docLTR ? leftSide : wrapX(to, dir, "after") 3156 botRight = docLTR && openEnd && last ? rightSide : toPos.right 3157 } else { 3158 topLeft = !docLTR ? leftSide : wrapX(from, dir, "before") 3159 topRight = !docLTR && openStart && first ? rightSide : fromPos.right 3160 botLeft = !docLTR && openEnd && last ? leftSide : toPos.left 3161 botRight = !docLTR ? rightSide : wrapX(to, dir, "after") 3162 } 3163 add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom) 3164 if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top) } 3165 add(botLeft, toPos.top, botRight - botLeft, toPos.bottom) 3166 } 3167 3168 if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos } 3169 if (cmpCoords(toPos, start) < 0) { start = toPos } 3170 if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos } 3171 if (cmpCoords(toPos, end) < 0) { end = toPos } 3172 }) 3173 return {start: start, end: end} 3174 } 3175 3176 var sFrom = range.from(), sTo = range.to() 3177 if (sFrom.line == sTo.line) { 3178 drawForLine(sFrom.line, sFrom.ch, sTo.ch) 3179 } else { 3180 var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line) 3181 var singleVLine = visualLine(fromLine) == visualLine(toLine) 3182 var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end 3183 var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start 3184 if (singleVLine) { 3185 if (leftEnd.top < rightStart.top - 2) { 3186 add(leftEnd.right, leftEnd.top, null, leftEnd.bottom) 3187 add(leftSide, rightStart.top, rightStart.left, rightStart.bottom) 3188 } else { 3189 add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom) 3190 } 3191 } 3192 if (leftEnd.bottom < rightStart.top) 3193 { add(leftSide, leftEnd.bottom, null, rightStart.top) } 3194 } 3195 3196 output.appendChild(fragment) 3197} 3198 3199// Cursor-blinking 3200function restartBlink(cm) { 3201 if (!cm.state.focused) { return } 3202 var display = cm.display 3203 clearInterval(display.blinker) 3204 var on = true 3205 display.cursorDiv.style.visibility = "" 3206 if (cm.options.cursorBlinkRate > 0) 3207 { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; }, 3208 cm.options.cursorBlinkRate) } 3209 else if (cm.options.cursorBlinkRate < 0) 3210 { display.cursorDiv.style.visibility = "hidden" } 3211} 3212 3213function ensureFocus(cm) { 3214 if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm) } 3215} 3216 3217function delayBlurEvent(cm) { 3218 cm.state.delayingBlurEvent = true 3219 setTimeout(function () { if (cm.state.delayingBlurEvent) { 3220 cm.state.delayingBlurEvent = false 3221 onBlur(cm) 3222 } }, 100) 3223} 3224 3225function onFocus(cm, e) { 3226 if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false } 3227 3228 if (cm.options.readOnly == "nocursor") { return } 3229 if (!cm.state.focused) { 3230 signal(cm, "focus", cm, e) 3231 cm.state.focused = true 3232 addClass(cm.display.wrapper, "CodeMirror-focused") 3233 // This test prevents this from firing when a context 3234 // menu is closed (since the input reset would kill the 3235 // select-all detection hack) 3236 if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { 3237 cm.display.input.reset() 3238 if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20) } // Issue #1730 3239 } 3240 cm.display.input.receivedFocus() 3241 } 3242 restartBlink(cm) 3243} 3244function onBlur(cm, e) { 3245 if (cm.state.delayingBlurEvent) { return } 3246 3247 if (cm.state.focused) { 3248 signal(cm, "blur", cm, e) 3249 cm.state.focused = false 3250 rmClass(cm.display.wrapper, "CodeMirror-focused") 3251 } 3252 clearInterval(cm.display.blinker) 3253 setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false } }, 150) 3254} 3255 3256// Read the actual heights of the rendered lines, and update their 3257// stored heights to match. 3258function updateHeightsInViewport(cm) { 3259 var display = cm.display 3260 var prevBottom = display.lineDiv.offsetTop 3261 for (var i = 0; i < display.view.length; i++) { 3262 var cur = display.view[i], height = (void 0) 3263 if (cur.hidden) { continue } 3264 if (ie && ie_version < 8) { 3265 var bot = cur.node.offsetTop + cur.node.offsetHeight 3266 height = bot - prevBottom 3267 prevBottom = bot 3268 } else { 3269 var box = cur.node.getBoundingClientRect() 3270 height = box.bottom - box.top 3271 }
3272 var diff = cur.line.height - height 3273 if (height < 2) { height = textHeight(display) } 3274 if (diff > .005 || diff < -.005) { 3275 updateLineHeight(cur.line, height) 3276 updateWidgetHeight(cur.line) 3277 if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) 3278 { updateWidgetHeight(cur.rest[j]) } } 3279 } 3280 } 3281} 3282 3283// Read and store the height of line widgets associated with the 3284// given line. 3285function updateWidgetHeight(line) { 3286 if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) { 3287 var w = line.widgets[i], parent = w.node.parentNode 3288 if (parent) { w.height = parent.offsetHeight } 3289 } } 3290} 3291 3292// Compute the lines that are visible in a given viewport (defaults 3293// the the current scroll position). viewport may contain top, 3294// height, and ensure (see op.scrollToPos) properties. 3295function visibleLines(display, doc, viewport) { 3296 var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop 3297 top = Math.floor(top - paddingTop(display)) 3298 var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight 3299 3300 var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom) 3301 // Ensure is a {from: {line, ch}, to: {line, ch}} object, and 3302 // forces those lines into the viewport (if possible). 3303 if (viewport && viewport.ensure) { 3304 var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line 3305 if (ensureFrom < from) { 3306 from = ensureFrom 3307 to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight) 3308 } else if (Math.min(ensureTo, doc.lastLine()) >= to) { 3309 from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight) 3310 to = ensureTo 3311 } 3312 } 3313 return {from: from, to: Math.max(to, from + 1)} 3314} 3315 3316// Re-align line numbers and gutter marks to compensate for 3317// horizontal scrolling. 3318function alignHorizontally(cm) { 3319 var display = cm.display, view = display.view 3320 if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return } 3321 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft 3322 var gutterW = display.gutters.offsetWidth, left = comp + "px" 3323 for (var i = 0; i < view.length; i++) { if (!view[i].hidden) { 3324 if (cm.options.fixedGutter) { 3325 if (view[i].gutter) 3326 { view[i].gutter.style.left = left } 3327 if (view[i].gutterBackground) 3328 { view[i].gutterBackground.style.left = left } 3329 } 3330 var align = view[i].alignable 3331 if (align) { for (var j = 0; j < align.length; j++) 3332 { align[j].style.left = left } } 3333 } } 3334 if (cm.options.fixedGutter) 3335 { display.gutters.style.left = (comp + gutterW) + "px" } 3336} 3337 3338// Used to ensure that the line number gutter is still the right 3339// size for the current document size. Returns true when an update 3340// is needed. 3341function maybeUpdateLineNumberWidth(cm) { 3342 if (!cm.options.lineNumbers) { return false } 3343 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display 3344 if (last.length != display.lineNumChars) { 3345 var test = display.measure.appendChild(elt("div", [elt("div", last)], 3346 "CodeMirror-linenumber CodeMirror-gutter-elt")) 3347 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW 3348 display.lineGutter.style.width = "" 3349 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1 3350 display.lineNumWidth = display.lineNumInnerWidth + padding 3351 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1 3352 display.lineGutter.style.width = display.lineNumWidth + "px" 3353 updateGutterSpace(cm) 3354 return true 3355 } 3356 return false 3357} 3358 3359// SCROLLING THINGS INTO VIEW 3360 3361// If an editor sits on the top or bottom of the window, partially 3362// scrolled out of view, this ensures that the cursor is visible. 3363function maybeScrollWindow(cm, rect) { 3364 if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } 3365 3366 var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null 3367 if (rect.top + box.top < 0) { doScroll = true } 3368 else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false } 3369 if (doScroll != null && !phantom) { 3370 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;")) 3371 cm.display.lineSpace.appendChild(scrollNode) 3372 scrollNode.scrollIntoView(doScroll) 3373 cm.display.lineSpace.removeChild(scrollNode) 3374 } 3375} 3376 3377// Scroll a given position into view (immediately), verifying that 3378// it actually became visible (as line heights are accurately 3379// measured, the position of something may 'drift' during drawing). 3380function scrollPosIntoView(cm, pos, end, margin) { 3381 if (margin == null) { margin = 0 } 3382 var rect 3383 if (!cm.options.lineWrapping && pos == end) { 3384 // Set pos and end to the cursor positions around the character pos sticks to 3385 // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch 3386 // If pos == Pos(_, 0, "before"), pos and end are unchanged 3387 pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos
3388 end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos 3389 } 3390 for (var limit = 0; limit < 5; limit++) { 3391 var changed = false 3392 var coords = cursorCoords(cm, pos) 3393 var endCoords = !end || end == pos ? coords : cursorCoords(cm, end) 3394 rect = {left: Math.min(coords.left, endCoords.left), 3395 top: Math.min(coords.top, endCoords.top) - margin, 3396 right: Math.max(coords.left, endCoords.left), 3397 bottom: Math.max(coords.bottom, endCoords.bottom) + margin} 3398 var scrollPos = calculateScrollPos(cm, rect) 3399 var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft 3400 if (scrollPos.scrollTop != null) { 3401 updateScrollTop(cm, scrollPos.scrollTop) 3402 if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true } 3403 } 3404 if (scrollPos.scrollLeft != null) { 3405 setScrollLeft(cm, scrollPos.scrollLeft) 3406 if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true } 3407 } 3408 if (!changed) { break } 3409 } 3410 return rect 3411} 3412 3413// Scroll a given set of coordinates into view (immediately). 3414function scrollIntoView(cm, rect) { 3415 var scrollPos = calculateScrollPos(cm, rect) 3416 if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop) } 3417 if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft) } 3418} 3419 3420// Calculate a new scroll position needed to scroll the given 3421// rectangle into view. Returns an object with scrollTop and 3422// scrollLeft properties. When these are undefined, the 3423// vertical/horizontal position does not need to be adjusted. 3424function calculateScrollPos(cm, rect) { 3425 var display = cm.display, snapMargin = textHeight(cm.display) 3426 if (rect.top < 0) { rect.top = 0 } 3427 var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop 3428 var screen = displayHeight(cm), result = {} 3429 if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen } 3430 var docBottom = cm.doc.height + paddingVert(display) 3431 var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin 3432 if (rect.top < screentop) { 3433 result.scrollTop = atTop ? 0 : rect.top 3434 } else if (rect.bottom > screentop + screen) { 3435 var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen) 3436 if (newTop != screentop) { result.scrollTop = newTop } 3437 } 3438 3439 var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft 3440 var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0) 3441 var tooWide = rect.right - rect.left > screenw 3442 if (tooWide) { rect.right = rect.left + screenw } 3443 if (rect.left < 10) 3444 { result.scrollLeft = 0 } 3445 else if (rect.left < screenleft) 3446 { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)) } 3447 else if (rect.right > screenw + screenleft - 3) 3448 { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw } 3449 return result 3450} 3451 3452// Store a relative adjustment to the scroll position in the current 3453// operation (to be applied when the operation finishes). 3454function addToScrollTop(cm, top) { 3455 if (top == null) { return } 3456 resolveScrollToPos(cm) 3457 cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top 3458} 3459 3460// Make sure that at the end of the operation the current cursor is 3461// shown. 3462function ensureCursorVisible(cm) { 3463 resolveScrollToPos(cm) 3464 var cur = cm.getCursor() 3465 cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin} 3466} 3467 3468function scrollToCoords(cm, x, y) { 3469 if (x != null || y != null) { resolveScrollToPos(cm) } 3470 if (x != null) { cm.curOp.scrollLeft = x } 3471 if (y != null) { cm.curOp.scrollTop = y } 3472} 3473 3474function scrollToRange(cm, range) { 3475 resolveScrollToPos(cm) 3476 cm.curOp.scrollToPos = range 3477} 3478 3479// When an operation has its scrollToPos property set, and another 3480// scroll action is applied before the end of the operation, this 3481// 'simulates' scrolling that position into view in a cheap way, so 3482// that the effect of intermediate scroll commands is not ignored. 3483function resolveScrollToPos(cm) { 3484 var range = cm.curOp.scrollToPos 3485 if (range) { 3486 cm.curOp.scrollToPos = null 3487 var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to) 3488 scrollToCoordsRange(cm, from, to, range.margin) 3489 } 3490} 3491 3492function scrollToCoordsRange(cm, from, to, margin) { 3493 var sPos = calculateScrollPos(cm, { 3494 left: Math.min(from.left, to.left), 3495 top: Math.min(from.top, to.top) - margin, 3496 right: Math.max(from.right, to.right), 3497 bottom: Math.max(from.bottom, to.bottom) + margin 3498 }) 3499 scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop) 3500} 3501 3502// Sync the scrollable area and scrollbars, ensure the viewport 3503// covers the visible area. 3504function updateScrollTop(cm, val) { 3505 if (Math.abs(cm.doc.scrollTop - val) < 2) { return } 3506 if (!gecko) { updateDisplaySimple(cm, {top: val}) } 3507 setScrollTop(cm, val, true) 3508 if (gecko) { updateDisplaySimple(cm) } 3509 startWorker(cm, 100) 3510} 3511 3512function setScrollTop(cm, val, forceScroll) { 3513 val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val) 3514 if (cm.display.scroller.scrollTop == val && !forceScroll) { return } 3515 cm.doc.scrollTop = val 3516 cm.display.scrollbars.setScrollTop(val) 3517 if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val } 3518} 3519 3520// Sync scroller and scrollbar, ensure the gutter elements are 3521// aligned. 3522function setScrollLeft(cm, val, isScroller, forceScroll) { 3523 val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth) 3524 if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return } 3525 cm.doc.scrollLeft = val 3526 alignHorizontally(cm) 3527 if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val } 3528 cm.display.scrollbars.setScrollLeft(val) 3529} 3530 3531// SCROLLBARS 3532 3533// Prepare DOM reads needed to update the scrollbars. Done in one 3534// shot to minimize update/measure roundtrips. 3535function measureForScrollbars(cm) { 3536 var d = cm.display, gutterW = d.gutters.offsetWidth 3537 var docH = Math.round(cm.doc.height + paddingVert(cm.display)) 3538 return { 3539 clientHeight: d.scroller.clientHeight, 3540 viewHeight: d.wrapper.clientHeight, 3541 scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, 3542 viewWidth: d.wrapper.clientWidth, 3543 barLeft: cm.options.fixedGutter ? gutterW : 0, 3544 docHeight: docH, 3545 scrollHeight: docH + scrollGap(cm) + d.barHeight, 3546 nativeBarWidth: d.nativeBarWidth, 3547 gutterWidth: gutterW 3548 } 3549} 3550 3551var NativeScrollbars = function(place, scroll, cm) { 3552 this.cm = cm 3553 var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar") 3554 var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar") 3555 vert.tabIndex = horiz.tabIndex = -1 3556 place(vert); place(horiz) 3557 3558 on(vert, "scroll", function () { 3559 if (vert.clientHeight) { scroll(vert.scrollTop, "vertical") } 3560 }) 3561 on(horiz, "scroll", function () { 3562 if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal") } 3563 }) 3564 3565 this.checkedZeroWidth = false 3566 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). 3567 if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px" } 3568}; 3569 3570NativeScrollbars.prototype.update = function (measure) { 3571 var needsH = measure.scrollWidth > measure.clientWidth + 1 3572 var needsV = measure.scrollHeight > measure.clientHeight + 1 3573 var sWidth = measure.nativeBarWidth 3574 3575 if (needsV) { 3576 this.vert.style.display = "block" 3577 this.vert.style.bottom = needsH ? sWidth + "px" : "0" 3578 var totalHeight = measure.viewHeight - (needsH ? sWidth : 0) 3579 // A bug in IE8 can cause this value to be negative, so guard it.
3580 this.vert.firstChild.style.height = 3581 Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px" 3582 } else { 3583 this.vert.style.display = "" 3584 this.vert.firstChild.style.height = "0" 3585 } 3586 3587 if (needsH) { 3588 this.horiz.style.display = "block" 3589 this.horiz.style.right = needsV ? sWidth + "px" : "0" 3590 this.horiz.style.left = measure.barLeft + "px" 3591 var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0) 3592 this.horiz.firstChild.style.width = 3593 Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px" 3594 } else { 3595 this.horiz.style.display = "" 3596 this.horiz.firstChild.style.width = "0" 3597 } 3598 3599 if (!this.checkedZeroWidth && measure.clientHeight > 0) { 3600 if (sWidth == 0) { this.zeroWidthHack() } 3601 this.checkedZeroWidth = true 3602 } 3603 3604 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} 3605}; 3606 3607NativeScrollbars.prototype.setScrollLeft = function (pos) { 3608 if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos } 3609 if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz") } 3610}; 3611 3612NativeScrollbars.prototype.setScrollTop = function (pos) { 3613 if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos } 3614 if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert") } 3615}; 3616 3617NativeScrollbars.prototype.zeroWidthHack = function () { 3618 var w = mac && !mac_geMountainLion ? "12px" : "18px" 3619 this.horiz.style.height = this.vert.style.width = w 3620 this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none" 3621 this.disableHoriz = new Delayed 3622 this.disableVert = new Delayed 3623}; 3624 3625NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { 3626 bar.style.pointerEvents = "auto" 3627 function maybeDisable() { 3628 // To find out whether the scrollbar is still visible, we 3629 // check whether the element under the pixel in the bottom 3630 // right corner of the scrollbar box is the scrollbar box 3631 // itself (when the bar is still visible) or its filler child 3632 // (when the bar is hidden). If it is still visible, we keep 3633 // it enabled, if it's hidden, we disable pointer events. 3634 var box = bar.getBoundingClientRect() 3635 var elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) 3636 : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1) 3637 if (elt != bar) { bar.style.pointerEvents = "none" } 3638 else { delay.set(1000, maybeDisable) } 3639 } 3640 delay.set(1000, maybeDisable) 3641}; 3642 3643NativeScrollbars.prototype.clear = function () { 3644 var parent = this.horiz.parentNode 3645 parent.removeChild(this.horiz) 3646 parent.removeChild(this.vert) 3647}; 3648 3649var NullScrollbars = function () {}; 3650 3651NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} }; 3652NullScrollbars.prototype.setScrollLeft = function () {}; 3653NullScrollbars.prototype.setScrollTop = function () {}; 3654NullScrollbars.prototype.clear = function () {}; 3655 3656function updateScrollbars(cm, measure) { 3657 if (!measure) { measure = measureForScrollbars(cm) } 3658 var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight 3659 updateScrollbarsInner(cm, measure) 3660 for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { 3661 if (startWidth != cm.display.barWidth && cm.options.lineWrapping) 3662 { updateHeightsInViewport(cm) } 3663 updateScrollbarsInner(cm, measureForScrollbars(cm)) 3664 startWidth = cm.display.barWidth; startHeight = cm.display.barHeight 3665 } 3666} 3667 3668// Re-synchronize the fake scrollbars with the actual size of the 3669// content. 3670function updateScrollbarsInner(cm, measure) { 3671 var d = cm.display 3672 var sizes = d.scrollbars.update(measure) 3673 3674 d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px" 3675 d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px" 3676 d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" 3677 3678 if (sizes.right && sizes.bottom) { 3679 d.scrollbarFiller.style.display = "block" 3680 d.scrollbarFiller.style.height = sizes.bottom + "px" 3681 d.scrollbarFiller.style.width = sizes.right + "px" 3682 } else { d.scrollbarFiller.style.display = "" } 3683 if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { 3684 d.gutterFiller.style.display = "block" 3685 d.gutterFiller.style.height = sizes.bottom + "px" 3686 d.gutterFiller.style.width = measure.gutterWidth + "px" 3687 } else { d.gutterFiller.style.display = "" } 3688} 3689 3690var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars} 3691 3692function initScrollbars(cm) { 3693 if (cm.display.scrollbars) { 3694 cm.display.scrollbars.clear() 3695 if (cm.display.scrollbars.addClass) 3696 { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass) } 3697 } 3698 3699 cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) { 3700 cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller) 3701 // Prevent clicks in the scrollbars from killing focus 3702 on(node, "mousedown", function () { 3703 if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0) } 3704 }) 3705 node.setAttribute("cm-not-content", "true") 3706 }, function (pos, axis) { 3707 if (axis == "horizontal") { setScrollLeft(cm, pos) } 3708 else { updateScrollTop(cm, pos) } 3709 }, cm) 3710 if (cm.display.scrollbars.addClass) 3711 { addClass(cm.display.wrapper, cm.display.scrollbars.addClass) } 3712} 3713 3714// Operations are used to wrap a series of changes to the editor
3715// state in such a way that each change won't have to update the 3716// cursor and display (which would be awkward, slow, and 3717// error-prone). Instead, display updates are batched and then all 3718// combined and executed at once. 3719 3720var nextOpId = 0 3721// Start a new operation. 3722function startOperation(cm) { 3723 cm.curOp = { 3724 cm: cm, 3725 viewChanged: false, // Flag that indicates that lines might need to be redrawn 3726 startHeight: cm.doc.height, // Used to detect need to update scrollbar 3727 forceUpdate: false, // Used to force a redraw 3728 updateInput: null, // Whether to reset the input textarea 3729 typing: false, // Whether this reset should be careful to leave existing text (for compositing) 3730 changeObjs: null, // Accumulated changes, for firing change events 3731 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on 3732 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already 3733 selectionChanged: false, // Whether the selection needs to be redrawn 3734 updateMaxLine: false, // Set when the widest line needs to be determined anew 3735 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet 3736 scrollToPos: null, // Used to scroll to a specific position 3737 focus: false, 3738 id: ++nextOpId // Unique ID 3739 } 3740 pushOperation(cm.curOp) 3741} 3742 3743// Finish an operation, updating the display and signalling delayed events 3744function endOperation(cm) { 3745 var op = cm.curOp 3746 finishOperation(op, function (group) { 3747 for (var i = 0; i < group.ops.length; i++) 3748 { group.ops[i].cm.curOp = null } 3749 endOperations(group) 3750 }) 3751} 3752 3753// The DOM updates done when an operation finishes are batched so 3754// that the minimum number of relayouts are required. 3755function endOperations(group) { 3756 var ops = group.ops 3757 for (var i = 0; i < ops.length; i++) // Read DOM 3758 { endOperation_R1(ops[i]) } 3759 for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe) 3760 { endOperation_W1(ops[i$1]) } 3761 for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM 3762 { endOperation_R2(ops[i$2]) } 3763 for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe) 3764 { endOperation_W2(ops[i$3]) } 3765 for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM 3766 { endOperation_finish(ops[i$4]) } 3767} 3768 3769function endOperation_R1(op) { 3770 var cm = op.cm, display = cm.display 3771 maybeClipScrollbars(cm) 3772 if (op.updateMaxLine) { findMaxLine(cm) } 3773 3774 op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || 3775 op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || 3776 op.scrollToPos.to.line >= display.viewTo) || 3777 display.maxLineChanged && cm.options.lineWrapping 3778 op.update = op.mustUpdate && 3779 new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate) 3780} 3781 3782function endOperation_W1(op) { 3783 op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update) 3784} 3785 3786function endOperation_R2(op) { 3787 var cm = op.cm, display = cm.display 3788 if (op.updatedDisplay) { updateHeightsInViewport(cm) } 3789 3790 op.barMeasure = measureForScrollbars(cm) 3791 3792 // If the max line changed since it was last measured, measure it, 3793 // and ensure the document's width matches it. 3794 // updateDisplay_W2 will use these properties to do the actual resizing 3795 if (display.maxLineChanged && !cm.options.lineWrapping) { 3796 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3 3797 cm.display.sizerWidth = op.adjustWidthTo 3798 op.barMeasure.scrollWidth = 3799 Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth) 3800 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)) 3801 } 3802 3803 if (op.updatedDisplay || op.selectionChanged) 3804 { op.preparedSelection = display.input.prepareSelection() } 3805} 3806 3807function endOperation_W2(op) { 3808 var cm = op.cm 3809 3810 if (op.adjustWidthTo != null) { 3811 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px" 3812 if (op.maxScrollLeft < cm.doc.scrollLeft) 3813 { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true) } 3814 cm.display.maxLineChanged = false 3815 } 3816 3817 var takeFocus = op.focus && op.focus == activeElt() 3818 if (op.preparedSelection) 3819 { cm.display.input.showSelection(op.preparedSelection, takeFocus) } 3820 if (op.updatedDisplay || op.startHeight != cm.doc.height) 3821 { updateScrollbars(cm, op.barMeasure) } 3822 if (op.updatedDisplay) 3823 { setDocumentHeight(cm, op.barMeasure) } 3824 3825 if (op.selectionChanged) { restartBlink(cm) } 3826 3827 if (cm.state.focused && op.updateInput) 3828 { cm.display.input.reset(op.typing) } 3829 if (takeFocus) { ensureFocus(op.cm) } 3830} 3831 3832function endOperation_finish(op) { 3833 var cm = op.cm, display = cm.display, doc = cm.doc 3834 3835 if (op.updatedDisplay) { postUpdateDisplay(cm, op.update) } 3836 3837 // Abort mouse wheel delta measurement, when scrolling explicitly 3838 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) 3839 { display.wheelStartX = display.wheelStartY = null } 3840 3841 // Propagate the scroll position to the actual DOM scroller 3842 if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll) } 3843 3844 if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true) } 3845 // If we need to scroll a specific position into view, do so. 3846 if (op.scrollToPos) { 3847 var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), 3848 clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin) 3849 maybeScrollWindow(cm, rect) 3850 } 3851 3852 // Fire events for markers that are hidden/unidden by editing or 3853 // undoing 3854 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers 3855 if (hidden) { for (var i = 0; i < hidden.length; ++i) 3856 { if (!hidden[i].lines.length) { signal(hidden[i], "hide") } } } 3857 if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1) 3858 { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide") } } } 3859 3860 if (display.wrapper.offsetHeight) 3861 { doc.scrollTop = cm.display.scroller.scrollTop } 3862 3863 // Fire change events, and delayed event handlers
3864 if (op.changeObjs) 3865 { signal(cm, "changes", cm, op.changeObjs) } 3866 if (op.update) 3867 { op.update.finish() } 3868} 3869 3870// Run the given function in an operation 3871function runInOp(cm, f) { 3872 if (cm.curOp) { return f() } 3873 startOperation(cm) 3874 try { return f() } 3875 finally { endOperation(cm) } 3876} 3877// Wraps a function in an operation. Returns the wrapped function. 3878function operation(cm, f) { 3879 return function() { 3880 if (cm.curOp) { return f.apply(cm, arguments) } 3881 startOperation(cm) 3882 try { return f.apply(cm, arguments) } 3883 finally { endOperation(cm) } 3884 } 3885} 3886// Used to add methods to editor and doc instances, wrapping them in 3887// operations. 3888function methodOp(f) { 3889 return function() { 3890 if (this.curOp) { return f.apply(this, arguments) } 3891 startOperation(this) 3892 try { return f.apply(this, arguments) } 3893 finally { endOperation(this) } 3894 } 3895} 3896function docMethodOp(f) { 3897 return function() { 3898 var cm = this.cm 3899 if (!cm || cm.curOp) { return f.apply(this, arguments) } 3900 startOperation(cm) 3901 try { return f.apply(this, arguments) } 3902 finally { endOperation(cm) } 3903 } 3904} 3905 3906// Updates the display.view data structure for a given change to the 3907// document. From and to are in pre-change coordinates. Lendiff is 3908// the amount of lines added or subtracted by the change. This is 3909// used for changes that span multiple lines, or change the way 3910// lines are divided into visual lines. regLineChange (below) 3911// registers single-line changes. 3912function regChange(cm, from, to, lendiff) { 3913 if (from == null) { from = cm.doc.first } 3914 if (to == null) { to = cm.doc.first + cm.doc.size } 3915 if (!lendiff) { lendiff = 0 } 3916 3917 var display = cm.display 3918 if (lendiff && to < display.viewTo && 3919 (display.updateLineNumbers == null || display.updateLineNumbers > from)) 3920 { display.updateLineNumbers = from } 3921 3922 cm.curOp.viewChanged = true 3923 3924 if (from >= display.viewTo) { // Change after 3925 if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) 3926 { resetView(cm) } 3927 } else if (to <= display.viewFrom) { // Change before 3928 if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { 3929 resetView(cm) 3930 } else { 3931 display.viewFrom += lendiff 3932 display.viewTo += lendiff 3933 } 3934 } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap 3935 resetView(cm) 3936 } else if (from <= display.viewFrom) { // Top overlap 3937 var cut = viewCuttingPoint(cm, to, to + lendiff, 1) 3938 if (cut) { 3939 display.view = display.view.slice(cut.index) 3940 display.viewFrom = cut.lineN 3941 display.viewTo += lendiff 3942 } else { 3943 resetView(cm) 3944 } 3945 } else if (to >= display.viewTo) { // Bottom overlap 3946 var cut$1 = viewCuttingPoint(cm, from, from, -1) 3947 if (cut$1) { 3948 display.view = display.view.slice(0, cut$1.index) 3949 display.viewTo = cut$1.lineN 3950 } else { 3951 resetView(cm) 3952 } 3953 } else { // Gap in the middle 3954 var cutTop = viewCuttingPoint(cm, from, from, -1) 3955 var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1) 3956 if (cutTop && cutBot) { 3957 display.view = display.view.slice(0, cutTop.index) 3958 .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) 3959 .concat(display.view.slice(cutBot.index)) 3960 display.viewTo += lendiff 3961 } else { 3962 resetView(cm) 3963 } 3964 } 3965 3966 var ext = display.externalMeasured 3967 if (ext) { 3968 if (to < ext.lineN) 3969 { ext.lineN += lendiff } 3970 else if (from < ext.lineN + ext.size) 3971 { display.externalMeasured = null } 3972 } 3973} 3974 3975// Register a change to a single line. Type must be one of "text", 3976// "gutter", "class", "widget" 3977function regLineChange(cm, line, type) { 3978 cm.curOp.viewChanged = true 3979 var display = cm.display, ext = cm.display.externalMeasured 3980 if (ext && line >= ext.lineN && line < ext.lineN + ext.size) 3981 { display.externalMeasured = null } 3982 3983 if (line < display.viewFrom || line >= display.viewTo) { return } 3984 var lineView = display.view[findViewIndex(cm, line)] 3985 if (lineView.node == null) { return } 3986 var arr = lineView.changes || (lineView.changes = []) 3987 if (indexOf(arr, type) == -1) { arr.push(type) } 3988} 3989 3990// Clear the view. 3991function resetView(cm) { 3992 cm.display.viewFrom = cm.display.viewTo = cm.doc.first 3993 cm.display.view = [] 3994 cm.display.viewOffset = 0 3995} 3996 3997function viewCuttingPoint(cm, oldN, newN, dir) { 3998 var index = findViewIndex(cm, oldN), diff, view = cm.display.view 3999 if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) 4000 { return {index: index, lineN: newN} } 4001 var n = cm.display.viewFrom 4002 for (var i = 0; i < index; i++) 4003 { n += view[i].size } 4004 if (n != oldN) { 4005 if (dir > 0) { 4006 if (index == view.length - 1) { return null } 4007 diff = (n + view[index].size) - oldN 4008 index++ 4009 } else { 4010 diff = n - oldN 4011 } 4012 oldN += diff; newN += diff 4013 } 4014 while (visualLineNo(cm.doc, newN) != newN) { 4015 if (index == (dir < 0 ? 0 : view.length - 1)) { return null } 4016 newN += dir * view[index - (dir < 0 ? 1 : 0)].size 4017 index += dir 4018 } 4019 return {index: index, lineN: newN} 4020} 4021 4022// Force the view to cover a given range, adding empty view element 4023// or clipping off existing ones as needed. 4024function adjustView(cm, from, to) { 4025 var display = cm.display, view = display.view 4026 if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { 4027 display.view = buildViewArray(cm, from, to) 4028 display.viewFrom = from 4029 } else { 4030 if (display.viewFrom > from) 4031 { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view) } 4032 else if (display.viewFrom < from) 4033 { display.view = display.view.slice(findViewIndex(cm, from)) } 4034 display.viewFrom = from 4035 if (display.viewTo < to) 4036 { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)) } 4037 else if (display.viewTo > to) 4038 { display.view = display.view.slice(0, findViewIndex(cm, to)) } 4039 } 4040 display.viewTo = to 4041} 4042 4043// Count the number of lines in the view whose DOM representation is 4044// out of date (or nonexistent). 4045function countDirtyView(cm) { 4046 var view = cm.display.view, dirty = 0 4047 for (var i = 0; i < view.length; i++) { 4048 var lineView = view[i] 4049 if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty } 4050 } 4051 return dirty 4052} 4053 4054// HIGHLIGHT WORKER 4055 4056function startWorker(cm, time) { 4057 if (cm.doc.highlightFrontier < cm.display.viewTo) 4058 { cm.state.highlight.set(time, bind(highlightWorker, cm)) } 4059} 4060 4061function highlightWorker(cm) { 4062 var doc = cm.doc 4063 if (doc.highlightFrontier >
4063= cm.display.viewTo) { return } 4064 var end = +new Date + cm.options.workTime 4065 var context = getContextBefore(cm, doc.highlightFrontier) 4066 var changedLines = [] 4067 4068 doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { 4069 if (context.line >= cm.display.viewFrom) { // Visible 4070 var oldStyles = line.styles 4071 var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null 4072 var highlighted = highlightLine(cm, line, context, true) 4073 if (resetState) { context.state = resetState } 4074 line.styles = highlighted.styles 4075 var oldCls = line.styleClasses, newCls = highlighted.classes 4076 if (newCls) { line.styleClasses = newCls } 4077 else if (oldCls) { line.styleClasses = null } 4078 var ischange = !oldStyles || oldStyles.length != line.styles.length || 4079 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass) 4080 for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i] } 4081 if (ischange) { changedLines.push(context.line) } 4082 line.stateAfter = context.save() 4083 context.nextLine() 4084 } else { 4085 if (line.text.length <= cm.options.maxHighlightLength) 4086 { processLine(cm, line.text, context) } 4087 line.stateAfter = context.line % 5 == 0 ? context.save() : null 4088 context.nextLine() 4089 } 4090 if (+new Date >
4090 end) { 4091 startWorker(cm, cm.options.workDelay) 4092 return true 4093 } 4094 }) 4095 doc.highlightFrontier = context.line 4096 doc.modeFrontier = Math.max(doc.modeFrontier, context.line) 4097 if (changedLines.length) { runInOp(cm, function () { 4098 for (var i = 0; i < changedLines.length; i++) 4099 { regLineChange(cm, changedLines[i], "text") } 4100 }) } 4101} 4102 4103// DISPLAY DRAWING 4104 4105var DisplayUpdate = function(cm, viewport, force) { 4106 var display = cm.display 4107 4108 this.viewport = viewport 4109 // Store some values that we'll need later (but don't want to force a relayout for) 4110 this.visible = visibleLines(display, cm.doc, viewport) 4111 this.editorIsHidden = !display.wrapper.offsetWidth 4112 this.wrapperHeight = display.wrapper.clientHeight 4113 this.wrapperWidth = display.wrapper.clientWidth 4114 this.oldDisplayWidth = displayWidth(cm) 4115 this.force = force 4116 this.dims = getDimensions(cm) 4117 this.events = [] 4118}; 4119 4120DisplayUpdate.prototype.signal = function (emitter, type) { 4121 if (hasHandler(emitter, type)) 4122 { this.events.push(arguments) } 4123}; 4124DisplayUpdate.prototype.finish = function () { 4125 var this$1 = this; 4126 4127 for (var i = 0; i < this.events.length; i++) 4128 { signal.apply(null, this$1.events[i]) } 4129}; 4130 4131function maybeClipScrollbars(cm) { 4132 var display = cm.display 4133 if (!display.scrollbarsClipped && display.scroller.offsetWidth) { 4134 display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth 4135 display.heightForcer.style.height = scrollGap(cm) + "px" 4136 display.sizer.style.marginBottom = -display.nativeBarWidth + "px" 4137 display.sizer.style.borderRightWidth = scrollGap(cm) + "px" 4138 display.scrollbarsClipped = true 4139 } 4140} 4141 4142function selectionSnapshot(cm) { 4143 if (cm.hasFocus()) { return null } 4144 var active = activeElt() 4145 if (!active || !contains(cm.display.lineDiv, active)) { return null } 4146 var result = {activeElt: active} 4147 if (window.getSelection) { 4148 var sel = window.getSelection() 4149 if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { 4150 result.anchorNode = sel.anchorNode 4151 result.anchorOffset = sel.anchorOffset 4152 result.focusNode = sel.focusNode 4153 result.focusOffset = sel.focusOffset 4154 } 4155 } 4156 return result 4157} 4158 4159function restoreSelection(snapshot) { 4160 if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return } 4161 snapshot.activeElt.focus() 4162 if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { 4163 var sel = window.getSelection(), range = document.createRange() 4164 range.setEnd(snapshot.anchorNode, snapshot.anchorOffset) 4165 range.collapse(false) 4166 sel.removeAllRanges() 4167 sel.addRange(range) 4168 sel.extend(snapshot.focusNode, snapshot.focusOffset) 4169 } 4170} 4171 4172// Does the actual updating of the line display. Bails out 4173// (returning false) when there is nothing to be done and forced is 4174// false. 4175function updateDisplayIfNeeded(cm, update) { 4176 var display = cm.display, doc = cm.doc 4177 4178 if (update.editorIsHidden) { 4179 resetView(cm) 4180 return false 4181 } 4182 4183 // Bail out if the visible area is already rendered and nothing changed. 4184 if (!update.force && 4185 update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && 4186 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && 4187 display.renderedView == display.view && countDirtyView(cm) == 0) 4188 { return false } 4189 4190 if (maybeUpdateLineNumberWidth(cm)) { 4191 resetView(cm) 4192 update.dims = getDimensions(cm) 4193 } 4194 4195 // Compute a suitable new viewport (from & to) 4196 var end = doc.first + doc.size 4197 var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first) 4198 var to = Math.min(end, update.visible.to + cm.options.viewportMargin) 4199 if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom) } 4200 if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo) } 4201 if (sawCollapsedSpans) { 4202 from = visualLineNo(cm.doc, from)
4203 to = visualLineEndNo(cm.doc, to) 4204 } 4205 4206 var different = from != display.viewFrom || to != display.viewTo || 4207 display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth 4208 adjustView(cm, from, to) 4209 4210 display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)) 4211 // Position the mover div to align with the current scroll position 4212 cm.display.mover.style.top = display.viewOffset + "px" 4213 4214 var toUpdate = countDirtyView(cm) 4215 if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && 4216 (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) 4217 { return false } 4218 4219 // For big changes, we hide the enclosing element during the 4220 // update, since that speeds up the operations on most browsers. 4221 var selSnapshot = selectionSnapshot(cm) 4222 if (toUpdate > 4) { display.lineDiv.style.display = "none" } 4223 patchDisplay(cm, display.updateLineNumbers, update.dims) 4224 if (toUpdate > 4) { display.lineDiv.style.display = "" } 4225 display.renderedView = display.view 4226 // There might have been a widget with a focused element that got 4227 // hidden or updated, if so re-focus it. 4228 restoreSelection(selSnapshot) 4229 4230 // Prevent selection and cursors from interfering with the scroll 4231 // width and height. 4232 removeChildren(display.cursorDiv) 4233 removeChildren(display.selectionDiv) 4234 display.gutters.style.height = display.sizer.style.minHeight = 0 4235 4236 if (different) { 4237 display.lastWrapHeight = update.wrapperHeight 4238 display.lastWrapWidth = update.wrapperWidth 4239 startWorker(cm, 400) 4240 } 4241 4242 display.updateLineNumbers = null 4243 4244 return true 4245} 4246 4247function postUpdateDisplay(cm, update) { 4248 var viewport = update.viewport 4249 4250 for (var first = true;; first = false) { 4251 if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { 4252 // Clip forced viewport to actual scrollable area. 4253 if (viewport && viewport.top != null) 4254 { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)} } 4255 // Updated line heights might result in the drawn area not 4256 // actually covering the viewport. Keep looping until it does. 4257 update.visible = visibleLines(cm.display, cm.doc, viewport) 4258 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) 4259 { break } 4260 } 4261 if (!updateDisplayIfNeeded(cm, update)) { break } 4262 updateHeightsInViewport(cm) 4263 var barMeasure = measureForScrollbars(cm) 4264 updateSelection(cm) 4265 updateScrollbars(cm, barMeasure) 4266 setDocumentHeight(cm, barMeasure) 4267 update.force = false 4268 } 4269 4270 update.signal(cm, "update", cm) 4271 if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { 4272 update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo) 4273 cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo 4274 } 4275} 4276 4277function updateDisplaySimple(cm, viewport) { 4278 var update = new DisplayUpdate(cm, viewport) 4279 if (updateDisplayIfNeeded(cm, update)) { 4280 updateHeightsInViewport(cm) 4281 postUpdateDisplay(cm, update) 4282 var barMeasure = measureForScrollbars(cm) 4283 updateSelection(cm) 4284 updateScrollbars(cm, barMeasure) 4285 setDocumentHeight(cm, barMeasure) 4286 update.finish() 4287 } 4288} 4289 4290// Sync the actual display DOM structure with display.view, removing 4291// nodes for lines that are no longer in view, and creating the ones 4292// that are not there yet, and updating the ones that are out of 4293// date. 4294function patchDisplay(cm, updateNumbersFrom, dims) { 4295 var display = cm.display, lineNumbers = cm.options.lineNumbers 4296 var container = display.lineDiv, cur = container.firstChild 4297 4298 function rm(node) { 4299 var next = node.nextSibling 4300 // Works around a throw-scroll bug in OS X Webkit 4301 if (webkit && mac && cm.display.currentWheelTarget == node) 4302 { node.style.display = "none" } 4303 else 4304 { node.parentNode.removeChild(node) } 4305 return next 4306 } 4307 4308 var view = display.view, lineN = display.viewFrom 4309 // Loop over the elements in the view, syncing cur (the DOM nodes 4310 // in display.lineDiv) with the view as we go. 4311 for (var i = 0; i < view.length; i++) { 4312 var lineView = view[i] 4313 if (lineView.hidden) { 4314 } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet 4315 var node = buildLineElement(cm, lineView, lineN, dims) 4316 container.insertBefore(node, cur) 4317 } else { // Already drawn 4318 while (cur != lineView.node) { cur = rm(cur) } 4319 var updateNumber = lineNumbers && updateNumbersFrom != null && 4320 updateNumbersFrom <= lineN && lineView.lineNumber 4321 if (lineView.changes) { 4322 if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false } 4323 updateLineForChanges(cm, lineView, lineN, dims) 4324 } 4325 if (updateNumber) { 4326 removeChildren(lineView.lineNumber) 4327 lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))) 4328 } 4329 cur = lineView.node.nextSibling 4330 } 4331 lineN += lineView.size 4332 } 4333 while (cur) { cur = rm(cur) } 4334} 4335 4336function updateGutterSpace(cm) { 4337 var width = cm.display.gutters.offsetWidth 4338 cm.display.sizer.style.marginLeft = width + "px" 4339} 4340 4341function setDocumentHeight(cm, measure) { 4342 cm.display.sizer.style.minHeight = measure.docHeight + "px" 4343 cm.display.heightForcer.style.top = measure.docHeight + "px" 4344 cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px" 4345} 4346 4347// Rebuild the gutter elements, ensure the margin to the left of the 4348// code matches their width. 4349function updateGutters(cm) { 4350 var gutters = cm.display.gutters, specs = cm.options.gutters 4351 removeChildren(gutters) 4352 var i = 0 4353 for (; i < specs.length; ++i) { 4354 var gutterClass = specs[i] 4355 var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)) 4356 if (gutterClass == "CodeMirror-linenumbers") { 4357 cm.display.lineGutter = gElt 4358 gElt.style.width = (cm.display.lineNumWidth || 1) + "px" 4359 } 4360 } 4361 gutters.style.display = i ? "" : "none" 4362 updateGutterSpace(cm) 4363} 4364 4365// Make sure the gutters options contains the element 4366// "CodeMirror-linenumbers" when the lineNumbers option is true. 4367function setGuttersForLineNumbers(options) { 4368 var found = indexOf(options.gutters, "CodeMirror-linenumbers") 4369 if (found == -1 && options.lineNumbers) { 4370 options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]) 4371 } else if (found > -1 && !options.lineNumbers) { 4372 options.gutters = options.gutters.slice(0) 4373 options.gutters.splice(found, 1) 4374 } 4375} 4376 4377var wheelSamples = 0; 4378var wheelPixelsPerUnit = null; 4379// Fill in a browser-detected starting value on browsers where we
4380// know one. These don't have to be accurate -- the result of them 4381// being wrong would just be a slight flicker on the first wheel 4382// scroll (if it is large enough). 4383if (ie) { wheelPixelsPerUnit = -.53 } 4384else if (gecko) { wheelPixelsPerUnit = 15 } 4385else if (chrome) { wheelPixelsPerUnit = -.7 } 4386else if (safari) { wheelPixelsPerUnit = -1/3 } 4387 4388function wheelEventDelta(e) { 4389 var dx = e.wheelDeltaX, dy = e.wheelDeltaY 4390 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail } 4391 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail } 4392 else if (dy == null) { dy = e.wheelDelta } 4393 return {x: dx, y: dy} 4394} 4395function wheelEventPixels(e) { 4396 var delta = wheelEventDelta(e) 4397 delta.x *= wheelPixelsPerUnit 4398 delta.y *= wheelPixelsPerUnit 4399 return delta 4400} 4401 4402function onScrollWheel(cm, e) { 4403 var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y 4404 4405 var display = cm.display, scroll = display.scroller 4406 // Quit if there's nothing to scroll here 4407 var canScrollX = scroll.scrollWidth > scroll.clientWidth 4408 var canScrollY = scroll.scrollHeight > scroll.clientHeight 4409 if (!(dx && canScrollX || dy && canScrollY)) { return } 4410 4411 // Webkit browsers on OS X abort momentum scrolls when the target 4412 // of the scroll event is removed from the scrollable element. 4413 // This hack (see related code in patchDisplay) makes sure the 4414 // element is kept around. 4415 if (dy && mac && webkit) { 4416 outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { 4417 for (var i = 0; i < view.length; i++) { 4418 if (view[i].node == cur) { 4419 cm.display.currentWheelTarget = cur 4420 break outer 4421 } 4422 } 4423 } 4424 } 4425 4426 // On some browsers, horizontal scrolling will cause redraws to 4427 // happen before the gutter has been realigned, causing it to 4428 // wriggle around in a most unseemly way. When we have an 4429 // estimated pixels/delta value, we just handle horizontal 4430 // scrolling entirely here. It'll be slightly off from native, but 4431 // better than glitching out. 4432 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { 4433 if (dy && canScrollY) 4434 { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)) } 4435 setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit)) 4436 // Only prevent default scrolling if vertical scrolling is 4437 // actually possible. Otherwise, it causes vertical scroll 4438 // jitter on OSX trackpads when deltaX is small and deltaY 4439 // is large (issue #3579) 4440 if (!dy || (dy && canScrollY)) 4441 { e_preventDefault(e) } 4442 display.wheelStartX = null // Abort measurement, if in progress 4443 return 4444 } 4445 4446 // 'Project' the visible viewport to cover the area that is being 4447 // scrolled into view (if we know enough to estimate it). 4448 if (dy && wheelPixelsPerUnit != null) { 4449 var pixels = dy * wheelPixelsPerUnit 4450 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight 4451 if (pixels < 0) { top = Math.max(0, top + pixels - 50) } 4452 else { bot = Math.min(cm.doc.height, bot + pixels + 50) } 4453 updateDisplaySimple(cm, {top: top, bottom: bot}) 4454 } 4455 4456 if (wheelSamples < 20) { 4457 if (display.wheelStartX == null) { 4458 display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop 4459 display.wheelDX = dx; display.wheelDY = dy 4460 setTimeout(function () { 4461 if (display.wheelStartX == null) { return } 4462 var movedX = scroll.scrollLeft - display.wheelStartX 4463 var movedY = scroll.scrollTop - display.wheelStartY 4464 var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || 4465 (movedX && display.wheelDX && movedX / display.wheelDX) 4466 display.wheelStartX = display.wheelStartY = null 4467 if (!sample) { return } 4468 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1) 4469 ++wheelSamples 4470 }, 200) 4471 } else { 4472 display.wheelDX += dx; display.wheelDY += dy 4473 } 4474 } 4475} 4476 4477// Selection objects are immutable. A new one is created every time 4478// the selection changes. A selection is one or more non-overlapping 4479// (and non-touching) ranges, sorted, and an integer that indicates 4480// which one is the primary selection (the one that's scrolled into 4481// view, that getCursor returns, etc). 4482var Selection = function(ranges, primIndex) { 4483 this.ranges = ranges 4484 this.primIndex = primIndex 4485}; 4486 4487Selection.prototype.primary = function () { return this.ranges[this.primIndex] }; 4488 4489Selection.prototype.equals = function (other) { 4490 var this$1 = this; 4491 4492 if (other == this) { return true } 4493 if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false } 4494 for (var i = 0; i < this.ranges.length; i++) { 4495 var here = this$1.ranges[i], there = other.ranges[i] 4496 if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)
4496) { return false } 4497 } 4498 return true 4499}; 4500 4501Selection.prototype.deepCopy = function () { 4502 var this$1 = this; 4503 4504 var out = [] 4505 for (var i = 0; i < this.ranges.length; i++) 4506 { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)) } 4507 return new Selection(out, this.primIndex) 4508}; 4509 4510Selection.prototype.somethingSelected = function () { 4511 var this$1 = this; 4512 4513 for (var i = 0; i < this.ranges.length; i++) 4514 { if (!this$1.ranges[i].empty()) { return true } } 4515 return false 4516}; 4517 4518Selection.prototype.contains = function (pos, end) { 4519 var this$1 = this; 4520 4521 if (!end) { end = pos } 4522 for (var i = 0; i < this.ranges.length; i++) { 4523 var range = this$1.ranges[i] 4524 if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) 4525 { return i } 4526 } 4527 return -1 4528}; 4529 4530var Range = function(anchor, head) { 4531 this.anchor = anchor; this.head = head 4532}; 4533 4534Range.prototype.from = function () { return minPos(this.anchor, this.head) }; 4535Range.prototype.to = function () { return maxPos(this.anchor, this.head) }; 4536Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch }; 4537 4538// Take an unsorted, potentially overlapping set of ranges, and 4539// build a selection out of it. 'Consumes' ranges array (modifying 4540// it). 4541function normalizeSelection(ranges, primIndex) { 4542 var prim = ranges[primIndex] 4543 ranges.sort(function (a, b) { return cmp(a.from(), b.from()); }) 4544 primIndex = indexOf(ranges, prim) 4545 for (var i = 1; i < ranges.length; i++) { 4546 var cur = ranges[i], prev = ranges[i - 1] 4547 if (cmp(prev.to(), cur.from()) >= 0) { 4548 var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()) 4549 var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head 4550 if (i <= primIndex) { --primIndex } 4551 ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)) 4552 } 4553 } 4554 return new Selection(ranges, primIndex) 4555} 4556 4557function simpleSelection(anchor, head) { 4558 return new Selection([new Range(anchor, head || anchor)], 0) 4559} 4560 4561// Compute the position of the end of a change (its 'to' property 4562// refers to the pre-change end). 4563function changeEnd(change) { 4564 if (!change.text) { return change.to } 4565 return Pos(change.from.line + change.text.length - 1, 4566 lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) 4567} 4568 4569// Adjust a position to refer to the post-change position of the 4570// same text, or the end of the change if the change covers it. 4571function adjustForChange(pos, change) { 4572 if (cmp(pos, change.from) < 0) { return pos } 4573 if (cmp(pos, change.to) <= 0) { return changeEnd(change) } 4574 4575 var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch 4576 if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch } 4577 return Pos(line, ch) 4578} 4579 4580function computeSelAfterChange(doc, change) { 4581 var out = [] 4582 for (var i = 0; i < doc.sel.ranges.length; i++) { 4583 var range = doc.sel.ranges[i] 4584 out.push(new Range(adjustForChange(range.anchor, change), 4585 adjustForChange(range.head, change))) 4586 } 4587 return normalizeSelection(out, doc.sel.primIndex) 4588} 4589 4590function offsetPos(pos, old, nw) { 4591 if (pos.line == old.line) 4592 { return Pos(nw.line, pos.ch - old.ch + nw.ch) } 4593 else 4594 { return Pos(nw.line + (pos.line - old.line), pos.ch) } 4595} 4596 4597// Used by replaceSelections to allow moving the selection to the 4598// start or around the replaced test. Hint may be "start" or "around". 4599function computeReplacedSel(doc, changes, hint) { 4600 var out = [] 4601 var oldPrev = Pos(doc.first, 0), newPrev = oldPrev 4602 for (var i = 0; i < changes.length; i++) { 4603 var change = changes[i] 4604 var from = offsetPos(change.from, oldPrev, newPrev) 4605 var to = offsetPos(changeEnd(change), oldPrev, newPrev) 4606 oldPrev = change.to 4607 newPrev = to 4608 if (hint == "around") { 4609 var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0 4610 out[i] = new Range(inv ? to : from, inv ? from : to) 4611 } else { 4612 out[i] = new Range(from, from) 4613 } 4614 } 4615 return new Selection(out, doc.sel.primIndex) 4616} 4617 4618// Used to get the editor into a consistent state again when options change. 4619 4620function loadMode(cm) { 4621 cm.doc.mode = getMode(cm.options, cm.doc.modeOption) 4622 resetModeState(cm) 4623} 4624 4625function resetModeState(cm) { 4626 cm.doc.iter(function (line) { 4627 if (line.stateAfter) { line.stateAfter = null } 4628 if (line.styles) { line.styles = null } 4629 }) 4630 cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first 4631 startWorker(cm, 100) 4632 cm.state.modeGen++ 4633 if (cm.curOp) { regChange(cm) } 4634}
4634 4635 4636// DOCUMENT DATA STRUCTURE 4637 4638// By default, updates that start and end at the beginning of a line 4639// are treated specially, in order to make the association of line 4640// widgets and marker elements with the text behave more intuitive. 4641function isWholeLineUpdate(doc, change) { 4642 return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && 4643 (!doc.cm || doc.cm.options.wholeLineUpdateBefore) 4644} 4645 4646// Perform a change on the document data structure. 4647function updateDoc(doc, change, markedSpans, estimateHeight) { 4648 function spansFor(n) {return markedSpans ? markedSpans[n] : null} 4649 function update(line, text, spans) { 4650 updateLine(line, text, spans, estimateHeight) 4651 signalLater(line, "change", line, change) 4652 } 4653 function linesFor(start, end) { 4654 var result = [] 4655 for (var i = start; i < end; ++i) 4656 { result.push(new Line(text[i], spansFor(i), estimateHeight)) } 4657 return result 4658 } 4659 4660 var from = change.from, to = change.to, text = change.text 4661 var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line) 4662 var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line 4663 4664 // Adjust the line structure 4665 if (change.full) { 4666 doc.insert(0, linesFor(0, text.length)) 4667 doc.remove(text.length, doc.size - text.length) 4668 } else if (isWholeLineUpdate(doc, change)) { 4669 // This is a whole-line replace. Treated specially to make 4670 // sure line objects move the way they are supposed to. 4671 var added = linesFor(0, text.length - 1) 4672 update(lastLine, lastLine.text, lastSpans) 4673 if (nlines) { doc.remove(from.line, nlines) } 4674 if (added.length) { doc.insert(from.line, added) } 4675 } else if (firstLine == lastLine) { 4676 if (text.length == 1) { 4677 update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans) 4678 } else { 4679 var added$1 = linesFor(1, text.length - 1) 4680 added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)) 4681 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) 4682 doc.insert(from.line + 1, added$1) 4683 } 4684 } else if (text.length == 1) { 4685 update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)) 4686 doc.remove(from.line + 1, nlines) 4687 } else { 4688 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) 4689 update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans) 4690 var added$2 = linesFor(1, text.length - 1) 4691 if (nlines > 1) { doc.remove(from.line + 1, nlines - 1) } 4692 doc.insert(from.line + 1, added$2) 4693 } 4694 4695 signalLater(doc, "change", doc, change) 4696} 4697 4698// Call f for all linked documents. 4699function linkedDocs(doc, f, sharedHistOnly) { 4700 function propagate(doc, skip, sharedHist) { 4701 if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) { 4702 var rel = doc.linked[i] 4703 if (rel.doc == skip) { continue } 4704 var shared = sharedHist && rel.sharedHist 4705 if (sharedHistOnly && !shared) { continue } 4706 f(rel.doc, shared) 4707 propagate(rel.doc, doc, shared) 4708 } } 4709 } 4710 propagate(doc, null, true) 4711} 4712 4713// Attach a document to an editor. 4714function attachDoc(cm, doc) { 4715 if (doc.cm) { throw new Error("This document is already in use.") } 4716 cm.doc = doc 4717 doc.cm = cm 4718 estimateLineHeights(cm) 4719 loadMode(cm) 4720 setDirectionClass(cm) 4721 if (!cm.options.lineWrapping) { findMaxLine(cm) } 4722 cm.options.mode = doc.modeOption 4723 regChange(cm) 4724} 4725 4726function setDirectionClass(cm) { 4727 ;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl") 4728} 4729 4730function directionChanged(cm) { 4731 runInOp(cm, function () { 4732 setDirectionClass(cm) 4733 regChange(cm) 4734 }) 4735} 4736 4737function History(startGen) { 4738 // Arrays of change events and selections. Doing something adds an 4739 // event to done and clears undo. Undoing moves events from done 4740 // to undone, redoing moves them in the other direction. 4741 this.done = []; this.undone = [] 4742 this.undoDepth = Infinity 4743 // Used to track when changes can be merged into a single undo 4744 // event 4745 this.lastModTime = this.lastSelTime = 0 4746 this.lastOp = this.lastSelOp = null 4747 this.lastOrigin = this.lastSelOrigin = null 4748 // Used by the isClean() method 4749 this.generation = this.maxGeneration = startGen || 1 4750} 4751 4752// Create a history change event from an updateDoc-style change 4753// object. 4754function historyChangeFromChange(doc, change) { 4755 var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)} 4756 attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1) 4757 linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true) 4758 return histChange 4759} 4760 4761// Pop all selection events off the end of a history array. Stop at 4762// a change event. 4763function clearSelectionEvents(array) { 4764 while (array.length) { 4765 var last = lst(array) 4766 if (last.ranges) { array.pop() } 4767 else { break } 4768 } 4769} 4770 4771// Find the top change event in the history. Pop off selection 4772// events that are in the way. 4773function lastChangeEvent(hist, force) { 4774 if (force) { 4775 clearSelectionEvents(hist.done) 4776 return lst(hist.done) 4777 } else if (hist.done.length && !lst(hist.done).ranges) { 4778 return lst(hist.done) 4779 } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { 4780 hist.done.pop() 4781 return lst(hist.done) 4782 } 4783} 4784 4785// Register a change in the history. Merges changes that are within 4786// a single operation, or are close together with an origin that 4787// allows merging (starting with "+") into a single event. 4788function addChangeToHistory(doc, change, selAfter, opId) { 4789 var hist = doc.history 4790 hist.undone.length = 0 4791 var time = +new Date, cur 4792 var last 4793 4794 if ((hist.lastOp == opId || 4795 hist.lastOrigin == change.origin && change.origin && 4796 ((change.origin.charAt(0) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) || 4797 change.origin.charAt(0) == "*")) && 4798 (cur = lastChangeEvent(hist, hist.lastOp == opId))) { 4799 // Merge this change into the last event 4800 last = lst(cur.changes) 4801 if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { 4802 // Optimized case for simple insertion -- don't want to add 4803 // new changesets for every character typed 4804 last.to = changeEnd(change) 4805 } else { 4806 // Add new sub-event 4807 cur.changes.push(historyChangeFromChange(doc, change)) 4808 } 4809 } else { 4810 // Can not be merged, start a new event. 4811 var before = lst(hist.done) 4812 if (!before || !before.ranges) 4813 { pushSelectionToHistory(doc.sel, hist.done) } 4814 cur = {changes: [historyChangeFromChange(doc, change)], 4815 generation: hist.generation} 4816 hist.done.push(cur) 4817 while (hist.done.length > hist.undoDepth) { 4818 hist.done.shift() 4819 if (!hist.done[0].ranges) { hist.done.shift() } 4820 } 4821 } 4822 hist.done.push(selAfter)
4823 hist.generation = ++hist.maxGeneration 4824 hist.lastModTime = hist.lastSelTime = time 4825 hist.lastOp = hist.lastSelOp = opId 4826 hist.lastOrigin = hist.lastSelOrigin = change.origin 4827 4828 if (!last) { signal(doc, "historyAdded") } 4829} 4830 4831function selectionEventCanBeMerged(doc, origin, prev, sel) { 4832 var ch = origin.charAt(0) 4833 return ch == "*" || 4834 ch == "+" && 4835 prev.ranges.length == sel.ranges.length && 4836 prev.somethingSelected() == sel.somethingSelected() && 4837 new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) 4838} 4839 4840// Called whenever the selection changes, sets the new selection as 4841// the pending selection in the history, and pushes the old pending 4842// selection into the 'done' array when it was significantly 4843// different (in number of selected ranges, emptiness, or time). 4844function addSelectionToHistory(doc, sel, opId, options) { 4845 var hist = doc.history, origin = options && options.origin 4846 4847 // A new event is started when the previous origin does not match 4848 // the current, or the origins don't allow matching. Origins 4849 // starting with * are always merged, those starting with + are 4850 // merged when similar and close together in time. 4851 if (opId == hist.lastSelOp || 4852 (origin && hist.lastSelOrigin == origin && 4853 (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || 4854 selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) 4855 { hist.done[hist.done.length - 1] = sel } 4856 else 4857 { pushSelectionToHistory(sel, hist.done) } 4858 4859 hist.lastSelTime = +new Date 4860 hist.lastSelOrigin = origin 4861 hist.lastSelOp = opId 4862 if (options && options.clearRedo !== false) 4863 { clearSelectionEvents(hist.undone) } 4864} 4865 4866function pushSelectionToHistory(sel, dest) { 4867 var top = lst(dest) 4868 if (!(top && top.ranges && top.equals(sel))) 4869 { dest.push(sel) } 4870} 4871 4872// Used to store marked span information in the history. 4873function attachLocalSpans(doc, change, from, to) { 4874 var existing = change["spans_" + doc.id], n = 0 4875 doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) { 4876 if (line.markedSpans) 4877 { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans } 4878 ++n 4879 }) 4880} 4881 4882// When un/re-doing restores text containing marked spans, those 4883// that have been explicitly cleared should not be restored. 4884function removeClearedSpans(spans) { 4885 if (!spans) { return null } 4886 var out 4887 for (var i = 0; i < spans.length; ++i) { 4888 if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i) } } 4889 else if (out) { out.push(spans[i]) } 4890 } 4891 return !out ? spans : out.length ? out : null 4892} 4893 4894// Retrieve and filter the old marked spans stored in a change event. 4895function getOldSpans(doc, change) { 4896 var found = change["spans_" + doc.id] 4897 if (!found) { return null } 4898 var nw = [] 4899 for (var i = 0; i < change.text.length; ++i) 4900 { nw.push(removeClearedSpans(found[i])) } 4901 return nw 4902} 4903 4904// Used for un/re-doing changes from the history. Combines the 4905// result of computing the existing spans with the set of spans that 4906// existed in the history (so that deleting around a span and then 4907// undoing brings back the span). 4908function mergeOldSpans(doc, change) { 4909 var old = getOldSpans(doc, change) 4910 var stretched = stretchSpansOverChange(doc, change) 4911 if (!old) { return stretched } 4912 if (!stretched) { return old } 4913 4914 for (var i = 0; i < old.length; ++i) { 4915 var oldCur = old[i], stretchCur = stretched[i] 4916 if (oldCur && stretchCur) { 4917 spans: for (var j = 0; j < stretchCur.length; ++j) { 4918 var span = stretchCur[j] 4919 for (var k = 0; k < oldCur.length; ++k) 4920 { if (oldCur[k].marker == span.marker) { continue spans } } 4921 oldCur.push(span) 4922 } 4923 } else if (stretchCur) { 4924 old[i] = stretchCur 4925 } 4926 } 4927 return old 4928} 4929 4930// Used both to provide a JSON-safe object in .getHistory, and, when 4931// detaching a document, to split the history in two 4932function copyHistoryArray(events, newGroup, instantiateSel) { 4933 var copy = [] 4934 for (var i = 0; i < events.length; ++i) { 4935 var event = events[i] 4936 if (event.ranges) { 4937 copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event) 4938 continue 4939 } 4940 var changes = event.changes, newChanges = [] 4941 copy.push({changes: newChanges}) 4942 for (var j = 0; j < changes.length; ++j) { 4943 var change = changes[j], m = (void 0) 4944 newChanges.push({from: change.from, to: change.to, text: change.text}) 4945 if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) { 4946 if (indexOf(newGroup, Number(m[1])) > -1) { 4947 lst(newChanges)[prop] = change[prop] 4948 delete change[prop] 4949 } 4950 } } } 4951 } 4952 } 4953 return copy 4954} 4955 4956// The 'scroll' parameter given to many of these indicated whether 4957// the new cursor position should be scrolled into view after 4958// modifying the selection. 4959 4960// If shift is held or the extend flag is set, extends a range to 4961// include a given position (and optionally a second position). 4962// Otherwise, simply returns the range between the given positions. 4963// Used for cursor motion and such. 4964function extendRange(range, head, other, extend) { 4965 if (extend) { 4966 var anchor = range.anchor 4967 if (other) { 4968 var posBefore = cmp(head, anchor) < 0 4969 if (posBefore != (cmp(other, anchor) < 0)) { 4970 anchor = head 4971 head = other 4972 } else if (posBefore != (cmp(head, other) < 0)) { 4973 head = other 4974 } 4975 } 4976 return new Range(anchor, head) 4977 } else { 4978 return new Range(other || head, head) 4979 } 4980} 4981 4982// Extend the primary selection range, discard the rest. 4983function extendSelection(doc, head, other, options, extend) { 4984 if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend) } 4985 setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options) 4986} 4987 4988// Extend all selections (pos is an array of selections with length 4989// equal the number of selections) 4990function extendSelections(doc, heads, options) { 4991 var out = [] 4992 var extend = doc.cm && (doc.cm.display.shift || doc.extend) 4993 for (var i = 0; i < doc.sel.ranges.length; i++) 4994 { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend) } 4995 var newSel = normalizeSelection(out, doc.sel.primIndex) 4996 setSelection(doc, newSel, options) 4997} 4998 4999// Updates a single range in the selection. 5000function replaceOneSelection(doc, i, range, options) { 5001 var ranges = doc.sel.ranges.slice(0) 5002 ranges[i] = range 5003 setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options) 5004} 5005 5006// Reset the selection to a single range. 5007function setSimpleSelection(doc, anchor, head, options) { 5008 setSelection(doc, simpleSelection(anchor, head), options) 5009} 5010 5011// Give beforeSelectionChange handlers a change to influence a 5012// selection update. 5013function filterSelectionChange(doc, sel, options) { 5014 var obj = { 5015 ranges: sel.ranges, 5016 update: function(ranges) { 5017 var this$1 = this; 5018 5019 this.ranges = [] 5020 for (var i = 0; i < ranges.length; i++) 5021 { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), 5022 clipPos(doc, ranges[i].head)) } 5023 }, 5024 origin: options && options.origin 5025 } 5026 signal(doc, "beforeSelectionChange", doc, obj) 5027 if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj) } 5028 if (obj.ranges != sel.ranges) { return normalizeSelection(obj.ranges, obj.ranges.length - 1) } 5029 else { return sel } 5030} 5031 5032function setSelectionReplaceHistory(doc, sel, options) { 5033 var done = doc.history.done, last = lst(done) 5034 if (last && last.ranges) { 5035 done[done.length - 1] = sel 5036 setSelectionNoUndo(doc, sel, options) 5037 } else { 5038 setSelection(doc, sel, options) 5039 } 5040} 5041 5042// Set a new selection. 5043function setSelection(doc, sel, options) { 5044 setSelectionNoUndo(doc, sel, options) 5045 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options) 5046} 5047 5048function setSelectionNoUndo(doc, sel, options) { 5049 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) 5050 { sel = filterSelectionChange(doc, sel, options) } 5051 5052 var bias = options && options.bias || 5053 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1) 5054 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)) 5055 5056 if (!(options && options.scroll === false) && doc.cm) 5057 { ensureCursorVisible(doc.cm) } 5058} 5059 5060function setSelectionInner(doc, sel) { 5061 if (sel.equals(doc.sel)) { return } 5062 5063 doc.sel = sel 5064 5065 if (doc.cm) { 5066 doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true 5067 signalCursorActivity(doc.cm) 5068 } 5069 signalLater(doc, "cursorActivity", doc) 5070} 5071 5072// Verify that the selection does not partially select any atomic 5073// marked ranges. 5074function reCheckSelection(doc) { 5075 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)) 5076} 5077 5078// Return a selection that does not partially select any atomic 5079// ranges. 5080function skipAtomicInSelection(doc, sel, bias, mayClear) { 5081 var out 5082 for (var i = 0; i < sel.ranges.length; i++) { 5083 var range = sel.ranges[i] 5084 var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i] 5085 var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear) 5086 var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear) 5087 if (out || newAnchor != range.anchor || newHead != range.head) { 5088 if (!out) { out = sel.ranges.slice(0, i) } 5089 out[i] = new Range(newAnchor, newHead) 5090 } 5091 } 5092 return out ? normalizeSelection(out, sel.primIndex) : sel 5093} 5094 5095function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { 5096 var line = getLine(doc, pos.line) 5097 if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { 5098 var sp = line.markedSpans[i], m = sp.marker 5099 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && 5100 (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { 5101 if (mayClear) { 5102 signal(m, "beforeCursorEnter") 5103 if (m.explicitlyCleared) { 5104 if (!line.markedSpans) { break } 5105 else {--i; continue} 5106 } 5107 } 5108 if (!m.atomic) { continue } 5109 5110 if (oldPos) { 5111 var near = m.find(dir < 0 ? 1 : -1), diff = (void 0) 5112 if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) 5113 { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null) } 5114 if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) 5115 { return skipAtomicInner(doc, near, pos, dir, mayClear) } 5116 } 5117 5118 var far = m.find(dir < 0 ? -1 : 1) 5119 if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) 5120 { far = movePos(doc, far, dir, far.line == pos.line ? line : null) } 5121 return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null 5122 } 5123 } } 5124 return pos 5125} 5126 5127// Ensure a given position is not inside an atomic range. 5128function skipAtomic(doc, pos, oldPos, bias, mayClear) { 5129 var dir = bias || 1 5130 var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || 5131 (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || 5132 skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || 5133 (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)) 5134 if (!found) { 5135 doc.cantEdit = true 5136 return Pos(doc.first, 0) 5137 } 5138 return found 5139} 5140 5141function movePos(doc, pos, dir, line) { 5142 if (dir < 0 && pos.ch == 0) { 5143 if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) } 5144 else { return null } 5145 } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { 5146 if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) } 5147 else { return null } 5148 } else { 5149 return new Pos(pos.line, pos.ch + dir) 5150 } 5151} 5152 5153function selectAll(cm) {
5154 cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll) 5155} 5156 5157// UPDATING 5158 5159// Allow "beforeChange" event handlers to influence a change 5160function filterChange(doc, change, update) { 5161 var obj = { 5162 canceled: false, 5163 from: change.from, 5164 to: change.to, 5165 text: change.text, 5166 origin: change.origin, 5167 cancel: function () { return obj.canceled = true; } 5168 } 5169 if (update) { obj.update = function (from, to, text, origin) { 5170 if (from) { obj.from = clipPos(doc, from) } 5171 if (to) { obj.to = clipPos(doc, to) } 5172 if (text) { obj.text = text } 5173 if (origin !== undefined) { obj.origin = origin } 5174 } } 5175 signal(doc, "beforeChange", doc, obj) 5176 if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj) } 5177 5178 if (obj.canceled) { return null } 5179 return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} 5180} 5181 5182// Apply a change to a document, and add it to the document's 5183// history, and propagating it to all linked documents. 5184function makeChange(doc, change, ignoreReadOnly) { 5185 if (doc.cm) { 5186 if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) } 5187 if (doc.cm.state.suppressEdits) { return } 5188 } 5189 5190 if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { 5191 change = filterChange(doc, change, true) 5192 if (!change) { return } 5193 } 5194 5195 // Possibly split or suppress the update based on the presence 5196 // of read-only spans in its range. 5197 var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to) 5198 if (split) { 5199 for (var i = split.length - 1; i >= 0; --i) 5200 { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}) } 5201 } else { 5202 makeChangeInner(doc, change) 5203 } 5204} 5205 5206function makeChangeInner(doc, change) { 5207 if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return } 5208 var selAfter = computeSelAfterChange(doc, change) 5209 addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN) 5210 5211 makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)) 5212 var rebased = [] 5213 5214 linkedDocs(doc, function (doc, sharedHist) { 5215 if (!sharedHist && indexOf(rebased, doc.history) == -1) { 5216 rebaseHist(doc.history, change) 5217 rebased.push(doc.history) 5218 } 5219 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)) 5220 }) 5221} 5222 5223// Revert a change stored in a document's history. 5224function makeChangeFromHistory(doc, type, allowSelectionOnly) { 5225 var suppress = doc.cm && doc.cm.state.suppressEdits 5226 if (suppress && !allowSelectionOnly) { return } 5227 5228 var hist = doc.history, event, selAfter = doc.sel 5229 var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done 5230 5231 // Verify that there is a useable event (so that ctrl-z won't 5232 // needlessly clear selection events) 5233 var i = 0 5234 for (; i < source.length; i++) { 5235 event = source[i] 5236 if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) 5237 { break } 5238 } 5239 if (i == source.length) { return } 5240 hist.lastOrigin = hist.lastSelOrigin = null 5241 5242 for (;;) { 5243 event = source.pop() 5244 if (event.ranges) { 5245 pushSelectionToHistory(event, dest) 5246 if (allowSelectionOnly && !event.equals(doc.sel)) { 5247 setSelection(doc, event, {clearRedo: false}) 5248 return 5249 } 5250 selAfter = event 5251 } else if (suppress) { 5252 source.push(event) 5253 return 5254 } else { break } 5255 } 5256 5257 // Build up a reverse change object to add to the opposite history 5258 // stack (redo when undoing, and vice versa). 5259 var antiChanges = [] 5260 pushSelectionToHistory(selAfter, dest) 5261 dest.push({changes: antiChanges, generation: hist.generation}) 5262 hist.generation = event.generation || ++hist.maxGeneration 5263 5264 var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange") 5265 5266 var loop = function ( i ) { 5267 var change = event.changes[i] 5268 change.origin = type 5269 if (filter && !filterChange(doc, change, false)) { 5270 source.length = 0 5271 return {} 5272 } 5273 5274 antiChanges.push(historyChangeFromChange(doc, change)) 5275 5276 var after = i ? computeSelAfterChange(doc, change) : lst(source) 5277 makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)) 5278 if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}) } 5279 var rebased = [] 5280 5281 // Propagate to the linked documents 5282 linkedDocs(doc, function (doc, sharedHist) { 5283 if (!sharedHist && indexOf(rebased, doc.history) == -1) { 5284 rebaseHist(doc.history, change) 5285 rebased.push(doc.history) 5286 } 5287 makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)) 5288 }) 5289 }; 5290 5291 for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { 5292 var returned = loop( i$1 ); 5293 5294 if ( returned ) return returned.v; 5295 } 5296} 5297 5298// Sub-views need their line numbers shifted when text is added 5299// above or below them in the parent document. 5300function shiftDoc(doc, distance) { 5301 if (distance == 0) { return } 5302 doc.first += distance 5303 doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range( 5304 Pos(range.anchor.line + distance, range.anchor.ch), 5305 Pos(range.head.line + distance, range.head.ch) 5306 ); }), doc.sel.primIndex) 5307 if (doc.cm) { 5308 regChange(doc.cm, doc.first, doc.first - distance, distance) 5309 for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) 5310 { regLineChange(doc.cm, l, "gutter") } 5311 } 5312} 5313 5314// More lower-level change function, handling only a single document 5315// (not linked ones). 5316function makeChangeSingleDoc(doc, change, selAfter, spans) { 5317 if (doc.cm && !doc.cm.curOp) 5318 { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) } 5319 5320 if (change.to.line < doc.first) { 5321 shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)) 5322 return 5323 } 5324 if (change.from.line >
5324 doc.lastLine()) { return } 5325 5326 // Clip the change to the size of this doc 5327 if (change.from.line < doc.first) { 5328 var shift = change.text.length - 1 - (doc.first - change.from.line) 5329 shiftDoc(doc, shift) 5330 change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), 5331 text: [lst(change.text)], origin: change.origin} 5332 } 5333 var last = doc.lastLine() 5334 if (change.to.line > last) { 5335 change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), 5336 text: [change.text[0]], origin: change.origin} 5337 } 5338 5339 change.removed = getBetween(doc, change.from, change.to) 5340 5341 if (!selAfter) { selAfter = computeSelAfterChange(doc, change) } 5342 if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans) } 5343 else { updateDoc(doc, change, spans) } 5344 setSelectionNoUndo(doc, selAfter, sel_dontScroll) 5345} 5346 5347// Handle the interaction of a change to a document with the editor 5348// that this document is part of. 5349function makeChangeSingleDocInEditor(cm, change, spans) { 5350 var doc = cm.doc, display = cm.display, from = change.from, to = change.to 5351 5352 var recomputeMaxLength = false, checkWidthStart = from.line 5353 if (!cm.options.lineWrapping) { 5354 checkWidthStart = lineNo(visualLine(getLine(doc, from.line))) 5355 doc.iter(checkWidthStart, to.line + 1, function (line) { 5356 if (line == display.maxLine) { 5357 recomputeMaxLength = true 5358 return true 5359 } 5360 }) 5361 } 5362 5363 if (doc.sel.contains(change.from, change.to) > -1) 5364 { signalCursorActivity(cm) } 5365 5366 updateDoc(doc, change, spans, estimateHeight(cm)) 5367 5368 if (!cm.options.lineWrapping) { 5369 doc.iter(checkWidthStart, from.line + change.text.length, function (line) { 5370 var len = lineLength(line) 5371 if (len > display.maxLineLength) { 5372 display.maxLine = line 5373 display.maxLineLength = len 5374 display.maxLineChanged = true 5375 recomputeMaxLength = false 5376 } 5377 }) 5378 if (recomputeMaxLength) { cm.curOp.updateMaxLine = true } 5379 } 5380 5381 retreatFrontier(doc, from.line) 5382 startWorker(cm, 400) 5383 5384 var lendiff = change.text.length - (to.line - from.line) - 1 5385 // Remember that these lines changed, for updating the display 5386 if (change.full) 5387 { regChange(cm) } 5388 else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) 5389 { regLineChange(cm, from.line, "text") } 5390 else 5391 { regChange(cm, from.line, to.line + 1, lendiff) } 5392 5393 var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change") 5394 if (changeHandler || changesHandler) { 5395 var obj = { 5396 from: from, to: to, 5397 text: change.text, 5398 removed: change.removed, 5399 origin: change.origin 5400 } 5401 if (changeHandler) { signalLater(cm, "change", cm, obj) } 5402 if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj) } 5403 } 5404 cm.display.selForContextMenu = null 5405} 5406 5407function replaceRange(doc, code, from, to, origin) { 5408 if (!to) { to = from } 5409 if (cmp(to, from) < 0) { var assign; 5410 (assign = [to, from], from = assign[0], to = assign[1], assign) } 5411 if (typeof code == "string") { code = doc.splitLines(code) } 5412 makeChange(doc, {from: from, to: to, text: code, origin: origin}) 5413} 5414 5415// Rebasing/resetting history to deal with externally-sourced changes 5416 5417function rebaseHistSelSingle(pos, from, to, diff) { 5418 if (to < pos.line) { 5419 pos.line += diff 5420 } else if (from < pos.line) { 5421 pos.line = from 5422 pos.ch = 0 5423 } 5424} 5425 5426// Tries to rebase an array of history events given a change in the 5427// document. If the change touches the same lines as the event, the 5428// event, and everything 'behind' it, is discarded. If the change is 5429// before the event, the event's positions are updated. Uses a 5430// copy-on-write scheme for the positions, to avoid having to 5431// reallocate them all on every rebase, but also avoid problems with 5432// shared position objects being unsafely updated. 5433function rebaseHistArray(array, from, to, diff) { 5434 for (var i = 0; i < array.length; ++i) { 5435 var sub = array[i], ok = true 5436 if (sub.ranges) { 5437 if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true } 5438 for (var j = 0; j < sub.ranges.length; j++) { 5439 rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff) 5440 rebaseHistSelSingle(sub.ranges[j].head, from, to, diff) 5441 } 5442 continue 5443 } 5444 for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { 5445 var cur = sub.changes[j$1] 5446 if (to < cur.from.line) { 5447 cur.from = Pos(cur.from.line + diff, cur.from.ch) 5448 cur.to = Pos(cur.to.line + diff, cur.to.ch) 5449 } else if (from <= cur.to.line) { 5450 ok = false 5451 break 5452 } 5453 } 5454 if (!ok) { 5455 array.splice(0, i + 1) 5456 i = 0 5457 } 5458 } 5459} 5460 5461function rebaseHist(hist, change) { 5462 var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1 5463 rebaseHistArray(hist.done, from, to, diff) 5464 rebaseHistArray(hist.undone, from, to, diff) 5465} 5466 5467// Utility for applying a change to a line by handle or number, 5468// returning the number and optionally registering the line as 5469// changed. 5470function changeLine(doc, handle, changeType, op) { 5471 var no = handle, line = handle 5472 if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)) } 5473 else { no = lineNo(handle) } 5474 if (no == null) { return null } 5475 if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType) } 5476 return line 5477} 5478 5479// The document is represented as a BTree consisting of leaves, with 5480// chunk of lines in them, and branches, with up to ten leaves or 5481// other branch nodes below them. The top node is always a branch 5482// node, and is the document object itself (meaning it has 5483// additional methods and properties). 5484// 5485// All nodes have parent links. The tree is used both to go from 5486// line numbers to line objects, and to go from objects to numbers.
5487// It also indexes by height, and is used to convert between height 5488// and line object, and to find the total height of the document. 5489// 5490// See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html 5491 5492function LeafChunk(lines) { 5493 var this$1 = this; 5494 5495 this.lines = lines 5496 this.parent = null 5497 var height = 0 5498 for (var i = 0; i < lines.length; ++i) { 5499 lines[i].parent = this$1 5500 height += lines[i].height 5501 } 5502 this.height = height 5503} 5504 5505LeafChunk.prototype = { 5506 chunkSize: function chunkSize() { return this.lines.length }, 5507 5508 // Remove the n lines at offset 'at'. 5509 removeInner: function removeInner(at, n) { 5510 var this$1 = this; 5511 5512 for (var i = at, e = at + n; i < e; ++i) { 5513 var line = this$1.lines[i] 5514 this$1.height -= line.height 5515 cleanUpLine(line) 5516 signalLater(line, "delete") 5517 } 5518 this.lines.splice(at, n) 5519 }, 5520 5521 // Helper used to collapse a small branch into a single leaf. 5522 collapse: function collapse(lines) { 5523 lines.push.apply(lines, this.lines) 5524 }, 5525 5526 // Insert the given array of lines at offset 'at', count them as 5527 // having the given height. 5528 insertInner: function insertInner(at, lines, height) { 5529 var this$1 = this; 5530 5531 this.height += height 5532 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)) 5533 for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1 } 5534 }, 5535 5536 // Used to iterate over a part of the tree. 5537 iterN: function iterN(at, n, op) { 5538 var this$1 = this; 5539 5540 for (var e = at + n; at < e; ++at) 5541 { if (op(this$1.lines[at])) { return true } } 5542 } 5543} 5544 5545function BranchChunk(children) { 5546 var this$1 = this; 5547 5548 this.children = children 5549 var size = 0, height = 0 5550 for (var i = 0; i < children.length; ++i) { 5551 var ch = children[i] 5552 size += ch.chunkSize(); height += ch.height 5553 ch.parent = this$1 5554 } 5555 this.size = size 5556 this.height = height 5557 this.parent = null 5558} 5559 5560BranchChunk.prototype = { 5561 chunkSize: function chunkSize() { return this.size }, 5562 5563 removeInner: function removeInner(at, n) { 5564 var this$1 = this; 5565 5566 this.size -= n 5567 for (var i = 0; i < this.children.length; ++i) { 5568 var child = this$1.children[i], sz = child.chunkSize() 5569 if (at < sz) { 5570 var rm = Math.min(n, sz - at), oldHeight = child.height 5571 child.removeInner(at, rm) 5572 this$1.height -= oldHeight - child.height 5573 if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null } 5574 if ((n -= rm) == 0) { break } 5575 at = 0 5576 } else { at -= sz } 5577 } 5578 // If the result is smaller than 25 lines, ensure that it is a 5579 // single leaf node. 5580 if (this.size - n < 25 && 5581 (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { 5582 var lines = [] 5583 this.collapse(lines) 5584 this.children = [new LeafChunk(lines)] 5585 this.children[0].parent = this 5586 } 5587 }, 5588 5589 collapse: function collapse(lines) { 5590 var this$1 = this; 5591 5592 for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines) } 5593 }, 5594 5595 insertInner: function insertInner(at, lines, height) { 5596 var this$1 = this; 5597 5598 this.size += lines.length 5599 this.height += height 5600 for (var i = 0; i < this.children.length; ++i) { 5601 var child = this$1.children[i], sz = child.chunkSize() 5602 if (at <= sz) { 5603 child.insertInner(at, lines, height) 5604 if (child.lines && child.lines.length > 50) { 5605 // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. 5606 // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. 5607 var remaining = child.lines.length % 25 + 25 5608 for (var pos = remaining; pos < child.lines.length;) { 5609 var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) 5610 child.height -= leaf.height 5611 this$1.children.splice(++i, 0, leaf) 5612 leaf.parent = this$1 5613 } 5614 child.lines = child.lines.slice(0, remaining) 5615 this$1.maybeSpill() 5616 } 5617 break 5618 } 5619 at -= sz 5620 } 5621 }, 5622 5623 // When a node has grown, check whether it should be split. 5624 maybeSpill: function maybeSpill() { 5625 if (this.children.length <= 10) { return } 5626 var me = this 5627 do { 5628 var spilled = me.children.splice(me.children.length - 5, 5) 5629 var sibling = new BranchChunk(spilled) 5630 if (!me.parent) { // Become the parent node 5631 var copy = new BranchChunk(me.children) 5632 copy.parent = me 5633 me.children = [copy, sibling] 5634 me = copy 5635 } else { 5636 me.size -= sibling.size 5637 me.height -= sibling.height 5638 var myIndex = indexOf(me.parent.children, me) 5639 me.parent.children.splice(myIndex + 1, 0, sibling) 5640 } 5641 sibling.parent = me.parent 5642 } while (me.children.length > 10) 5643 me.parent.maybeSpill() 5644 }, 5645 5646 iterN: function iterN(at, n, op) { 5647 var this$1 = this; 5648 5649 for (var i = 0; i < this.children.length; ++i) { 5650 var child = this$1.children[i], sz = child.chunkSize() 5651 if (at < sz) { 5652 var used = Math.min(n, sz - at) 5653 if (child.iterN(at, used, op)) { return true } 5654 if ((n -= used) == 0) { break } 5655 at = 0 5656 } else { at -= sz } 5657 } 5658 } 5659} 5660 5661// Line widgets are block elements displayed above or below a line. 5662 5663var LineWidget = function(doc, node, options) { 5664 var this$1 = this; 5665 5666 if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) 5667 { this$1[opt] = options[opt] } } } 5668 this.doc = doc 5669 this.node = node 5670}; 5671
5672LineWidget.prototype.clear = function () { 5673 var this$1 = this; 5674 5675 var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line) 5676 if (no == null || !ws) { return } 5677 for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1) } } 5678 if (!ws.length) { line.widgets = null } 5679 var height = widgetHeight(this) 5680 updateLineHeight(line, Math.max(0, line.height - height)) 5681 if (cm) { 5682 runInOp(cm, function () { 5683 adjustScrollWhenAboveVisible(cm, line, -height) 5684 regLineChange(cm, no, "widget") 5685 }) 5686 signalLater(cm, "lineWidgetCleared", cm, this, no) 5687 } 5688}; 5689 5690LineWidget.prototype.changed = function () { 5691 var this$1 = this; 5692 5693 var oldH = this.height, cm = this.doc.cm, line = this.line 5694 this.height = null 5695 var diff = widgetHeight(this) - oldH 5696 if (!diff) { return } 5697 updateLineHeight(line, line.height + diff) 5698 if (cm) { 5699 runInOp(cm, function () { 5700 cm.curOp.forceUpdate = true 5701 adjustScrollWhenAboveVisible(cm, line, diff) 5702 signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line)) 5703 }) 5704 } 5705}; 5706eventMixin(LineWidget) 5707 5708function adjustScrollWhenAboveVisible(cm, line, diff) { 5709 if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) 5710 { addToScrollTop(cm, diff) } 5711} 5712 5713function addLineWidget(doc, handle, node, options) { 5714 var widget = new LineWidget(doc, node, options) 5715 var cm = doc.cm 5716 if (cm && widget.noHScroll) { cm.display.alignWidgets = true } 5717 changeLine(doc, handle, "widget", function (line) { 5718 var widgets = line.widgets || (line.widgets = []) 5719 if (widget.insertAt == null) { widgets.push(widget) } 5720 else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget) } 5721 widget.line = line 5722 if (cm && !lineIsHidden(doc, line)) { 5723 var aboveVisible = heightAtLine(line) < doc.scrollTop 5724 updateLineHeight(line, line.height + widgetHeight(widget)) 5725 if (aboveVisible) { addToScrollTop(cm, widget.height) } 5726 cm.curOp.forceUpdate = true 5727 } 5728 return true 5729 }) 5730 if (cm) { signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)) } 5731 return widget 5732} 5733 5734// TEXTMARKERS 5735 5736// Created with markText and setBookmark methods. A TextMarker is a 5737// handle that can be used to clear or find a marked position in the 5738// document. Line objects hold arrays (markedSpans) containing 5739// {from, to, marker} object pointing to such marker objects, and 5740// indicating that such a marker is present on that line. Multiple 5741// lines may point to the same marker when it spans across lines. 5742// The spans will have null for their from/to properties when the 5743// marker continues beyond the start/end of the line. Markers have 5744// links back to the lines they currently touch. 5745 5746// Collapsed markers have unique ids, in order to be able to order 5747// them, which is needed for uniquely determining an outer marker 5748// when they overlap (they may nest, but not partially overlap). 5749var nextMarkerId = 0 5750 5751var TextMarker = function(doc, type) { 5752 this.lines = [] 5753 this.type = type 5754 this.doc = doc 5755 this.id = ++nextMarkerId 5756}; 5757 5758// Clear the marker. 5759TextMarker.prototype.clear = function () { 5760 var this$1 = this; 5761 5762 if (this.explicitlyCleared) { return } 5763 var cm = this.doc.cm, withOp = cm && !cm.curOp 5764 if (withOp) { startOperation(cm) } 5765 if (hasHandler(this, "clear")) { 5766 var found = this.find() 5767 if (found) { signalLater(this, "clear", found.from, found.to) } 5768 } 5769 var min = null, max = null 5770 for (var i = 0; i < this.lines.length; ++i) { 5771 var line = this$1.lines[i] 5772 var span = getMarkedSpanFor(line.markedSpans, this$1) 5773 if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text") } 5774 else if (cm) { 5775 if (span.to != null) { max = lineNo(line) } 5776 if (span.from != null) { min = lineNo(line) } 5777 } 5778 line.markedSpans = removeMarkedSpan(line.markedSpans, span) 5779 if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm) 5780 { updateLineHeight(line, textHeight(cm.display)) } 5781 } 5782 if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) {
5783 var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual) 5784 if (len > cm.display.maxLineLength) { 5785 cm.display.maxLine = visual 5786 cm.display.maxLineLength = len 5787 cm.display.maxLineChanged = true 5788 } 5789 } } 5790 5791 if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1) } 5792 this.lines.length = 0 5793 this.explicitlyCleared = true 5794 if (this.atomic && this.doc.cantEdit) { 5795 this.doc.cantEdit = false 5796 if (cm) { reCheckSelection(cm.doc) } 5797 } 5798 if (cm) { signalLater(cm, "markerCleared", cm, this, min, max) } 5799 if (withOp) { endOperation(cm) } 5800 if (this.parent) { this.parent.clear() } 5801}; 5802 5803// Find the position of the marker in the document. Returns a {from, 5804// to} object by default. Side can be passed to get a specific side 5805// -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the 5806// Pos objects returned contain a line object, rather than a line 5807// number (used to prevent looking up the same line twice). 5808TextMarker.prototype.find = function (side, lineObj) { 5809 var this$1 = this; 5810 5811 if (side == null && this.type == "bookmark") { side = 1 } 5812 var from, to 5813 for (var i = 0; i < this.lines.length; ++i) { 5814 var line = this$1.lines[i] 5815 var span = getMarkedSpanFor(line.markedSpans, this$1) 5816 if (span.from != null) { 5817 from = Pos(lineObj ? line : lineNo(line), span.from) 5818 if (side == -1) { return from } 5819 } 5820 if (span.to != null) { 5821 to = Pos(lineObj ? line : lineNo(line), span.to) 5822 if (side == 1) { return to } 5823 } 5824 } 5825 return from && {from: from, to: to} 5826}; 5827 5828// Signals that the marker's widget changed, and surrounding layout 5829// should be recomputed. 5830TextMarker.prototype.changed = function () { 5831 var this$1 = this; 5832 5833 var pos = this.find(-1, true), widget = this, cm = this.doc.cm 5834 if (!pos || !cm) { return } 5835 runInOp(cm, function () { 5836 var line = pos.line, lineN = lineNo(pos.line) 5837 var view = findViewForLine(cm, lineN) 5838 if (view) { 5839 clearLineMeasurementCacheFor(view) 5840 cm.curOp.selectionChanged = cm.curOp.forceUpdate = true 5841 } 5842 cm.curOp.updateMaxLine = true 5843 if (!lineIsHidden(widget.doc, line) && widget.height != null) { 5844 var oldHeight = widget.height 5845 widget.height = null 5846 var dHeight = widgetHeight(widget) - oldHeight 5847 if (dHeight) 5848 { updateLineHeight(line, line.height + dHeight) } 5849 } 5850 signalLater(cm, "markerChanged", cm, this$1) 5851 }) 5852}; 5853 5854TextMarker.prototype.attachLine = function (line) { 5855 if (!this.lines.length && this.doc.cm) { 5856 var op = this.doc.cm.curOp 5857 if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) 5858 { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) } 5859 } 5860 this.lines.push(line) 5861}; 5862 5863TextMarker.prototype.detachLine = function (line) { 5864 this.lines.splice(indexOf(this.lines, line), 1) 5865 if (!this.lines.length && this.doc.cm) { 5866 var op = this.doc.cm.curOp 5867 ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this) 5868 } 5869}; 5870eventMixin(TextMarker) 5871 5872// Create a marker, wire it up to the right lines, and 5873function markText(doc, from, to, options, type) { 5874 // Shared markers (across linked documents) are handled separately 5875 // (markTextShared will call out to this again, once per 5876 // document). 5877 if (options && options.shared) { return markTextShared(doc, from, to, options, type) } 5878 // Ensure we are in an operation. 5879 if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) } 5880 5881 var marker = new TextMarker(doc, type), diff = cmp(from, to) 5882 if (options) { copyObj(options, marker, false) } 5883 // Don't connect empty markers unless clearWhenEmpty is false 5884 if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) 5885 { return marker } 5886 if (marker.replacedWith) { 5887 // Showing up as a widget implies collapsed (widget replaces text) 5888 marker.collapsed = true 5889 marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget") 5890 if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true") } 5891 if (options.insertLeft) { marker.widgetNode.insertLeft = true } 5892 } 5893 if (marker.collapsed) { 5894 if (conflictingCollapsedRange(doc, from.line, from, to, marker) || 5895 from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) 5896 { throw new Error("Inserting collapsed marker partially overlapping an existing one") } 5897 seeCollapsedSpans() 5898 } 5899 5900 if (marker.addToHistory) 5901 { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN) } 5902 5903 var curLine = from.line, cm = doc.cm, updateMaxLine 5904 doc.iter(curLine, to.line + 1, function (line) { 5905 if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.dis
5905play.maxLine) 5906 { updateMaxLine = true } 5907 if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0) } 5908 addMarkedSpan(line, new MarkedSpan(marker, 5909 curLine == from.line ? from.ch : null, 5910 curLine == to.line ? to.ch : null)) 5911 ++curLine 5912 }) 5913 // lineIsHidden depends on the presence of the spans, so needs a second pass 5914 if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) { 5915 if (lineIsHidden(doc, line)) { updateLineHeight(line, 0) } 5916 }) } 5917 5918 if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }) } 5919 5920 if (marker.readOnly) { 5921 seeReadOnlySpans() 5922 if (doc.history.done.length || doc.history.undone.length) 5923 { doc.clearHistory() } 5924 } 5925 if (marker.collapsed) { 5926 marker.id = ++nextMarkerId 5927 marker.atomic = true 5928 } 5929 if (cm) { 5930 // Sync editor state 5931 if (updateMaxLine) { cm.curOp.updateMaxLine = true } 5932 if (marker.collapsed) 5933 { regChange(cm, from.line, to.line + 1) } 5934 else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) 5935 { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text") } } 5936 if (marker.atomic) { reCheckSelection(cm.doc) } 5937 signalLater(cm, "markerAdded", cm, marker) 5938 } 5939 return marker 5940} 5941 5942// SHARED TEXTMARKERS 5943 5944// A shared marker spans multiple linked documents. It is 5945// implemented as a meta-marker-object controlling multiple normal 5946// markers. 5947var SharedTextMarker = function(markers, primary) { 5948 var this$1 = this; 5949 5950 this.markers = markers 5951 this.primary = primary 5952 for (var i = 0; i < markers.length; ++i) 5953 { markers[i].parent = this$1 } 5954}; 5955 5956SharedTextMarker.prototype.clear = function () { 5957 var this$1 = this; 5958 5959 if (this.explicitlyCleared) { return } 5960 this.explicitlyCleared = true 5961 for (var i = 0; i < this.markers.length; ++i) 5962 { this$1.markers[i].clear() } 5963 signalLater(this, "clear") 5964}; 5965 5966SharedTextMarker.prototype.find = function (side, lineObj) { 5967 return this.primary.find(side, lineObj) 5968}; 5969eventMixin(SharedTextMarker) 5970 5971function markTextShared(doc, from, to, options, type) { 5972 options = copyObj(options) 5973 options.shared = false 5974 var markers = [markText(doc, from, to, options, type)], primary = markers[0] 5975 var widget = options.widgetNode 5976 linkedDocs(doc, function (doc) { 5977 if (widget) { options.widgetNode = widget.cloneNode(true) } 5978 markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)) 5979 for (var i = 0; i < doc.linked.length; ++i) 5980 { if (doc.linked[i].isParent) { return } } 5981 primary = lst(markers) 5982 }) 5983 return new SharedTextMarker(markers, primary) 5984} 5985 5986function findSharedMarkers(doc) { 5987 return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; }) 5988} 5989 5990function copySharedMarkers(doc, markers) { 5991 for (var i = 0; i < markers.length; i++) { 5992 var marker = markers[i], pos = marker.find() 5993 var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to) 5994 if (cmp(mFrom, mTo)) { 5995 var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type) 5996 marker.markers.push(subMark) 5997 subMark.parent = marker 5998 } 5999 } 6000} 6001 6002function detachSharedMarkers(markers) { 6003 var loop = function ( i ) { 6004 var marker = markers[i], linked = [marker.primary.doc] 6005 linkedDocs(marker.primary.doc, function (d) { return linked.push(d); }) 6006 for (var j = 0; j < marker.markers.length; j++) { 6007 var subMarker = marker.markers[j] 6008 if (indexOf(linked, subMarker.doc) == -1) { 6009 subMarker.parent = null 6010 marker.markers.splice(j--, 1) 6011 } 6012 } 6013 }; 6014 6015 for (var i = 0; i < markers.length; i++) loop( i ); 6016} 6017 6018var nextDocId = 0 6019var Doc = function(text, mode, firstLine, lineSep, direction) { 6020 if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) } 6021 if (firstLine == null) { firstLine = 0 } 6022 6023 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]) 6024 this.first = firstLine 6025 this.scrollTop = this.scrollLeft = 0 6026 this.cantEdit = false 6027 this.cleanGeneration = 1 6028 this.modeFrontier = this.highlightFrontier = firstLine 6029 var start = Pos(firstLine, 0) 6030 this.sel = simpleSelection(start) 6031 this.history = new History(null) 6032 this.id = ++nextDocId 6033 this.modeOption = mode 6034 this.lineSep = lineSep 6035 this.direction = (direction == "rtl") ? "rtl" : "ltr" 6036 this.extend = false 6037 6038 if (typeof text == "string") { text = this.splitLines(text) } 6039 updateDoc(this, {from: start, to: start, text: text}) 6040 setSelection(this, simpleSelection(start), sel_dontScroll) 6041} 6042 6043Doc.prototype = createObj(BranchChunk.prototype, { 6044 constructor: Doc, 6045 // Iterate over the document. Supports two forms -- with only one 6046 // argument, it calls that for each line in the document. With 6047 // three, it iterates over the range given by the first two (with 6048 // the second being non-inclusive). 6049 iter: function(from, to, op) { 6050 if (op) { this.iterN(from - this.first, to - from, op) } 6051 else { this.iterN(this.first, this.first + this.size, from) } 6052 }, 6053 6054 // Non-public interface for adding and removing lines. 6055 insert: function(at, lines) { 6056 var height = 0 6057 for (var i = 0; i < lines.length; ++i) { height += lines[i].height } 6058 this.insertInner(at - this.first, lines, height) 6059 }, 6060 remove: function(at, n) { this.removeInner(at - this.first, n) }, 6061 6062 // From here, the methods are part of the public interface. Most 6063 // are also available from CodeMirror (editor) instances. 6064 6065 getValue: function(lineSep) { 6066 var lines = getLines(this, this.first, this.first + this.size) 6067 if (lineSep === false) { return lines } 6068 return lines.join(lineSep || this.lineSeparator()) 6069 }, 6070 setValue: docMethodOp(function(code) { 6071 var top = Pos(this.first, 0), last = this.first + this.size - 1 6072 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), 6073 text: this.splitLines(code), origin: "setValue", full: true}, true) 6074 if (this.cm) { scrollToCoords(this.cm, 0, 0) } 6075 setSelection(this, simpleSelection(top), sel_dontScroll) 6076 }), 6077 replaceRange: function(code, from, to, origin) { 6078 from = clipPos(this, from) 6079 to = to ? clipPos(this, to) : from 6080 replaceRange(this, code, from, to, origin) 6081 }, 6082 getRange: function(from, to, lineSep) { 6083 var lines = getBetween(this, clipPos(this, from), clipPos(this, to)) 6084 if (lineSep === false) { return lines } 6085 return lines.join(lineSep || this.lineSeparator()) 6086 }, 6087 6088 getLine: function(line) {var l = this.getLineHandle(line); return l && l.text}, 6089 6090 getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }}, 6091 getLineNumber: function(line) {return lineNo(line)}, 6092 6093 getLineHandleVisualStart: function(line) { 6094 if (typeof line == "number") { line = getLine(this, line) }
6095 return visualLine(line) 6096 }, 6097 6098 lineCount: function() {return this.size}, 6099 firstLine: function() {return this.first}, 6100 lastLine: function() {return this.first + this.size - 1}, 6101 6102 clipPos: function(pos) {return clipPos(this, pos)}, 6103 6104 getCursor: function(start) { 6105 var range = this.sel.primary(), pos 6106 if (start == null || start == "head") { pos = range.head } 6107 else if (start == "anchor") { pos = range.anchor } 6108 else if (start == "end" || start == "to" || start === false) { pos = range.to() } 6109 else { pos = range.from() } 6110 return pos 6111 }, 6112 listSelections: function() { return this.sel.ranges }, 6113 somethingSelected: function() {return this.sel.somethingSelected()}, 6114 6115 setCursor: docMethodOp(function(line, ch, options) { 6116 setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options) 6117 }), 6118 setSelection: docMethodOp(function(anchor, head, options) { 6119 setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options) 6120 }), 6121 extendSelection: docMethodOp(function(head, other, options) { 6122 extendSelection(this, clipPos(this, head), other && clipPos(this, other), options) 6123 }), 6124 extendSelections: docMethodOp(function(heads, options) { 6125 extendSelections(this, clipPosArray(this, heads), options) 6126 }), 6127 extendSelectionsBy: docMethodOp(function(f, options) { 6128 var heads = map(this.sel.ranges, f) 6129 extendSelections(this, clipPosArray(this, heads), options) 6130 }), 6131 setSelections: docMethodOp(function(ranges, primary, options) { 6132 var this$1 = this; 6133 6134 if (!ranges.length) { return } 6135 var out = [] 6136 for (var i = 0; i < ranges.length; i++) 6137 { out[i] = new Range(clipPos(this$1, ranges[i].anchor), 6138 clipPos(this$1, ranges[i].head)) } 6139 if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex) } 6140 setSelection(this, normalizeSelection(out, primary), options) 6141 }), 6142 addSelection: docMethodOp(function(anchor, head, options) { 6143 var ranges = this.sel.ranges.slice(0) 6144 ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))) 6145 setSelection(this, normalizeSelection(ranges, ranges.length - 1), options) 6146 }), 6147 6148 getSelection: function(lineSep) { 6149 var this$1 = this; 6150 6151 var ranges = this.sel.ranges, lines 6152 for (var i = 0; i < ranges.length; i++) { 6153 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) 6154 lines = lines ? lines.concat(sel) : sel 6155 } 6156 if (lineSep === false) { return lines } 6157 else { return lines.join(lineSep || this.lineSeparator()) } 6158 }, 6159 getSelections: function(lineSep) { 6160 var this$1 = this; 6161 6162 var parts = [], ranges = this.sel.ranges 6163 for (var i = 0; i < ranges.length; i++) { 6164 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) 6165 if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()) } 6166 parts[i] = sel 6167 } 6168 return parts 6169 }, 6170 replaceSelection: function(code, collapse, origin) { 6171 var dup = [] 6172 for (var i = 0; i < this.sel.ranges.length; i++) 6173 { dup[i] = code } 6174 this.replaceSelections(dup, collapse, origin || "+input") 6175 }, 6176 replaceSelections: docMethodOp(function(code, collapse, origin) { 6177 var this$1 = this; 6178 6179 var changes = [], sel = this.sel 6180 for (var i = 0; i < sel.ranges.length; i++) { 6181 var range = sel.ranges[i] 6182 changes[i] = {from: range.from(), to: range.to(), text: this$1.splitLines(code[i]), origin: origin} 6183 } 6184 var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse) 6185 for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) 6186 { makeChange(this$1, changes[i$1]) } 6187 if (newSel) { setSelectionReplaceHistory(this, newSel) } 6188 else if (this.cm) { ensureCursorVisible(this.cm) } 6189 }), 6190 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}), 6191 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}), 6192 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true)}), 6193 redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true)}), 6194 6195 setExtending: function(val) {this.extend = val}, 6196 getExtending: function() {return this.extend}, 6197 6198 historySize: function() { 6199 var hist = this.history, done = 0, undone = 0 6200 for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done } } 6201 for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone } } 6202 return {undo: done, redo: undone} 6203 }, 6204 clearHistory: function() {this.history = new History(this.history.maxGeneration)}, 6205 6206 markClean: function() { 6207 this.cleanGeneration = this.changeGeneration(true) 6208 }, 6209 changeGeneration: function(forceSplit) { 6210 if (forceSplit) 6211 { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null } 6212 return this.history.generation 6213 }, 6214 isClean: function (gen) { 6215 return this.history.generation == (gen || this.cleanGeneration) 6216 }, 6217 6218 getHistory: function() { 6219 return {done: copyHistoryArray(this.history.done), 6220 undone: copyHistoryArray(this.history.undone)} 6221 }, 6222 setHistory: function(histData) { 6223 var hist = this.history = new History(this.history.maxGeneration) 6224 hist.done = copyHistoryArray(histData.done.slice(0), null, true) 6225 hist.undone = copyHistoryArray(histData.undone.slice(0), null, true) 6226 }, 6227 6228 setGutterMarker: docMethodOp(function(line, gutterID, value) { 6229 return changeLine(this, line, "gutter", function (line) { 6230 var markers = line.gutterMarkers || (line.gutterMarkers = {}) 6231 markers[gutterID] = value 6232 if (!value && isEmpty(markers)) { line.gutterMarkers = null } 6233 return true 6234 }) 6235 }), 6236 6237 clearGutter: docMethodOp(function(gutterID) { 6238 var this$1 = this; 6239 6240 this.iter(function (line) { 6241 if (line.gutterMarkers && line.gutterMarkers[gutterID]) { 6242 changeLine(this$1, line, "gutter", function () { 6243 line.gutterMarkers[gutterID] = null 6244 if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null } 6245 return true 6246 }) 6247 } 6248 }) 6249 }), 6250 6251 lineInfo: function(line) { 6252 var n 6253 if (typeof line == "number") { 6254 if (!isLine(this, line)) { return null } 6255 n = line 6256 line = getLine(this, line) 6257 if (!line) { return null } 6258 } else { 6259 n = lineNo(line) 6260 if (n == null) { return null } 6261 } 6262 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, 6263 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, 6264 widgets: line.widgets} 6265 }, 6266 6267 addLineClass: docMethodOp(function(handle, where, cls) { 6268 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { 6269 var prop = where == "text" ? "textClass" 6270 : where == "background" ? "bgClass" 6271 : where == "gutter" ? "gutterClass" : "wrapClass" 6272 if (!line[prop]) { line[prop] = cls }
6273 else if (classTest(cls).test(line[prop])) { return false } 6274 else { line[prop] += " " + cls } 6275 return true 6276 }) 6277 }), 6278 removeLineClass: docMethodOp(function(handle, where, cls) { 6279 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { 6280 var prop = where == "text" ? "textClass" 6281 : where == "background" ? "bgClass" 6282 : where == "gutter" ? "gutterClass" : "wrapClass" 6283 var cur = line[prop] 6284 if (!cur) { return false } 6285 else if (cls == null) { line[prop] = null } 6286 else { 6287 var found = cur.match(classTest(cls)) 6288 if (!found) { return false } 6289 var end = found.index + found[0].length 6290 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null 6291 } 6292 return true 6293 }) 6294 }), 6295 6296 addLineWidget: docMethodOp(function(handle, node, options) { 6297 return addLineWidget(this, handle, node, options) 6298 }), 6299 removeLineWidget: function(widget) { widget.clear() }, 6300 6301 markText: function(from, to, options) { 6302 return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") 6303 }, 6304 setBookmark: function(pos, options) { 6305 var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), 6306 insertLeft: options && options.insertLeft, 6307 clearWhenEmpty: false, shared: options && options.shared, 6308 handleMouseEvents: options && options.handleMouseEvents} 6309 pos = clipPos(this, pos) 6310 return markText(this, pos, pos, realOpts, "bookmark") 6311 }, 6312 findMarksAt: function(pos) { 6313 pos = clipPos(this, pos) 6314 var markers = [], spans = getLine(this, pos.line).markedSpans 6315 if (spans) { for (var i = 0; i < spans.length; ++i) { 6316 var span = spans[i] 6317 if ((span.from == null || span.from <= pos.ch) && 6318 (span.to == null || span.to >= pos.ch)) 6319 { markers.push(span.marker.parent || span.marker) } 6320 } } 6321 return markers 6322 }, 6323 findMarks: function(from, to, filter) { 6324 from = clipPos(this, from); to = clipPos(this, to) 6325 var found = [], lineNo = from.line 6326 this.iter(from.line, to.line + 1, function (line) { 6327 var spans = line.markedSpans 6328 if (spans) { for (var i = 0; i < spans.length; i++) { 6329 var span = spans[i] 6330 if (!(span.to != null && lineNo == from.line && from.ch >= span.to || 6331 span.from == null && lineNo != from.line || 6332 span.from != null && lineNo == to.line && span.from >= to.ch) && 6333 (!filter || filter(span.marker))) 6334 { found.push(span.marker.parent || span.marker) } 6335 } } 6336 ++lineNo 6337 }) 6338 return found 6339 }, 6340 getAllMarks: function() { 6341 var markers = [] 6342 this.iter(function (line) { 6343 var sps = line.markedSpans 6344 if (sps) { for (var i = 0; i < sps.length; ++i) 6345 { if (sps[i].from != null) { markers.push(sps[i].marker) } } } 6346 }) 6347 return markers 6348 }, 6349 6350 posFromIndex: function(off) { 6351 var ch, lineNo = this.first, sepSize = this.lineSeparator().length 6352 this.iter(function (line) { 6353 var sz = line.text.length + sepSize 6354 if (sz > off) { ch = off; return true } 6355 off -= sz 6356 ++lineNo 6357 }) 6358 return clipPos(this, Pos(lineNo, ch)) 6359 }, 6360 indexFromPos: function (coords) { 6361 coords = clipPos(this, coords) 6362 var index = coords.ch 6363 if (coords.line < this.first || coords.ch < 0) { return 0 } 6364 var sepSize = this.lineSeparator().length 6365 this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value 6366 index += line.text.length + sepSize 6367 }) 6368 return index 6369 }, 6370 6371 copy: function(copyHistory) { 6372 var doc = new Doc(getLines(this, this.first, this.first + this.size), 6373 this.modeOption, this.first, this.lineSep, this.direction) 6374 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft 6375 doc.sel = this.sel 6376 doc.extend = false 6377 if (copyHistory) { 6378 doc.history.undoDepth = this.history.undoDepth 6379 doc.setHistory(this.getHistory()) 6380 } 6381 return doc 6382 }, 6383 6384 linkedDoc: function(options) { 6385 if (!options) { options = {} } 6386 var from = this.first, to = this.first + this.size 6387 if (options.from != null && options.from > from) { from = options.from } 6388 if (options.to != null && options.to < to) { to = options.to } 6389 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction) 6390 if (options.sharedHist) { copy.history = this.history 6391 ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}) 6392 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}] 6393 copySharedMarkers(copy, findSharedMarkers(this)) 6394 return copy 6395 }, 6396 unlinkDoc: function(other) { 6397 var this$1 = this; 6398 6399 if (other instanceof CodeMirror) { other = other.doc } 6400 if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { 6401 var link = this$1.linked[i] 6402 if (link.doc != other) { continue } 6403 this$1.linked.splice(i, 1) 6404 other.unlinkDoc(this$1) 6405 detachSharedMarkers(findSharedMarkers(this$1)) 6406 break 6407 } } 6408 // If the histories were shared, split them again 6409 if (other.history == this.history) { 6410 var splitIds = [other.id] 6411 linkedDocs(other, function (doc) { return splitIds.push(doc.id
6411); }, true) 6412 other.history = new History(null) 6413 other.history.done = copyHistoryArray(this.history.done, splitIds) 6414 other.history.undone = copyHistoryArray(this.history.undone, splitIds) 6415 } 6416 }, 6417 iterLinkedDocs: function(f) {linkedDocs(this, f)}, 6418 6419 getMode: function() {return this.mode}, 6420 getEditor: function() {return this.cm}, 6421 6422 splitLines: function(str) { 6423 if (this.lineSep) { return str.split(this.lineSep) } 6424 return splitLinesAuto(str) 6425 }, 6426 lineSeparator: function() { return this.lineSep || "\n" }, 6427 6428 setDirection: docMethodOp(function (dir) { 6429 if (dir != "rtl") { dir = "ltr" } 6430 if (dir == this.direction) { return } 6431 this.direction = dir 6432 this.iter(function (line) { return line.order = null; }) 6433 if (this.cm) { directionChanged(this.cm) } 6434 }) 6435}) 6436 6437// Public alias. 6438Doc.prototype.eachLine = Doc.prototype.iter 6439 6440// Kludge to work around strange IE behavior where it'll sometimes 6441// re-fire a series of drag-related events right after the drop (#1551) 6442var lastDrop = 0 6443 6444function onDrop(e) { 6445 var cm = this 6446 clearDragCursor(cm) 6447 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) 6448 { return } 6449 e_preventDefault(e) 6450 if (ie) { lastDrop = +new Date } 6451 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files 6452 if (!pos || cm.isReadOnly()) { return } 6453 // Might be a file drop, in which case we simply extract the text 6454 // and insert it. 6455 if (files && files.length && window.FileReader && window.File) { 6456 var n = files.length, text = Array(n), read = 0 6457 var loadFile = function (file, i) { 6458 if (cm.options.allowDropFileTypes && 6459 indexOf(cm.options.allowDropFileTypes, file.type) == -1) 6460 { return } 6461 6462 var reader = new FileReader 6463 reader.onload = operation(cm, function () { 6464 var content = reader.result 6465 if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = "" } 6466 text[i] = content 6467 if (++read == n) { 6468 pos = clipPos(cm.doc, pos) 6469 var change = {from: pos, to: pos, 6470 text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), 6471 origin: "paste"} 6472 makeChange(cm.doc, change) 6473 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))) 6474 } 6475 }) 6476 reader.readAsText(file) 6477 } 6478 for (var i = 0; i < n; ++i) { loadFile(files[i], i) } 6479 } else { // Normal drop 6480 // Don't do a replace if the drop happened inside of the selected text. 6481 if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { 6482 cm.state.draggingText(e) 6483 // Ensure the editor is re-focused 6484 setTimeout(function () { return cm.display.input.focus(); }, 20) 6485 return 6486 } 6487 try { 6488 var text$1 = e.dataTransfer.getData("Text") 6489 if (text$1) { 6490 var selected 6491 if (cm.state.draggingText && !cm.state.draggingText.copy) 6492 { selected = cm.listSelections() } 6493 setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)) 6494 if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1) 6495 { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag") } } 6496 cm.replaceSelection(text$1, "around", "paste") 6497 cm.display.input.focus() 6498 } 6499 } 6500 catch(e){} 6501 } 6502} 6503 6504function onDragStart(cm, e) { 6505 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } 6506 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } 6507 6508 e.dataTransfer.setData("Text", cm.getSelection()) 6509 e.dataTransfer.effectAllowed = "copyMove" 6510 6511 // Use dummy image instead of default browsers image. 6512 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. 6513 if (e.dataTransfer.setDragImage && !safari) { 6514 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;") 6515 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" 6516 if (presto) { 6517 img.width = img.height = 1 6518 cm.display.wrapper.appendChild(img) 6519 // Force a relayout, or Opera won't use our image for some obscure reason 6520 img._top = img.offsetTop 6521 } 6522 e.dataTransfer.setDragImage(img, 0, 0) 6523 if (presto) { img.parentNode.removeChild(img) } 6524 } 6525} 6526 6527function onDragOver(cm, e) { 6528 var pos = posFromMouse(cm, e) 6529 if (!pos) { return } 6530 var frag = document.createDocumentFragment() 6531 drawSelectionCursor(cm, pos, frag) 6532 if (!cm.display.dragCursor) { 6533 cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors") 6534 cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv) 6535 } 6536 removeChildrenAndAdd(cm.display.dragCursor, frag) 6537} 6538 6539function clearDragCursor(cm) { 6540 if (cm.display.dragCursor) { 6541 cm.display.lineSpace.removeChild(cm.display.dragCursor) 6542 cm.display.dragCursor = null 6543 } 6544} 6545 6546// These must be handled carefully, because naively registering a 6547// handler for each editor will cause the editors to never be 6548// garbage collected. 6549 6550function forEachCodeMirror(f) { 6551 if (!document.getElementsByClassName) { return } 6552 var byClass = document.getElementsByClassName("CodeMirror") 6553 for (var i = 0; i < byClass.length; i++) { 6554 var cm = byClass[i].CodeMirror 6555 if (cm) { f(cm) } 6556 } 6557} 6558 6559var globalsRegistered = false 6560function ensureGlobalHandlers() { 6561 if (globalsRegistered) { return } 6562 registerGlobalHandlers() 6563 globalsRegistered = true 6564} 6565function registerGlobalHandlers() { 6566 // When the window resizes, we need to refresh active editors. 6567 var resizeTimer 6568 on(window, "resize", function () { 6569 if (resizeTimer == null) { resizeTimer = setTimeout(function () { 6570 resizeTimer = null
6571 forEachCodeMirror(onResize) 6572 }, 100) } 6573 }) 6574 // When the window loses focus, we want to show the editor as blurred 6575 on(window, "blur", function () { return forEachCodeMirror(onBlur); }) 6576} 6577// Called when the window resizes 6578function onResize(cm) { 6579 var d = cm.display 6580 // Might be a text scaling operation, clear size caches. 6581 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null 6582 d.scrollbarsClipped = false 6583 cm.setSize() 6584} 6585 6586var keyNames = { 6587 3: "Pause", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 6588 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 6589 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", 6590 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 6591 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", 145: "ScrollLock", 6592 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 6593 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", 6594 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" 6595} 6596 6597// Number keys 6598for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i) } 6599// Alphabetic keys 6600for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1) } 6601// Function keys 6602for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2 } 6603 6604var keyMap = {} 6605 6606keyMap.basic = { 6607 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", 6608 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", 6609 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", 6610 "Tab": "defaultTab", "Shift-Tab": "indentAuto", 6611 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", 6612 "Esc": "singleSelection" 6613} 6614// Note that the save and find-related commands aren't defined by 6615// default. User code or addons can define them. Unknown commands 6616// are simply ignored. 6617keyMap.pcDefault = { 6618 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", 6619 "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", 6620 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", 6621 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", 6622 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", 6623 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", 6624 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", 6625 fallthrough: "basic" 6626} 6627// Very basic readline/emacs-style bindings, which are standard on Mac. 6628keyMap.emacsy = { 6629 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", 6630 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", 6631 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", 6632 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", 6633 "Ctrl-O": "openLine" 6634} 6635keyMap.macDefault = { 6636 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", 6637 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", 6638 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", 6639 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", 6640 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", 6641 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", 6642 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", 6643 fallthrough: ["basic", "emacsy"] 6644} 6645keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault 6646 6647// KEYMAP DISPATCH 6648 6649function normalizeKeyName(name) { 6650 var parts = name.split(/-(?!$)/) 6651 name = parts[parts.length - 1] 6652 var alt, ctrl, shift, cmd 6653 for (var i = 0; i < parts.length - 1; i++) { 6654 var mod = parts[i] 6655 if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true } 6656 else if (/^a(lt)?$/i.test(mod)) { alt = true } 6657 else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true } 6658 else if (/^s(hift)?$/i.test(mod)) { shift = true } 6659 else { throw new Error("Unrecognized modifier name: " + mod) } 6660 } 6661 if (alt) { name = "Alt-" + name } 6662 if (ctrl) { name = "Ctrl-" + name } 6663 if (cmd) { name = "Cmd-" + name } 6664 if (shift) { name = "Shift-" + name } 6665 return name 6666} 6667 6668// This is a kludge to keep keymaps mostly working as raw objects 6669// (backwards compatibility) while at the same time support features 6670// like normalization and multi-stroke key bindings. It compiles a 6671// new normalized keymap, and then updates the old object to reflect
6672// this. 6673function normalizeKeyMap(keymap) { 6674 var copy = {} 6675 for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) { 6676 var value = keymap[keyname] 6677 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue } 6678 if (value == "...") { delete keymap[keyname]; continue } 6679 6680 var keys = map(keyname.split(" "), normalizeKeyName) 6681 for (var i = 0; i < keys.length; i++) { 6682 var val = (void 0), name = (void 0) 6683 if (i == keys.length - 1) { 6684 name = keys.join(" ") 6685 val = value 6686 } else { 6687 name = keys.slice(0, i + 1).join(" ") 6688 val = "..." 6689 } 6690 var prev = copy[name] 6691 if (!prev) { copy[name] = val } 6692 else if (prev != val) { throw new Error("Inconsistent bindings for " + name) } 6693 } 6694 delete keymap[keyname] 6695 } } 6696 for (var prop in copy) { keymap[prop] = copy[prop] } 6697 return keymap 6698} 6699 6700function lookupKey(key, map, handle, context) { 6701 map = getKeyMap(map) 6702 var found = map.call ? map.call(key, context) : map[key] 6703 if (found === false) { return "nothing" } 6704 if (found === "...") { return "multi" } 6705 if (found != null && handle(found)) { return "handled" } 6706 6707 if (map.fallthrough) { 6708 if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") 6709 { return lookupKey(key, map.fallthrough, handle, context) } 6710 for (var i = 0; i < map.fallthrough.length; i++) { 6711 var result = lookupKey(key, map.fallthrough[i], handle, context) 6712 if (result) { return result } 6713 } 6714 } 6715} 6716 6717// Modifier key presses don't count as 'real' key presses for the 6718// purpose of keymap fallthrough. 6719function isModifierKey(value) { 6720 var name = typeof value == "string" ? value : keyNames[value.keyCode] 6721 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" 6722} 6723 6724function addModifierNames(name, event, noShift) { 6725 var base = name 6726 if (event.altKey && base != "Alt") { name = "Alt-" + name } 6727 if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name } 6728 if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name } 6729 if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name } 6730 return name 6731} 6732 6733// Look up the name of a key as indicated by an event object. 6734function keyName(event, noShift) { 6735 if (presto && event.keyCode == 34 && event["char"]) { return false } 6736 var name = keyNames[event.keyCode] 6737 if (name == null || event.altGraphKey) { return false } 6738 // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause, 6739 // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+) 6740 if (event.keyCode == 3 && event.code) { name = event.code } 6741 return addModifierNames(name, event, noShift) 6742} 6743 6744function getKeyMap(val) { 6745 return typeof val == "string" ? keyMap[val] : val 6746} 6747 6748// Helper for deleting text near the selection(s), used to implement 6749// backspace, delete, and similar functionality. 6750function deleteNearSelection(cm, compute) { 6751 var ranges = cm.doc.sel.ranges, kill = [] 6752 // Build up a set of ranges to kill first, merging overlapping 6753 // ranges. 6754 for (var i = 0; i < ranges.length; i++) { 6755 var toKill = compute(ranges[i]) 6756 while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { 6757 var replaced = kill.pop() 6758 if (cmp(replaced.from, toKill.from) < 0) { 6759 toKill.from = replaced.from 6760 break 6761 } 6762 } 6763 kill.push(toKill) 6764 } 6765 // Next, remove those actual ranges. 6766 runInOp(cm, function () { 6767 for (var i = kill.length - 1; i >= 0; i--) 6768 { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete") } 6769 ensureCursorVisible(cm) 6770 }) 6771} 6772 6773function moveCharLogically(line, ch, dir) { 6774 var target = skipExtendingChars(line.text, ch + dir, dir) 6775 return target < 0 || target > line.text.length ? null : target 6776} 6777 6778function moveLogically(line, start, dir) { 6779 var ch = moveCharLogically(line, start.ch, dir) 6780 return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") 6781} 6782 6783function endOfLine(visually, cm, lineObj, lineNo, dir) { 6784 if (visually) { 6785 var order = getOrder(lineObj, cm.doc.direction) 6786 if (order) {
6787 var part = dir < 0 ? lst(order) : order[0] 6788 var moveInStorageOrder = (dir < 0) == (part.level == 1) 6789 var sticky = moveInStorageOrder ? "after" : "before" 6790 var ch 6791 // With a wrapped rtl chunk (possibly spanning multiple bidi parts), 6792 // it could be that the last bidi part is not on the last visual line, 6793 // since visual lines contain content order-consecutive chunks. 6794 // Thus, in rtl, we are looking for the first (content-order) character 6795 // in the rtl chunk that is on the last line (that is, the same line 6796 // as the last (content-order) character). 6797 if (part.level > 0 || cm.doc.direction == "rtl") { 6798 var prep = prepareMeasureForLine(cm, lineObj) 6799 ch = dir < 0 ? lineObj.text.length - 1 : 0 6800 var targetTop = measureCharPrepared(cm, prep, ch).top 6801 ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch) 6802 if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1) } 6803 } else { ch = dir < 0 ? part.to : part.from } 6804 return new Pos(lineNo, ch, sticky) 6805 } 6806 } 6807 return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") 6808} 6809 6810function moveVisually(cm, line, start, dir) { 6811 var bidi = getOrder(line, cm.doc.direction) 6812 if (!bidi) { return moveLogically(line, start, dir) } 6813 if (start.ch >= line.text.length) { 6814 start.ch = line.text.length 6815 start.sticky = "before" 6816 } else if (start.ch <= 0) { 6817 start.ch = 0 6818 start.sticky = "after" 6819 } 6820 var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos] 6821 if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { 6822 // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, 6823 // nothing interesting happens. 6824 return moveLogically(line, start, dir) 6825 } 6826 6827 var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); } 6828 var prep 6829 var getWrappedLineExtent = function (ch) { 6830 if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } 6831 prep = prep || prepareMeasureForLine(cm, line) 6832 return wrappedLineExtentChar(cm, line, prep, ch) 6833 } 6834 var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch) 6835 6836 if (cm.doc.direction == "rtl" || part.level == 1) { 6837 var moveInStorageOrder = (part.level == 1) == (dir < 0) 6838 var ch = mv(start, moveInStorageOrder ? 1 : -1) 6839 if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { 6840 // Case 2: We move within an rtl part or in an rtl editor on the same visual line 6841 var sticky = moveInStorageOrder ? "before" : "after" 6842 return new Pos(start.line, ch, sticky) 6843 } 6844 } 6845 6846 // Case 3: Could not move within this bidi part in this visual line, so leave 6847 // the current bidi part 6848 6849 var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { 6850 var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder 6851 ? new Pos(start.line, mv(ch, 1), "before") 6852 : new Pos(start.line, ch, "after"); } 6853 6854 for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { 6855 var part = bidi[partPos] 6856 var moveInStorageOrder = (dir > 0) == (part.level != 1) 6857 var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1) 6858 if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) } 6859 ch = moveInStorageOrder ? part.from : mv(part.to, -1) 6860 if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } 6861 } 6862 } 6863 6864 // Case 3a: Look for other bidi parts on the same visual line 6865 var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent) 6866 if (res) { return res } 6867 6868 // Case 3b: Look for other bidi parts on the next visual line 6869 var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1) 6870 if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { 6871 res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)) 6872 if (res) { return res } 6873 } 6874 6875 // Case 4: Nowhere to move 6876 return null 6877} 6878 6879// Commands are parameter-less actions that can be performed on an 6880// editor, mostly used for keybindings. 6881var commands = { 6882 selectAll: selectAll, 6883 singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anch
6883or"), cm.getCursor("head"), sel_dontScroll); }, 6884 killLine: function (cm) { return deleteNearSelection(cm, function (range) { 6885 if (range.empty()) { 6886 var len = getLine(cm.doc, range.head.line).text.length 6887 if (range.head.ch == len && range.head.line < cm.lastLine()) 6888 { return {from: range.head, to: Pos(range.head.line + 1, 0)} } 6889 else 6890 { return {from: range.head, to: Pos(range.head.line, len)} } 6891 } else { 6892 return {from: range.from(), to: range.to()} 6893 } 6894 }); }, 6895 deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({ 6896 from: Pos(range.from().line, 0), 6897 to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) 6898 }); }); }, 6899 delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({ 6900 from: Pos(range.from().line, 0), to: range.from() 6901 }); }); }, 6902 delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { 6903 var top = cm.charCoords(range.head, "div").top + 5 6904 var leftPos = cm.coordsChar({left: 0, top: top}, "div") 6905 return {from: leftPos, to: range.from()} 6906 }); }, 6907 delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) { 6908 var top = cm.charCoords(range.head, "div").top + 5 6909 var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") 6910 return {from: range.from(), to: rightPos } 6911 }); }, 6912 undo: function (cm) { return cm.undo(); }, 6913 redo: function (cm) { return cm.redo(); }, 6914 undoSelection: function (cm) { return cm.undoSelection(); }, 6915 redoSelection: function (cm) { return cm.redoSelection(); }, 6916 goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); }, 6917 goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); }, 6918 goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); }, 6919 {origin: "+move", bias: 1} 6920 ); }, 6921 goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); }, 6922 {origin: "+move", bias: 1} 6923 ); }, 6924 goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); }, 6925 {origin: "+move", bias: -1} 6926 ); }, 6927 goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { 6928 var top = cm.cursorCoords(range.head, "div").top + 5 6929 return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") 6930 }, sel_move); }, 6931 goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { 6932 var top = cm.cursorCoords(range.head, "div").top + 5 6933 return cm.coordsChar({left: 0, top: top}, "div") 6934 }, sel_move); }, 6935 goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) { 6936 var top = cm.cursorCoords(range.head, "div").top + 5 6937 var pos = cm.coordsChar({left: 0, top: top}, "div") 6938 if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) } 6939 return pos 6940 }, sel_move); }, 6941 goLineUp: function (cm) { return cm.moveV(-1, "line"); }, 6942 goLineDown: function (cm) { return cm.moveV(1, "line"); }, 6943 goPageUp: function (cm) { return cm.moveV(-1, "page"); }, 6944 goPageDown: function (cm) { return cm.moveV(1, "page"); }, 6945 goCharLeft: function (cm) { return cm.moveH(-1, "char"); }, 6946 goCharRight: function (cm) { return cm.moveH(1, "char"); }, 6947 goColumnLeft: function (cm) { return cm.moveH(-1, "column"); }, 6948 goColumnRight: function (cm) { return cm.moveH(1, "column"); }, 6949 goWordLeft: function (cm) { return cm.moveH(-1, "word"); }, 6950 goGroupRight: function (cm) { return cm.moveH(1, "group"); }, 6951 goGroupLeft: function (cm) { return cm.moveH(-1, "group"); }, 6952 goWordRight: function (cm) { return cm.moveH(1, "word"); }, 6953 delCharBefore: function (cm) { return cm.deleteH(-1, "char"); }, 6954 delCharAfter: function (cm) { return cm.deleteH(1, "char"); }, 6955 delWordBefore: function (cm) { return cm.deleteH(-1, "word"); }, 6956 delWordAfter: function (cm) { return cm.deleteH(1, "word"); }, 6957 delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); }, 6958 delGroupAfter: function (cm) { return cm.deleteH(1, "group"); }, 6959 indentAuto: function (cm) { return cm.indentSelection("smart"); }, 6960 indentMore: function (cm) { return cm.indentSelection("add"); }, 6961 indentLess: function (cm) { return cm.indentSelection("subtract"); }, 6962 insertTab: function (cm) { return cm.replaceSelection("\t"); }, 6963 insertSoftTab: function (cm) { 6964 var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize 6965 for (var i = 0; i < ranges.length; i++) { 6966 var pos = ranges[i].from() 6967 var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize)
6968 spaces.push(spaceStr(tabSize - col % tabSize)) 6969 } 6970 cm.replaceSelections(spaces) 6971 }, 6972 defaultTab: function (cm) { 6973 if (cm.somethingSelected()) { cm.indentSelection("add") } 6974 else { cm.execCommand("insertTab") } 6975 }, 6976 // Swap the two chars left and right of each selection's head. 6977 // Move cursor behind the two swapped characters afterwards. 6978 // 6979 // Doesn't consider line feeds a character. 6980 // Doesn't scan more than one line above to find a character. 6981 // Doesn't do anything on an empty line. 6982 // Doesn't do anything with non-empty selections. 6983 transposeChars: function (cm) { return runInOp(cm, function () { 6984 var ranges = cm.listSelections(), newSel = [] 6985 for (var i = 0; i < ranges.length; i++) { 6986 if (!ranges[i].empty()) { continue } 6987 var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text 6988 if (line) { 6989 if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1) } 6990 if (cur.ch > 0) { 6991 cur = new Pos(cur.line, cur.ch + 1) 6992 cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), 6993 Pos(cur.line, cur.ch - 2), cur, "+transpose") 6994 } else if (cur.line > cm.doc.first) { 6995 var prev = getLine(cm.doc, cur.line - 1).text 6996 if (prev) { 6997 cur = new Pos(cur.line, 1) 6998 cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + 6999 prev.charAt(prev.length - 1), 7000 Pos(cur.line - 1, prev.length - 1), cur, "+transpose") 7001 } 7002 } 7003 } 7004 newSel.push(new Range(cur, cur)) 7005 } 7006 cm.setSelections(newSel) 7007 }); }, 7008 newlineAndIndent: function (cm) { return runInOp(cm, function () { 7009 var sels = cm.listSelections() 7010 for (var i = sels.length - 1; i >= 0; i--) 7011 { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input") } 7012 sels = cm.listSelections() 7013 for (var i$1 = 0; i$1 < sels.length; i$1++) 7014 { cm.indentLine(sels[i$1].from().line, null, true) } 7015 ensureCursorVisible(cm) 7016 }); }, 7017 openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, 7018 toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } 7019} 7020 7021 7022function lineStart(cm, lineN) { 7023 var line = getLine(cm.doc, lineN) 7024 var visual = visualLine(line) 7025 if (visual != line) { lineN = lineNo(visual) } 7026 return endOfLine(true, cm, visual, lineN, 1) 7027} 7028function lineEnd(cm, lineN) { 7029 var line = getLine(cm.doc, lineN) 7030 var visual = visualLineEnd(line) 7031 if (visual != line) { lineN = lineNo(visual) } 7032 return endOfLine(true, cm, line, lineN, -1) 7033} 7034function lineStartSmart(cm, pos) { 7035 var start = lineStart(cm, pos.line) 7036 var line = getLine(cm.doc, start.line) 7037 var order = getOrder(line, cm.doc.direction) 7038 if (!order || order[0].level == 0) { 7039 var firstNonWS = Math.max(0, line.text.search(/\S/)) 7040 var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch 7041 return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) 7042 } 7043 return start 7044} 7045 7046// Run a handler that was bound to a key. 7047function doHandleBinding(cm, bound, dropShift) { 7048 if (typeof bound == "string") { 7049 bound = commands[bound] 7050 if (!bound) { return false } 7051 } 7052 // Ensure previous input has been read, so that the handler sees a 7053 // consistent view of the document 7054 cm.display.input.ensurePolled() 7055 var prevShift = cm.display.shift, done = false 7056 try { 7057 if (cm.isReadOnly()) { cm.state.suppressEdits = true } 7058 if (dropShift) { cm.display.shift = false } 7059 done = bound(cm) != Pass 7060 } finally { 7061 cm.display.shift = prevShift 7062 cm.state.suppressEdits = false 7063 } 7064 return done 7065} 7066 7067function lookupKeyForEditor(cm, name, handle) { 7068 for (var i = 0; i < cm.state.keyMaps.length; i++) { 7069 var result = lookupKey(name, cm.state.keyMaps[i], handle, cm) 7070 if (result) { return result } 7071 } 7072 return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) 7073 || lookupKey(name, cm.options.keyMap, handle, cm) 7074} 7075 7076// Note that, despite the name, this function is also used to check 7077// for bound mouse clicks. 7078 7079var stopSeq = new Delayed 7080 7081function dispatchKey(cm, name, e, handle) { 7082 var seq = cm.state.keySeq 7083 if (seq) { 7084 if (isModifierKey(name)) { return "handled" } 7085 if (/\'$/.test(name)) 7086 { cm.state.keySeq = null } 7087 else 7088 { stopSeq.set(50, function () { 7089 if (cm.state.keySeq == seq) { 7090 cm.state.keySeq = null 7091 cm.display.input.reset() 7092 } 7093 }) }
7094 if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true } 7095 } 7096 return dispatchKeyInner(cm, name, e, handle) 7097} 7098 7099function dispatchKeyInner(cm, name, e, handle) { 7100 var result = lookupKeyForEditor(cm, name, handle) 7101 7102 if (result == "multi") 7103 { cm.state.keySeq = name } 7104 if (result == "handled") 7105 { signalLater(cm, "keyHandled", cm, name, e) } 7106 7107 if (result == "handled" || result == "multi") { 7108 e_preventDefault(e) 7109 restartBlink(cm) 7110 } 7111 7112 return !!result 7113} 7114 7115// Handle a key from the keydown event. 7116function handleKeyBinding(cm, e) { 7117 var name = keyName(e, true) 7118 if (!name) { return false } 7119 7120 if (e.shiftKey && !cm.state.keySeq) { 7121 // First try to resolve full name (including 'Shift-'). Failing 7122 // that, see if there is a cursor-motion command (starting with 7123 // 'go') bound to the keyname without 'Shift-'. 7124 return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); }) 7125 || dispatchKey(cm, name, e, function (b) { 7126 if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) 7127 { return doHandleBinding(cm, b) } 7128 }) 7129 } else { 7130 return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); }) 7131 } 7132} 7133 7134// Handle a key from the keypress event 7135function handleCharBinding(cm, e, ch) {
7136 return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); }) 7137} 7138 7139var lastStoppedKey = null 7140function onKeyDown(e) { 7141 var cm = this 7142 cm.curOp.focus = activeElt() 7143 if (signalDOMEvent(cm, e)) { return } 7144 // IE does strange things with escape. 7145 if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false } 7146 var code = e.keyCode 7147 cm.display.shift = code == 16 || e.shiftKey 7148 var handled = handleKeyBinding(cm, e) 7149 if (presto) { 7150 lastStoppedKey = handled ? code : null 7151 // Opera has no cut event... we try to at least catch the key combo 7152 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) 7153 { cm.replaceSelection("", null, "cut") } 7154 } 7155 7156 // Turn mouse into crosshair when Alt is held on Mac. 7157 if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) 7158 { showCrossHair(cm) } 7159} 7160 7161function showCrossHair(cm) { 7162 var lineDiv = cm.display.lineDiv 7163 addClass(lineDiv, "CodeMirror-crosshair") 7164 7165 function up(e) { 7166 if (e.keyCode == 18 || !e.altKey) { 7167 rmClass(lineDiv, "CodeMirror-crosshair") 7168 off(document, "keyup", up) 7169 off(document, "mouseover", up) 7170 } 7171 } 7172 on(document, "keyup", up) 7173 on(document, "mouseover", up) 7174} 7175 7176function onKeyUp(e) { 7177 if (e.keyCode == 16) { this.doc.sel.shift = false } 7178 signalDOMEvent(this, e) 7179} 7180 7181function onKeyPress(e) { 7182 var cm = this 7183 if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return } 7184 var keyCode = e.keyCode, charCode = e.charCode 7185 if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} 7186 if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return } 7187 var ch = String.fromCharCode(charCode == null ? keyCode : charCode) 7188 // Some browsers fire keypress events for backspace 7189 if (ch == "\x08") { return } 7190 if (handleCharBinding(cm, e, ch)) { return } 7191 cm.display.input.onKeyPress(e) 7192} 7193 7194var DOUBLECLICK_DELAY = 400 7195 7196var PastClick = function(time, pos, button) { 7197 this.time = time 7198 this.pos = pos 7199 this.button = button 7200}; 7201 7202PastClick.prototype.compare = function (time, pos, button) { 7203 return this.time + DOUBLECLICK_DELAY > time && 7204 cmp(pos, this.pos) == 0 && button == this.button 7205}; 7206 7207var lastClick; 7208var lastDoubleClick; 7209function clickRepeat(pos, button) { 7210 var now = +new Date 7211 if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { 7212 lastClick = lastDoubleClick = null 7213 return "triple" 7214 } else if (lastClick && lastClick.compare(now, pos, button)) { 7215 lastDoubleClick = new PastClick(now, pos, button) 7216 lastClick = null 7217 return "double" 7218 } else { 7219 lastClick = new PastClick(now, pos, button) 7220 lastDoubleClick = null 7221 return "single" 7222 } 7223} 7224 7225// A mouse down can be a single click, double click, triple click, 7226// start of selection drag, start of text drag, new cursor 7227// (ctrl-click), rectangle drag (alt-drag), or xwin 7228// middle-click-paste. Or it might be a click on something we should 7229// not interfere with, such as a scrollbar or widget. 7230function onMouseDown(e) { 7231 var cm = this, display = cm.display 7232 if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return } 7233 display.input.ensurePolled() 7234 display.shift = e.shiftKey 7235 7236 if (eventInWidget(display, e)) { 7237 if (!webkit) { 7238 // Briefly turn off draggability, to allow widgets to do 7239 // normal dragging things. 7240 display.scroller.draggable = false 7241 setTimeout(function () { return display.scroller.draggable = true; }, 100) 7242 } 7243 return 7244 } 7245 if (clickInGutter(cm, e)) { return } 7246 var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single" 7247 window.focus() 7248 7249 // #3261: make sure, that we're not starting a second selection 7250 if (button == 1 && cm.state.selectingText) 7251 { cm.state.selectingText(e) } 7252 7253 if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return } 7254 7255 if (button == 1) { 7256 if (pos) { leftButtonDown(cm, pos, repeat, e) } 7257 else if (e_target(e) == display.scroller) { e_preventDefault(e) } 7258 } else if (button == 2) { 7259 if (pos) { extendSelection(cm.doc, pos) }
7260 setTimeout(function () { return display.input.focus(); }, 20) 7261 } else if (button == 3) { 7262 if (captureRightClick) { onContextMenu(cm, e) } 7263 else { delayBlurEvent(cm) } 7264 } 7265} 7266 7267function handleMappedButton(cm, button, pos, repeat, event) { 7268 var name = "Click" 7269 if (repeat == "double") { name = "Double" + name } 7270 else if (repeat == "triple") { name = "Triple" + name } 7271 name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name 7272 7273 return dispatchKey(cm, addModifierNames(name, event), event, function (bound) { 7274 if (typeof bound == "string") { bound = commands[bound] } 7275 if (!bound) { return false } 7276 var done = false 7277 try { 7278 if (cm.isReadOnly()) { cm.state.suppressEdits = true } 7279 done = bound(cm, pos) != Pass 7280 } finally { 7281 cm.state.suppressEdits = false 7282 } 7283 return done 7284 }) 7285} 7286 7287function configureMouse(cm, repeat, event) { 7288 var option = cm.getOption("configureMouse") 7289 var value = option ? option(cm, repeat, event) : {} 7290 if (value.unit == null) { 7291 var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey 7292 value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line" 7293 } 7294 if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey }
7295 if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey } 7296 if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey) } 7297 return value 7298} 7299 7300function leftButtonDown(cm, pos, repeat, event) { 7301 if (ie) { setTimeout(bind(ensureFocus, cm), 0) } 7302 else { cm.curOp.focus = activeElt() } 7303 7304 var behavior = configureMouse(cm, repeat, event) 7305 7306 var sel = cm.doc.sel, contained 7307 if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && 7308 repeat == "single" && (contained = sel.contains(pos)) > -1 && 7309 (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && 7310 (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) 7311 { leftButtonStartDrag(cm, event, pos, behavior) } 7312 else 7313 { leftButtonSelect(cm, event, pos, behavior) } 7314} 7315 7316// Start a text drag. When it ends, see if any dragging actually 7317// happen, and treat as a click if it didn't. 7318function leftButtonStartDrag(cm, event, pos, behavior) { 7319 var display = cm.display, moved = false 7320 var dragEnd = operation(cm, function (e) { 7321 if (webkit) { display.scroller.draggable = false } 7322 cm.state.draggingText = false 7323 off(display.wrapper.ownerDocument, "mouseup", dragEnd) 7324 off(display.wrapper.ownerDocument, "mousemove", mouseMove) 7325 off(display.scroller, "dragstart", dragStart) 7326 off(display.scroller, "drop", dragEnd) 7327 if (!moved) { 7328 e_preventDefault(e) 7329 if (!behavior.addNew) 7330 { extendSelection(cm.doc, pos, null, null, behavior.extend) } 7331 // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) 7332 if (webkit || ie && ie_version == 9) 7333 { setTimeout(function () {display.wrapper.ownerDocument.body.focus(); display.input.focus()}, 20) } 7334 else 7335 { display.input.focus() } 7336 } 7337 }) 7338 var mouseMove = function(e2) { 7339 moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10 7340 } 7341 var dragStart = function () { return moved = true; } 7342 // Let the drag handler handle this. 7343 if (webkit) { display.scroller.draggable = true } 7344 cm.state.draggingText = dragEnd 7345 dragEnd.copy = !behavior.moveOnDrag 7346 // IE's approach to draggable 7347 if (display.scroller.dragDrop) { display.scroller.dragDrop() } 7348 on(display.wrapper.ownerDocument, "mouseup", dragEnd) 7349 on(display.wrapper.ownerDocument, "mousemove", mouseMove) 7350 on(display.scroller, "dragstart", dragStart) 7351 on(display.scroller, "drop", dragEnd) 7352 7353 delayBlurEvent(cm) 7354 setTimeout(function () { return display.input.focus(); }, 20) 7355} 7356 7357function rangeForUnit(cm, pos, unit) { 7358 if (unit == "char") { return new Range(pos, pos) } 7359 if (unit == "word") { return cm.findWordAt(pos) } 7360 if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } 7361 var result = unit(cm, pos) 7362 return new Range(result.from, result.to) 7363} 7364 7365// Normal selection, as opposed to text dragging. 7366function leftButtonSelect(cm, event, start, behavior) { 7367 var display = cm.display, doc = cm.doc 7368 e_preventDefault(event) 7369 7370 var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges 7371 if (behavior.addNew && !behavior.extend) { 7372 ourIndex = doc.sel.contains(start) 7373 if (ourIndex > -1) 7374 { ourRange = ranges[ourIndex] } 7375 else 7376 { ourRange = new Range(start, start) } 7377 } else { 7378 ourRange = doc.sel.primary() 7379 ourIndex = doc.sel.primIndex 7380 } 7381 7382 if (behavior.unit == "rectangle") { 7383 if (!behavior.addNew) { ourRange = new Range(start, start) } 7384 start = posFromMouse(cm, event, true, true) 7385 ourIndex = -1 7386 } else { 7387 var range = rangeForUnit(cm, start, behavior.unit) 7388 if (behavior.extend) 7389 { ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend) } 7390 else 7391 { ourRange = range } 7392 } 7393 7394 if (!behavior.addNew) { 7395 ourIndex = 0 7396 setSelection(doc, new Selection([ourRange], 0), sel_mouse) 7397 startSel = doc.sel 7398 } else if (ourIndex == -1) { 7399 ourIndex = ranges.length 7400 setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), 7401 {scroll: false, origin: "*mouse"}) 7402 } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { 7403 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), 7404 {scroll: false, origin: "*mouse"}) 7405 startSel = doc.sel 7406 } else { 7407 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse) 7408 } 7409
7410 var lastPos = start 7411 function extendTo(pos) { 7412 if (cmp(lastPos, pos) == 0) { return } 7413 lastPos = pos 7414 7415 if (behavior.unit == "rectangle") { 7416 var ranges = [], tabSize = cm.options.tabSize 7417 var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize) 7418 var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize) 7419 var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol) 7420 for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); 7421 line <= end; line++) { 7422 var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize) 7423 if (left == right) 7424 { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))) } 7425 else if (text.length > leftPos) 7426 { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))) } 7427 } 7428 if (!ranges.length) { ranges.push(new Range(start, start)) } 7429 setSelection(doc, normalizeSelection(start
7429Sel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), 7430 {origin: "*mouse", scroll: false}) 7431 cm.scrollIntoView(pos) 7432 } else { 7433 var oldRange = ourRange 7434 var range = rangeForUnit(cm, pos, behavior.unit) 7435 var anchor = oldRange.anchor, head 7436 if (cmp(range.anchor, anchor) > 0) { 7437 head = range.head 7438 anchor = minPos(oldRange.from(), range.anchor) 7439 } else { 7440 head = range.anchor 7441 anchor = maxPos(oldRange.to(), range.head) 7442 } 7443 var ranges$1 = startSel.ranges.slice(0) 7444 ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head)) 7445 setSelection(doc, normalizeSelection(ranges$1, ourIndex), sel_mouse) 7446 } 7447 } 7448 7449 var editorSize = display.wrapper.getBoundingClientRect() 7450 // Used to ensure timeout re-tries don't fire when another extend 7451 // happened in the meantime (clearTimeout isn't reliable -- at 7452 // least on Chrome, the timeouts still happen even when cleared, 7453 // if the clear happens after their scheduled firing time). 7454 var counter = 0 7455 7456 function extend(e) { 7457 var curCount = ++counter 7458 var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle") 7459 if (!cur) { return } 7460 if (cmp(cur, lastPos) != 0) { 7461 cm.curOp.focus = activeElt() 7462 extendTo(cur) 7463 var visible = visibleLines(display, doc) 7464 if (cur.line >= visible.to || cur.line < visible.from) 7465 { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e) }}), 150) } 7466 } else { 7467 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0 7468 if (outside) { setTimeout(operation(cm, function () { 7469 if (counter != curCount) { return } 7470 display.scroller.scrollTop += outside 7471 extend(e) 7472 }), 50) } 7473 } 7474 } 7475 7476 function done(e) { 7477 cm.state.selectingText = false 7478 counter = Infinity 7479 e_preventDefault(e) 7480 display.input.focus() 7481 off(display.wrapper.ownerDocument, "mousemove", move) 7482 off(display.wrapper.ownerDocument, "mouseup", up) 7483 doc.history.lastSelOrigin = null 7484 } 7485 7486 var move = operation(cm, function (e) { 7487 if (e.buttons === 0 || !e_button(e)) { done(e) } 7488 else { extend(e) } 7489 }) 7490 var up = operation(cm, done) 7491 cm.state.selectingText = up 7492 on(display.wrapper.ownerDocument, "mousemove", move) 7493 on(display.wrapper.ownerDocument, "mouseup", up) 7494} 7495 7496// Used when mouse-selecting to adjust the anchor to the proper side 7497// of a bidi jump depending on the visual position of the head. 7498function bidiSimplify(cm, range) { 7499 var anchor = range.anchor; 7500 var head = range.head; 7501 var anchorLine = getLine(cm.doc, anchor.line) 7502 if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range } 7503 var order = getOrder(anchorLine) 7504 if (!order) { return range } 7505 var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index] 7506 if (part.from != anchor.ch && part.to != anchor.ch) { return range } 7507 var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1) 7508 if (boundary == 0 || boundary == order.length) { return range } 7509 7510 // Compute the relative visual position of the head compared to the 7511 // anchor (<0 is to the left, >0 to the right) 7512 var leftSide 7513 if (head.line != anchor.line) { 7514 leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0 7515 } else { 7516 var headIndex = getBidiPartAt(order, head.ch, head.sticky) 7517 var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1) 7518 if (headIndex == boundary - 1 || headIndex == boundary) 7519 { leftSide = dir < 0 } 7520 else 7521 { leftSide = dir > 0 } 7522 } 7523 7524 var usePart = order[boundary + (leftSide ? -1 : 0)] 7525 var from = leftSide == (usePart.level == 1) 7526 var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before" 7527 return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head) 7528} 7529 7530 7531// Determines whether an event happened in the gutter, and fires the 7532// handlers for the corresponding event. 7533function gutterEvent(cm, e, type, prevent) { 7534 var mX, mY 7535 if (e.touches) { 7536 mX = e.touches[0].clientX 7537 mY = e.touches[0].clientY 7538 } else { 7539 try { mX = e.clientX; mY = e.clientY } 7540 catch(e) { return false } 7541 } 7542 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false } 7543 if (prevent) { e_preventDefault(e) } 7544 7545 var display = cm.display 7546 var lineBox = display.lineDiv.getBoundingClientRect() 7547 7548 if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) } 7549 mY -= lineBox.top - display.viewOffset 7550 7551 for (var i = 0; i < cm.options.gutters.length; ++i) { 7552 var g = display.gutters.childNodes[i] 7553 if (g && g.getBoundingClientRect().right >= mX) { 7554 var line = lineAtHeight(cm.doc, mY) 7555 var gutter = cm.options.gutters[i] 7556 signal(cm, type, cm, line, gutter, e) 7557 return e_defaultPrevented(e) 7558 } 7559 } 7560} 7561 7562function clickInGutter(cm, e) { 7563 return gutterEvent(cm, e, "gutterClick", true) 7564} 7565 7566// CONTEXT MENU HANDLING 7567 7568// To make the context menu work, we need to briefly unhide the 7569// textarea (making it as unobtrusive as possible) to let the 7570// right-click take effect on it. 7571function onContextMenu(cm, e) { 7572 if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } 7573 if (signalDOMEvent(cm, e, "contextmenu")) { return } 7574 cm.display.input.onContextMenu(e) 7575} 7576 7577function contextMenuInGutter(cm, e) { 7578 if (!hasHandler(cm, "gutterContextMenu")) { return false } 7579 return gutterEvent(cm, e, "gutterContextMenu", false) 7580} 7581 7582function themeChanged(cm) { 7583 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + 7584 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-") 7585 clearCaches(cm) 7586} 7587 7588var Init = {toString: function(){return "CodeMirror.Init"}} 7589 7590var defaults = {} 7591var optionHandlers = {} 7592 7593function defineOptions(CodeMirror) { 7594 var optionHandlers = CodeMirror.optionHandlers 7595 7596 function option(name, deflt, handle, notOnInit) { 7597 CodeMirror.defaults[name] = deflt 7598 if (handle) { optionHandlers[name] = 7599 notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old) }} : handle } 7600 } 7601 7602 CodeMirror.defineOption = option 7603 7604 // Passed to option handlers when there is no old value. 7605 CodeMirror.Init = Init 7606 7607 // These two are, on init, called from the constructor because they 7608 // have to be initialized before the editor can start at all. 7609 option("value", "", function (cm, val) { return cm.setValue(val); }, true) 7610 option("mode", null, function (cm, val) { 7611 cm.doc.modeOption = val
7612 loadMode(cm) 7613 }, true) 7614 7615 option("indentUnit", 2, loadMode, true) 7616 option("indentWithTabs", false) 7617 option("smartIndent", true) 7618 option("tabSize", 4, function (cm) { 7619 resetModeState(cm) 7620 clearCaches(cm) 7621 regChange(cm) 7622 }, true) 7623 7624 option("lineSeparator", null, function (cm, val) { 7625 cm.doc.lineSep = val 7626 if (!val) { return } 7627 var newBreaks = [], lineNo = cm.doc.first 7628 cm.doc.iter(function (line) { 7629 for (var pos = 0;;) { 7630 var found = line.text.indexOf(val, pos) 7631 if (found == -1) { break } 7632 pos = found + val.length 7633 newBreaks.push(Pos(lineNo, found)) 7634 } 7635 lineNo++ 7636 }) 7637 for (var i = newBreaks.length - 1; i >= 0; i--) 7638 { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) } 7639 }) 7640 option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, function (cm, val, old) { 7641 cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g") 7642 if (old != Init) { cm.refresh() } 7643 }) 7644 option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true) 7645 option("electricChars", true) 7646 option("inputStyle", mobile ? "contenteditable" : "textarea", function () { 7647 throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME 7648 }, true) 7649 option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true) 7650 option("rtlMoveVisually", !windows) 7651 option("wholeLineUpdateBefore", true) 7652 7653 option("theme", "default", function (cm) { 7654 themeChanged(cm) 7655 guttersChanged(cm) 7656 }, true) 7657 option("keyMap", "default", function (cm, val, old) { 7658 var next = getKeyMap(val) 7659 var prev = old != Init && getKeyMap(old) 7660 if (prev && prev.detach) { prev.detach(cm, next) } 7661 if (next.attach) { next.attach(cm, prev || null) } 7662 }) 7663 option("extraKeys", null) 7664 option("configureMouse", null) 7665 7666 option("lineWrapping", false, wrappingChanged, true) 7667 option("gutters", [], function (cm) { 7668 setGuttersForLineNumbers(cm.options) 7669 guttersChanged(cm) 7670 }, true) 7671 option("fixedGutter", true, function (cm, val) { 7672 cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0" 7673 cm.refresh() 7674 }, true) 7675 option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true) 7676 option("scrollbarStyle", "native", function (cm) { 7677 initScrollbars(cm) 7678 updateScrollbars(cm) 7679 cm.display.scrollbars.setScrollTop(cm.doc.scrollTop) 7680 cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft) 7681 }, true) 7682 option("lineNumbers", false, function (cm) { 7683 setGuttersForLineNumbers(cm.options) 7684 guttersChanged(cm) 7685 }, true) 7686 option("firstLineNumber", 1, guttersChanged, true) 7687 option("lineNumberFormatter", function (integer) { return integer; }, guttersChanged, true) 7688 option("showCursorWhenSelecting", false, updateSelection, true) 7689 7690 option("resetSelectionOnContextMenu", true) 7691 option("lineWiseCopyCut", true) 7692 option("pasteLinesPerSelection", true) 7693 7694 option("readOnly", false, function (cm, val) { 7695 if (val == "nocursor") { 7696 onBlur(cm) 7697 cm.display.input.blur() 7698 } 7699 cm.display.input.readOnlyChanged(val) 7700 }) 7701 option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset() }}, true) 7702 option("dragDrop", true, dragDropChanged) 7703 option("allowDropFileTypes", null) 7704 7705 option("cursorBlinkRate", 530) 7706 option("cursorScrollMargin", 0) 7707 option("cursorHeight", 1, updateSelection, true) 7708 option("singleCursorHeightPerLine", true, updateSelection, true) 7709 option("workTime", 100) 7710 option("workDelay", 100) 7711 option("flattenSpans", true, resetModeState, true) 7712 option("addModeClass", false, resetModeState, true) 7713 option("pollInterval", 100) 7714 option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; }) 7715 option("historyEventDelay", 1250) 7716 option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true) 7717 option("maxHighlightLength", 10000, resetModeState, true) 7718 option("moveInputWithCursor", true, function (cm, val) { 7719 if (!val) { cm.display.input.resetPosition() } 7720 }) 7721 7722 option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; }) 7723 option("autofocus", null) 7724 option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true) 7725} 7726 7727function guttersChanged(cm) { 7728 updateGutters(cm) 7729 regChange(cm) 7730 alignHorizontally(cm) 7731} 7732 7733function dragDropChanged(cm, value, old) { 7734 var wasOn = old && old != Init 7735 if (!value != !wasOn) { 7736 var funcs = cm.display.dragFunctions 7737 var toggle = value ? on : off 7738 toggle(cm.display.scroller, "dragstart", funcs.start) 7739 toggle(cm.display.scroller, "dragenter", funcs.enter) 7740 toggle(cm.display.scroller, "dragover", funcs.over) 7741 toggle(cm.display.scroller, "dragleave", funcs.leave) 7742 toggle(cm.display.scroller, "drop", funcs.drop) 7743 } 7744} 7745 7746function wrappingChanged(cm) { 7747 if (cm.options.lineWrapping) { 7748 addClass(cm.display.wrapper, "CodeMirror-wrap") 7749 cm.display.sizer.style.minWidth = "" 7750 cm.display.sizerWidth = null 7751 } else { 7752 rmClass(cm.display.wrapper, "CodeMirror-wrap") 7753 findMaxLine(cm) 7754 } 7755 estimateLineHeights(cm) 7756 regChange(cm) 7757 clearCaches(cm) 7758 setTimeout(function () { return updateScrollbars(cm); }, 100) 7759} 7760 7761// A CodeMirror instance represents an editor. This is the object
7762// that user code is usually dealing with. 7763 7764function CodeMirror(place, options) { 7765 var this$1 = this; 7766 7767 if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) } 7768 7769 this.options = options = options ? copyObj(options) : {} 7770 // Determine effective options based on given values and defaults. 7771 copyObj(defaults, options, false) 7772 setGuttersForLineNumbers(options) 7773 7774 var doc = options.value 7775 if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction) } 7776 this.doc = doc 7777 7778 var input = new CodeMirror.inputStyles[options.inputStyle](this) 7779 var display = this.display = new Display(place, doc, input) 7780 display.wrapper.CodeMirror = this 7781 updateGutters(this) 7782 themeChanged(this) 7783 if (options.lineWrapping) 7784 { this.display.wrapper.className += " CodeMirror-wrap" } 7785 initScrollbars(this) 7786 7787 this.state = { 7788 keyMaps: [], // stores maps added by addKeyMap 7789 overlays: [], // highlighting overlays, as added by addOverlay 7790 modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info 7791 overwrite: false, 7792 delayingBlurEvent: false, 7793 focused: false, 7794 suppressEdits: false, // used to disable editing during key handlers when in readOnly mode 7795 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll 7796 selectingText: false, 7797 draggingText: false, 7798 highlight: new Delayed(), // stores highlight worker timeout 7799 keySeq: null, // Unfinished key sequence 7800 specialChars: null 7801 } 7802 7803 if (options.autofocus && !mobile) { display.input.focus() } 7804 7805 // Override magic textarea content restore that IE sometimes does 7806 // on our hidden textarea on reload 7807 if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20) } 7808 7809 registerEventHandlers(this) 7810 ensureGlobalHandlers() 7811 7812 startOperation(this) 7813 this.curOp.forceUpdate = true 7814 attachDoc(this, doc) 7815 7816 if ((options.autofocus && !mobile) || this.hasFocus()) 7817 { setTimeout(bind(onFocus, this), 20) } 7818 else 7819 { onBlur(this) } 7820 7821 for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) 7822 { optionHandlers[opt](this$1, options[opt], Init) } } 7823 maybeUpdateLineNumberWidth(this) 7824 if (options.finishInit) { options.finishInit(this) } 7825 for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1) } 7826 endOperation(this) 7827 // Suppress optimizelegibility in Webkit, since it breaks text 7828 // measuring on line wrapping boundaries. 7829 if (webkit && options.lineWrapping && 7830 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") 7831 { display.lineDiv.style.textRendering = "auto" } 7832} 7833 7834// The default configuration options. 7835CodeMirror.defaults = defaults 7836// Functions to run when options are changed. 7837CodeMirror.optionHandlers = optionHandlers 7838 7839// Attach the necessary event handlers when initializing the editor 7840function registerEventHandlers(cm) { 7841 var d = cm.display 7842 on(d.scroller, "mousedown", operation(cm, onMouseDown)) 7843 // Older IE's will not fire a second mousedown for a double click 7844 if (ie && ie_version < 11) 7845 { on(d.scroller, "dblclick", operation(cm, function (e) { 7846 if (signalDOMEvent(cm, e)) { return } 7847 var pos = posFromMouse(cm, e) 7848 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return } 7849 e_preventDefault(e) 7850 var word = cm.findWordAt(pos) 7851 extendSelection(cm.doc, word.anchor, word.head) 7852 })) } 7853 else 7854 { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }) } 7855 // Some browsers fire contextmenu *after* opening the menu, at 7856 // which point we can't mess with it anymore. Context menu is 7857 // handled in onMouseDown for these browsers. 7858 if (!captureRightClick) { on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }) } 7859 7860 // Used to suppress mouse event handling when a touch happens 7861 var touchFinished, prevTouch = {end: 0} 7862 function finishTouch() { 7863 if (d.activeTouch) { 7864 touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000) 7865 prevTouch = d.activeTouch 7866 prevTouch.end = +new Date 7867 } 7868 } 7869 function isMouseLikeTouchEvent(e) { 7870 if (e.touches.length != 1) { return false } 7871 var touch = e.touches[0] 7872 return touch.radiusX <= 1 && touch.radiusY <= 1 7873 } 7874 function farAway(touch, other) { 7875 if (other.left == null) { return true } 7876 var dx = other.left - touch.left, dy = other.top - touch.top 7877 return dx * dx + dy * dy > 20 * 20 7878 } 7879 on(d.scroller, "touchstart", function (e) { 7880 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { 7881 d.input.ensurePolled() 7882 clearTimeout(touchFinished) 7883 var now = +new Date 7884 d.activeTouch = {start: now, moved: false, 7885 prev: now - prevTouch.end <= 300 ? prevTouch : null} 7886 if (e.touches.length == 1) { 7887 d.activeTouch.left = e.touches[0].pageX 7888 d.activeTouch.top = e.touches[0].pageY 7889 } 7890 } 7891 }) 7892 on(d.scroller, "touchmove", function () { 7893 if (d.activeTouch) { d.activeTouch.moved = true } 7894 }) 7895 on(d.scroller, "touchend", function (e) { 7896 var touch = d.activeTouch 7897 if (touch && !eventInWidget(d, e) && touch.left != null && 7898 !touch.moved && new Date - touch.start < 300) { 7899 var pos = cm.coordsChar(d.activeTouch, "page"), range 7900 if (!touch.prev || farAway(touch, touch.prev)) // Single tap 7901 { range = new Range(pos, pos) } 7902 else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap 7903 { range = cm.findWordAt(pos) }
7904 else // Triple tap 7905 { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } 7906 cm.setSelection(range.anchor, range.head) 7907 cm.focus() 7908 e_preventDefault(e) 7909 } 7910 finishTouch() 7911 }) 7912 on(d.scroller, "touchcancel", finishTouch) 7913 7914 // Sync scrolling between fake scrollbars and real scrollable 7915 // area, ensure viewport is updated when scrolling. 7916 on(d.scroller, "scroll", function () { 7917 if (d.scroller.clientHeight) { 7918 updateScrollTop(cm, d.scroller.scrollTop) 7919 setScrollLeft(cm, d.scroller.scrollLeft, true) 7920 signal(cm, "scroll", cm) 7921 } 7922 }) 7923 7924 // Listen to wheel events in order to try and update the viewport on time. 7925 on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); }) 7926 on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); }) 7927 7928 // Prevent wrapper from ever scrolling 7929 on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }) 7930 7931 d.dragFunctions = { 7932 enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e) }}, 7933 over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e) }}, 7934 start: function (e) { return onDragStart(cm, e); }, 7935 drop: operation(cm, onDrop), 7936 leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }} 7937 } 7938 7939 var inp = d.input.getField() 7940 on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); }) 7941 on(inp, "keydown", operation(cm, onKeyDown)) 7942 on(inp, "keypress", operation(cm, onKeyPress)) 7943 on(inp, "focus", function (e) { return onFocus(cm, e); }) 7944 on(inp, "blur", function (e) { return onBlur(cm, e); }) 7945} 7946 7947var initHooks = [] 7948CodeMirror.defineInitHook = function (f) { return initHooks.push(f); } 7949 7950// Indent the given line. The how parameter can be "smart", 7951// "add"/null, "subtract", or "prev". When aggressive is false 7952// (typically set to true for forced single-line indents), empty 7953// lines are not indented, and places where the mode returns Pass 7954// are left alone. 7955function indentLine(cm, n, how, aggressive) { 7956 var doc = cm.doc, state 7957 if (how == null) { how = "add" } 7958 if (how == "smart") { 7959 // Fall back to "prev" when the mode doesn't have an indentation 7960 // method. 7961 if (!doc.mode.indent) { how = "prev" } 7962 else { state = getContextBefore(cm, n).state } 7963 } 7964 7965 var tabSize = cm.options.tabSize 7966 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize) 7967 if (line.stateAfter) { line.stateAfter = null } 7968 var curSpaceString = line.text.match(/^\s*/)[0], indentation 7969 if (!aggressive && !/\S/.test(line.text)) { 7970 indentation = 0 7971 how = "not" 7972 } else if (how == "smart") { 7973 indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text) 7974 if (indentation == Pass || indentation > 150) { 7975 if (!aggressive) { return } 7976 how = "prev" 7977 } 7978 } 7979 if (how == "prev") { 7980 if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize) } 7981 else { indentation = 0 } 7982 } else if (how == "add") { 7983 indentation = curSpace + cm.options.indentUnit 7984 } else if (how == "subtract") { 7985 indentation = curSpace - cm.options.indentUnit 7986 } else if (typeof how == "number") { 7987 indentation = curSpace + how 7988 } 7989 indentation = Math.max(0, indentation) 7990 7991 var indentString = "", pos = 0 7992 if (cm.options.indentWithTabs) 7993 { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t"} } 7994 if (pos < indentation) { indentString += spaceStr(indentation - pos) } 7995 7996 if (indentString != curSpaceString) { 7997 replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input") 7998 line.stateAfter = null 7999 return true 8000 } else { 8001 // Ensure that, if the cursor was in the whitespace at the start 8002 // of the line, it is moved to the end of that space. 8003 for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { 8004 var range = doc.sel.ranges[i$1] 8005 if (range.head.line == n && range.head.ch < curSpaceString.length) { 8006 var pos$1 = Pos(n, curSpaceString.length) 8007 replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)) 8008 break 8009 } 8010 } 8011 } 8012} 8013 8014// This will be set to a {lineWise: bool, text: [string]} object, so
8015// that, when pasting, we know what kind of selections the copied 8016// text was made out of. 8017var lastCopied = null 8018 8019function setLastCopied(newLastCopied) { 8020 lastCopied = newLastCopied 8021} 8022 8023function applyTextInput(cm, inserted, deleted, sel, origin) { 8024 var doc = cm.doc 8025 cm.display.shift = false 8026 if (!sel) { sel = doc.sel } 8027 8028 var paste = cm.state.pasteIncoming || origin == "paste" 8029 var textLines = splitLinesAuto(inserted), multiPaste = null 8030 // When pasting N lines into N selections, insert one line per selection 8031 if (paste && sel.ranges.length > 1) { 8032 if (lastCopied && lastCopied.text.join("\n") == inserted) { 8033 if (sel.ranges.length % lastCopied.text.length == 0) { 8034 multiPaste = [] 8035 for (var i = 0; i < lastCopied.text.length; i++) 8036 { multiPaste.push(doc.splitLines(lastCopied.text[i])) } 8037 } 8038 } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { 8039 multiPaste = map(textLines, function (l) { return [l]; }) 8040 } 8041 } 8042 8043 var updateInput 8044 // Normal behavior is to insert the new text into every selection 8045 for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { 8046 var range = sel.ranges[i$1] 8047 var from = range.from(), to = range.to() 8048 if (range.empty()) { 8049 if (deleted && deleted > 0) // Handle deletion 8050 { from = Pos(from.line, from.ch - deleted) } 8051 else if (cm.state.overwrite && !paste) // Handle overwrite 8052 { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)) } 8053 else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) 8054 { from = to = Pos(from.line, 0) } 8055 } 8056 updateInput = cm.curOp.updateInput 8057 var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, 8058 origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")} 8059 makeChange(cm.doc, changeEvent) 8060 signalLater(cm, "inputRead", cm, changeEvent) 8061 } 8062 if (inserted && !paste) 8063 { triggerElectric(cm, inserted) } 8064 8065 ensureCursorVisible(cm) 8066 cm.curOp.updateInput = updateInput 8067 cm.curOp.typing = true 8068 cm.state.pasteIncoming = cm.state.cutIncoming = false 8069} 8070 8071function handlePaste(e, cm) { 8072 var pasted = e.clipboardData && e.clipboardData.getData("Text") 8073 if (pasted) { 8074 e.preventDefault() 8075 if (!cm.isReadOnly() && !cm.options.disableInput) 8076 { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }) } 8077 return true 8078 } 8079} 8080 8081function triggerElectric(cm, inserted) { 8082 // When an 'electric' character is inserted, immediately trigger a reindent 8083 if (!cm.options.electricChars || !cm.options.smartIndent) { return } 8084 var sel = cm.doc.sel 8085 8086 for (var i = sel.ranges.length - 1; i >= 0; i--) { 8087 var range = sel.ranges[i] 8088 if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) { continue } 8089 var mode = cm.getModeAt(range.head) 8090 var indented = false 8091 if (mode.electricChars) { 8092 for (var j = 0; j < mode.electricChars.length; j++) 8093 { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { 8094 indented = indentLine(cm, range.head.line, "smart") 8095 break 8096 } } 8097 } else if (mode.electricInput) { 8098 if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) 8099 { indented = indentLine(cm, range.head.line, "smart") } 8100 } 8101 if (indented) { signalLater(cm, "electricInput", cm, range.head.line) } 8102 } 8103} 8104 8105function copyableRanges(cm) { 8106 var text = [], ranges = [] 8107 for (var i = 0; i < cm.doc.sel.ranges.length; i++) { 8108 var line = cm.doc.sel.ranges[i].head.line 8109 var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)} 8110 ranges.push(lineRange) 8111 text.push(cm.getRange(lineRange.anchor, lineRange.head)) 8112 } 8113 return {text: text, ranges: ranges} 8114} 8115 8116function disableBrowserMagic(field, spellcheck) { 8117 field.setAttribute("autocorrect", "off") 8118 field.setAttribute("autocapitalize", "off") 8119 field.setAttribute("spellcheck", !!spellcheck) 8120} 8121 8122function hiddenTextarea() { 8123 var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none") 8124 var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;") 8125 // The textarea is kept positioned near the cursor to prevent the 8126 // fact that it'll be scrolled into view on input from scrolling 8127 // our fake cursor out of view. On webkit, when wrap=off, paste is 8128 // very slow. So make the area wide instead. 8129 if (webkit) { te.style.width = "1000px" } 8130 else { te.setAttribute("wrap", "off") } 8131 // If border: 0; -- iOS fails to open keyboard (issue #1287) 8132 if (ios) { te.style.border = "1px solid black" } 8133 disableBrowserMagic(te) 8134 return div 8135} 8136 8137// The publicly visible API. Note that methodOp(f) means 8138// 'wrap f in an operation, performed on its `this` parameter'. 8139 8140// This is not the complete set of editor methods. Most of the 8141// methods defined on the Doc type are also injected into 8142// CodeMirror.prototype, for backwards compatibility and 8143// convenience. 8144 8145function addEditorMethods(CodeMirror) { 8146 var optionHandlers = CodeMirror.optionHandlers 8147 8148 var helpers = CodeMirror.helpers = {} 8149 8150 CodeMirror.prototype = { 8151 constructor: CodeMirror, 8152 focus: function(){window.focus(); this.display.input.focus()}, 8153 8154 setOption: function(option, value) { 8155 var options = this.options, old = options[option] 8156 if (options[option] == value && option != "mode") { return } 8157 options[option] = value 8158 if (optionHandlers.hasOwnProperty(option)) 8159 { operation(this, optionHandlers[option])(this, value, old) } 8160 signal(this, "optionChange", this, option) 8161 }, 8162 8163 getOption: function(option) {return this.options[option]}, 8164 getDoc: function() {return this.doc}, 8165 8166 addKeyMap: function(map, bottom) { 8167 this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)) 8168 }, 8169 removeKeyMap: function(map) { 8170 var maps = this.state.keyMaps 8171 for (var i = 0; i < maps.length; ++i) 8172 { if (maps[i] == map || maps[i].name == map) { 8173 maps.splice(i, 1) 8174 return true 8175 } } 8176 }, 8177 8178 addOverlay: methodOp(function(spec, options) { 8179 var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec) 8180 if (mode.startState) { throw new Error("Overlays may not be stateful.") } 8181 insertSorted(this.state.overlays, 8182 {mode: mode, modeSpec: spec, opaque: options && options.opaque, 8183 priority: (options && options.priority) || 0}, 8184 function (overlay) { return overlay.priority; }) 8185 this.state.modeGen++ 8186 regChange(this) 8187 }), 8188 removeOverlay: methodOp(function(spec) { 8189 var this$1 = this; 8190 8191 var overlays = this.state.overlays 8192 for (var i = 0; i < overlays.length; ++i) { 8193 var cur = overlays[i].modeSpec
8194 if (cur == spec || typeof spec == "string" && cur.name == spec) { 8195 overlays.splice(i, 1) 8196 this$1.state.modeGen++ 8197 regChange(this$1) 8198 return 8199 } 8200 } 8201 }), 8202 8203 indentLine: methodOp(function(n, dir, aggressive) { 8204 if (typeof dir != "string" && typeof dir != "number") { 8205 if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev" } 8206 else { dir = dir ? "add" : "subtract" } 8207 } 8208 if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive) } 8209 }), 8210 indentSelection: methodOp(function(how) { 8211 var this$1 = this; 8212 8213 var ranges = this.doc.sel.ranges, end = -1 8214 for (var i = 0; i < ranges.length; i++) { 8215 var range = ranges[i] 8216 if (!range.empty()) { 8217 var from = range.from(), to = range.to() 8218 var start = Math.max(end, from.line) 8219 end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1 8220 for (var j = start; j < end; ++j) 8221 { indentLine(this$1, j, how) } 8222 var newRanges = this$1.doc.sel.ranges 8223 if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) 8224 { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll) } 8225 } else if (range.head.line > end) { 8226 indentLine(this$1, range.head.line, how, true) 8227 end = range.head.line 8228 if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1) } 8229 } 8230 } 8231 }), 8232 8233 // Fetch the parser token for a given character. Useful for hacks 8234 // that want to inspect the mode state (say, for completion). 8235 getTokenAt: function(pos, precise) { 8236 return takeToken(this, pos, precise) 8237 }, 8238 8239 getLineTokens: function(line, precise) { 8240 return takeToken(this, Pos(line), precise, true) 8241 }, 8242 8243 getTokenTypeAt: function(pos) { 8244 pos = clipPos(this.doc, pos) 8245 var styles = getLineStyles(this, getLine(this.doc, pos.line)) 8246 var before = 0, after = (styles.length - 1) / 2, ch = pos.ch 8247 var type 8248 if (ch == 0) { type = styles[2] } 8249 else { for (;;) { 8250 var mid = (before + after) >> 1 8251 if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid } 8252 else if (styles[mid * 2 + 1] < ch) { before = mid + 1 } 8253 else { type = styles[mid * 2 + 2]; break } 8254 } } 8255 var cut = type ? type.indexOf("overlay ") : -1 8256 return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) 8257 }, 8258 8259 getModeAt: function(pos) { 8260 var mode = this.doc.mode 8261 if (!mode.innerMode) { return mode } 8262 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode 8263 }, 8264 8265 getHelper: function(pos, type) { 8266 return this.getHelpers(pos, type)[0] 8267 }, 8268 8269 getHelpers: function(pos, type) { 8270 var this$1 = this; 8271 8272 var found = [] 8273 if (!helpers.hasOwnProperty(type)) { return found } 8274 var help = helpers[type], mode = this.getModeAt(pos) 8275 if (typeof mode[type] == "string") { 8276 if (help[mode[type]]) { found.push(help[mode[type]]) } 8277 } else if (mode[type]) { 8278 for (var i = 0; i < mode[type].length; i++) { 8279 var val = help[mode[type][i]] 8280 if (val) { found.push(val) } 8281 } 8282 } else if (mode.helperType && help[mode.helperType]) { 8283 found.push(help[mode.helperType]) 8284 } else if (help[mode.name]) { 8285 found.push(help[mode.name]) 8286 } 8287 for (var i$1 = 0; i$1 < help._global.length; i$1++) { 8288 var cur = help._global[i$1] 8289 if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1) 8290 { found.push(cur.val) } 8291 } 8292 return found 8293 }, 8294 8295 getStateAfter: function(line, precise) { 8296 var doc = this.doc 8297 line = clipLine(doc, line == null ? doc.first + doc.size - 1: line) 8298 return getContextBefore(this, line + 1, precise).state 8299 }, 8300 8301 cursorCoords: function(start, mode) { 8302 var pos, range = this.doc.sel.primary() 8303 if (start == null) { pos = range.head } 8304 else if (typeof start == "object") { pos = clipPos(this.doc, start) } 8305 else { pos = start ? range.from() : range.to() } 8306 return cursorCoords(this, pos, mode || "page") 8307 }, 8308 8309 charCoords: function(pos, mode) { 8310 return charCoords(this, clipPos(this.doc, pos), mode || "page") 8311 }, 8312 8313 coordsChar: function(coords, mode) { 8314 coords = fromCoordSystem(this, coords, mode || "page") 8315 return coordsChar(this, coords.left, coords.top) 8316 }, 8317 8318 lineAtHeight: function(height, mode) { 8319 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top 8320 return lineAtHeight(this.doc, height + this.display.viewOffset) 8321 }, 8322 heightAtLine: function(line, mode, includeWidgets) { 8323 var end = false, lineObj 8324 if (typeof line == "number") { 8325 var last = this.doc.first + this.doc.size - 1 8326 if (line < this.doc.first) { line = this.doc.first } 8327 else if (line > last) { line = last; end = true } 8328 lineObj = getLine(this.doc, line) 8329 } else { 8330 lineObj = line 8331 } 8332 return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + 8333 (end ? this.doc.height - heightAtLine(lineObj) : 0) 8334 }, 8335 8336 defaultTextHeight: function() { return textHeight(this.display) }, 8337 defaultCharWidth: function() { return charWidth(this.display) }, 8338 8339 getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, 8340 8341 addWidget: function(pos, node, scroll, vert, horiz) { 8342 var display = this.display 8343 pos = cursorCoords(this, clipPos(this.doc, pos)) 8344 var top = pos.bottom, left = pos.left 8345 node.style.position = "absolute" 8346 node.setAttribute("cm-ignore-events", "true") 8347 this.display.input.setUneditable(node) 8348 display.sizer.appendChild(node) 8349 if (vert == "over") { 8350 top = pos.top 8351 } else if (vert == "above" || vert == "near") { 8352 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), 8353 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth) 8354 // Default to positioning above (if specified and possible); otherwise default to positioning below 8355 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) 8356 { top = pos.top - node.offsetHeight } 8357 else if (pos.bottom + node.offsetHeight <= vspace) 8358 { top = pos.bottom } 8359 if (left + node.offsetWidth > hspace) 8360 { left = hspace - node.offsetWidth } 8361 } 8362 node.style.top = top + "px" 8363 node.style.left = node.style.right = "" 8364 if (horiz == "right") { 8365 left = display.sizer.clientWidth - node.offsetWidth 8366 node.style.right = "0px" 8367 } else { 8368 if (horiz == "left") { left = 0 } 8369 else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2 } 8370 node.style.left = left + "px" 8371 } 8372 if (scroll) 8373 { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}) } 8374 }, 8375 8376 triggerOnKeyDown: methodOp(onKeyDown), 8377 triggerOnKeyPress: methodOp(onKeyPress), 8378 triggerOnKeyUp: onKeyUp,
8379 triggerOnMouseDown: methodOp(onMouseDown), 8380 8381 execCommand: function(cmd) { 8382 if (commands.hasOwnProperty(cmd)) 8383 { return commands[cmd].call(null, this) } 8384 }, 8385 8386 triggerElectric: methodOp(function(text) { triggerElectric(this, text) }), 8387 8388 findPosH: function(from, amount, unit, visually) { 8389 var this$1 = this; 8390 8391 var dir = 1 8392 if (amount < 0) { dir = -1; amount = -amount } 8393 var cur = clipPos(this.doc, from) 8394 for (var i = 0; i < amount; ++i) { 8395 cur = findPosH(this$1.doc, cur, dir, unit, visually) 8396 if (cur.hitSide) { break } 8397 } 8398 return cur 8399 }, 8400 8401 moveH: methodOp(function(dir, unit) { 8402 var this$1 = this; 8403 8404 this.extendSelectionsBy(function (range) { 8405 if (this$1.display.shift || this$1.doc.extend || range.empty()) 8406 { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rtlMoveVisually) } 8407 else 8408 { return dir < 0 ? range.from() : range.to() } 8409 }, sel_move) 8410 }), 8411 8412 deleteH: methodOp(function(dir, unit) { 8413 var sel = this.doc.sel, doc = this.doc 8414 if (sel.somethingSelected()) 8415 { doc.replaceSelection("", null, "+delete") } 8416 else 8417 { deleteNearSelection(this, function (range) { 8418 var other = findPosH(doc, range.head, dir, unit, false) 8419 return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other} 8420 }) } 8421 }), 8422 8423 findPosV: function(from, amount, unit, goalColumn) { 8424 var this$1 = this; 8425 8426 var dir = 1, x = goalColumn 8427 if (amount < 0) { dir = -1; amount = -amount } 8428 var cur = clipPos(this.doc, from) 8429 for (var i = 0; i < amount; ++i) { 8430 var coords = cursorCoords(this$1, cur, "div") 8431 if (x == null) { x = coords.left } 8432 else { coords.left = x } 8433 cur = findPosV(this$1, coords, dir, unit) 8434 if (cur.hitSide) { break } 8435 } 8436 return cur 8437 }, 8438 8439 moveV: methodOp(function(dir, unit) { 8440 var this$1 = this; 8441 8442 var doc = this.doc, goals = [] 8443 var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected() 8444 doc.extendSelectionsBy(function (range) { 8445 if (collapse) 8446 { return dir < 0 ? range.from() : range.to() } 8447 var headPos = cursorCoords(this$1, range.head, "div") 8448 if (range.goalColumn != null) { headPos.left = range.goalColumn } 8449 goals.push(headPos.left) 8450 var pos = findPosV(this$1, headPos, dir, unit) 8451 if (unit == "page" && range == doc.sel.primary()) 8452 { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top) } 8453 return pos 8454 }, sel_move) 8455 if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) 8456 { doc.sel.ranges[i].goalColumn = goals[i] } } 8457 }), 8458 8459 // Find the word at the given position (as returned by coordsChar). 8460 findWordAt: function(pos) { 8461 var doc = this.doc, line = getLine(doc, pos.line).text 8462 var start = pos.ch, end = pos.ch 8463 if (line) { 8464 var helper = this.getHelper(pos, "wordChars") 8465 if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end } 8466 var startChar = line.charAt(start) 8467 var check = isWordChar(startChar, helper) 8468 ? function (ch) { return isWordChar(ch, helper); } 8469 : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } 8470 : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); } 8471 while (start > 0 && check(line.charAt(start - 1))) { --start } 8472 while (end < line.length && check(line.charAt(end))) { ++end } 8473 } 8474 return new Range(Pos(pos.line, start), Pos(pos.line, end)) 8475 }, 8476 8477 toggleOverwrite: function(value) { 8478 if (value != null && value == this.state.overwrite) { return } 8479 if (this.state.overwrite = !this.state.overwrite) 8480 { addClass(this.display.cursorDiv, "CodeMirror-overwrite") } 8481 else 8482 { rmClass(this.display.cursorDiv, "CodeMirror-overwrite") } 8483 8484 signal(this, "overwriteToggle", this, this.state.overwrite) 8485 }, 8486 hasFocus: function() { return this.display.input.getField() == activeElt() }, 8487 isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, 8488 8489 scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y) }), 8490 getScrollInfo: function() { 8491 var scroller = this.display.scroller 8492 return {left: scroller.scrollLeft, top: scroller.scrollTop, 8493 height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, 8494 width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, 8495 clientHeight: displayHeight(this), clientWidth: displayWidth(this)} 8496 }, 8497 8498 scrollIntoView: methodOp(function(range, margin) { 8499 if (range == null) { 8500 range = {from: this.doc.sel.primary().head, to: null} 8501 if (margin == null) { margin = this.options.cursorScrollMargin } 8502 } else if (typeof range == "number") { 8503 range = {from: Pos(range, 0), to: null} 8504 } else if (range.from == null) { 8505 range = {from: range, to: null} 8506 } 8507 if (!range.to) { range.to = range.from } 8508 range.margin = margin || 0 8509 8510 if (range.from.line != null) { 8511 scrollToRange(this, range) 8512 } else { 8513 scrollToCoordsRange(this, range.from, range.to, range.margin) 8514 } 8515 }), 8516 8517 setSize: methodOp(function(width, height) { 8518 var this$1 = this; 8519 8520 var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; } 8521 if (width != null) { this.display.wrapper.style.width = interpret(width) } 8522 if (height != null) { this.display.wrapper.style.height = interpret(height) } 8523 if (this.options.lineWrapping) { clearLineMeasurementCache(this) } 8524 var lineNo = this.display.viewFrom 8525 this.doc.iter(lineNo, this.display.viewTo, function (line) { 8526 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) 8527 { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widget"); break } } } 8528 ++lineNo 8529 }) 8530 this.curOp.forceUpdate = true 8531 signal(this, "refresh", this) 8532 }), 8533 8534 operation: function(f){return runInOp(this, f)}, 8535 startOperation: function(){return startOperation(this)}, 8536 endOperation: function(){return endOperation(this)}, 8537 8538 refresh: methodOp(function() { 8539 var oldHeight = this.display.cachedTextHeight 8540 regChange(this) 8541 this.curOp.forceUpdate = true 8542 clearCaches(this) 8543 scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop) 8544 updateGutterSpace(this) 8545 if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) 8546 { estimateLineHeights(this) } 8547 signal(this, "refresh", this) 8548 }), 8549 8550 swapDoc: methodOp(function(doc) { 8551 var old = this.doc 8552 old.cm = null 8553 attachDoc(this, doc) 8554 clearCaches(this) 8555 this.display.input.reset() 8556 scrollToCoords(this, doc.scrollLeft, doc.scrollTop) 8557 this.curOp.forceScroll = true 8558 signalLater(this, "swapDoc", this, old) 8559 return old 8560 }), 8561 8562 getInputField: function(){return this.display.input.getField()}, 8563 getWrapperElement: function(){return this.display.wrapper}, 8564 getScrollerElement: function(){return this.display.scroller}, 8565 getGutterElement: function(){return this.display.gutters} 8566 } 8567 eventMixin(CodeMirror) 8568
8569 CodeMirror.registerHelper = function(type, name, value) { 8570 if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []} } 8571 helpers[type][name] = value 8572 } 8573 CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { 8574 CodeMirror.registerHelper(type, name, value) 8575 helpers[type]._global.push({pred: predicate, val: value}) 8576 } 8577} 8578 8579// Used for horizontal relative motion. Dir is -1 or 1 (left or 8580// right), unit can be "char", "column" (like char, but doesn't 8581// cross line boundaries), "word" (across next word), or "group" (to 8582// the start of next group of word or non-word-non-whitespace 8583// chars). The visually param controls whether, in right-to-left 8584// text, direction 1 means to move towards the next index in the 8585// string, or towards the character to the right of the current 8586// position. The resulting position will have a hitSide=true 8587// property if it reached the end of the document. 8588function findPosH(doc, pos, dir, unit, visually) { 8589 var oldPos = pos 8590 var origDir = dir 8591 var lineObj = getLine(doc, pos.line) 8592 function findNextLine() { 8593 var l = pos.line + dir 8594 if (l < doc.first || l >= doc.first + doc.size) { return false } 8595 pos = new Pos(l, pos.ch, pos.sticky) 8596 return lineObj = getLine(doc, l) 8597 } 8598 function moveOnce(boundToLine) { 8599 var next 8600 if (visually) { 8601 next = moveVisually(doc.cm, lineObj, pos, dir) 8602 } else { 8603 next = moveLogically(lineObj, pos, dir) 8604 } 8605 if (next == null) { 8606 if (!boundToLine && findNextLine()) 8607 { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir) } 8608 else 8609 { return false } 8610 } else { 8611 pos = next 8612 } 8613 return true 8614 } 8615 8616 if (unit == "char") { 8617 moveOnce() 8618 } else if (unit == "column") { 8619 moveOnce(true) 8620 } else if (unit == "word" || unit == "group") { 8621 var sawType = null, group = unit == "group" 8622 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars") 8623 for (var first = true;; first = false) { 8624 if (dir < 0 && !moveOnce(!first)) { break } 8625 var cur = lineObj.text.charAt(pos.ch) || "\n" 8626 var type = isWordChar(cur, helper) ? "w" 8627 : group && cur == "\n" ? "n" 8628 : !group || /\s/.test(cur) ? null 8629 : "p" 8630 if (group && !first && !type) { type = "s" } 8631 if (sawType && sawType != type) { 8632 if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after"} 8633 break 8634 } 8635 8636 if (type) { sawType = type } 8637 if (dir > 0 && !moveOnce(!first)) { break } 8638 } 8639 } 8640 var result = skipAtomic(doc, pos, oldPos, origDir, true) 8641 if (equalCursorPos(oldPos, result)) { result.hitSide = true } 8642 return result 8643} 8644 8645// For relative vertical movement. Dir may be -1 or 1. Unit can be 8646// "page" or "line". The resulting position will have a hitSide=true 8647// property if it reached the end of the document. 8648function findPosV(cm, pos, dir, unit) { 8649 var doc = cm.doc, x = pos.left, y 8650 if (unit == "page") { 8651 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight) 8652 var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3) 8653 y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount 8654 8655 } else if (unit == "line") { 8656 y = dir > 0 ? pos.bottom + 3 : pos.top - 3 8657 } 8658 var target 8659 for (;;) { 8660 target = coordsChar(cm, x, y) 8661 if (!target.outside) { break } 8662 if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } 8663 y += dir * 5 8664 } 8665 return target 8666} 8667 8668// CONTENTEDITABLE INPUT STYLE 8669 8670var ContentEditableInput = function(cm) { 8671 this.cm = cm 8672 this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null 8673 this.polling = new Delayed() 8674 this.composing = null 8675 this.gracePeriod = false 8676 this.readDOMTimeout = null 8677}; 8678 8679ContentEditableInput.prototype.init = function (display) { 8680 var this$1 = this; 8681 8682 var input = this, cm = input.cm 8683 var div = input.div = display.lineDiv 8684 disableBrowserMagic(div, cm.options.spellcheck) 8685 8686 on(div, "paste", function (e) { 8687 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } 8688 // IE doesn't fire input events, so we schedule a read for the pasted content in this way 8689 if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20) } 8690 }) 8691 8692 on(div, "compositionstart", function (e) { 8693 this$1.composing = {data: e.data, done: false} 8694 }) 8695 on(div, "compositionupdate", function (e) { 8696 if (!this$1.composing) { this$1.composing = {data: e.data, done: false} } 8697 }) 8698 on(div, "compositionend", function (e) { 8699 if (this$1.composing) { 8700 if (e.data != this$1.composing.data) { this$1.readFromDOMSoon() } 8701 this$1.composing.done = true 8702 } 8703 }) 8704 8705 on(div, "touchstart", function () { return input.forceCompositionEnd(); }) 8706 8707 on(div, "input", function () { 8708 if (!this$1.composing) { this$1.readFromDOMSoon() } 8709 }) 8710 8711 function onCopyCut(e) { 8712 if (signalDOMEvent(cm, e)) { return } 8713 if (cm.somethingSelected()) { 8714 setLastCopied({lineWise: false, text: cm.getSelections()}) 8715 if (e.type == "cut") { cm.replaceSelection("", null, "cut") } 8716 } else if (!cm.options.lineWiseCopyCut) { 8717 return 8718 } else { 8719 var ranges = copyableRanges(cm) 8720 setLastCopied({lineWise: true, text: ranges.text}) 8721 if (e.type == "cut") { 8722 cm.operation(function () { 8723 cm.setSelections(ranges.ranges, 0, sel_dontScroll) 8724 cm.replaceSelection("", null, "cut") 8725 }) 8726 } 8727 } 8728 if (e.clipboardData) { 8729 e.clipboardData.clearData()
8730 var content = lastCopied.text.join("\n") 8731 // iOS exposes the clipboard API, but seems to discard content inserted into it 8732 e.clipboardData.setData("Text", content) 8733 if (e.clipboardData.getData("Text") == content) { 8734 e.preventDefault() 8735 return 8736 } 8737 } 8738 // Old-fashioned briefly-focus-a-textarea hack 8739 var kludge = hiddenTextarea(), te = kludge.firstChild 8740 cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild) 8741 te.value = lastCopied.text.join("\n") 8742 var hadFocus = document.activeElement 8743 selectInput(te) 8744 setTimeout(function () { 8745 cm.display.lineSpace.removeChild(kludge) 8746 hadFocus.focus() 8747 if (hadFocus == div) { input.showPrimarySelection() } 8748 }, 50) 8749 } 8750 on(div, "copy", onCopyCut) 8751 on(div, "cut", onCopyCut) 8752}; 8753 8754ContentEditableInput.prototype.prepareSelection = function () { 8755 var result = prepareSelection(this.cm, false) 8756 result.focus = this.cm.state.focused 8757 return result 8758}; 8759 8760ContentEditableInput.prototype.showSelection = function (info, takeFocus) { 8761 if (!info || !this.cm.display.view.length) { return } 8762 if (info.focus || takeFocus) { this.showPrimarySelection() } 8763 this.showMultipleSelections(info) 8764}; 8765 8766ContentEditableInput.prototype.getSelection = function () { 8767 return this.cm.display.wrapper.ownerDocument.getSelection() 8768}; 8769 8770ContentEditableInput.prototype.showPrimarySelection = function () { 8771 var sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary() 8772 var from = prim.from(), to = prim.to() 8773 8774 if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { 8775 sel.removeAllRanges() 8776 return 8777 } 8778 8779 var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) 8780 var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset) 8781 if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && 8782 cmp(minPos(curAnchor, curFocus), from) == 0 && 8783 cmp(maxPos(curAnchor, curFocus), to) == 0) 8784 { return } 8785 8786 var view = cm.display.view 8787 var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || 8788 {node: view[0].measure.map[2], offset: 0} 8789 var end = to.line < cm.display.viewTo && posToDOM(cm, to) 8790 if (!end) { 8791 var measure = view[view.length - 1].measure 8792 var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map 8793 end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]} 8794 } 8795 8796 if (!start || !end) { 8797 sel.removeAllRanges() 8798 return 8799 } 8800 8801 var old = sel.rangeCount && sel.getRangeAt(0), rng 8802 try { rng = range(start.node, start.offset, end.offset, end.node) } 8803 catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible 8804 if (rng) { 8805 if (!gecko && cm.state.focused) { 8806 sel.collapse(start.node, start.offset) 8807 if (!rng.collapsed) { 8808 sel.removeAllRanges() 8809 sel.addRange(rng) 8810 } 8811 } else { 8812 sel.removeAllRanges() 8813 sel.addRange(rng) 8814 } 8815 if (old && sel.anchorNode == null) { sel.addRange(old) } 8816 else if (gecko) { this.startGracePeriod() } 8817 } 8818 this.rememberSelection() 8819}; 8820 8821ContentEditableInput.prototype.startGracePeriod = function () { 8822 var this$1 = this; 8823 8824 clearTimeout(this.gracePeriod) 8825 this.gracePeriod = setTimeout(function () { 8826 this$1.gracePeriod = false 8827 if (this$1.selectionChanged()) 8828 { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }) } 8829 }, 20) 8830}; 8831 8832ContentEditableInput.prototype.showMultipleSelections = function (info) { 8833 removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors) 8834 removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection) 8835}; 8836 8837ContentEditableInput.prototype.rememberSelection = function () { 8838 var sel = this.getSelection() 8839 this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset 8840 this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset 8841}; 8842 8843ContentEditableInput.prototype.selectionInEditor = function () { 8844 var sel = this.getSelection() 8845 if (!sel.rangeCount) { return false } 8846 var node = sel.getRangeAt(0).commonAncestorContainer 8847 return contains(this.div, node) 8848}; 8849
8850ContentEditableInput.prototype.focus = function () { 8851 if (this.cm.options.readOnly != "nocursor") { 8852 if (!this.selectionInEditor()) 8853 { this.showSelection(this.prepareSelection(), true) } 8854 this.div.focus() 8855 } 8856}; 8857ContentEditableInput.prototype.blur = function () { this.div.blur() }; 8858ContentEditableInput.prototype.getField = function () { return this.div }; 8859 8860ContentEditableInput.prototype.supportsTouch = function () { return true }; 8861 8862ContentEditableInput.prototype.receivedFocus = function () { 8863 var input = this 8864 if (this.selectionInEditor()) 8865 { this.pollSelection() } 8866 else 8867 { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }) } 8868 8869 function poll() { 8870 if (input.cm.state.focused) { 8871 input.pollSelection() 8872 input.polling.set(input.cm.options.pollInterval, poll) 8873 } 8874 } 8875 this.polling.set(this.cm.options.pollInterval, poll) 8876}; 8877 8878ContentEditableInput.prototype.selectionChanged = function () { 8879 var sel = this.getSelection() 8880 return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || 8881 sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset 8882}; 8883 8884ContentEditableInput.prototype.pollSelection = function () { 8885 if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return } 8886 var sel = this.getSelection(), cm = this.cm 8887 // On Android Chrome (version 56, at least), backspacing into an 8888 // uneditable block element will put the cursor in that element, 8889 // and then, because it's not editable, hide the virtual keyboard. 8890 // Because Android doesn't allow us to actually detect backspace 8891 // presses in a sane way, this code checks for when that happens 8892 // and simulates a backspace press in this case. 8893 if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anchorNode)) { 8894 this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}) 8895 this.blur() 8896 this.focus() 8897 return 8898 } 8899 if (this.composing) { return } 8900 this.rememberSelection() 8901 var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) 8902 var head = domToPos(cm, sel.focusNode, sel.focusOffset) 8903 if (anchor && head) { runInOp(cm, function () { 8904 setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll) 8905 if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true } 8906 }) } 8907}; 8908 8909ContentEditableInput.prototype.pollContent = function () { 8910 if (this.readDOMTimeout != null) { 8911 clearTimeout(this.readDOMTimeout) 8912 this.readDOMTimeout = null 8913 } 8914 8915 var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary() 8916 var from = sel.from(), to = sel.to() 8917 if (from.ch == 0 && from.line > cm.firstLine()) 8918 { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) } 8919 if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) 8920 { to = Pos(to.line + 1, 0) } 8921 if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false } 8922 8923 var fromIndex, fromLine, fromNode 8924 if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { 8925 fromLine = lineNo(display.view[0].line) 8926 fromNode = display.view[0].node 8927 } else { 8928 fromLine = lineNo(display.view[fromIndex].line) 8929 fromNode = display.view[fromIndex - 1].node.nextSibling 8930 } 8931 var toIndex = findViewIndex(cm, to.line) 8932 var toLine, toNode 8933 if (toIndex == display.view.length - 1) { 8934 toLine = display.viewTo - 1 8935 toNode = display.lineDiv.lastChild 8936 } else { 8937 toLine = lineNo(display.view[toIndex + 1].line) - 1 8938 toNode = display.view[toIndex + 1].node.previousSibling 8939 } 8940 8941 if (!fromNode) { return false } 8942 var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)) 8943 var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)) 8944 while (newText.length > 1 && oldText.length > 1) { 8945 if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- } 8946 else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ } 8947 else { break } 8948 } 8949 8950 var cutFront = 0, cutEnd = 0 8951 var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length)
8952 while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) 8953 { ++cutFront } 8954 var newBot = lst(newText), oldBot = lst(oldText) 8955 var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), 8956 oldBot.length - (oldText.length == 1 ? cutFront : 0)) 8957 while (cutEnd < maxCutEnd && 8958 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) 8959 { ++cutEnd } 8960 // Try to move start of change to start of selection if ambiguous 8961 if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { 8962 while (cutFront && cutFront > from.ch && 8963 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { 8964 cutFront-- 8965 cutEnd++ 8966 } 8967 } 8968 8969 newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "") 8970 newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "") 8971 8972 var chFrom = Pos(fromLine, cutFront) 8973 var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0) 8974 if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { 8975 replaceRange(cm.doc, newText, chFrom, chTo, "+input") 8976 return true 8977 } 8978}; 8979 8980ContentEditableInput.prototype.ensurePolled = function () { 8981 this.forceCompositionEnd() 8982}; 8983ContentEditableInput.prototype.reset = function () { 8984 this.forceCompositionEnd() 8985}; 8986ContentEditableInput.prototype.forceCompositionEnd = function () { 8987 if (!this.composing) { return } 8988 clearTimeout(this.readDOMTimeout) 8989 this.composing = null 8990 this.updateFromDOM() 8991 this.div.blur() 8992 this.div.focus() 8993}; 8994ContentEditableInput.prototype.readFromDOMSoon = function () { 8995 var this$1 = this; 8996 8997 if (this.readDOMTimeout != null) { return } 8998 this.readDOMTimeout = setTimeout(function () { 8999 this$1.readDOMTimeout = null 9000 if (this$1.composing) { 9001 if (this$1.composing.done) { this$1.composing = null } 9002 else { return } 9003 } 9004 this$1.updateFromDOM() 9005 }, 80) 9006}; 9007 9008ContentEditableInput.prototype.updateFromDOM = function () { 9009 var this$1 = this; 9010 9011 if (this.cm.isReadOnly() || !this.pollContent()) 9012 { runInOp(this.cm, function () { return regChange(this$1.cm); }) } 9013}; 9014 9015ContentEditableInput.prototype.setUneditable = function (node) { 9016 node.contentEditable = "false" 9017}; 9018 9019ContentEditableInput.prototype.onKeyPress = function (e) { 9020 if (e.charCode == 0 || this.composing) { return } 9021 e.preventDefault() 9022 if (!this.cm.isReadOnly()) 9023 { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0) } 9024}; 9025 9026ContentEditableInput.prototype.readOnlyChanged = function (val) { 9027 this.div.contentEditable = String(val != "nocursor") 9028}; 9029 9030ContentEditableInput.prototype.onContextMenu = function () {}; 9031ContentEditableInput.prototype.resetPosition = function () {}; 9032 9033ContentEditableInput.prototype.needsContentAttribute = true 9034 9035function posToDOM(cm, pos) { 9036 var view = findViewForLine(cm, pos.line) 9037 if (!view || view.hidden) { return null } 9038 var line = getLine(cm.doc, pos.line) 9039 var info = mapFromLineView(view, line, pos.line) 9040 9041 var order = getOrder(line, cm.doc.direction), side = "left" 9042 if (order) { 9043 var partPos = getBidiPartAt(order, pos.ch) 9044 side = partPos % 2 ? "right" : "left" 9045 } 9046 var result = nodeAndOffsetInLineMap(info.map, pos.ch, side) 9047 result.offset = result.collapse == "right" ? result.end : result.start 9048 return result 9049} 9050 9051function isInGutter(node) { 9052 for (var scan = node; scan; scan = scan.parentNode) 9053 { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } } 9054 return false 9055} 9056 9057function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } 9058 9059function domTextBetween(cm, from, to, fromLine, toLine) { 9060 var text = "", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false 9061 function recognizeMarker(id) { return function (marker) { return marker.id == id; } } 9062 function close() { 9063 if (closing) { 9064 text += lineSep 9065 if (extraLinebreak) { text += lineSep } 9066 closing = extraLinebreak = false 9067 } 9068 } 9069 function addText(str) { 9070 if (str) { 9071 close() 9072 text += str 9073 } 9074 } 9075 function walk(node) { 9076 if (node.nodeType == 1) { 9077 var cmText = node.getAttribute("cm-text") 9078 if (cmText) { 9079 addText(cmText) 9080 return 9081 } 9082 var markerID = node.getAttribute("cm-marker"), range 9083 if (markerID) { 9084 var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)) 9085 if (found.length && (range = found[0].find(0))) 9086 { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)) } 9087 return 9088 } 9089 if (node.getAttribute("contenteditable") == "false") { return } 9090 var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName) 9091 if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return } 9092 9093 if (isBlock) { close() } 9094 for (var i = 0; i < node.childNodes.length; i++) 9095 { walk(node.childNodes[i]) } 9096 9097 if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true } 9098 if (isBlock) { closing = true } 9099 } else if (node.nodeType == 3) { 9100 addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " ")) 9101 } 9102 } 9103 for (;;) { 9104 walk(from) 9105 if (from == to) { break } 9106 from = from.nextSibling 9107 extraLinebreak = false 9108 } 9109 return text 9110} 9111 9112function domToPos(cm, node, offset) { 9113 var lineNode
9114 if (node == cm.display.lineDiv) { 9115 lineNode = cm.display.lineDiv.childNodes[offset] 9116 if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) } 9117 node = null; offset = 0 9118 } else { 9119 for (lineNode = node;; lineNode = lineNode.parentNode) { 9120 if (!lineNode || lineNode == cm.display.lineDiv) { return null } 9121 if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break } 9122 } 9123 } 9124 for (var i = 0; i < cm.display.view.length; i++) { 9125 var lineView = cm.display.view[i] 9126 if (lineView.node == lineNode) 9127 { return locateNodeInLineView(lineView, node, offset) } 9128 } 9129} 9130 9131function locateNodeInLineView(lineView, node, offset) { 9132 var wrapper = lineView.text.firstChild, bad = false 9133 if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) } 9134 if (node == wrapper) { 9135 bad = true 9136 node = wrapper.childNodes[offset] 9137 offset = 0 9138 if (!node) { 9139 var line = lineView.rest ? lst(lineView.rest) : lineView.line 9140 return badPos(Pos(lineNo(line), line.text.length), bad) 9141 } 9142 } 9143 9144 var textNode = node.nodeType == 3 ? node : null, topNode = node 9145 if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { 9146 textNode = node.firstChild 9147 if (offset) { offset = textNode.nodeValue.length } 9148 } 9149 while (topNode.parentNode != wrapper) { topNode = topNode.parentNode } 9150 var measure = lineView.measure, maps = measure.maps 9151 9152 function find(textNode, topNode, offset) { 9153 for (var i = -1; i < (maps ? maps.length : 0); i++) { 9154 var map = i < 0 ? measure.map : maps[i] 9155 for (var j = 0; j < map.length; j += 3) { 9156 var curNode = map[j + 2] 9157 if (curNode == textNode || curNode == topNode) { 9158 var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]) 9159 var ch = map[j] + offset 9160 if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0)] } 9161 return Pos(line, ch) 9162 } 9163 } 9164 } 9165 } 9166 var found = find(textNode, topNode, offset) 9167 if (found) { return badPos(found, bad) } 9168 9169 // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems 9170 for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { 9171 found = find(after, after.firstChild, 0) 9172 if (found) 9173 { return badPos(Pos(found.line, found.ch - dist), bad) } 9174 else 9175 { dist += after.textContent.length } 9176 } 9177 for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) { 9178 found = find(before, before.firstChild, -1) 9179 if (found) 9180 { return badPos(Pos(found.line, found.ch + dist$1), bad) } 9181 else 9182 { dist$1 += before.textContent.length } 9183 } 9184} 9185 9186// TEXTAREA INPUT STYLE 9187 9188var TextareaInput = function(cm) { 9189 this.cm = cm 9190 // See input.poll and input.reset 9191 this.prevInput = "" 9192 9193 // Flag that indicates whether we expect input to appear real soon 9194 // now (after some event like 'keypress' or 'input') and are 9195 // polling intensively. 9196 this.pollingFast = false 9197 // Self-resetting timeout for the poller 9198 this.polling = new Delayed() 9199 // Used to work around IE issue with selection being forgotten when focus moves away from textarea 9200 this.hasSelection = false 9201 this.composing = null 9202}; 9203 9204TextareaInput.prototype.init = function (display) { 9205 var this$1 = this; 9206 9207 var input = this, cm = this.cm 9208 this.createField(display) 9209 var te = this.textarea 9210 9211 display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild) 9212 9213 // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) 9214 if (ios) { te.style.width = "0px" } 9215 9216 on(te, "input", function () { 9217 if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null } 9218 input.poll() 9219 }) 9220 9221 on(te, "paste", function (e) { 9222 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } 9223 9224 cm.state.pasteIncoming = true 9225 input.fastPoll() 9226 }) 9227 9228 function prepareCopyCut(e) { 9229 if (signalDOMEvent(cm, e)) { return } 9230 if (cm.somethingSelected()) { 9231 setLastCopied({lineWise: false, text: cm.getSelections()}) 9232 } else if (!cm.options.lineWiseCopyCut) { 9233 return 9234 } else { 9235 var ranges = copyableRanges(cm) 9236 setLastCopied({lineWise: true, text: ranges.text}) 9237 if (e.type == "cut") { 9238 cm.setSelections(ranges.ranges, null, sel_dontScroll) 9239 } else { 9240 input.prevInput = "" 9241 te.value = ranges.text.join("\n") 9242 selectInput(te) 9243 } 9244 } 9245 if (e.type == "cut") { cm.state.cutIncoming = true } 9246 } 9247 on(te, "cut", prepareCopyCut) 9248 on(te, "copy", prepareCopyCut) 9249 9250 on(display.scroller, "paste", function (e) { 9251 if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } 9252 cm.state.pasteIncoming = true 9253 input.focus() 9254 }) 9255 9256 // Prevent normal selection in the editor (we handle our own) 9257 on(display.lineSpace, "selectstart", function (e) { 9258 if (!eventInWidget(display, e)) { e_preventDefault(e) } 9259 }) 9260 9261 on(te, "compositionstart", function () { 9262 var start = cm.getCursor("from") 9263 if (input.composing) { input.composing.range.clear() } 9264 input.composing = { 9265 start: start, 9266 range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) 9267 } 9268 }) 9269 on(te, "compositionend", function () { 9270 if (input.composing) { 9271 input.poll() 9272 input.composing.range.clear() 9273 input.composing = null 9274 } 9275 }) 9276}; 9277 9278TextareaInput.prototype.createField = function (_display) { 9279 // Wraps and hides input textarea 9280 this.wrapper = hiddenTextarea() 9281 // The semihidden textarea that is focused when the editor is 9282 // focused, and receives input. 9283 this.textarea = this.wrapper.firstChild 9284}; 9285 9286TextareaInput.prototype.prepareSelection = function () { 9287 // Redraw the selection and/or cursor 9288 var cm = this.cm, display = cm.display, doc = cm.doc 9289 var result = prepareSelection(cm) 9290 9291 // Move the hidden textarea near the cursor to prevent scrolling artifacts 9292 if (cm.options.moveInputWithCursor) { 9293 var headPos = cursorCoords(cm, doc.sel.primary().head, "div") 9294 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect() 9295 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, 9296 headPos.top + lineOff.top - wrapOff.top)) 9297 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, 9298 headPos.left + lineOff.left - wrapOff.left)) 9299 } 9300 9301 return result 9302}; 9303 9304TextareaInput.prototype.showSelection = function (drawn) { 9305 var cm = this.cm, display = cm.display 9306 removeChildrenAndAdd(display.cursorDiv, drawn.cursors) 9307 removeChildrenAndAdd(display.selectionDiv, drawn.selection) 9308 if (drawn.teTop != null) { 9309 this.wrapper.style.top = drawn.teTop + "px" 9310 this.wrapper.style.left = drawn.teLeft + "px" 9311 } 9312}; 9313 9314// Reset the input to correspond to the selection (or to be empty, 9315// when not typing and nothing is selected) 9316TextareaInput.prototype.reset = function (typing) { 9317 if (this.contextMenuPending || this.composing) { return } 9318 var cm = this.cm 9319 if (cm.somethingSelected()) { 9320 this.prevInput = "" 9321 var content = cm.getSelection() 9322 this.textarea.value = content 9323 if (cm.state.focused) { selectInput(this.textarea) } 9324 if (ie && ie_version >= 9) { this.hasSelection = content } 9325 } else if (!typing) { 9326 this.prevInput = this.textarea.value = "" 9327 if (ie && ie_version >= 9) { this.hasSelection = null } 9328 } 9329}; 9330 9331TextareaInput.prototype.getField = function () { return this.textarea }; 9332 9333TextareaInput.prototype.supportsTouch = function () { return false }; 9334
9335TextareaInput.prototype.focus = function () { 9336 if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { 9337 try { this.textarea.focus() } 9338 catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM 9339 } 9340}; 9341 9342TextareaInput.prototype.blur = function () { this.textarea.blur() }; 9343 9344TextareaInput.prototype.resetPosition = function () { 9345 this.wrapper.style.top = this.wrapper.style.left = 0 9346}; 9347 9348TextareaInput.prototype.receivedFocus = function () { this.slowPoll() }; 9349 9350// Poll for input changes, using the normal rate of polling. This 9351// runs as long as the editor is focused. 9352TextareaInput.prototype.slowPoll = function () { 9353 var this$1 = this; 9354 9355 if (this.pollingFast) { return } 9356 this.polling.set(this.cm.options.pollInterval, function () { 9357 this$1.poll() 9358 if (this$1.cm.state.focused) { this$1.slowPoll() } 9359 }) 9360}; 9361 9362// When an event has just come in that is likely to add or change 9363// something in the input textarea, we poll faster, to ensure that 9364// the change appears on the screen quickly. 9365TextareaInput.prototype.fastPoll = function () { 9366 var missed = false, input = this 9367 input.pollingFast = true 9368 function p() { 9369 var changed = input.poll() 9370 if (!changed && !missed) {missed = true; input.polling.set(60, p)} 9371 else {input.pollingFast = false; input.slowPoll()} 9372 } 9373 input.polling.set(20, p) 9374}; 9375 9376// Read input from the textarea, and update the document to match. 9377// When something is selected, it is present in the textarea, and 9378// selected (unless it is huge, in which case a placeholder is 9379// used). When nothing is selected, the cursor sits after previously 9380// seen text (can be empty), which is stored in prevInput (we must 9381// not reset the textarea when typing, because that breaks IME). 9382TextareaInput.prototype.poll = function () { 9383 var this$1 = this; 9384 9385 var cm = this.cm, input = this.textarea, prevInput = this.prevInput 9386 // Since this is called a *lot*, try to bail out as cheaply as 9387 // possible when it is clear that nothing happened. hasSelection 9388 // will be the case when there is a lot of text in the textarea, 9389 // in which case reading its value would be expensive. 9390 if (this.contextMenuPending || !cm.state.focused || 9391 (hasSelection(input) && !prevInput && !this.composing) || 9392 cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) 9393 { return false } 9394 9395 var text = input.value 9396 // If nothing changed, bail. 9397 if (text == prevInput && !cm.somethingSelected()) { return false } 9398 // Work around nonsensical selection resetting in IE9/10, and 9399 // inexplicable appearance of private area unicode characters on 9400 // some key combos in Mac (#2689). 9401 if (ie && ie_version >= 9 && this.hasSelection === text || 9402 mac && /[\uf700-\uf7ff]/.test(text)) { 9403 cm.display.input.reset() 9404 return false 9405 } 9406 9407 if (cm.doc.sel == cm.display.selForContextMenu) { 9408 var first = text.charCodeAt(0) 9409 if (first == 0x200b && !prevInput) { prevInput = "\u200b" } 9410 if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } 9411 } 9412 // Find the part of the input that is actually new 9413 var same = 0, l = Math.min(prevInput.length, text.length) 9414 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same } 9415 9416 runInOp(cm, function () { 9417 applyTextInput(cm, text.slice(same), prevInput.length - same, 9418 null, this$1.composing ? "*compose" : null) 9419 9420 // Don't leave long text in the textarea, since it makes further polling slow 9421 if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = "" } 9422 else { this$1.prevInput = text } 9423 9424 if (this$1.composing) { 9425 this$1.composing.range.clear() 9426 this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"), 9427 {className: "CodeMirror-composing"}) 9428 } 9429 }) 9430 return true 9431}; 9432 9433TextareaInput.prototype.ensurePolled = function () { 9434 if (this.pollingFast && this.poll()) { this.pollingFast = false } 9435}; 9436 9437TextareaInput.prototype.onKeyPress = function () { 9438 if (ie && ie_version >= 9) { this.hasSelection = null } 9439 this.fastPoll() 9440}; 9441 9442TextareaInput.prototype.onContextMenu = function (e) { 9443 var input = this, cm = input.cm, display = cm.display, te = input.textarea 9444 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop 9445 if (!pos || presto) { return } // Opera is difficult. 9446 9447 // Reset the current text selection only if the click is done outside of the selection 9448 // and 'resetSelectionOnContextMenu' option is true. 9449 var reset = cm.options.resetSelectionOnContextMenu 9450 if (reset && cm.doc.sel.contains(pos) == -1) 9451 { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) } 9452 9453 var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText 9454 input.wrapper.style.cssText = "position: absolute" 9455 var wrapperBox = input.wrapper.getBoundingClientRect() 9456 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);" 9457 var oldScrollY 9458 if (webkit) { oldScrollY = window.scrollY } // Work around Chrome issue (#2712) 9459 display.input.focus() 9460 if (webkit) { window.scrollTo(null, oldScrollY) } 9461 display.input.reset() 9462 // Adds "Select all" to context menu in FF 9463 if (!cm.somethingSelected()) { te.value = input.prevInput = " " } 9464 input.contextMenuPending = true 9465 display.selForContextMenu = cm.doc.sel 9466 clearTimeout(display.detectingSelectAll) 9467 9468 // Select-all will be greyed out if there's nothing to select, so 9469 // this adds a zero-width space so that we can later check whether 9470 // it got selected. 9471 function prepareSelectAllHack() { 9472 if (te.selectionStart != null) { 9473 var selected = cm.somethingSelected() 9474 var extval = "\u200b" + (selected ? te.value : "") 9475 te.value = "\u21da" // Used to catch context-menu undo 9476 te.value = extval 9477 input.prevInput = selected ? "" : "\u200b" 9478 te.selectionStart = 1; te.selectionEnd = extval.length 9479 // Re-set this, in case some other handler touched the 9480 // selection in the meantime. 9481 display.selForContextMenu = cm.doc.sel 9482 } 9483 } 9484 function rehide() { 9485 input.contextMenuPending = false 9486 input.wrapper.style.cssText = oldWrapperCSS 9487 te.style.cssText = oldCSS 9488 if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos) } 9489 9490 // Try to detect the user choosing select-all 9491 if (te.selectionStart != null) { 9492 if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack() } 9493 var i = 0, poll = function () { 9494 if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && 9495 te.selectionEnd > 0 && input.prevInput == "\u200b") { 9496 operation(cm, selectAll)(cm) 9497 } else if (i++ < 10) { 9498 display.detectingSelectAll = setTimeout(poll, 500) 9499 } else { 9500 display.selForContextMenu = null 9501 display.input.reset() 9502 } 9503 } 9504 display.detectingSelectAll = setTimeout(poll, 200) 9505 } 9506 } 9507 9508 if (ie && ie_version >= 9) { prepareSelectAllHack() } 9509 if (captureRightClick) { 9510 e_stop(e) 9511 var mouseup = function () { 9512 off(window, "mouseup", mouseup) 9513 setTimeout(rehide, 20) 9514 } 9515 on(window, "mouseup", mouseup) 9516 } else { 9517 setTimeout(rehide, 50) 9518 } 9519}; 9520 9521TextareaInput.prototype.readOnlyChanged = function (val) { 9522 if (!val) { this.reset() } 9523 this.textarea.disabled = val == "nocursor" 9524}; 9525 9526TextareaInput.prototype.setUneditable = function () {}; 9527 9528TextareaInput.prototype.needsContentAttribute = false 9529 9530function fromTextArea(textarea, options) { 9531 options = options ? copyObj(options) : {} 9532 options.value = textarea.value 9533 if (!options.tabindex && textarea.tabIndex) 9534 { options.tabindex = textarea.tabIndex } 9535 if (!options.placeholder && textarea.placeholder) 9536 { options.placeholder = textarea.placeholder } 9537 // Set autofocus to true if this textarea is focused, or if it has 9538 // autofocus and no other element is focused. 9539 if (options.autofocus == null) { 9540 var hasFocus = activeElt() 9541 options.autofocus = hasFocus == textarea || 9542 textarea.getAttribute("autofocus") != null && hasFocus == document.body 9543 } 9544 9545 function save() {textarea.value = cm.getValue()} 9546 9547 var realSubmit 9548 if (textarea.form) { 9549 on(textarea.form, "submit", save) 9550 // Deplorable hack to make the submit method do the right thing. 9551 if (!options.leaveSubmitMethodAlone) { 9552 var form = textarea.form 9553 realSubmit = form.submit
9554 try { 9555 var wrappedSubmit = form.submit = function () { 9556 save() 9557 form.submit = realSubmit 9558 form.submit() 9559 form.submit = wrappedSubmit 9560 } 9561 } catch(e) {} 9562 } 9563 } 9564 9565 options.finishInit = function (cm) { 9566 cm.save = save 9567 cm.getTextArea = function () { return textarea; } 9568 cm.toTextArea = function () { 9569 cm.toTextArea = isNaN // Prevent this from being ran twice 9570 save() 9571 textarea.parentNode.removeChild(cm.getWrapperElement()) 9572 textarea.style.display = "" 9573 if (textarea.form) { 9574 off(textarea.form, "submit", save) 9575 if (typeof textarea.form.submit == "function") 9576 { textarea.form.submit = realSubmit } 9577 } 9578 } 9579 } 9580 9581 textarea.style.display = "none" 9582 var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); }, 9583 options) 9584 return cm 9585} 9586 9587function addLegacyProps(CodeMirror) { 9588 CodeMirror.off = off 9589 CodeMirror.on = on 9590 CodeMirror.wheelEventPixels = wheelEventPixels 9591 CodeMirror.Doc = Doc 9592 CodeMirror.splitLines = splitLinesAuto 9593 CodeMirror.countColumn = countColumn 9594 CodeMirror.findColumn = findColumn 9595 CodeMirror.isWordChar = isWordCharBasic 9596 CodeMirror.Pass = Pass 9597 CodeMirror.signal = signal 9598 CodeMirror.Line = Line 9599 CodeMirror.changeEnd = changeEnd 9600 CodeMirror.scrollbarModel = scrollbarModel 9601 CodeMirror.Pos = Pos 9602 CodeMirror.cmpPos = cmp 9603 CodeMirror.modes = modes 9604 CodeMirror.mimeModes = mimeModes 9605 CodeMirror.resolveMode = resolveMode 9606 CodeMirror.getMode = getMode 9607 CodeMirror.modeExtensions = modeExtensions 9608 CodeMirror.extendMode = extendMode 9609 CodeMirror.copyState = copyState 9610 CodeMirror.startState = startState 9611 CodeMirror.innerMode = innerMode 9612 CodeMirror.commands = commands 9613 CodeMirror.keyMap = keyMap 9614 CodeMirror.keyName = keyName 9615 CodeMirror.isModifierKey = isModifierKey 9616 CodeMirror.lookupKey = lookupKey 9617 CodeMirror.normalizeKeyMap = normalizeKeyMap 9618 CodeMirror.StringStream = StringStream 9619 CodeMirror.SharedTextMarker = SharedTextMarker 9620 CodeMirror.TextMarker = TextMarker 9621 CodeMirror.LineWidget = LineWidget 9622 CodeMirror.e_preventDefault = e_preventDefault 9623 CodeMirror.e_stopPropagation = e_stopPropagation 9624 CodeMirror.e_stop = e_stop 9625 CodeMirror.addClass = addClass 9626 CodeMirror.contains = contains 9627 CodeMirror.rmClass = rmClass 9628 CodeMirror.keyNames = keyNames 9629} 9630 9631// EDITOR CONSTRUCTOR 9632 9633defineOptions(CodeMirror) 9634 9635addEditorMethods(CodeMirror) 9636 9637// Set up methods on CodeMirror's prototype to redirect to the editor's document. 9638var dontDelegate = "iter insert remove copy getEditor constructor".split(" ") 9639for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) 9640 { CodeMirror.prototype[prop] = (function(method) { 9641 return function() {return method.apply(this.doc, arguments)} 9642 })(Doc.prototype[prop]) } } 9643 9644eventMixin(Doc) 9645 9646// INPUT HANDLING 9647 9648CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput} 9649 9650// MODE DEFINITION AND QUERYING 9651 9652// Extra arguments are stored as the mode's dependencies, which is 9653// used by (legacy) mechanisms like loadmode.js to automatically 9654// load a mode. (Preferred mechanism is the require/define calls.) 9655CodeMirror.defineMode = function(name/*, mode, â¦*/) { 9656 if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name } 9657 defineMode.apply(this, arguments) 9658} 9659 9660CodeMirror.defineMIME = defineMIME 9661 9662// Minimal default mode. 9663CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); }) 9664CodeMirror.defineMIME("text/plain", "null") 9665 9666// EXTENSIONS 9667 9668CodeMirror.defineExtension = function (name, func) { 9669 CodeMirror.prototype[name] = func 9670} 9671CodeMirror.defineDocExtension = function (name, func) { 9672 Doc.prototype[name] = func 9673} 9674 9675CodeMirror.fromTextArea = fromTextArea 9676 9677addLegacyProps(CodeMirror) 9678 9679CodeMirror.version = "5.39.0" 9680 9681return CodeMirror; 9682 9683})));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.