| OLD | NEW |
| 1 // CodeMirror, copyright (c) by Marijn Haverbeke and others | 1 // CodeMirror, copyright (c) by Marijn Haverbeke and others |
| 2 // Distributed under an MIT license: http://codemirror.net/LICENSE | 2 // Distributed under an MIT license: http://codemirror.net/LICENSE |
| 3 | 3 |
| 4 // This is CodeMirror (http://codemirror.net), a code editor | 4 // This is CodeMirror (http://codemirror.net), a code editor |
| 5 // implemented in JavaScript on top of the browser's DOM. | 5 // implemented in JavaScript on top of the browser's DOM. |
| 6 // | 6 // |
| 7 // You can find some technical background for some of the code below | 7 // You can find some technical background for some of the code below |
| 8 // at http://marijnhaverbeke.nl/blog/#cm-internals . | 8 // at http://marijnhaverbeke.nl/blog/#cm-internals . |
| 9 | 9 |
| 10 (function(mod) { | 10 (function (global, factory) { |
| 11 if (typeof exports == "object" && typeof module == "object") // CommonJS | 11 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports
= factory() : |
| 12 module.exports = mod(); | 12 typeof define === 'function' && define.amd ? define(factory) : |
| 13 else if (typeof define == "function" && define.amd) // AMD | 13 (global.CodeMirror = factory()); |
| 14 return define([], mod); | 14 }(this, (function () { 'use strict'; |
| 15 else // Plain browser env | 15 |
| 16 (this || window).CodeMirror = mod(); | 16 // Kludges for bugs and behavior differences that can't be feature |
| 17 })(function() { | 17 // detected are enabled based on userAgent etc sniffing. |
| 18 "use strict"; | 18 var userAgent = navigator.userAgent |
| 19 | 19 var platform = navigator.platform |
| 20 // BROWSER SNIFFING | 20 |
| 21 | 21 var gecko = /gecko\/\d/i.test(userAgent) |
| 22 // Kludges for bugs and behavior differences that can't be feature | 22 var ie_upto10 = /MSIE \d/.test(userAgent) |
| 23 // detected are enabled based on userAgent etc sniffing. | 23 var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent) |
| 24 var userAgent = navigator.userAgent; | 24 var edge = /Edge\/(\d+)/.exec(userAgent) |
| 25 var platform = navigator.platform; | 25 var ie = ie_upto10 || ie_11up || edge |
| 26 | 26 var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11
up)[1]) |
| 27 var gecko = /gecko\/\d/i.test(userAgent); | 27 var webkit = !edge && /WebKit\//.test(userAgent) |
| 28 var ie_upto10 = /MSIE \d/.test(userAgent); | 28 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent) |
| 29 var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); | 29 var chrome = !edge && /Chrome\//.test(userAgent) |
| 30 var ie = ie_upto10 || ie_11up; | 30 var presto = /Opera\//.test(userAgent) |
| 31 var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); | 31 var safari = /Apple Computer/.test(navigator.vendor) |
| 32 var webkit = /WebKit\//.test(userAgent); | 32 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent) |
| 33 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); | 33 var phantom = /PhantomJS/.test(userAgent) |
| 34 var chrome = /Chrome\//.test(userAgent); | 34 |
| 35 var presto = /Opera\//.test(userAgent); | 35 var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent
) |
| 36 var safari = /Apple Computer/.test(navigator.vendor); | 36 var android = /Android/.test(userAgent) |
| 37 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); | 37 // This is woefully incomplete. Suggestions for alternative methods welcome. |
| 38 var phantom = /PhantomJS/.test(userAgent); | 38 var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/
i.test(userAgent) |
| 39 | 39 var mac = ios || /Mac/.test(platform) |
| 40 var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent); | 40 var chromeOS = /\bCrOS\b/.test(userAgent) |
| 41 // This is woefully incomplete. Suggestions for alternative methods welcome. | 41 var windows = /win/i.test(platform) |
| 42 var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i
.test(userAgent); | 42 |
| 43 var mac = ios || /Mac/.test(platform); | 43 var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/) |
| 44 var chromeOS = /\bCrOS\b/.test(userAgent); | 44 if (presto_version) { presto_version = Number(presto_version[1]) } |
| 45 var windows = /win/i.test(platform); | 45 if (presto_version && presto_version >= 15) { presto = false; webkit = true } |
| 46 | 46 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X |
| 47 var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); | 47 var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || prest
o_version < 12.11)) |
| 48 if (presto_version) presto_version = Number(presto_version[1]); | 48 var captureRightClick = gecko || (ie && ie_version >= 9) |
| 49 if (presto_version && presto_version >= 15) { presto = false; webkit = true; } | 49 |
| 50 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X | 50 function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } |
| 51 var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || pre
sto_version < 12.11)); | 51 |
| 52 var captureRightClick = gecko || (ie && ie_version >= 9); | 52 var rmClass = function(node, cls) { |
| 53 | 53 var current = node.className |
| 54 // Optimize some code when these features are not used. | 54 var match = classTest(cls).exec(current) |
| 55 var sawReadOnlySpans = false, sawCollapsedSpans = false; | 55 if (match) { |
| 56 | 56 var after = current.slice(match.index + match[0].length) |
| 57 // EDITOR CONSTRUCTOR | 57 node.className = current.slice(0, match.index) + (after ? match[1] + after :
"") |
| 58 | 58 } |
| 59 // A CodeMirror instance represents an editor. This is the object | 59 } |
| 60 // that user code is usually dealing with. | 60 |
| 61 | 61 function removeChildren(e) { |
| 62 function CodeMirror(place, options) { | 62 for (var count = e.childNodes.length; count > 0; --count) |
| 63 if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); | 63 { e.removeChild(e.firstChild) } |
| 64 | 64 return e |
| 65 this.options = options = options ? copyObj(options) : {}; | 65 } |
| 66 // Determine effective options based on given values and defaults. | 66 |
| 67 copyObj(defaults, options, false); | 67 function removeChildrenAndAdd(parent, e) { |
| 68 setGuttersForLineNumbers(options); | 68 return removeChildren(parent).appendChild(e) |
| 69 | 69 } |
| 70 var doc = options.value; | 70 |
| 71 if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.l
ineSeparator); | 71 function elt(tag, content, className, style) { |
| 72 this.doc = doc; | 72 var e = document.createElement(tag) |
| 73 | 73 if (className) { e.className = className } |
| 74 var input = new CodeMirror.inputStyles[options.inputStyle](this); | 74 if (style) { e.style.cssText = style } |
| 75 var display = this.display = new Display(place, doc, input); | 75 if (typeof content == "string") { e.appendChild(document.createTextNode(conten
t)) } |
| 76 display.wrapper.CodeMirror = this; | 76 else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(c
ontent[i]) } } |
| 77 updateGutters(this); | 77 return e |
| 78 themeChanged(this); | 78 } |
| 79 if (options.lineWrapping) | 79 // wrapper for elt, which removes the elt from the accessibility tree |
| 80 this.display.wrapper.className += " CodeMirror-wrap"; | 80 function eltP(tag, content, className, style) { |
| 81 if (options.autofocus && !mobile) display.input.focus(); | 81 var e = elt(tag, content, className, style) |
| 82 initScrollbars(this); | 82 e.setAttribute("role", "presentation") |
| 83 | 83 return e |
| 84 this.state = { | 84 } |
| 85 keyMaps: [], // stores maps added by addKeyMap | 85 |
| 86 overlays: [], // highlighting overlays, as added by addOverlay | 86 var range |
| 87 modeGen: 0, // bumped when mode/overlay changes, used to invalidate high
lighting info | 87 if (document.createRange) { range = function(node, start, end, endNode) { |
| 88 overwrite: false, | 88 var r = document.createRange() |
| 89 delayingBlurEvent: false, | 89 r.setEnd(endNode || node, end) |
| 90 focused: false, | 90 r.setStart(node, start) |
| 91 suppressEdits: false, // used to disable editing during key handlers when
in readOnly mode | 91 return r |
| 92 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edit
s in input.poll | 92 } } |
| 93 selectingText: false, | 93 else { range = function(node, start, end) { |
| 94 draggingText: false, | 94 var r = document.body.createTextRange() |
| 95 highlight: new Delayed(), // stores highlight worker timeout | 95 try { r.moveToElementText(node.parentNode) } |
| 96 keySeq: null, // Unfinished key sequence | 96 catch(e) { return r } |
| 97 specialChars: null | 97 r.collapse(true) |
| 98 }; | 98 r.moveEnd("character", end) |
| 99 | 99 r.moveStart("character", start) |
| 100 var cm = this; | 100 return r |
| 101 | 101 } } |
| 102 // Override magic textarea content restore that IE sometimes does | 102 |
| 103 // on our hidden textarea on reload | 103 function contains(parent, child) { |
| 104 if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(tr
ue); }, 20); | 104 if (child.nodeType == 3) // Android browser always returns false when child is
a textnode |
| 105 | 105 { child = child.parentNode } |
| 106 registerEventHandlers(this); | 106 if (parent.contains) |
| 107 ensureGlobalHandlers(); | 107 { return parent.contains(child) } |
| 108 | 108 do { |
| 109 startOperation(this); | 109 if (child.nodeType == 11) { child = child.host } |
| 110 this.curOp.forceUpdate = true; | 110 if (child == parent) { return true } |
| 111 attachDoc(this, doc); | 111 } while (child = child.parentNode) |
| 112 | 112 } |
| 113 if ((options.autofocus && !mobile) || cm.hasFocus()) | 113 |
| 114 setTimeout(bind(onFocus, this), 20); | 114 function activeElt() { |
| 115 else | 115 // IE and Edge may throw an "Unspecified Error" when accessing document.active
Element. |
| 116 onBlur(this); | 116 // IE < 10 will throw when accessed while the page is loading or in an iframe. |
| 117 | 117 // IE > 9 and Edge will throw when accessed in an iframe if document.body is u
navailable. |
| 118 for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) | 118 var activeElement |
| 119 optionHandlers[opt](this, options[opt], Init); | 119 try { |
| 120 maybeUpdateLineNumberWidth(this); | 120 activeElement = document.activeElement |
| 121 if (options.finishInit) options.finishInit(this); | 121 } catch(e) { |
| 122 for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); | 122 activeElement = document.body || null |
| 123 endOperation(this); | 123 } |
| 124 // Suppress optimizelegibility in Webkit, since it breaks text | 124 while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.a
ctiveElement) |
| 125 // measuring on line wrapping boundaries. | 125 { activeElement = activeElement.shadowRoot.activeElement } |
| 126 if (webkit && options.lineWrapping && | 126 return activeElement |
| 127 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") | 127 } |
| 128 display.lineDiv.style.textRendering = "auto"; | 128 |
| 129 } | 129 function addClass(node, cls) { |
| 130 | 130 var current = node.className |
| 131 // DISPLAY CONSTRUCTOR | 131 if (!classTest(cls).test(current)) { node.className += (current ? " " : "") +
cls } |
| 132 | 132 } |
| 133 // The display handles the DOM integration, both for input reading | 133 function joinClasses(a, b) { |
| 134 // and content drawing. It holds references to DOM nodes and | 134 var as = a.split(" ") |
| 135 // display-related state. | 135 for (var i = 0; i < as.length; i++) |
| 136 | 136 { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i] } } |
| 137 function Display(place, doc, input) { | 137 return b |
| 138 var d = this; | 138 } |
| 139 this.input = input; | 139 |
| 140 | 140 var selectInput = function(node) { node.select() } |
| 141 // Covers bottom-right square when both scrollbars are present. | 141 if (ios) // Mobile Safari apparently has a bug where select() is broken. |
| 142 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); | 142 { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd =
node.value.length } } |
| 143 d.scrollbarFiller.setAttribute("cm-not-content", "true"); | 143 else if (ie) // Suppress mysterious IE10 errors |
| 144 // Covers bottom of gutter when coverGutterNextToScrollbar is on | 144 { selectInput = function(node) { try { node.select() } catch(_e) {} } } |
| 145 // and h scrollbar is present. | 145 |
| 146 d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); | 146 function bind(f) { |
| 147 d.gutterFiller.setAttribute("cm-not-content", "true"); | 147 var args = Array.prototype.slice.call(arguments, 1) |
| 148 // Will contain the actual code, positioned to cover the viewport. | 148 return function(){return f.apply(null, args)} |
| 149 d.lineDiv = elt("div", null, "CodeMirror-code"); | 149 } |
| 150 // Elements are added to these to represent selection and cursors. | 150 |
| 151 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); | 151 function copyObj(obj, target, overwrite) { |
| 152 d.cursorDiv = elt("div", null, "CodeMirror-cursors"); | 152 if (!target) { target = {} } |
| 153 // A visibility: hidden element used to find the size of things. | 153 for (var prop in obj) |
| 154 d.measure = elt("div", null, "CodeMirror-measure"); | 154 { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProp
erty(prop))) |
| 155 // When lines outside of the viewport are measured, they are drawn in this. | 155 { target[prop] = obj[prop] } } |
| 156 d.lineMeasure = elt("div", null, "CodeMirror-measure"); | 156 return target |
| 157 // Wraps everything that needs to exist inside the vertically-padded coordin
ate system | 157 } |
| 158 d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursor
Div, d.lineDiv], | 158 |
| 159 null, "position: relative; outline: none"); | 159 // Counts the column offset in a string, taking tabs into account. |
| 160 // Moved around its parent to cover visible view. | 160 // Used mostly to find indentation. |
| 161 d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null,
"position: relative"); | 161 function countColumn(string, end, tabSize, startIndex, startValue) { |
| 162 // Set to the height of the document, allowing scrolling. | 162 if (end == null) { |
| 163 d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); | 163 end = string.search(/[^\s\u00a0]/) |
| 164 d.sizerWidth = null; | 164 if (end == -1) { end = string.length } |
| 165 // Behavior of elts with overflow: auto and padding is | 165 } |
| 166 // inconsistent across browsers. This is used to ensure the | 166 for (var i = startIndex || 0, n = startValue || 0;;) { |
| 167 // scrollable area is big enough. | 167 var nextTab = string.indexOf("\t", i) |
| 168 d.heightForcer = elt("div", null, null, "position: absolute; height: " + scr
ollerGap + "px; width: 1px;"); | 168 if (nextTab < 0 || nextTab >= end) |
| 169 // Will contain the gutters, if any. | 169 { return n + (end - i) } |
| 170 d.gutters = elt("div", null, "CodeMirror-gutters"); | 170 n += nextTab - i |
| 171 d.lineGutter = null; | 171 n += tabSize - (n % tabSize) |
| 172 // Actual scrollable element. | 172 i = nextTab + 1 |
| 173 d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-sc
roll"); | 173 } |
| 174 d.scroller.setAttribute("tabIndex", "-1"); | 174 } |
| 175 // The element in which the editor lives. | 175 |
| 176 d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "Cod
eMirror"); | 176 var Delayed = function Delayed() {this.id = null}; |
| 177 | 177 Delayed.prototype.set = function set (ms, f) { |
| 178 // Work around IE7 z-index bug (not perfect, hence IE7 not really being supp
orted) | 178 clearTimeout(this.id) |
| 179 if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.pa
ddingRight = 0; } | 179 this.id = setTimeout(f, ms) |
| 180 if (!webkit && !(gecko && mobile)) d.scroller.draggable = true; | 180 }; |
| 181 | 181 |
| 182 if (place) { | 182 function indexOf(array, elt) { |
| 183 if (place.appendChild) place.appendChild(d.wrapper); | 183 for (var i = 0; i < array.length; ++i) |
| 184 else place(d.wrapper); | 184 { if (array[i] == elt) { return i } } |
| 185 } | 185 return -1 |
| 186 | 186 } |
| 187 // Current rendered range (may be bigger than the view window). | 187 |
| 188 d.viewFrom = d.viewTo = doc.first; | 188 // Number of pixels added to scroller and sizer to hide scrollbar |
| 189 d.reportedViewFrom = d.reportedViewTo = doc.first; | 189 var scrollerGap = 30 |
| 190 // Information about the rendered lines. | 190 |
| 191 d.view = []; | 191 // Returned or thrown by various protocols to signal 'I'm not |
| 192 d.renderedView = null; | 192 // handling this'. |
| 193 // Holds info about a single rendered line when it was rendered | 193 var Pass = {toString: function(){return "CodeMirror.Pass"}} |
| 194 // for measurement, while not in view. | 194 |
| 195 d.externalMeasured = null; | 195 // Reused option objects for setSelection & friends |
| 196 // Empty space (in pixels) above the view | 196 var sel_dontScroll = {scroll: false}; |
| 197 d.viewOffset = 0; | 197 var sel_mouse = {origin: "*mouse"}; |
| 198 d.lastWrapHeight = d.lastWrapWidth = 0; | 198 var sel_move = {origin: "+move"}; |
| 199 d.updateLineNumbers = null; | 199 // The inverse of countColumn -- find the offset that corresponds to |
| 200 | 200 // a particular column. |
| 201 d.nativeBarWidth = d.barHeight = d.barWidth = 0; | 201 function findColumn(string, goal, tabSize) { |
| 202 d.scrollbarsClipped = false; | 202 for (var pos = 0, col = 0;;) { |
| 203 | 203 var nextTab = string.indexOf("\t", pos) |
| 204 // Used to only resize the line number gutter when necessary (when | 204 if (nextTab == -1) { nextTab = string.length } |
| 205 // the amount of lines crosses a boundary that makes its width change) | 205 var skipped = nextTab - pos |
| 206 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; | 206 if (nextTab == string.length || col + skipped >= goal) |
| 207 // Set to true when a non-horizontal-scrolling line widget is | 207 { return pos + Math.min(skipped, goal - col) } |
| 208 // added. As an optimization, line widget aligning is skipped when | 208 col += nextTab - pos |
| 209 // this is false. | 209 col += tabSize - (col % tabSize) |
| 210 d.alignWidgets = false; | 210 pos = nextTab + 1 |
| 211 | 211 if (col >= goal) { return pos } |
| 212 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; | 212 } |
| 213 | 213 } |
| 214 // Tracks the maximum line length so that the horizontal scrollbar | 214 |
| 215 // can be kept static when scrolling. | 215 var spaceStrs = [""] |
| 216 d.maxLine = null; | 216 function spaceStr(n) { |
| 217 d.maxLineLength = 0; | 217 while (spaceStrs.length <= n) |
| 218 d.maxLineChanged = false; | 218 { spaceStrs.push(lst(spaceStrs) + " ") } |
| 219 | 219 return spaceStrs[n] |
| 220 // Used for measuring wheel scrolling granularity | 220 } |
| 221 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; | 221 |
| 222 | 222 function lst(arr) { return arr[arr.length-1] } |
| 223 // True when shift is held down. | 223 |
| 224 d.shift = false; | 224 function map(array, f) { |
| 225 | 225 var out = [] |
| 226 // Used to track whether anything happened since the context menu | 226 for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i) } |
| 227 // was opened. | 227 return out |
| 228 d.selForContextMenu = null; | 228 } |
| 229 | 229 |
| 230 d.activeTouch = null; | 230 function insertSorted(array, value, score) { |
| 231 | 231 var pos = 0, priority = score(value) |
| 232 input.init(d); | 232 while (pos < array.length && score(array[pos]) <= priority) { pos++ } |
| 233 } | 233 array.splice(pos, 0, value) |
| 234 | 234 } |
| 235 // STATE UPDATES | 235 |
| 236 | 236 function nothing() {} |
| 237 // Used to get the editor into a consistent state again when options change. | 237 |
| 238 | 238 function createObj(base, props) { |
| 239 function loadMode(cm) { | 239 var inst |
| 240 cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); | 240 if (Object.create) { |
| 241 resetModeState(cm); | 241 inst = Object.create(base) |
| 242 } | 242 } else { |
| 243 | 243 nothing.prototype = base |
| 244 function resetModeState(cm) { | 244 inst = new nothing() |
| 245 cm.doc.iter(function(line) { | 245 } |
| 246 if (line.stateAfter) line.stateAfter = null; | 246 if (props) { copyObj(props, inst) } |
| 247 if (line.styles) line.styles = null; | 247 return inst |
| 248 }); | 248 } |
| 249 cm.doc.frontier = cm.doc.first; | 249 |
| 250 startWorker(cm, 100); | 250 var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-
\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/ |
| 251 cm.state.modeGen++; | 251 function isWordCharBasic(ch) { |
| 252 if (cm.curOp) regChange(cm); | 252 return /\w/.test(ch) || ch > "\x80" && |
| 253 } | 253 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)
) |
| 254 | 254 } |
| 255 function wrappingChanged(cm) { | 255 function isWordChar(ch, helper) { |
| 256 if (cm.options.lineWrapping) { | 256 if (!helper) { return isWordCharBasic(ch) } |
| 257 addClass(cm.display.wrapper, "CodeMirror-wrap"); | 257 if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true } |
| 258 cm.display.sizer.style.minWidth = ""; | 258 return helper.test(ch) |
| 259 cm.display.sizerWidth = null; | 259 } |
| 260 |
| 261 function isEmpty(obj) { |
| 262 for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } } |
| 263 return true |
| 264 } |
| 265 |
| 266 // Extending unicode characters. A series of a non-extending char + |
| 267 // any number of extending chars is treated as a single unit as far |
| 268 // as editing and measuring is concerned. This is not fully correct, |
| 269 // since some scripts/fonts/browsers also treat other configurations |
| 270 // of code points as a group. |
| 271 var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2
\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06
e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\
u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u09
51-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a
01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a8
1\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f
\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-
\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6
\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\
u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4
-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f8
0-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u
1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u
108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\
u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939
-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a
7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u
1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8
\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2
dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua
80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\
ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-
\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-
\ufe0f\ufe20-\ufe26\uff9e\uff9f]/ |
| 272 function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.
test(ch) } |
| 273 |
| 274 // Returns a number from the range [`0`; `str.length`] unless `pos` is outside t
hat range. |
| 275 function skipExtendingChars(str, pos, dir) { |
| 276 while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(po
s))) { 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` satisfies `pred`. |
| 282 function findFirst(pred, from, to) { |
| 283 for (;;) { |
| 284 if (Math.abs(from - to) <= 1) { return pred(from) ? from : to } |
| 285 var mid = Math.floor((from + to) / 2) |
| 286 if (pred(mid)) { to = mid } |
| 287 else { from = mid } |
| 288 } |
| 289 } |
| 290 |
| 291 // The display handles the DOM integration, both for input reading |
| 292 // and content drawing. It holds references to DOM nodes and |
| 293 // display-related state. |
| 294 |
| 295 function Display(place, doc, input) { |
| 296 var d = this |
| 297 this.input = input |
| 298 |
| 299 // Covers bottom-right square when both scrollbars are present. |
| 300 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler") |
| 301 d.scrollbarFiller.setAttribute("cm-not-content", "true") |
| 302 // Covers bottom of gutter when coverGutterNextToScrollbar is on |
| 303 // and h scrollbar is present. |
| 304 d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler") |
| 305 d.gutterFiller.setAttribute("cm-not-content", "true") |
| 306 // Will contain the actual code, positioned to cover the viewport. |
| 307 d.lineDiv = eltP("div", null, "CodeMirror-code") |
| 308 // Elements are added to these to represent selection and cursors. |
| 309 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1") |
| 310 d.cursorDiv = elt("div", null, "CodeMirror-cursors") |
| 311 // A visibility: hidden element used to find the size of things. |
| 312 d.measure = elt("div", null, "CodeMirror-measure") |
| 313 // When lines outside of the viewport are measured, they are drawn in this. |
| 314 d.lineMeasure = elt("div", null, "CodeMirror-measure") |
| 315 // Wraps everything that needs to exist inside the vertically-padded coordinat
e system |
| 316 d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorD
iv, d.lineDiv], |
| 317 null, "position: relative; outline: none") |
| 318 var lines = eltP("div", [d.lineSpace], "CodeMirror-lines") |
| 319 // Moved around its parent to cover visible view. |
| 320 d.mover = elt("div", [lines], null, "position: relative") |
| 321 // Set to the height of the document, allowing scrolling. |
| 322 d.sizer = elt("div", [d.mover], "CodeMirror-sizer") |
| 323 d.sizerWidth = null |
| 324 // Behavior of elts with overflow: auto and padding is |
| 325 // inconsistent across browsers. This is used to ensure the |
| 326 // scrollable area is big enough. |
| 327 d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrol
lerGap + "px; width: 1px;") |
| 328 // Will contain the gutters, if any. |
| 329 d.gutters = elt("div", null, "CodeMirror-gutters") |
| 330 d.lineGutter = null |
| 331 // Actual scrollable element. |
| 332 d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scro
ll") |
| 333 d.scroller.setAttribute("tabIndex", "-1") |
| 334 // The element in which the editor lives. |
| 335 d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeM
irror") |
| 336 |
| 337 // Work around IE7 z-index bug (not perfect, hence IE7 not really being suppor
ted) |
| 338 if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.padd
ingRight = 0 } |
| 339 if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true } |
| 340 |
| 341 if (place) { |
| 342 if (place.appendChild) { place.appendChild(d.wrapper) } |
| 343 else { place(d.wrapper) } |
| 344 } |
| 345 |
| 346 // Current rendered range (may be bigger than the view window). |
| 347 d.viewFrom = d.viewTo = doc.first |
| 348 d.reportedViewFrom = d.reportedViewTo = doc.first |
| 349 // Information about the rendered lines. |
| 350 d.view = [] |
| 351 d.renderedView = null |
| 352 // Holds info about a single rendered line when it was rendered |
| 353 // for measurement, while not in view. |
| 354 d.externalMeasured = null |
| 355 // Empty space (in pixels) above the view |
| 356 d.viewOffset = 0 |
| 357 d.lastWrapHeight = d.lastWrapWidth = 0 |
| 358 d.updateLineNumbers = null |
| 359 |
| 360 d.nativeBarWidth = d.barHeight = d.barWidth = 0 |
| 361 d.scrollbarsClipped = false |
| 362 |
| 363 // Used to only resize the line number gutter when necessary (when |
| 364 // the amount of lines crosses a boundary that makes its width change) |
| 365 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null |
| 366 // Set to true when a non-horizontal-scrolling line widget is |
| 367 // added. As an optimization, line widget aligning is skipped when |
| 368 // this is false. |
| 369 d.alignWidgets = false |
| 370 |
| 371 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null |
| 372 |
| 373 // Tracks the maximum line length so that the horizontal scrollbar |
| 374 // can be kept static when scrolling. |
| 375 d.maxLine = null |
| 376 d.maxLineLength = 0 |
| 377 d.maxLineChanged = false |
| 378 |
| 379 // Used for measuring wheel scrolling granularity |
| 380 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null |
| 381 |
| 382 // True when shift is held down. |
| 383 d.shift = false |
| 384 |
| 385 // Used to track whether anything happened since the context menu |
| 386 // was opened. |
| 387 d.selForContextMenu = null |
| 388 |
| 389 d.activeTouch = null |
| 390 |
| 391 input.init(d) |
| 392 } |
| 393 |
| 394 // Find the line object corresponding to the given line number. |
| 395 function getLine(doc, n) { |
| 396 n -= doc.first |
| 397 if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.f
irst) + " in the document.") } |
| 398 var chunk = doc |
| 399 while (!chunk.lines) { |
| 400 for (var i = 0;; ++i) { |
| 401 var child = chunk.children[i], sz = child.chunkSize() |
| 402 if (n < sz) { chunk = child; break } |
| 403 n -= sz |
| 404 } |
| 405 } |
| 406 return chunk.lines[n] |
| 407 } |
| 408 |
| 409 // Get the part of a document between two positions, as an array of |
| 410 // strings. |
| 411 function getBetween(doc, start, end) { |
| 412 var out = [], n = start.line |
| 413 doc.iter(start.line, end.line + 1, function (line) { |
| 414 var text = line.text |
| 415 if (n == end.line) { text = text.slice(0, end.ch) } |
| 416 if (n == start.line) { text = text.slice(start.ch) } |
| 417 out.push(text) |
| 418 ++n |
| 419 }) |
| 420 return out |
| 421 } |
| 422 // Get the lines between from and to, as array of strings. |
| 423 function getLines(doc, from, to) { |
| 424 var out = [] |
| 425 doc.iter(from, to, function (line) { out.push(line.text) }) // iter aborts whe
n callback returns truthy value |
| 426 return out |
| 427 } |
| 428 |
| 429 // Update the height of a line, propagating the height change |
| 430 // upwards to parent nodes. |
| 431 function updateLineHeight(line, height) { |
| 432 var diff = height - line.height |
| 433 if (diff) { for (var n = line; n; n = n.parent) { n.height += diff } } |
| 434 } |
| 435 |
| 436 // Given a line object, find its line number by walking up through |
| 437 // its parent links. |
| 438 function lineNo(line) { |
| 439 if (line.parent == null) { return null } |
| 440 var cur = line.parent, no = indexOf(cur.lines, line) |
| 441 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { |
| 442 for (var i = 0;; ++i) { |
| 443 if (chunk.children[i] == cur) { break } |
| 444 no += chunk.children[i].chunkSize() |
| 445 } |
| 446 } |
| 447 return no + cur.first |
| 448 } |
| 449 |
| 450 // Find the line at the given vertical position, using the height |
| 451 // information in the document tree. |
| 452 function lineAtHeight(chunk, h) { |
| 453 var n = chunk.first |
| 454 outer: do { |
| 455 for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) { |
| 456 var child = chunk.children[i$1], ch = child.height |
| 457 if (h < ch) { chunk = child; continue outer } |
| 458 h -= ch |
| 459 n += child.chunkSize() |
| 460 } |
| 461 return n |
| 462 } while (!chunk.lines) |
| 463 var i = 0 |
| 464 for (; i < chunk.lines.length; ++i) { |
| 465 var line = chunk.lines[i], lh = line.height |
| 466 if (h < lh) { break } |
| 467 h -= lh |
| 468 } |
| 469 return n + i |
| 470 } |
| 471 |
| 472 function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} |
| 473 |
| 474 function lineNumberFor(options, i) { |
| 475 return String(options.lineNumberFormatter(i + options.firstLineNumber)) |
| 476 } |
| 477 |
| 478 // A Pos instance represents a position within the text. |
| 479 function Pos(line, ch, sticky) { |
| 480 if ( sticky === void 0 ) sticky = null; |
| 481 |
| 482 if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) } |
| 483 this.line = line |
| 484 this.ch = ch |
| 485 this.sticky = sticky |
| 486 } |
| 487 |
| 488 // Compare two positions, return 0 if they are the same, a negative |
| 489 // number when a is less, and a positive number otherwise. |
| 490 function cmp(a, b) { return a.line - b.line || a.ch - b.ch } |
| 491 |
| 492 function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } |
| 493 |
| 494 function copyPos(x) {return Pos(x.line, x.ch)} |
| 495 function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } |
| 496 function minPos(a, b) { return cmp(a, b) < 0 ? a : b } |
| 497 |
| 498 // Most of the external API clips given positions to make sure they |
| 499 // actually exist within the document. |
| 500 function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + do
c.size - 1))} |
| 501 function clipPos(doc, pos) { |
| 502 if (pos.line < doc.first) { return Pos(doc.first, 0) } |
| 503 var last = doc.first + doc.size - 1 |
| 504 if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) } |
| 505 return clipToLen(pos, getLine(doc, pos.line).text.length) |
| 506 } |
| 507 function clipToLen(pos, linelen) { |
| 508 var ch = pos.ch |
| 509 if (ch == null || ch > linelen) { return Pos(pos.line, linelen) } |
| 510 else if (ch < 0) { return Pos(pos.line, 0) } |
| 511 else { return pos } |
| 512 } |
| 513 function clipPosArray(doc, array) { |
| 514 var out = [] |
| 515 for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]) } |
| 516 return out |
| 517 } |
| 518 |
| 519 // Optimize some code when these features are not used. |
| 520 var sawReadOnlySpans = false; |
| 521 var sawCollapsedSpans = false; |
| 522 function seeReadOnlySpans() { |
| 523 sawReadOnlySpans = true |
| 524 } |
| 525 |
| 526 function seeCollapsedSpans() { |
| 527 sawCollapsedSpans = true |
| 528 } |
| 529 |
| 530 // TEXTMARKER SPANS |
| 531 |
| 532 function MarkedSpan(marker, from, to) { |
| 533 this.marker = marker |
| 534 this.from = from; this.to = to |
| 535 } |
| 536 |
| 537 // Search an array of spans for a span matching the given marker. |
| 538 function getMarkedSpanFor(spans, marker) { |
| 539 if (spans) { for (var i = 0; i < spans.length; ++i) { |
| 540 var span = spans[i] |
| 541 if (span.marker == marker) { return span } |
| 542 } } |
| 543 } |
| 544 // Remove a span from an array, returning undefined if no spans are |
| 545 // left (we don't store arrays for lines without spans). |
| 546 function removeMarkedSpan(spans, span) { |
| 547 var r |
| 548 for (var i = 0; i < spans.length; ++i) |
| 549 { if (spans[i] != span) { (r || (r = [])).push(spans[i]) } } |
| 550 return r |
| 551 } |
| 552 // Add a span to a line. |
| 553 function addMarkedSpan(line, span) { |
| 554 line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span] |
| 555 span.marker.attachLine(line) |
| 556 } |
| 557 |
| 558 // Used for the algorithm that adjusts markers for a change in the |
| 559 // document. These functions cut an array of spans at a given |
| 560 // character position, returning an array of remaining chunks (or |
| 561 // undefined if nothing remains). |
| 562 function markedSpansBefore(old, startCh, isInsert) { |
| 563 var nw |
| 564 if (old) { for (var i = 0; i < old.length; ++i) { |
| 565 var span = old[i], marker = span.marker |
| 566 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <=
startCh : span.from < startCh) |
| 567 if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!i
sInsert || !span.marker.insertLeft)) { |
| 568 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= sta
rtCh : span.to > startCh) |
| 569 ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null
: span.to)) |
| 570 } |
| 571 } } |
| 572 return nw |
| 573 } |
| 574 function markedSpansAfter(old, endCh, isInsert) { |
| 575 var nw |
| 576 if (old) { for (var i = 0; i < old.length; ++i) { |
| 577 var span = old[i], marker = span.marker |
| 578 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh
: span.to > endCh) |
| 579 if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInse
rt || span.marker.insertLeft)) { |
| 580 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from
<= endCh : span.from < endCh) |
| 581 ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.
from - endCh, |
| 582 span.to == null ? null : span.to - e
ndCh)) |
| 583 } |
| 584 } } |
| 585 return nw |
| 586 } |
| 587 |
| 588 // Given a change object, compute the new set of marker spans that |
| 589 // cover the line in which the change took place. Removes spans |
| 590 // entirely within the change, reconnects spans belonging to the |
| 591 // same marker that appear on both sides of the change, and cuts off |
| 592 // spans partially within the change. Returns an array of span |
| 593 // arrays with one element for each line in (after) the change. |
| 594 function stretchSpansOverChange(doc, change) { |
| 595 if (change.full) { return null } |
| 596 var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line)
.markedSpans |
| 597 var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).mark
edSpans |
| 598 if (!oldFirst && !oldLast) { return null } |
| 599 |
| 600 var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from
, change.to) == 0 |
| 601 // Get the spans that 'stick out' on both sides |
| 602 var first = markedSpansBefore(oldFirst, startCh, isInsert) |
| 603 var last = markedSpansAfter(oldLast, endCh, isInsert) |
| 604 |
| 605 // Next, merge those two ends |
| 606 var sameLine = change.text.length == 1, offset = lst(change.text).length + (sa
meLine ? startCh : 0) |
| 607 if (first) { |
| 608 // Fix up .to properties of first |
| 609 for (var i = 0; i < first.length; ++i) { |
| 610 var span = first[i] |
| 611 if (span.to == null) { |
| 612 var found = getMarkedSpanFor(last, span.marker) |
| 613 if (!found) { span.to = startCh } |
| 614 else if (sameLine) { span.to = found.to == null ? null : found.to + offs
et } |
| 615 } |
| 616 } |
| 617 } |
| 618 if (last) { |
| 619 // Fix up .from in last (or move them into first in case of sameLine) |
| 620 for (var i$1 = 0; i$1 < last.length; ++i$1) { |
| 621 var span$1 = last[i$1] |
| 622 if (span$1.to != null) { span$1.to += offset } |
| 623 if (span$1.from == null) { |
| 624 var found$1 = getMarkedSpanFor(first, span$1.marker) |
| 625 if (!found$1) { |
| 626 span$1.from = offset |
| 627 if (sameLine) { (first || (first = [])).push(span$1) } |
| 628 } |
| 629 } else { |
| 630 span$1.from += offset |
| 631 if (sameLine) { (first || (first = [])).push(span$1) } |
| 632 } |
| 633 } |
| 634 } |
| 635 // Make sure we didn't create any zero-length spans |
| 636 if (first) { first = clearEmptySpans(first) } |
| 637 if (last && last != first) { last = clearEmptySpans(last) } |
| 638 |
| 639 var newMarkers = [first] |
| 640 if (!sameLine) { |
| 641 // Fill gap with whole-line-spans |
| 642 var gap = change.text.length - 2, gapMarkers |
| 643 if (gap > 0 && first) |
| 644 { for (var i$2 = 0; i$2 < first.length; ++i$2) |
| 645 { if (first[i$2].to == null) |
| 646 { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].mar
ker, null, null)) } } } |
| 647 for (var i$3 = 0; i$3 < gap; ++i$3) |
| 648 { newMarkers.push(gapMarkers) } |
| 649 newMarkers.push(last) |
| 650 } |
| 651 return newMarkers |
| 652 } |
| 653 |
| 654 // Remove spans that are empty and don't have a clearWhenEmpty |
| 655 // option of false. |
| 656 function clearEmptySpans(spans) { |
| 657 for (var i = 0; i < spans.length; ++i) { |
| 658 var span = spans[i] |
| 659 if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty
!== false) |
| 660 { spans.splice(i--, 1) } |
| 661 } |
| 662 if (!spans.length) { return null } |
| 663 return spans |
| 664 } |
| 665 |
| 666 // Used to 'clip' out readOnly ranges when making a change. |
| 667 function removeReadOnlyRanges(doc, from, to) { |
| 668 var markers = null |
| 669 doc.iter(from.line, to.line + 1, function (line) { |
| 670 if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { |
| 671 var mark = line.markedSpans[i].marker |
| 672 if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) |
| 673 { (markers || (markers = [])).push(mark) } |
| 674 } } |
| 675 }) |
| 676 if (!markers) { return null } |
| 677 var parts = [{from: from, to: to}] |
| 678 for (var i = 0; i < markers.length; ++i) { |
| 679 var mk = markers[i], m = mk.find(0) |
| 680 for (var j = 0; j < parts.length; ++j) { |
| 681 var p = parts[j] |
| 682 if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue } |
| 683 var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to) |
| 684 if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) |
| 685 { newParts.push({from: p.from, to: m.from}) } |
| 686 if (dto > 0 || !mk.inclusiveRight && !dto) |
| 687 { newParts.push({from: m.to, to: p.to}) } |
| 688 parts.splice.apply(parts, newParts) |
| 689 j += newParts.length - 3 |
| 690 } |
| 691 } |
| 692 return parts |
| 693 } |
| 694 |
| 695 // Connect or disconnect spans from a line. |
| 696 function detachMarkedSpans(line) { |
| 697 var spans = line.markedSpans |
| 698 if (!spans) { return } |
| 699 for (var i = 0; i < spans.length; ++i) |
| 700 { spans[i].marker.detachLine(line) } |
| 701 line.markedSpans = null |
| 702 } |
| 703 function attachMarkedSpans(line, spans) { |
| 704 if (!spans) { return } |
| 705 for (var i = 0; i < spans.length; ++i) |
| 706 { spans[i].marker.attachLine(line) } |
| 707 line.markedSpans = spans |
| 708 } |
| 709 |
| 710 // Helpers used when computing which overlapping collapsed span |
| 711 // counts as the larger one. |
| 712 function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } |
| 713 function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } |
| 714 |
| 715 // Returns a number indicating which of two overlapping collapsed |
| 716 // spans is larger (and thus includes the other). Falls back to |
| 717 // comparing ids when the spans cover exactly the same range. |
| 718 function compareCollapsedMarkers(a, b) { |
| 719 var lenDiff = a.lines.length - b.lines.length |
| 720 if (lenDiff != 0) { return lenDiff } |
| 721 var aPos = a.find(), bPos = b.find() |
| 722 var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b) |
| 723 if (fromCmp) { return -fromCmp } |
| 724 var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b) |
| 725 if (toCmp) { return toCmp } |
| 726 return b.id - a.id |
| 727 } |
| 728 |
| 729 // Find out whether a line ends or starts in a collapsed span. If |
| 730 // so, return the marker for that span. |
| 731 function collapsedSpanAtSide(line, start) { |
| 732 var sps = sawCollapsedSpans && line.markedSpans, found |
| 733 if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { |
| 734 sp = sps[i] |
| 735 if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && |
| 736 (!found || compareCollapsedMarkers(found, sp.marker) < 0)) |
| 737 { found = sp.marker } |
| 738 } } |
| 739 return found |
| 740 } |
| 741 function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } |
| 742 function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } |
| 743 |
| 744 // Test whether there exists a collapsed span that partially |
| 745 // overlaps (covers the start or end, but not both) of a new span. |
| 746 // Such overlap is not allowed. |
| 747 function conflictingCollapsedRange(doc, lineNo, from, to, marker) { |
| 748 var line = getLine(doc, lineNo) |
| 749 var sps = sawCollapsedSpans && line.markedSpans |
| 750 if (sps) { for (var i = 0; i < sps.length; ++i) { |
| 751 var sp = sps[i] |
| 752 if (!sp.marker.collapsed) { continue } |
| 753 var found = sp.marker.find(0) |
| 754 var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(mark
er) |
| 755 var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker) |
| 756 if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue } |
| 757 if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(
found.to, from) >= 0 : cmp(found.to, from) > 0) || |
| 758 fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(
found.from, to) <= 0 : cmp(found.from, to) < 0)) |
| 759 { return true } |
| 760 } } |
| 761 } |
| 762 |
| 763 // A visual line is a line as drawn on the screen. Folding, for |
| 764 // example, can cause multiple logical lines to appear on the same |
| 765 // visual line. This finds the start of the visual line that the |
| 766 // given line is part of (usually that is the line itself). |
| 767 function visualLine(line) { |
| 768 var merged |
| 769 while (merged = collapsedSpanAtStart(line)) |
| 770 { line = merged.find(-1, true).line } |
| 771 return line |
| 772 } |
| 773 |
| 774 function visualLineEnd(line) { |
| 775 var merged |
| 776 while (merged = collapsedSpanAtEnd(line)) |
| 777 { line = merged.find(1, true).line } |
| 778 return line |
| 779 } |
| 780 |
| 781 // Returns an array of logical lines that continue the visual line |
| 782 // started by the argument, or undefined if there are no such lines. |
| 783 function visualLineContinued(line) { |
| 784 var merged, lines |
| 785 while (merged = collapsedSpanAtEnd(line)) { |
| 786 line = merged.find(1, true).line |
| 787 ;(lines || (lines = [])).push(line) |
| 788 } |
| 789 return lines |
| 790 } |
| 791 |
| 792 // Get the line number of the start of the visual line that the |
| 793 // given line number is part of. |
| 794 function visualLineNo(doc, lineN) { |
| 795 var line = getLine(doc, lineN), vis = visualLine(line) |
| 796 if (line == vis) { return lineN } |
| 797 return lineNo(vis) |
| 798 } |
| 799 |
| 800 // Get the line number of the start of the next visual line after |
| 801 // the given line. |
| 802 function visualLineEndNo(doc, lineN) { |
| 803 if (lineN > doc.lastLine()) { return lineN } |
| 804 var line = getLine(doc, lineN), merged |
| 805 if (!lineIsHidden(doc, line)) { return lineN } |
| 806 while (merged = collapsedSpanAtEnd(line)) |
| 807 { line = merged.find(1, true).line } |
| 808 return lineNo(line) + 1 |
| 809 } |
| 810 |
| 811 // Compute whether a line is hidden. Lines count as hidden when they |
| 812 // are part of a visual line that starts with another line, or when |
| 813 // they are entirely covered by collapsed, non-widget span. |
| 814 function lineIsHidden(doc, line) { |
| 815 var sps = sawCollapsedSpans && line.markedSpans |
| 816 if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { |
| 817 sp = sps[i] |
| 818 if (!sp.marker.collapsed) { continue } |
| 819 if (sp.from == null) { return true } |
| 820 if (sp.marker.widgetNode) { continue } |
| 821 if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line,
sp)) |
| 822 { return true } |
| 823 } } |
| 824 } |
| 825 function lineIsHiddenInner(doc, line, span) { |
| 826 if (span.to == null) { |
| 827 var end = span.marker.find(1, true) |
| 828 return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpan
s, span.marker)) |
| 829 } |
| 830 if (span.marker.inclusiveRight && span.to == line.text.length) |
| 831 { return true } |
| 832 for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) { |
| 833 sp = line.markedSpans[i] |
| 834 if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && |
| 835 (sp.to == null || sp.to != span.from) && |
| 836 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && |
| 837 lineIsHiddenInner(doc, line, sp)) { return true } |
| 838 } |
| 839 } |
| 840 |
| 841 // Find the height above the given line. |
| 842 function heightAtLine(lineObj) { |
| 843 lineObj = visualLine(lineObj) |
| 844 |
| 845 var h = 0, chunk = lineObj.parent |
| 846 for (var i = 0; i < chunk.lines.length; ++i) { |
| 847 var line = chunk.lines[i] |
| 848 if (line == lineObj) { break } |
| 849 else { h += line.height } |
| 850 } |
| 851 for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { |
| 852 for (var i$1 = 0; i$1 < p.children.length; ++i$1) { |
| 853 var cur = p.children[i$1] |
| 854 if (cur == chunk) { break } |
| 855 else { h += cur.height } |
| 856 } |
| 857 } |
| 858 return h |
| 859 } |
| 860 |
| 861 // Compute the character length of a line, taking into account |
| 862 // collapsed ranges (see markText) that might hide parts, and join |
| 863 // other lines onto it. |
| 864 function lineLength(line) { |
| 865 if (line.height == 0) { return 0 } |
| 866 var len = line.text.length, merged, cur = line |
| 867 while (merged = collapsedSpanAtStart(cur)) { |
| 868 var found = merged.find(0, true) |
| 869 cur = found.from.line |
| 870 len += found.from.ch - found.to.ch |
| 871 } |
| 872 cur = line |
| 873 while (merged = collapsedSpanAtEnd(cur)) { |
| 874 var found$1 = merged.find(0, true) |
| 875 len -= cur.text.length - found$1.from.ch |
| 876 cur = found$1.to.line |
| 877 len += cur.text.length - found$1.to.ch |
| 878 } |
| 879 return len |
| 880 } |
| 881 |
| 882 // Find the longest line in the document. |
| 883 function findMaxLine(cm) { |
| 884 var d = cm.display, doc = cm.doc |
| 885 d.maxLine = getLine(doc, doc.first) |
| 886 d.maxLineLength = lineLength(d.maxLine) |
| 887 d.maxLineChanged = true |
| 888 doc.iter(function (line) { |
| 889 var len = lineLength(line) |
| 890 if (len > d.maxLineLength) { |
| 891 d.maxLineLength = len |
| 892 d.maxLine = line |
| 893 } |
| 894 }) |
| 895 } |
| 896 |
| 897 // BIDI HELPERS |
| 898 |
| 899 function iterateBidiSections(order, from, to, f) { |
| 900 if (!order) { return f(from, to, "ltr") } |
| 901 var found = false |
| 902 for (var i = 0; i < order.length; ++i) { |
| 903 var part = order[i] |
| 904 if (part.from < to && part.to > from || from == to && part.to == from) { |
| 905 f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl
" : "ltr") |
| 906 found = true |
| 907 } |
| 908 } |
| 909 if (!found) { f(from, to, "ltr") } |
| 910 } |
| 911 |
| 912 var bidiOther = null |
| 913 function getBidiPartAt(order, ch, sticky) { |
| 914 var found |
| 915 bidiOther = null |
| 916 for (var i = 0; i < order.length; ++i) { |
| 917 var cur = order[i] |
| 918 if (cur.from < ch && cur.to > ch) { return i } |
| 919 if (cur.to == ch) { |
| 920 if (cur.from != cur.to && sticky == "before") { found = i } |
| 921 else { bidiOther = i } |
| 922 } |
| 923 if (cur.from == ch) { |
| 924 if (cur.from != cur.to && sticky != "before") { found = i } |
| 925 else { bidiOther = i } |
| 926 } |
| 927 } |
| 928 return found != null ? found : bidiOther |
| 929 } |
| 930 |
| 931 // Bidirectional ordering algorithm |
| 932 // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm |
| 933 // that this (partially) implements. |
| 934 |
| 935 // One-char codes used for character types: |
| 936 // L (L): Left-to-Right |
| 937 // R (R): Right-to-Left |
| 938 // r (AL): Right-to-Left Arabic |
| 939 // 1 (EN): European Number |
| 940 // + (ES): European Number Separator |
| 941 // % (ET): European Number Terminator |
| 942 // n (AN): Arabic Number |
| 943 // , (CS): Common Number Separator |
| 944 // m (NSM): Non-Spacing Mark |
| 945 // b (BN): Boundary Neutral |
| 946 // s (B): Paragraph Separator |
| 947 // t (S): Segment Separator |
| 948 // w (WS): Whitespace |
| 949 // N (ON): Other Neutrals |
| 950 |
| 951 // Returns null if characters are ordered as they appear |
| 952 // (left-to-right), or an array of sections ({from, to, level} |
| 953 // objects) in the order in which they occur visually. |
| 954 var bidiOrdering = (function() { |
| 955 // Character types for codepoints 0 to 0xff |
| 956 var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNN
NNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbb
bbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLL
LLLLLLLLLLLLLLLLLLLLLLLLLN" |
| 957 // Character types for codepoints 0x600 to 0x6f9 |
| 958 var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmm
mmnNmmmmmmrrmmNmmmmrr1111111111" |
| 959 function charType(code) { |
| 960 if (code <= 0xf7) { return lowTypes.charAt(code) } |
| 961 else if (0x590 <= code && code <= 0x5f4) { return "R" } |
| 962 else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code -
0x600) } |
| 963 else if (0x6ee <= code && code <= 0x8ac) { return "r" } |
| 964 else if (0x2000 <= code && code <= 0x200b) { return "w" } |
| 965 else if (code == 0x200c) { return "b" } |
| 966 else { return "L" } |
| 967 } |
| 968 |
| 969 var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/ |
| 970 var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsA
sNum = /[1n]/ |
| 971 |
| 972 function BidiSpan(level, from, to) { |
| 973 this.level = level |
| 974 this.from = from; this.to = to |
| 975 } |
| 976 |
| 977 return function(str, direction) { |
| 978 var outerType = direction == "ltr" ? "L" : "R" |
| 979 |
| 980 if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return fal
se } |
| 981 var len = str.length, types = [] |
| 982 for (var i = 0; i < len; ++i) |
| 983 { types.push(charType(str.charCodeAt(i))) } |
| 984 |
| 985 // W1. Examine each non-spacing mark (NSM) in the level run, and |
| 986 // change the type of the NSM to the type of the previous |
| 987 // character. If the NSM is at the start of the level run, it will |
| 988 // get the type of sor. |
| 989 for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) { |
| 990 var type = types[i$1] |
| 991 if (type == "m") { types[i$1] = prev } |
| 992 else { prev = type } |
| 993 } |
| 994 |
| 995 // W2. Search backwards from each instance of a European number |
| 996 // until the first strong type (R, L, AL, or sor) is found. If an |
| 997 // AL is found, change the type of the European number to Arabic |
| 998 // number. |
| 999 // W3. Change all ALs to R. |
| 1000 for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) { |
| 1001 var type$1 = types[i$2] |
| 1002 if (type$1 == "1" && cur == "r") { types[i$2] = "n" } |
| 1003 else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types
[i$2] = "R" } } |
| 1004 } |
| 1005 |
| 1006 // W4. A single European separator between two European numbers |
| 1007 // changes to a European number. A single common separator between |
| 1008 // two numbers of the same type changes to that type. |
| 1009 for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) { |
| 1010 var type$2 = types[i$3] |
| 1011 if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] =
"1" } |
| 1012 else if (type$2 == "," && prev$1 == types[i$3+1] && |
| 1013 (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1 } |
| 1014 prev$1 = type$2 |
| 1015 } |
| 1016 |
| 1017 // W5. A sequence of European terminators adjacent to European |
| 1018 // numbers changes to all European numbers. |
| 1019 // W6. Otherwise, separators and terminators change to Other |
| 1020 // Neutral. |
| 1021 for (var i$4 = 0; i$4 < len; ++i$4) { |
| 1022 var type$3 = types[i$4] |
| 1023 if (type$3 == ",") { types[i$4] = "N" } |
| 1024 else if (type$3 == "%") { |
| 1025 var end = (void 0) |
| 1026 for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {} |
| 1027 var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] =
= "1") ? "1" : "N" |
| 1028 for (var j = i$4; j < end; ++j) { types[j] = replace } |
| 1029 i$4 = end - 1 |
| 1030 } |
| 1031 } |
| 1032 |
| 1033 // W7. Search backwards from each instance of a European number |
| 1034 // until the first strong type (R, L, or sor) is found. If an L is |
| 1035 // found, then change the type of the European number to L. |
| 1036 for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) { |
| 1037 var type$4 = types[i$5] |
| 1038 if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L" } |
| 1039 else if (isStrong.test(type$4)) { cur$1 = type$4 } |
| 1040 } |
| 1041 |
| 1042 // N1. A sequence of neutrals takes the direction of the |
| 1043 // surrounding strong text if the text on both sides has the same |
| 1044 // direction. European and Arabic numbers act as if they were R in |
| 1045 // terms of their influence on neutrals. Start-of-level-run (sor) |
| 1046 // and end-of-level-run (eor) are used at level run boundaries. |
| 1047 // N2. Any remaining neutrals take the embedding direction. |
| 1048 for (var i$6 = 0; i$6 < len; ++i$6) { |
| 1049 if (isNeutral.test(types[i$6])) { |
| 1050 var end$1 = (void 0) |
| 1051 for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end
$1) {} |
| 1052 var before = (i$6 ? types[i$6-1] : outerType) == "L" |
| 1053 var after = (end$1 < len ? types[end$1] : outerType) == "L" |
| 1054 var replace$1 = before == after ? (before ? "L" : "R") : outerType |
| 1055 for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1 } |
| 1056 i$6 = end$1 - 1 |
| 1057 } |
| 1058 } |
| 1059 |
| 1060 // Here we depart from the documented algorithm, in order to avoid |
| 1061 // building up an actual levels array. Since there are only three |
| 1062 // levels (0, 1, 2) in an implementation that doesn't take |
| 1063 // explicit embedding into account, we can build up the order on |
| 1064 // the fly, without following the level-based algorithm. |
| 1065 var order = [], m |
| 1066 for (var i$7 = 0; i$7 < len;) { |
| 1067 if (countsAsLeft.test(types[i$7])) { |
| 1068 var start = i$7 |
| 1069 for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {} |
| 1070 order.push(new BidiSpan(0, start, i$7)) |
| 1071 } else { |
| 1072 var pos = i$7, at = order.length |
| 1073 for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {} |
| 1074 for (var j$2 = pos; j$2 < i$7;) { |
| 1075 if (countsAsNum.test(types[j$2])) { |
| 1076 if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)) } |
| 1077 var nstart = j$2 |
| 1078 for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {} |
| 1079 order.splice(at, 0, new BidiSpan(2, nstart, j$2)) |
| 1080 pos = j$2 |
| 1081 } else { ++j$2 } |
| 1082 } |
| 1083 if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)) } |
| 1084 } |
| 1085 } |
| 1086 if (order[0].level == 1 && (m = str.match(/^\s+/))) { |
| 1087 order[0].from = m[0].length |
| 1088 order.unshift(new BidiSpan(0, 0, m[0].length)) |
| 1089 } |
| 1090 if (lst(order).level == 1 && (m = str.match(/\s+$/))) { |
| 1091 lst(order).to -= m[0].length |
| 1092 order.push(new BidiSpan(0, len - m[0].length, len)) |
| 1093 } |
| 1094 |
| 1095 return direction == "rtl" ? order.reverse() : order |
| 1096 } |
| 1097 })() |
| 1098 |
| 1099 // Get the bidi ordering for the given line (and cache it). Returns |
| 1100 // false for lines that are fully left-to-right, and an array of |
| 1101 // BidiSpan objects otherwise. |
| 1102 function getOrder(line, direction) { |
| 1103 var order = line.order |
| 1104 if (order == null) { order = line.order = bidiOrdering(line.text, direction) } |
| 1105 return order |
| 1106 } |
| 1107 |
| 1108 function moveCharLogically(line, ch, dir) { |
| 1109 var target = skipExtendingChars(line.text, ch + dir, dir) |
| 1110 return target < 0 || target > line.text.length ? null : target |
| 1111 } |
| 1112 |
| 1113 function moveLogically(line, start, dir) { |
| 1114 var ch = moveCharLogically(line, start.ch, dir) |
| 1115 return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before
") |
| 1116 } |
| 1117 |
| 1118 function endOfLine(visually, cm, lineObj, lineNo, dir) { |
| 1119 if (visually) { |
| 1120 var order = getOrder(lineObj, cm.doc.direction) |
| 1121 if (order) { |
| 1122 var part = dir < 0 ? lst(order) : order[0] |
| 1123 var moveInStorageOrder = (dir < 0) == (part.level == 1) |
| 1124 var sticky = moveInStorageOrder ? "after" : "before" |
| 1125 var ch |
| 1126 // With a wrapped rtl chunk (possibly spanning multiple bidi parts), |
| 1127 // it could be that the last bidi part is not on the last visual line, |
| 1128 // since visual lines contain content order-consecutive chunks. |
| 1129 // Thus, in rtl, we are looking for the first (content-order) character |
| 1130 // in the rtl chunk that is on the last line (that is, the same line |
| 1131 // as the last (content-order) character). |
| 1132 if (part.level > 0) { |
| 1133 var prep = prepareMeasureForLine(cm, lineObj) |
| 1134 ch = dir < 0 ? lineObj.text.length - 1 : 0 |
| 1135 var targetTop = measureCharPrepared(cm, prep, ch).top |
| 1136 ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).
top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, c
h) |
| 1137 if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1, true) } |
| 1138 } else { ch = dir < 0 ? part.to : part.from } |
| 1139 return new Pos(lineNo, ch, sticky) |
| 1140 } |
| 1141 } |
| 1142 return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" :
"after") |
| 1143 } |
| 1144 |
| 1145 function moveVisually(cm, line, start, dir) { |
| 1146 var bidi = getOrder(line, cm.doc.direction) |
| 1147 if (!bidi) { return moveLogically(line, start, dir) } |
| 1148 if (start.ch >= line.text.length) { |
| 1149 start.ch = line.text.length |
| 1150 start.sticky = "before" |
| 1151 } else if (start.ch <= 0) { |
| 1152 start.ch = 0 |
| 1153 start.sticky = "after" |
| 1154 } |
| 1155 var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos
] |
| 1156 if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > s
tart.ch : part.from < start.ch)) { |
| 1157 // Case 1: We move within an ltr part in an ltr editor. Even with wrapped li
nes, |
| 1158 // nothing interesting happens. |
| 1159 return moveLogically(line, start, dir) |
| 1160 } |
| 1161 |
| 1162 var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof P
os ? pos.ch : pos, dir); } |
| 1163 var prep |
| 1164 var getWrappedLineExtent = function (ch) { |
| 1165 if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } |
| 1166 prep = prep || prepareMeasureForLine(cm, line) |
| 1167 return wrappedLineExtentChar(cm, line, prep, ch) |
| 1168 } |
| 1169 var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(sta
rt, -1) : start.ch) |
| 1170 |
| 1171 if (cm.doc.direction == "rtl" || part.level == 1) { |
| 1172 var moveInStorageOrder = (part.level == 1) == (dir < 0) |
| 1173 var ch = mv(start, moveInStorageOrder ? 1 : -1) |
| 1174 if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLin
eExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { |
| 1175 // Case 2: We move within an rtl part or in an rtl editor on the same visu
al line |
| 1176 var sticky = moveInStorageOrder ? "before" : "after" |
| 1177 return new Pos(start.line, ch, sticky) |
| 1178 } |
| 1179 } |
| 1180 |
| 1181 // Case 3: Could not move within this bidi part in this visual line, so leave |
| 1182 // the current bidi part |
| 1183 |
| 1184 var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { |
| 1185 var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder |
| 1186 ? new Pos(start.line, mv(ch, 1), "before") |
| 1187 : new Pos(start.line, ch, "after"); } |
| 1188 |
| 1189 for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { |
| 1190 var part = bidi[partPos] |
| 1191 var moveInStorageOrder = (dir > 0) == (part.level != 1) |
| 1192 var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExte
nt.end, -1) |
| 1193 if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrde
r) } |
| 1194 ch = moveInStorageOrder ? part.from : mv(part.to, -1) |
| 1195 if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return
getRes(ch, moveInStorageOrder) } |
| 1196 } |
| 1197 } |
| 1198 |
| 1199 // Case 3a: Look for other bidi parts on the same visual line |
| 1200 var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent) |
| 1201 if (res) { return res } |
| 1202 |
| 1203 // Case 3b: Look for other bidi parts on the next visual line |
| 1204 var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1) |
| 1205 if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { |
| 1206 res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineE
xtent(nextCh)) |
| 1207 if (res) { return res } |
| 1208 } |
| 1209 |
| 1210 // Case 4: Nowhere to move |
| 1211 return null |
| 1212 } |
| 1213 |
| 1214 // EVENT HANDLING |
| 1215 |
| 1216 // Lightweight event framework. on/off also work on DOM nodes, |
| 1217 // registering native DOM handlers. |
| 1218 |
| 1219 var noHandlers = [] |
| 1220 |
| 1221 var on = function(emitter, type, f) { |
| 1222 if (emitter.addEventListener) { |
| 1223 emitter.addEventListener(type, f, false) |
| 1224 } else if (emitter.attachEvent) { |
| 1225 emitter.attachEvent("on" + type, f) |
| 1226 } else { |
| 1227 var map = emitter._handlers || (emitter._handlers = {}) |
| 1228 map[type] = (map[type] || noHandlers).concat(f) |
| 1229 } |
| 1230 } |
| 1231 |
| 1232 function getHandlers(emitter, type) { |
| 1233 return emitter._handlers && emitter._handlers[type] || noHandlers |
| 1234 } |
| 1235 |
| 1236 function off(emitter, type, f) { |
| 1237 if (emitter.removeEventListener) { |
| 1238 emitter.removeEventListener(type, f, false) |
| 1239 } else if (emitter.detachEvent) { |
| 1240 emitter.detachEvent("on" + type, f) |
| 1241 } else { |
| 1242 var map = emitter._handlers, arr = map && map[type] |
| 1243 if (arr) { |
| 1244 var index = indexOf(arr, f) |
| 1245 if (index > -1) |
| 1246 { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)) } |
| 1247 } |
| 1248 } |
| 1249 } |
| 1250 |
| 1251 function signal(emitter, type /*, values...*/) { |
| 1252 var handlers = getHandlers(emitter, type) |
| 1253 if (!handlers.length) { return } |
| 1254 var args = Array.prototype.slice.call(arguments, 2) |
| 1255 for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args) } |
| 1256 } |
| 1257 |
| 1258 // The DOM events that CodeMirror handles can be overridden by |
| 1259 // registering a (non-DOM) handler on the editor for the event name, |
| 1260 // and preventDefault-ing the event in that handler. |
| 1261 function signalDOMEvent(cm, e, override) { |
| 1262 if (typeof e == "string") |
| 1263 { e = {type: e, preventDefault: function() { this.defaultPrevented = true }}
} |
| 1264 signal(cm, override || e.type, cm, e) |
| 1265 return e_defaultPrevented(e) || e.codemirrorIgnore |
| 1266 } |
| 1267 |
| 1268 function signalCursorActivity(cm) { |
| 1269 var arr = cm._handlers && cm._handlers.cursorActivity |
| 1270 if (!arr) { return } |
| 1271 var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers
= []) |
| 1272 for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) |
| 1273 { set.push(arr[i]) } } |
| 1274 } |
| 1275 |
| 1276 function hasHandler(emitter, type) { |
| 1277 return getHandlers(emitter, type).length > 0 |
| 1278 } |
| 1279 |
| 1280 // Add on and off methods to a constructor's prototype, to make |
| 1281 // registering events on such objects more convenient. |
| 1282 function eventMixin(ctor) { |
| 1283 ctor.prototype.on = function(type, f) {on(this, type, f)} |
| 1284 ctor.prototype.off = function(type, f) {off(this, type, f)} |
| 1285 } |
| 1286 |
| 1287 // Due to the fact that we still support jurassic IE versions, some |
| 1288 // compatibility wrappers are needed. |
| 1289 |
| 1290 function e_preventDefault(e) { |
| 1291 if (e.preventDefault) { e.preventDefault() } |
| 1292 else { e.returnValue = false } |
| 1293 } |
| 1294 function e_stopPropagation(e) { |
| 1295 if (e.stopPropagation) { e.stopPropagation() } |
| 1296 else { e.cancelBubble = true } |
| 1297 } |
| 1298 function e_defaultPrevented(e) { |
| 1299 return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == fals
e |
| 1300 } |
| 1301 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e)} |
| 1302 |
| 1303 function e_target(e) {return e.target || e.srcElement} |
| 1304 function e_button(e) { |
| 1305 var b = e.which |
| 1306 if (b == null) { |
| 1307 if (e.button & 1) { b = 1 } |
| 1308 else if (e.button & 2) { b = 3 } |
| 1309 else if (e.button & 4) { b = 2 } |
| 1310 } |
| 1311 if (mac && e.ctrlKey && b == 1) { b = 3 } |
| 1312 return b |
| 1313 } |
| 1314 |
| 1315 // Detect drag-and-drop |
| 1316 var dragAndDrop = function() { |
| 1317 // There is *some* kind of drag-and-drop support in IE6-8, but I |
| 1318 // couldn't get it to work yet. |
| 1319 if (ie && ie_version < 9) { return false } |
| 1320 var div = elt('div') |
| 1321 return "draggable" in div || "dragDrop" in div |
| 1322 }() |
| 1323 |
| 1324 var zwspSupported |
| 1325 function zeroWidthElement(measure) { |
| 1326 if (zwspSupported == null) { |
| 1327 var test = elt("span", "\u200b") |
| 1328 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x"
)])) |
| 1329 if (measure.firstChild.offsetHeight != 0) |
| 1330 { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie &
& ie_version < 8) } |
| 1331 } |
| 1332 var node = zwspSupported ? elt("span", "\u200b") : |
| 1333 elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right
: -1px") |
| 1334 node.setAttribute("cm-text", "") |
| 1335 return node |
| 1336 } |
| 1337 |
| 1338 // Feature-detect IE's crummy client rect reporting for bidi text |
| 1339 var badBidiRects |
| 1340 function hasBadBidiRects(measure) { |
| 1341 if (badBidiRects != null) { return badBidiRects } |
| 1342 var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")) |
| 1343 var r0 = range(txt, 0, 1).getBoundingClientRect() |
| 1344 var r1 = range(txt, 1, 2).getBoundingClientRect() |
| 1345 removeChildren(measure) |
| 1346 if (!r0 || r0.left == r0.right) { return false } // Safari returns null in som
e cases (#2780) |
| 1347 return badBidiRects = (r1.right - r0.right < 3) |
| 1348 } |
| 1349 |
| 1350 // See if "".split is the broken IE version, if so, provide an |
| 1351 // alternative way to split lines. |
| 1352 var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { |
| 1353 var pos = 0, result = [], l = string.length |
| 1354 while (pos <= l) { |
| 1355 var nl = string.indexOf("\n", pos) |
| 1356 if (nl == -1) { nl = string.length } |
| 1357 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl) |
| 1358 var rt = line.indexOf("\r") |
| 1359 if (rt != -1) { |
| 1360 result.push(line.slice(0, rt)) |
| 1361 pos += rt + 1 |
| 260 } else { | 1362 } else { |
| 261 rmClass(cm.display.wrapper, "CodeMirror-wrap"); | 1363 result.push(line) |
| 262 findMaxLine(cm); | 1364 pos = nl + 1 |
| 263 } | 1365 } |
| 264 estimateLineHeights(cm); | 1366 } |
| 265 regChange(cm); | 1367 return result |
| 266 clearCaches(cm); | 1368 } : function (string) { return string.split(/\r\n?|\n/); } |
| 267 setTimeout(function(){updateScrollbars(cm);}, 100); | 1369 |
| 268 } | 1370 var hasSelection = window.getSelection ? function (te) { |
| 269 | 1371 try { return te.selectionStart != te.selectionEnd } |
| 270 // Returns a function that estimates the height of a line, to use as | 1372 catch(e) { return false } |
| 271 // first approximation until the line becomes visible (and is thus | 1373 } : function (te) { |
| 272 // properly measurable). | 1374 var range |
| 273 function estimateHeight(cm) { | 1375 try {range = te.ownerDocument.selection.createRange()} |
| 274 var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; | 1376 catch(e) {} |
| 275 var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / char
Width(cm.display) - 3); | 1377 if (!range || range.parentElement() != te) { return false } |
| 276 return function(line) { | 1378 return range.compareEndPoints("StartToEnd", range) != 0 |
| 277 if (lineIsHidden(cm.doc, line)) return 0; | 1379 } |
| 278 | 1380 |
| 279 var widgetsHeight = 0; | 1381 var hasCopyEvent = (function () { |
| 280 if (line.widgets) for (var i = 0; i < line.widgets.length; i++) { | 1382 var e = elt("div") |
| 281 if (line.widgets[i].height) widgetsHeight += line.widgets[i].height; | 1383 if ("oncopy" in e) { return true } |
| 1384 e.setAttribute("oncopy", "return;") |
| 1385 return typeof e.oncopy == "function" |
| 1386 })() |
| 1387 |
| 1388 var badZoomedRects = null |
| 1389 function hasBadZoomedRects(measure) { |
| 1390 if (badZoomedRects != null) { return badZoomedRects } |
| 1391 var node = removeChildrenAndAdd(measure, elt("span", "x")) |
| 1392 var normal = node.getBoundingClientRect() |
| 1393 var fromRange = range(node, 0, 1).getBoundingClientRect() |
| 1394 return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 |
| 1395 } |
| 1396 |
| 1397 var modes = {}; |
| 1398 var mimeModes = {}; |
| 1399 // Extra arguments are stored as the mode's dependencies, which is |
| 1400 // used by (legacy) mechanisms like loadmode.js to automatically |
| 1401 // load a mode. (Preferred mechanism is the require/define calls.) |
| 1402 function defineMode(name, mode) { |
| 1403 if (arguments.length > 2) |
| 1404 { mode.dependencies = Array.prototype.slice.call(arguments, 2) } |
| 1405 modes[name] = mode |
| 1406 } |
| 1407 |
| 1408 function defineMIME(mime, spec) { |
| 1409 mimeModes[mime] = spec |
| 1410 } |
| 1411 |
| 1412 // Given a MIME type, a {name, ...options} config object, or a name |
| 1413 // string, return a mode config object. |
| 1414 function resolveMode(spec) { |
| 1415 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { |
| 1416 spec = mimeModes[spec] |
| 1417 } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(sp
ec.name)) { |
| 1418 var found = mimeModes[spec.name] |
| 1419 if (typeof found == "string") { found = {name: found} } |
| 1420 spec = createObj(found, spec) |
| 1421 spec.name = found.name |
| 1422 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { |
| 1423 return resolveMode("application/xml") |
| 1424 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { |
| 1425 return resolveMode("application/json") |
| 1426 } |
| 1427 if (typeof spec == "string") { return {name: spec} } |
| 1428 else { return spec || {name: "null"} } |
| 1429 } |
| 1430 |
| 1431 // Given a mode spec (anything that resolveMode accepts), find and |
| 1432 // initialize an actual mode object. |
| 1433 function getMode(options, spec) { |
| 1434 spec = resolveMode(spec) |
| 1435 var mfactory = modes[spec.name] |
| 1436 if (!mfactory) { return getMode(options, "text/plain") } |
| 1437 var modeObj = mfactory(options, spec) |
| 1438 if (modeExtensions.hasOwnProperty(spec.name)) { |
| 1439 var exts = modeExtensions[spec.name] |
| 1440 for (var prop in exts) { |
| 1441 if (!exts.hasOwnProperty(prop)) { continue } |
| 1442 if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop] } |
| 1443 modeObj[prop] = exts[prop] |
| 1444 } |
| 1445 } |
| 1446 modeObj.name = spec.name |
| 1447 if (spec.helperType) { modeObj.helperType = spec.helperType } |
| 1448 if (spec.modeProps) { for (var prop$1 in spec.modeProps) |
| 1449 { modeObj[prop$1] = spec.modeProps[prop$1] } } |
| 1450 |
| 1451 return modeObj |
| 1452 } |
| 1453 |
| 1454 // This can be used to attach properties to mode objects from |
| 1455 // outside the actual mode definition. |
| 1456 var modeExtensions = {} |
| 1457 function extendMode(mode, properties) { |
| 1458 var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeE
xtensions[mode] = {}) |
| 1459 copyObj(properties, exts) |
| 1460 } |
| 1461 |
| 1462 function copyState(mode, state) { |
| 1463 if (state === true) { return state } |
| 1464 if (mode.copyState) { return mode.copyState(state) } |
| 1465 var nstate = {} |
| 1466 for (var n in state) { |
| 1467 var val = state[n] |
| 1468 if (val instanceof Array) { val = val.concat([]) } |
| 1469 nstate[n] = val |
| 1470 } |
| 1471 return nstate |
| 1472 } |
| 1473 |
| 1474 // Given a mode and a state (for that mode), find the inner mode and |
| 1475 // state at the position that the state refers to. |
| 1476 function innerMode(mode, state) { |
| 1477 var info |
| 1478 while (mode.innerMode) { |
| 1479 info = mode.innerMode(state) |
| 1480 if (!info || info.mode == mode) { break } |
| 1481 state = info.state |
| 1482 mode = info.mode |
| 1483 } |
| 1484 return info || {mode: mode, state: state} |
| 1485 } |
| 1486 |
| 1487 function startState(mode, a1, a2) { |
| 1488 return mode.startState ? mode.startState(a1, a2) : true |
| 1489 } |
| 1490 |
| 1491 // STRING STREAM |
| 1492 |
| 1493 // Fed to the mode parsers, provides helper functions to make |
| 1494 // parsers more succinct. |
| 1495 |
| 1496 var StringStream = function StringStream(string, tabSize) { |
| 1497 this.pos = this.start = 0 |
| 1498 this.string = string |
| 1499 this.tabSize = tabSize || 8 |
| 1500 this.lastColumnPos = this.lastColumnValue = 0 |
| 1501 this.lineStart = 0 |
| 1502 }; |
| 1503 |
| 1504 StringStream.prototype.eol = function eol () {return this.pos >= this.string.len
gth}; |
| 1505 StringStream.prototype.sol = function sol () {return this.pos == this.lineStart}
; |
| 1506 StringStream.prototype.peek = function peek () {return this.string.charAt(this.p
os) || undefined}; |
| 1507 StringStream.prototype.next = function next () { |
| 1508 if (this.pos < this.string.length) |
| 1509 { return this.string.charAt(this.pos++) } |
| 1510 }; |
| 1511 StringStream.prototype.eat = function eat (match) { |
| 1512 var ch = this.string.charAt(this.pos) |
| 1513 var ok |
| 1514 if (typeof match == "string") { ok = ch == match } |
| 1515 else { ok = ch && (match.test ? match.test(ch) : match(ch)) } |
| 1516 if (ok) {++this.pos; return ch} |
| 1517 }; |
| 1518 StringStream.prototype.eatWhile = function eatWhile (match) { |
| 1519 var start = this.pos |
| 1520 while (this.eat(match)){} |
| 1521 return this.pos > start |
| 1522 }; |
| 1523 StringStream.prototype.eatSpace = function eatSpace () { |
| 1524 var this$1 = this; |
| 1525 |
| 1526 var start = this.pos |
| 1527 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos } |
| 1528 return this.pos > start |
| 1529 }; |
| 1530 StringStream.prototype.skipToEnd = function skipToEnd () {this.pos = this.string
.length}; |
| 1531 StringStream.prototype.skipTo = function skipTo (ch) { |
| 1532 var found = this.string.indexOf(ch, this.pos) |
| 1533 if (found > -1) {this.pos = found; return true} |
| 1534 }; |
| 1535 StringStream.prototype.backUp = function backUp (n) {this.pos -= n}; |
| 1536 StringStream.prototype.column = function column () { |
| 1537 if (this.lastColumnPos < this.start) { |
| 1538 this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, th
is.lastColumnPos, this.lastColumnValue) |
| 1539 this.lastColumnPos = this.start |
| 1540 } |
| 1541 return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.
lineStart, this.tabSize) : 0) |
| 1542 }; |
| 1543 StringStream.prototype.indentation = function indentation () { |
| 1544 return countColumn(this.string, null, this.tabSize) - |
| 1545 (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0
) |
| 1546 }; |
| 1547 StringStream.prototype.match = function match (pattern, consume, caseInsensitive
) { |
| 1548 if (typeof pattern == "string") { |
| 1549 var cased = function (str) { return caseInsensitive ? str.toLowerCase() : st
r; } |
| 1550 var substr = this.string.substr(this.pos, pattern.length) |
| 1551 if (cased(substr) == cased(pattern)) { |
| 1552 if (consume !== false) { this.pos += pattern.length } |
| 1553 return true |
| 1554 } |
| 1555 } else { |
| 1556 var match = this.string.slice(this.pos).match(pattern) |
| 1557 if (match && match.index > 0) { return null } |
| 1558 if (match && consume !== false) { this.pos += match[0].length } |
| 1559 return match |
| 1560 } |
| 1561 }; |
| 1562 StringStream.prototype.current = function current (){return this.string.slice(th
is.start, this.pos)}; |
| 1563 StringStream.prototype.hideFirstChars = function hideFirstChars (n, inner) { |
| 1564 this.lineStart += n |
| 1565 try { return inner() } |
| 1566 finally { this.lineStart -= n } |
| 1567 }; |
| 1568 |
| 1569 // Compute a style array (an array starting with a mode generation |
| 1570 // -- for invalidation -- followed by pairs of end positions and |
| 1571 // style strings), which is used to highlight the tokens on the |
| 1572 // line. |
| 1573 function highlightLine(cm, line, state, forceToEnd) { |
| 1574 // A styles array always starts with a number identifying the |
| 1575 // mode/overlays that it is based on (for easy invalidation). |
| 1576 var st = [cm.state.modeGen], lineClasses = {} |
| 1577 // Compute the base array of styles |
| 1578 runMode(cm, line.text, cm.doc.mode, state, function (end, style) { return st.p
ush(end, style); }, |
| 1579 lineClasses, forceToEnd) |
| 1580 |
| 1581 // Run overlays, adjust style array. |
| 1582 var loop = function ( o ) { |
| 1583 var overlay = cm.state.overlays[o], i = 1, at = 0 |
| 1584 runMode(cm, line.text, overlay.mode, true, function (end, style) { |
| 1585 var start = i |
| 1586 // Ensure there's a token end at the current position, and that i points a
t it |
| 1587 while (at < end) { |
| 1588 var i_end = st[i] |
| 1589 if (i_end > end) |
| 1590 { st.splice(i, 1, end, st[i+1], i_end) } |
| 1591 i += 2 |
| 1592 at = Math.min(end, i_end) |
| 282 } | 1593 } |
| 283 | 1594 if (!style) { return } |
| 284 if (wrapping) | 1595 if (overlay.opaque) { |
| 285 return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th
; | 1596 st.splice(start, i - start, end, "overlay " + style) |
| 286 else | 1597 i = start + 2 |
| 287 return widgetsHeight + th; | |
| 288 }; | |
| 289 } | |
| 290 | |
| 291 function estimateLineHeights(cm) { | |
| 292 var doc = cm.doc, est = estimateHeight(cm); | |
| 293 doc.iter(function(line) { | |
| 294 var estHeight = est(line); | |
| 295 if (estHeight != line.height) updateLineHeight(line, estHeight); | |
| 296 }); | |
| 297 } | |
| 298 | |
| 299 function themeChanged(cm) { | |
| 300 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s
-\S+/g, "") + | |
| 301 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); | |
| 302 clearCaches(cm); | |
| 303 } | |
| 304 | |
| 305 function guttersChanged(cm) { | |
| 306 updateGutters(cm); | |
| 307 regChange(cm); | |
| 308 setTimeout(function(){alignHorizontally(cm);}, 20); | |
| 309 } | |
| 310 | |
| 311 // Rebuild the gutter elements, ensure the margin to the left of the | |
| 312 // code matches their width. | |
| 313 function updateGutters(cm) { | |
| 314 var gutters = cm.display.gutters, specs = cm.options.gutters; | |
| 315 removeChildren(gutters); | |
| 316 for (var i = 0; i < specs.length; ++i) { | |
| 317 var gutterClass = specs[i]; | |
| 318 var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gut
terClass)); | |
| 319 if (gutterClass == "CodeMirror-linenumbers") { | |
| 320 cm.display.lineGutter = gElt; | |
| 321 gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; | |
| 322 } | |
| 323 } | |
| 324 gutters.style.display = i ? "" : "none"; | |
| 325 updateGutterSpace(cm); | |
| 326 } | |
| 327 | |
| 328 function updateGutterSpace(cm) { | |
| 329 var width = cm.display.gutters.offsetWidth; | |
| 330 cm.display.sizer.style.marginLeft = width + "px"; | |
| 331 } | |
| 332 | |
| 333 // Compute the character length of a line, taking into account | |
| 334 // collapsed ranges (see markText) that might hide parts, and join | |
| 335 // other lines onto it. | |
| 336 function lineLength(line) { | |
| 337 if (line.height == 0) return 0; | |
| 338 var len = line.text.length, merged, cur = line; | |
| 339 while (merged = collapsedSpanAtStart(cur)) { | |
| 340 var found = merged.find(0, true); | |
| 341 cur = found.from.line; | |
| 342 len += found.from.ch - found.to.ch; | |
| 343 } | |
| 344 cur = line; | |
| 345 while (merged = collapsedSpanAtEnd(cur)) { | |
| 346 var found = merged.find(0, true); | |
| 347 len -= cur.text.length - found.from.ch; | |
| 348 cur = found.to.line; | |
| 349 len += cur.text.length - found.to.ch; | |
| 350 } | |
| 351 return len; | |
| 352 } | |
| 353 | |
| 354 // Find the longest line in the document. | |
| 355 function findMaxLine(cm) { | |
| 356 var d = cm.display, doc = cm.doc; | |
| 357 d.maxLine = getLine(doc, doc.first); | |
| 358 d.maxLineLength = lineLength(d.maxLine); | |
| 359 d.maxLineChanged = true; | |
| 360 doc.iter(function(line) { | |
| 361 var len = lineLength(line); | |
| 362 if (len > d.maxLineLength) { | |
| 363 d.maxLineLength = len; | |
| 364 d.maxLine = line; | |
| 365 } | |
| 366 }); | |
| 367 } | |
| 368 | |
| 369 // Make sure the gutters options contains the element | |
| 370 // "CodeMirror-linenumbers" when the lineNumbers option is true. | |
| 371 function setGuttersForLineNumbers(options) { | |
| 372 var found = indexOf(options.gutters, "CodeMirror-linenumbers"); | |
| 373 if (found == -1 && options.lineNumbers) { | |
| 374 options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); | |
| 375 } else if (found > -1 && !options.lineNumbers) { | |
| 376 options.gutters = options.gutters.slice(0); | |
| 377 options.gutters.splice(found, 1); | |
| 378 } | |
| 379 } | |
| 380 | |
| 381 // SCROLLBARS | |
| 382 | |
| 383 // Prepare DOM reads needed to update the scrollbars. Done in one | |
| 384 // shot to minimize update/measure roundtrips. | |
| 385 function measureForScrollbars(cm) { | |
| 386 var d = cm.display, gutterW = d.gutters.offsetWidth; | |
| 387 var docH = Math.round(cm.doc.height + paddingVert(cm.display)); | |
| 388 return { | |
| 389 clientHeight: d.scroller.clientHeight, | |
| 390 viewHeight: d.wrapper.clientHeight, | |
| 391 scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, | |
| 392 viewWidth: d.wrapper.clientWidth, | |
| 393 barLeft: cm.options.fixedGutter ? gutterW : 0, | |
| 394 docHeight: docH, | |
| 395 scrollHeight: docH + scrollGap(cm) + d.barHeight, | |
| 396 nativeBarWidth: d.nativeBarWidth, | |
| 397 gutterWidth: gutterW | |
| 398 }; | |
| 399 } | |
| 400 | |
| 401 function NativeScrollbars(place, scroll, cm) { | |
| 402 this.cm = cm; | |
| 403 var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")]
, "CodeMirror-vscrollbar"); | |
| 404 var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; m
in-height: 1px")], "CodeMirror-hscrollbar"); | |
| 405 place(vert); place(horiz); | |
| 406 | |
| 407 on(vert, "scroll", function() { | |
| 408 if (vert.clientHeight) scroll(vert.scrollTop, "vertical"); | |
| 409 }); | |
| 410 on(horiz, "scroll", function() { | |
| 411 if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); | |
| 412 }); | |
| 413 | |
| 414 this.checkedZeroWidth = false; | |
| 415 // Need to set a minimum width to see the scrollbar on IE7 (but must not set
it on IE8). | |
| 416 if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWi
dth = "18px"; | |
| 417 } | |
| 418 | |
| 419 NativeScrollbars.prototype = copyObj({ | |
| 420 update: function(measure) { | |
| 421 var needsH = measure.scrollWidth > measure.clientWidth + 1; | |
| 422 var needsV = measure.scrollHeight > measure.clientHeight + 1; | |
| 423 var sWidth = measure.nativeBarWidth; | |
| 424 | |
| 425 if (needsV) { | |
| 426 this.vert.style.display = "block"; | |
| 427 this.vert.style.bottom = needsH ? sWidth + "px" : "0"; | |
| 428 var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); | |
| 429 // A bug in IE8 can cause this value to be negative, so guard it. | |
| 430 this.vert.firstChild.style.height = | |
| 431 Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight)
+ "px"; | |
| 432 } else { | 1598 } else { |
| 433 this.vert.style.display = ""; | 1599 for (; start < i; start += 2) { |
| 434 this.vert.firstChild.style.height = "0"; | 1600 var cur = st[start+1] |
| 435 } | 1601 st[start+1] = (cur ? cur + " " : "") + "overlay " + style |
| 436 | |
| 437 if (needsH) { | |
| 438 this.horiz.style.display = "block"; | |
| 439 this.horiz.style.right = needsV ? sWidth + "px" : "0"; | |
| 440 this.horiz.style.left = measure.barLeft + "px"; | |
| 441 var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth
: 0); | |
| 442 this.horiz.firstChild.style.width = | |
| 443 (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; | |
| 444 } else { | |
| 445 this.horiz.style.display = ""; | |
| 446 this.horiz.firstChild.style.width = "0"; | |
| 447 } | |
| 448 | |
| 449 if (!this.checkedZeroWidth && measure.clientHeight > 0) { | |
| 450 if (sWidth == 0) this.zeroWidthHack(); | |
| 451 this.checkedZeroWidth = true; | |
| 452 } | |
| 453 | |
| 454 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; | |
| 455 }, | |
| 456 setScrollLeft: function(pos) { | |
| 457 if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; | |
| 458 if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHor
iz); | |
| 459 }, | |
| 460 setScrollTop: function(pos) { | |
| 461 if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; | |
| 462 if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert)
; | |
| 463 }, | |
| 464 zeroWidthHack: function() { | |
| 465 var w = mac && !mac_geMountainLion ? "12px" : "18px"; | |
| 466 this.horiz.style.height = this.vert.style.width = w; | |
| 467 this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"; | |
| 468 this.disableHoriz = new Delayed; | |
| 469 this.disableVert = new Delayed; | |
| 470 }, | |
| 471 enableZeroWidthBar: function(bar, delay) { | |
| 472 bar.style.pointerEvents = "auto"; | |
| 473 function maybeDisable() { | |
| 474 // To find out whether the scrollbar is still visible, we | |
| 475 // check whether the element under the pixel in the bottom | |
| 476 // left corner of the scrollbar box is the scrollbar box | |
| 477 // itself (when the bar is still visible) or its filler child | |
| 478 // (when the bar is hidden). If it is still visible, we keep | |
| 479 // it enabled, if it's hidden, we disable pointer events. | |
| 480 var box = bar.getBoundingClientRect(); | |
| 481 var elt = document.elementFromPoint(box.left + 1, box.bottom - 1); | |
| 482 if (elt != bar) bar.style.pointerEvents = "none"; | |
| 483 else delay.set(1000, maybeDisable); | |
| 484 } | |
| 485 delay.set(1000, maybeDisable); | |
| 486 }, | |
| 487 clear: function() { | |
| 488 var parent = this.horiz.parentNode; | |
| 489 parent.removeChild(this.horiz); | |
| 490 parent.removeChild(this.vert); | |
| 491 } | |
| 492 }, NativeScrollbars.prototype); | |
| 493 | |
| 494 function NullScrollbars() {} | |
| 495 | |
| 496 NullScrollbars.prototype = copyObj({ | |
| 497 update: function() { return {bottom: 0, right: 0}; }, | |
| 498 setScrollLeft: function() {}, | |
| 499 setScrollTop: function() {}, | |
| 500 clear: function() {} | |
| 501 }, NullScrollbars.prototype); | |
| 502 | |
| 503 CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbar
s}; | |
| 504 | |
| 505 function initScrollbars(cm) { | |
| 506 if (cm.display.scrollbars) { | |
| 507 cm.display.scrollbars.clear(); | |
| 508 if (cm.display.scrollbars.addClass) | |
| 509 rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); | |
| 510 } | |
| 511 | |
| 512 cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarSt
yle](function(node) { | |
| 513 cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); | |
| 514 // Prevent clicks in the scrollbars from killing focus | |
| 515 on(node, "mousedown", function() { | |
| 516 if (cm.state.focused) setTimeout(function() { cm.display.input.focus();
}, 0); | |
| 517 }); | |
| 518 node.setAttribute("cm-not-content", "true"); | |
| 519 }, function(pos, axis) { | |
| 520 if (axis == "horizontal") setScrollLeft(cm, pos); | |
| 521 else setScrollTop(cm, pos); | |
| 522 }, cm); | |
| 523 if (cm.display.scrollbars.addClass) | |
| 524 addClass(cm.display.wrapper, cm.display.scrollbars.addClass); | |
| 525 } | |
| 526 | |
| 527 function updateScrollbars(cm, measure) { | |
| 528 if (!measure) measure = measureForScrollbars(cm); | |
| 529 var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; | |
| 530 updateScrollbarsInner(cm, measure); | |
| 531 for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight !=
cm.display.barHeight; i++) { | |
| 532 if (startWidth != cm.display.barWidth && cm.options.lineWrapping) | |
| 533 updateHeightsInViewport(cm); | |
| 534 updateScrollbarsInner(cm, measureForScrollbars(cm)); | |
| 535 startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; | |
| 536 } | |
| 537 } | |
| 538 | |
| 539 // Re-synchronize the fake scrollbars with the actual size of the | |
| 540 // content. | |
| 541 function updateScrollbarsInner(cm, measure) { | |
| 542 var d = cm.display; | |
| 543 var sizes = d.scrollbars.update(measure); | |
| 544 | |
| 545 d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; | |
| 546 d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; | |
| 547 d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" | |
| 548 | |
| 549 if (sizes.right && sizes.bottom) { | |
| 550 d.scrollbarFiller.style.display = "block"; | |
| 551 d.scrollbarFiller.style.height = sizes.bottom + "px"; | |
| 552 d.scrollbarFiller.style.width = sizes.right + "px"; | |
| 553 } else d.scrollbarFiller.style.display = ""; | |
| 554 if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixe
dGutter) { | |
| 555 d.gutterFiller.style.display = "block"; | |
| 556 d.gutterFiller.style.height = sizes.bottom + "px"; | |
| 557 d.gutterFiller.style.width = measure.gutterWidth + "px"; | |
| 558 } else d.gutterFiller.style.display = ""; | |
| 559 } | |
| 560 | |
| 561 // Compute the lines that are visible in a given viewport (defaults | |
| 562 // the the current scroll position). viewport may contain top, | |
| 563 // height, and ensure (see op.scrollToPos) properties. | |
| 564 function visibleLines(display, doc, viewport) { | |
| 565 var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : dis
play.scroller.scrollTop; | |
| 566 top = Math.floor(top - paddingTop(display)); | |
| 567 var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + d
isplay.wrapper.clientHeight; | |
| 568 | |
| 569 var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); | |
| 570 // Ensure is a {from: {line, ch}, to: {line, ch}} object, and | |
| 571 // forces those lines into the viewport (if possible). | |
| 572 if (viewport && viewport.ensure) { | |
| 573 var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.
line; | |
| 574 if (ensureFrom < from) { | |
| 575 from = ensureFrom; | |
| 576 to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.
wrapper.clientHeight); | |
| 577 } else if (Math.min(ensureTo, doc.lastLine()) >= to) { | |
| 578 from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.
wrapper.clientHeight); | |
| 579 to = ensureTo; | |
| 580 } | |
| 581 } | |
| 582 return {from: from, to: Math.max(to, from + 1)}; | |
| 583 } | |
| 584 | |
| 585 // LINE NUMBERS | |
| 586 | |
| 587 // Re-align line numbers and gutter marks to compensate for | |
| 588 // horizontal scrolling. | |
| 589 function alignHorizontally(cm) { | |
| 590 var display = cm.display, view = display.view; | |
| 591 if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fix
edGutter)) return; | |
| 592 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.
doc.scrollLeft; | |
| 593 var gutterW = display.gutters.offsetWidth, left = comp + "px"; | |
| 594 for (var i = 0; i < view.length; i++) if (!view[i].hidden) { | |
| 595 if (cm.options.fixedGutter) { | |
| 596 if (view[i].gutter) | |
| 597 view[i].gutter.style.left = left; | |
| 598 if (view[i].gutterBackground) | |
| 599 view[i].gutterBackground.style.left = left; | |
| 600 } | |
| 601 var align = view[i].alignable; | |
| 602 if (align) for (var j = 0; j < align.length; j++) | |
| 603 align[j].style.left = left; | |
| 604 } | |
| 605 if (cm.options.fixedGutter) | |
| 606 display.gutters.style.left = (comp + gutterW) + "px"; | |
| 607 } | |
| 608 | |
| 609 // Used to ensure that the line number gutter is still the right | |
| 610 // size for the current document size. Returns true when an update | |
| 611 // is needed. | |
| 612 function maybeUpdateLineNumberWidth(cm) { | |
| 613 if (!cm.options.lineNumbers) return false; | |
| 614 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1)
, display = cm.display; | |
| 615 if (last.length != display.lineNumChars) { | |
| 616 var test = display.measure.appendChild(elt("div", [elt("div", last)], | |
| 617 "CodeMirror-linenumber CodeMirr
or-gutter-elt")); | |
| 618 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - inn
erW; | |
| 619 display.lineGutter.style.width = ""; | |
| 620 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidt
h - padding) + 1; | |
| 621 display.lineNumWidth = display.lineNumInnerWidth + padding; | |
| 622 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; | |
| 623 display.lineGutter.style.width = display.lineNumWidth + "px"; | |
| 624 updateGutterSpace(cm); | |
| 625 return true; | |
| 626 } | |
| 627 return false; | |
| 628 } | |
| 629 | |
| 630 function lineNumberFor(options, i) { | |
| 631 return String(options.lineNumberFormatter(i + options.firstLineNumber)); | |
| 632 } | |
| 633 | |
| 634 // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, | |
| 635 // but using getBoundingClientRect to get a sub-pixel-accurate | |
| 636 // result. | |
| 637 function compensateForHScroll(display) { | |
| 638 return display.scroller.getBoundingClientRect().left - display.sizer.getBoun
dingClientRect().left; | |
| 639 } | |
| 640 | |
| 641 // DISPLAY DRAWING | |
| 642 | |
| 643 function DisplayUpdate(cm, viewport, force) { | |
| 644 var display = cm.display; | |
| 645 | |
| 646 this.viewport = viewport; | |
| 647 // Store some values that we'll need later (but don't want to force a relayo
ut for) | |
| 648 this.visible = visibleLines(display, cm.doc, viewport); | |
| 649 this.editorIsHidden = !display.wrapper.offsetWidth; | |
| 650 this.wrapperHeight = display.wrapper.clientHeight; | |
| 651 this.wrapperWidth = display.wrapper.clientWidth; | |
| 652 this.oldDisplayWidth = displayWidth(cm); | |
| 653 this.force = force; | |
| 654 this.dims = getDimensions(cm); | |
| 655 this.events = []; | |
| 656 } | |
| 657 | |
| 658 DisplayUpdate.prototype.signal = function(emitter, type) { | |
| 659 if (hasHandler(emitter, type)) | |
| 660 this.events.push(arguments); | |
| 661 }; | |
| 662 DisplayUpdate.prototype.finish = function() { | |
| 663 for (var i = 0; i < this.events.length; i++) | |
| 664 signal.apply(null, this.events[i]); | |
| 665 }; | |
| 666 | |
| 667 function maybeClipScrollbars(cm) { | |
| 668 var display = cm.display; | |
| 669 if (!display.scrollbarsClipped && display.scroller.offsetWidth) { | |
| 670 display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.c
lientWidth; | |
| 671 display.heightForcer.style.height = scrollGap(cm) + "px"; | |
| 672 display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; | |
| 673 display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; | |
| 674 display.scrollbarsClipped = true; | |
| 675 } | |
| 676 } | |
| 677 | |
| 678 // Does the actual updating of the line display. Bails out | |
| 679 // (returning false) when there is nothing to be done and forced is | |
| 680 // false. | |
| 681 function updateDisplayIfNeeded(cm, update) { | |
| 682 var display = cm.display, doc = cm.doc; | |
| 683 | |
| 684 if (update.editorIsHidden) { | |
| 685 resetView(cm); | |
| 686 return false; | |
| 687 } | |
| 688 | |
| 689 // Bail out if the visible area is already rendered and nothing changed. | |
| 690 if (!update.force && | |
| 691 update.visible.from >= display.viewFrom && update.visible.to <= display.
viewTo && | |
| 692 (display.updateLineNumbers == null || display.updateLineNumbers >= displ
ay.viewTo) && | |
| 693 display.renderedView == display.view && countDirtyView(cm) == 0) | |
| 694 return false; | |
| 695 | |
| 696 if (maybeUpdateLineNumberWidth(cm)) { | |
| 697 resetView(cm); | |
| 698 update.dims = getDimensions(cm); | |
| 699 } | |
| 700 | |
| 701 // Compute a suitable new viewport (from & to) | |
| 702 var end = doc.first + doc.size; | |
| 703 var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.fir
st); | |
| 704 var to = Math.min(end, update.visible.to + cm.options.viewportMargin); | |
| 705 if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max
(doc.first, display.viewFrom); | |
| 706 if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, disp
lay.viewTo); | |
| 707 if (sawCollapsedSpans) { | |
| 708 from = visualLineNo(cm.doc, from); | |
| 709 to = visualLineEndNo(cm.doc, to); | |
| 710 } | |
| 711 | |
| 712 var different = from != display.viewFrom || to != display.viewTo || | |
| 713 display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth !=
update.wrapperWidth; | |
| 714 adjustView(cm, from, to); | |
| 715 | |
| 716 display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); | |
| 717 // Position the mover div to align with the current scroll position | |
| 718 cm.display.mover.style.top = display.viewOffset + "px"; | |
| 719 | |
| 720 var toUpdate = countDirtyView(cm); | |
| 721 if (!different && toUpdate == 0 && !update.force && display.renderedView ==
display.view && | |
| 722 (display.updateLineNumbers == null || display.updateLineNumbers >= displ
ay.viewTo)) | |
| 723 return false; | |
| 724 | |
| 725 // For big changes, we hide the enclosing element during the | |
| 726 // update, since that speeds up the operations on most browsers. | |
| 727 var focused = activeElt(); | |
| 728 if (toUpdate > 4) display.lineDiv.style.display = "none"; | |
| 729 patchDisplay(cm, display.updateLineNumbers, update.dims); | |
| 730 if (toUpdate > 4) display.lineDiv.style.display = ""; | |
| 731 display.renderedView = display.view; | |
| 732 // There might have been a widget with a focused element that got | |
| 733 // hidden or updated, if so re-focus it. | |
| 734 if (focused && activeElt() != focused && focused.offsetHeight) focused.focus
(); | |
| 735 | |
| 736 // Prevent selection and cursors from interfering with the scroll | |
| 737 // width and height. | |
| 738 removeChildren(display.cursorDiv); | |
| 739 removeChildren(display.selectionDiv); | |
| 740 display.gutters.style.height = display.sizer.style.minHeight = 0; | |
| 741 | |
| 742 if (different) { | |
| 743 display.lastWrapHeight = update.wrapperHeight; | |
| 744 display.lastWrapWidth = update.wrapperWidth; | |
| 745 startWorker(cm, 400); | |
| 746 } | |
| 747 | |
| 748 display.updateLineNumbers = null; | |
| 749 | |
| 750 return true; | |
| 751 } | |
| 752 | |
| 753 function postUpdateDisplay(cm, update) { | |
| 754 var viewport = update.viewport; | |
| 755 | |
| 756 for (var first = true;; first = false) { | |
| 757 if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displa
yWidth(cm)) { | |
| 758 // Clip forced viewport to actual scrollable area. | |
| 759 if (viewport && viewport.top != null) | |
| 760 viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - di
splayHeight(cm), viewport.top)}; | |
| 761 // Updated line heights might result in the drawn area not | |
| 762 // actually covering the viewport. Keep looping until it does. | |
| 763 update.visible = visibleLines(cm.display, cm.doc, viewport); | |
| 764 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= c
m.display.viewTo) | |
| 765 break; | |
| 766 } | |
| 767 if (!updateDisplayIfNeeded(cm, update)) break; | |
| 768 updateHeightsInViewport(cm); | |
| 769 var barMeasure = measureForScrollbars(cm); | |
| 770 updateSelection(cm); | |
| 771 updateScrollbars(cm, barMeasure); | |
| 772 setDocumentHeight(cm, barMeasure); | |
| 773 } | |
| 774 | |
| 775 update.signal(cm, "update", cm); | |
| 776 if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo
!= cm.display.reportedViewTo) { | |
| 777 update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.vi
ewTo); | |
| 778 cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedView
To = cm.display.viewTo; | |
| 779 } | |
| 780 } | |
| 781 | |
| 782 function updateDisplaySimple(cm, viewport) { | |
| 783 var update = new DisplayUpdate(cm, viewport); | |
| 784 if (updateDisplayIfNeeded(cm, update)) { | |
| 785 updateHeightsInViewport(cm); | |
| 786 postUpdateDisplay(cm, update); | |
| 787 var barMeasure = measureForScrollbars(cm); | |
| 788 updateSelection(cm); | |
| 789 updateScrollbars(cm, barMeasure); | |
| 790 setDocumentHeight(cm, barMeasure); | |
| 791 update.finish(); | |
| 792 } | |
| 793 } | |
| 794 | |
| 795 function setDocumentHeight(cm, measure) { | |
| 796 cm.display.sizer.style.minHeight = measure.docHeight + "px"; | |
| 797 cm.display.heightForcer.style.top = measure.docHeight + "px"; | |
| 798 cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight
+ scrollGap(cm)) + "px"; | |
| 799 } | |
| 800 | |
| 801 // Read the actual heights of the rendered lines, and update their | |
| 802 // stored heights to match. | |
| 803 function updateHeightsInViewport(cm) { | |
| 804 var display = cm.display; | |
| 805 var prevBottom = display.lineDiv.offsetTop; | |
| 806 for (var i = 0; i < display.view.length; i++) { | |
| 807 var cur = display.view[i], height; | |
| 808 if (cur.hidden) continue; | |
| 809 if (ie && ie_version < 8) { | |
| 810 var bot = cur.node.offsetTop + cur.node.offsetHeight; | |
| 811 height = bot - prevBottom; | |
| 812 prevBottom = bot; | |
| 813 } else { | |
| 814 var box = cur.node.getBoundingClientRect(); | |
| 815 height = box.bottom - box.top; | |
| 816 } | |
| 817 var diff = cur.line.height - height; | |
| 818 if (height < 2) height = textHeight(display); | |
| 819 if (diff > .001 || diff < -.001) { | |
| 820 updateLineHeight(cur.line, height); | |
| 821 updateWidgetHeight(cur.line); | |
| 822 if (cur.rest) for (var j = 0; j < cur.rest.length; j++) | |
| 823 updateWidgetHeight(cur.rest[j]); | |
| 824 } | |
| 825 } | |
| 826 } | |
| 827 | |
| 828 // Read and store the height of line widgets associated with the | |
| 829 // given line. | |
| 830 function updateWidgetHeight(line) { | |
| 831 if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) | |
| 832 line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight; | |
| 833 } | |
| 834 | |
| 835 // Do a bulk-read of the DOM positions and sizes needed to draw the | |
| 836 // view, so that we don't interleave reading and writing to the DOM. | |
| 837 function getDimensions(cm) { | |
| 838 var d = cm.display, left = {}, width = {}; | |
| 839 var gutterLeft = d.gutters.clientLeft; | |
| 840 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { | |
| 841 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; | |
| 842 width[cm.options.gutters[i]] = n.clientWidth; | |
| 843 } | |
| 844 return {fixedPos: compensateForHScroll(d), | |
| 845 gutterTotalWidth: d.gutters.offsetWidth, | |
| 846 gutterLeft: left, | |
| 847 gutterWidth: width, | |
| 848 wrapperWidth: d.wrapper.clientWidth}; | |
| 849 } | |
| 850 | |
| 851 // Sync the actual display DOM structure with display.view, removing | |
| 852 // nodes for lines that are no longer in view, and creating the ones | |
| 853 // that are not there yet, and updating the ones that are out of | |
| 854 // date. | |
| 855 function patchDisplay(cm, updateNumbersFrom, dims) { | |
| 856 var display = cm.display, lineNumbers = cm.options.lineNumbers; | |
| 857 var container = display.lineDiv, cur = container.firstChild; | |
| 858 | |
| 859 function rm(node) { | |
| 860 var next = node.nextSibling; | |
| 861 // Works around a throw-scroll bug in OS X Webkit | |
| 862 if (webkit && mac && cm.display.currentWheelTarget == node) | |
| 863 node.style.display = "none"; | |
| 864 else | |
| 865 node.parentNode.removeChild(node); | |
| 866 return next; | |
| 867 } | |
| 868 | |
| 869 var view = display.view, lineN = display.viewFrom; | |
| 870 // Loop over the elements in the view, syncing cur (the DOM nodes | |
| 871 // in display.lineDiv) with the view as we go. | |
| 872 for (var i = 0; i < view.length; i++) { | |
| 873 var lineView = view[i]; | |
| 874 if (lineView.hidden) { | |
| 875 } else if (!lineView.node || lineView.node.parentNode != container) { // N
ot drawn yet | |
| 876 var node = buildLineElement(cm, lineView, lineN, dims); | |
| 877 container.insertBefore(node, cur); | |
| 878 } else { // Already drawn | |
| 879 while (cur != lineView.node) cur = rm(cur); | |
| 880 var updateNumber = lineNumbers && updateNumbersFrom != null && | |
| 881 updateNumbersFrom <= lineN && lineView.lineNumber; | |
| 882 if (lineView.changes) { | |
| 883 if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; | |
| 884 updateLineForChanges(cm, lineView, lineN, dims); | |
| 885 } | |
| 886 if (updateNumber) { | |
| 887 removeChildren(lineView.lineNumber); | |
| 888 lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(
cm.options, lineN))); | |
| 889 } | |
| 890 cur = lineView.node.nextSibling; | |
| 891 } | |
| 892 lineN += lineView.size; | |
| 893 } | |
| 894 while (cur) cur = rm(cur); | |
| 895 } | |
| 896 | |
| 897 // When an aspect of a line changes, a string is added to | |
| 898 // lineView.changes. This updates the relevant part of the line's | |
| 899 // DOM structure. | |
| 900 function updateLineForChanges(cm, lineView, lineN, dims) { | |
| 901 for (var j = 0; j < lineView.changes.length; j++) { | |
| 902 var type = lineView.changes[j]; | |
| 903 if (type == "text") updateLineText(cm, lineView); | |
| 904 else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); | |
| 905 else if (type == "class") updateLineClasses(lineView); | |
| 906 else if (type == "widget") updateLineWidgets(cm, lineView, dims); | |
| 907 } | |
| 908 lineView.changes = null; | |
| 909 } | |
| 910 | |
| 911 // Lines with gutter elements, widgets or a background class need to | |
| 912 // be wrapped, and have the extra elements added to the wrapper div | |
| 913 function ensureLineWrapped(lineView) { | |
| 914 if (lineView.node == lineView.text) { | |
| 915 lineView.node = elt("div", null, null, "position: relative"); | |
| 916 if (lineView.text.parentNode) | |
| 917 lineView.text.parentNode.replaceChild(lineView.node, lineView.text); | |
| 918 lineView.node.appendChild(lineView.text); | |
| 919 if (ie && ie_version < 8) lineView.node.style.zIndex = 2; | |
| 920 } | |
| 921 return lineView.node; | |
| 922 } | |
| 923 | |
| 924 function updateLineBackground(lineView) { | |
| 925 var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass
|| "") : lineView.line.bgClass; | |
| 926 if (cls) cls += " CodeMirror-linebackground"; | |
| 927 if (lineView.background) { | |
| 928 if (cls) lineView.background.className = cls; | |
| 929 else { lineView.background.parentNode.removeChild(lineView.background); li
neView.background = null; } | |
| 930 } else if (cls) { | |
| 931 var wrap = ensureLineWrapped(lineView); | |
| 932 lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstC
hild); | |
| 933 } | |
| 934 } | |
| 935 | |
| 936 // Wrapper around buildLineContent which will reuse the structure | |
| 937 // in display.externalMeasured when possible. | |
| 938 function getLineContent(cm, lineView) { | |
| 939 var ext = cm.display.externalMeasured; | |
| 940 if (ext && ext.line == lineView.line) { | |
| 941 cm.display.externalMeasured = null; | |
| 942 lineView.measure = ext.measure; | |
| 943 return ext.built; | |
| 944 } | |
| 945 return buildLineContent(cm, lineView); | |
| 946 } | |
| 947 | |
| 948 // Redraw the line's text. Interacts with the background and text | |
| 949 // classes because the mode may output tokens that influence these | |
| 950 // classes. | |
| 951 function updateLineText(cm, lineView) { | |
| 952 var cls = lineView.text.className; | |
| 953 var built = getLineContent(cm, lineView); | |
| 954 if (lineView.text == lineView.node) lineView.node = built.pre; | |
| 955 lineView.text.parentNode.replaceChild(built.pre, lineView.text); | |
| 956 lineView.text = built.pre; | |
| 957 if (built.bgClass != lineView.bgClass || built.textClass != lineView.textCla
ss) { | |
| 958 lineView.bgClass = built.bgClass; | |
| 959 lineView.textClass = built.textClass; | |
| 960 updateLineClasses(lineView); | |
| 961 } else if (cls) { | |
| 962 lineView.text.className = cls; | |
| 963 } | |
| 964 } | |
| 965 | |
| 966 function updateLineClasses(lineView) { | |
| 967 updateLineBackground(lineView); | |
| 968 if (lineView.line.wrapClass) | |
| 969 ensureLineWrapped(lineView).className = lineView.line.wrapClass; | |
| 970 else if (lineView.node != lineView.text) | |
| 971 lineView.node.className = ""; | |
| 972 var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.li
ne.textClass || "") : lineView.line.textClass; | |
| 973 lineView.text.className = textClass || ""; | |
| 974 } | |
| 975 | |
| 976 function updateLineGutter(cm, lineView, lineN, dims) { | |
| 977 if (lineView.gutter) { | |
| 978 lineView.node.removeChild(lineView.gutter); | |
| 979 lineView.gutter = null; | |
| 980 } | |
| 981 if (lineView.gutterBackground) { | |
| 982 lineView.node.removeChild(lineView.gutterBackground); | |
| 983 lineView.gutterBackground = null; | |
| 984 } | |
| 985 if (lineView.line.gutterClass) { | |
| 986 var wrap = ensureLineWrapped(lineView); | |
| 987 lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background
" + lineView.line.gutterClass, | |
| 988 "left: " + (cm.options.fixedGutter ? dims.
fixedPos : -dims.gutterTotalWidth) + | |
| 989 "px; width: " + dims.gutterTotalWidth + "p
x"); | |
| 990 wrap.insertBefore(lineView.gutterBackground, lineView.text); | |
| 991 } | |
| 992 var markers = lineView.line.gutterMarkers; | |
| 993 if (cm.options.lineNumbers || markers) { | |
| 994 var wrap = ensureLineWrapped(lineView); | |
| 995 var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wra
pper", "left: " + | |
| 996 (cm.options.fixedGutter ? dims.fixe
dPos : -dims.gutterTotalWidth) + "px"); | |
| 997 cm.display.input.setUneditable(gutterWrap); | |
| 998 wrap.insertBefore(gutterWrap, lineView.text); | |
| 999 if (lineView.line.gutterClass) | |
| 1000 gutterWrap.className += " " + lineView.line.gutterClass; | |
| 1001 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumber
s"])) | |
| 1002 lineView.lineNumber = gutterWrap.appendChild( | |
| 1003 elt("div", lineNumberFor(cm.options, lineN), | |
| 1004 "CodeMirror-linenumber CodeMirror-gutter-elt", | |
| 1005 "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width:
" | |
| 1006 + cm.display.lineNumInnerWidth + "px")); | |
| 1007 if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { | |
| 1008 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && ma
rkers[id]; | |
| 1009 if (found) | |
| 1010 gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "l
eft: " + | |
| 1011 dims.gutterLeft[id] + "px; width: " + dims.
gutterWidth[id] + "px")); | |
| 1012 } | |
| 1013 } | |
| 1014 } | |
| 1015 | |
| 1016 function updateLineWidgets(cm, lineView, dims) { | |
| 1017 if (lineView.alignable) lineView.alignable = null; | |
| 1018 for (var node = lineView.node.firstChild, next; node; node = next) { | |
| 1019 var next = node.nextSibling; | |
| 1020 if (node.className == "CodeMirror-linewidget") | |
| 1021 lineView.node.removeChild(node); | |
| 1022 } | |
| 1023 insertLineWidgets(cm, lineView, dims); | |
| 1024 } | |
| 1025 | |
| 1026 // Build a line's DOM representation from scratch | |
| 1027 function buildLineElement(cm, lineView, lineN, dims) { | |
| 1028 var built = getLineContent(cm, lineView); | |
| 1029 lineView.text = lineView.node = built.pre; | |
| 1030 if (built.bgClass) lineView.bgClass = built.bgClass; | |
| 1031 if (built.textClass) lineView.textClass = built.textClass; | |
| 1032 | |
| 1033 updateLineClasses(lineView); | |
| 1034 updateLineGutter(cm, lineView, lineN, dims); | |
| 1035 insertLineWidgets(cm, lineView, dims); | |
| 1036 return lineView.node; | |
| 1037 } | |
| 1038 | |
| 1039 // A lineView may contain multiple logical lines (when merged by | |
| 1040 // collapsed spans). The widgets for all of them need to be drawn. | |
| 1041 function insertLineWidgets(cm, lineView, dims) { | |
| 1042 insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); | |
| 1043 if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) | |
| 1044 insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); | |
| 1045 } | |
| 1046 | |
| 1047 function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { | |
| 1048 if (!line.widgets) return; | |
| 1049 var wrap = ensureLineWrapped(lineView); | |
| 1050 for (var i = 0, ws = line.widgets; i < ws.length; ++i) { | |
| 1051 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidge
t"); | |
| 1052 if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true
"); | |
| 1053 positionLineWidget(widget, node, lineView, dims); | |
| 1054 cm.display.input.setUneditable(node); | |
| 1055 if (allowAbove && widget.above) | |
| 1056 wrap.insertBefore(node, lineView.gutter || lineView.text); | |
| 1057 else | |
| 1058 wrap.appendChild(node); | |
| 1059 signalLater(widget, "redraw"); | |
| 1060 } | |
| 1061 } | |
| 1062 | |
| 1063 function positionLineWidget(widget, node, lineView, dims) { | |
| 1064 if (widget.noHScroll) { | |
| 1065 (lineView.alignable || (lineView.alignable = [])).push(node); | |
| 1066 var width = dims.wrapperWidth; | |
| 1067 node.style.left = dims.fixedPos + "px"; | |
| 1068 if (!widget.coverGutter) { | |
| 1069 width -= dims.gutterTotalWidth; | |
| 1070 node.style.paddingLeft = dims.gutterTotalWidth + "px"; | |
| 1071 } | |
| 1072 node.style.width = width + "px"; | |
| 1073 } | |
| 1074 if (widget.coverGutter) { | |
| 1075 node.style.zIndex = 5; | |
| 1076 node.style.position = "relative"; | |
| 1077 if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "p
x"; | |
| 1078 } | |
| 1079 } | |
| 1080 | |
| 1081 // POSITION OBJECT | |
| 1082 | |
| 1083 // A Pos instance represents a position within the text. | |
| 1084 var Pos = CodeMirror.Pos = function(line, ch) { | |
| 1085 if (!(this instanceof Pos)) return new Pos(line, ch); | |
| 1086 this.line = line; this.ch = ch; | |
| 1087 }; | |
| 1088 | |
| 1089 // Compare two positions, return 0 if they are the same, a negative | |
| 1090 // number when a is less, and a positive number otherwise. | |
| 1091 var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch
- b.ch; }; | |
| 1092 | |
| 1093 function copyPos(x) {return Pos(x.line, x.ch);} | |
| 1094 function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } | |
| 1095 function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } | |
| 1096 | |
| 1097 // INPUT HANDLING | |
| 1098 | |
| 1099 function ensureFocus(cm) { | |
| 1100 if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } | |
| 1101 } | |
| 1102 | |
| 1103 // This will be set to a {lineWise: bool, text: [string]} object, so | |
| 1104 // that, when pasting, we know what kind of selections the copied | |
| 1105 // text was made out of. | |
| 1106 var lastCopied = null; | |
| 1107 | |
| 1108 function applyTextInput(cm, inserted, deleted, sel, origin) { | |
| 1109 var doc = cm.doc; | |
| 1110 cm.display.shift = false; | |
| 1111 if (!sel) sel = doc.sel; | |
| 1112 | |
| 1113 var paste = cm.state.pasteIncoming || origin == "paste"; | |
| 1114 var textLines = doc.splitLines(inserted), multiPaste = null | |
| 1115 // When pasing N lines into N selections, insert one line per selection | |
| 1116 if (paste && sel.ranges.length > 1) { | |
| 1117 if (lastCopied && lastCopied.text.join("\n") == inserted) { | |
| 1118 if (sel.ranges.length % lastCopied.text.length == 0) { | |
| 1119 multiPaste = []; | |
| 1120 for (var i = 0; i < lastCopied.text.length; i++) | |
| 1121 multiPaste.push(doc.splitLines(lastCopied.text[i])); | |
| 1122 } | |
| 1123 } else if (textLines.length == sel.ranges.length) { | |
| 1124 multiPaste = map(textLines, function(l) { return [l]; }); | |
| 1125 } | |
| 1126 } | |
| 1127 | |
| 1128 // Normal behavior is to insert the new text into every selection | |
| 1129 for (var i = sel.ranges.length - 1; i >= 0; i--) { | |
| 1130 var range = sel.ranges[i]; | |
| 1131 var from = range.from(), to = range.to(); | |
| 1132 if (range.empty()) { | |
| 1133 if (deleted && deleted > 0) // Handle deletion | |
| 1134 from = Pos(from.line, from.ch - deleted); | |
| 1135 else if (cm.state.overwrite && !paste) // Handle overwrite | |
| 1136 to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch +
lst(textLines).length)); | |
| 1137 else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n")
== inserted) | |
| 1138 from = to = Pos(from.line, 0) | |
| 1139 } | |
| 1140 var updateInput = cm.curOp.updateInput; | |
| 1141 var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % m
ultiPaste.length] : textLines, | |
| 1142 origin: origin || (paste ? "paste" : cm.state.cutIncomi
ng ? "cut" : "+input")}; | |
| 1143 makeChange(cm.doc, changeEvent); | |
| 1144 signalLater(cm, "inputRead", cm, changeEvent); | |
| 1145 } | |
| 1146 if (inserted && !paste) | |
| 1147 triggerElectric(cm, inserted); | |
| 1148 | |
| 1149 ensureCursorVisible(cm); | |
| 1150 cm.curOp.updateInput = updateInput; | |
| 1151 cm.curOp.typing = true; | |
| 1152 cm.state.pasteIncoming = cm.state.cutIncoming = false; | |
| 1153 } | |
| 1154 | |
| 1155 function handlePaste(e, cm) { | |
| 1156 var pasted = e.clipboardData && e.clipboardData.getData("text/plain"); | |
| 1157 if (pasted) { | |
| 1158 e.preventDefault(); | |
| 1159 if (!cm.isReadOnly() && !cm.options.disableInput) | |
| 1160 runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); }
); | |
| 1161 return true; | |
| 1162 } | |
| 1163 } | |
| 1164 | |
| 1165 function triggerElectric(cm, inserted) { | |
| 1166 // When an 'electric' character is inserted, immediately trigger a reindent | |
| 1167 if (!cm.options.electricChars || !cm.options.smartIndent) return; | |
| 1168 var sel = cm.doc.sel; | |
| 1169 | |
| 1170 for (var i = sel.ranges.length - 1; i >= 0; i--) { | |
| 1171 var range = sel.ranges[i]; | |
| 1172 if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head
.line)) continue; | |
| 1173 var mode = cm.getModeAt(range.head); | |
| 1174 var indented = false; | |
| 1175 if (mode.electricChars) { | |
| 1176 for (var j = 0; j < mode.electricChars.length; j++) | |
| 1177 if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { | |
| 1178 indented = indentLine(cm, range.head.line, "smart"); | |
| 1179 break; | |
| 1180 } | |
| 1181 } else if (mode.electricInput) { | |
| 1182 if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(
0, range.head.ch))) | |
| 1183 indented = indentLine(cm, range.head.line, "smart"); | |
| 1184 } | |
| 1185 if (indented) signalLater(cm, "electricInput", cm, range.head.line); | |
| 1186 } | |
| 1187 } | |
| 1188 | |
| 1189 function copyableRanges(cm) { | |
| 1190 var text = [], ranges = []; | |
| 1191 for (var i = 0; i < cm.doc.sel.ranges.length; i++) { | |
| 1192 var line = cm.doc.sel.ranges[i].head.line; | |
| 1193 var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; | |
| 1194 ranges.push(lineRange); | |
| 1195 text.push(cm.getRange(lineRange.anchor, lineRange.head)); | |
| 1196 } | |
| 1197 return {text: text, ranges: ranges}; | |
| 1198 } | |
| 1199 | |
| 1200 function disableBrowserMagic(field) { | |
| 1201 field.setAttribute("autocorrect", "off"); | |
| 1202 field.setAttribute("autocapitalize", "off"); | |
| 1203 field.setAttribute("spellcheck", "false"); | |
| 1204 } | |
| 1205 | |
| 1206 // TEXTAREA INPUT STYLE | |
| 1207 | |
| 1208 function TextareaInput(cm) { | |
| 1209 this.cm = cm; | |
| 1210 // See input.poll and input.reset | |
| 1211 this.prevInput = ""; | |
| 1212 | |
| 1213 // Flag that indicates whether we expect input to appear real soon | |
| 1214 // now (after some event like 'keypress' or 'input') and are | |
| 1215 // polling intensively. | |
| 1216 this.pollingFast = false; | |
| 1217 // Self-resetting timeout for the poller | |
| 1218 this.polling = new Delayed(); | |
| 1219 // Tracks when input.reset has punted to just putting a short | |
| 1220 // string into the textarea instead of the full selection. | |
| 1221 this.inaccurateSelection = false; | |
| 1222 // Used to work around IE issue with selection being forgotten when focus mo
ves away from textarea | |
| 1223 this.hasSelection = false; | |
| 1224 this.composing = null; | |
| 1225 }; | |
| 1226 | |
| 1227 function hiddenTextarea() { | |
| 1228 var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padd
ing: 0; width: 1px; height: 1em; outline: none"); | |
| 1229 var div = elt("div", [te], null, "overflow: hidden; position: relative; widt
h: 3px; height: 0px;"); | |
| 1230 // The textarea is kept positioned near the cursor to prevent the | |
| 1231 // fact that it'll be scrolled into view on input from scrolling | |
| 1232 // our fake cursor out of view. On webkit, when wrap=off, paste is | |
| 1233 // very slow. So make the area wide instead. | |
| 1234 if (webkit) te.style.width = "1000px"; | |
| 1235 else te.setAttribute("wrap", "off"); | |
| 1236 // If border: 0; -- iOS fails to open keyboard (issue #1287) | |
| 1237 if (ios) te.style.border = "1px solid black"; | |
| 1238 disableBrowserMagic(te); | |
| 1239 return div; | |
| 1240 } | |
| 1241 | |
| 1242 TextareaInput.prototype = copyObj({ | |
| 1243 init: function(display) { | |
| 1244 var input = this, cm = this.cm; | |
| 1245 | |
| 1246 // Wraps and hides input textarea | |
| 1247 var div = this.wrapper = hiddenTextarea(); | |
| 1248 // The semihidden textarea that is focused when the editor is | |
| 1249 // focused, and receives input. | |
| 1250 var te = this.textarea = div.firstChild; | |
| 1251 display.wrapper.insertBefore(div, display.wrapper.firstChild); | |
| 1252 | |
| 1253 // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem
to work in iOS 8 anymore) | |
| 1254 if (ios) te.style.width = "0px"; | |
| 1255 | |
| 1256 on(te, "input", function() { | |
| 1257 if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = nu
ll; | |
| 1258 input.poll(); | |
| 1259 }); | |
| 1260 | |
| 1261 on(te, "paste", function(e) { | |
| 1262 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return | |
| 1263 | |
| 1264 cm.state.pasteIncoming = true; | |
| 1265 input.fastPoll(); | |
| 1266 }); | |
| 1267 | |
| 1268 function prepareCopyCut(e) { | |
| 1269 if (signalDOMEvent(cm, e)) return | |
| 1270 if (cm.somethingSelected()) { | |
| 1271 lastCopied = {lineWise: false, text: cm.getSelections()}; | |
| 1272 if (input.inaccurateSelection) { | |
| 1273 input.prevInput = ""; | |
| 1274 input.inaccurateSelection = false; | |
| 1275 te.value = lastCopied.text.join("\n"); | |
| 1276 selectInput(te); | |
| 1277 } | |
| 1278 } else if (!cm.options.lineWiseCopyCut) { | |
| 1279 return; | |
| 1280 } else { | |
| 1281 var ranges = copyableRanges(cm); | |
| 1282 lastCopied = {lineWise: true, text: ranges.text}; | |
| 1283 if (e.type == "cut") { | |
| 1284 cm.setSelections(ranges.ranges, null, sel_dontScroll); | |
| 1285 } else { | |
| 1286 input.prevInput = ""; | |
| 1287 te.value = ranges.text.join("\n"); | |
| 1288 selectInput(te); | |
| 1289 } | |
| 1290 } | |
| 1291 if (e.type == "cut") cm.state.cutIncoming = true; | |
| 1292 } | |
| 1293 on(te, "cut", prepareCopyCut); | |
| 1294 on(te, "copy", prepareCopyCut); | |
| 1295 | |
| 1296 on(display.scroller, "paste", function(e) { | |
| 1297 if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return; | |
| 1298 cm.state.pasteIncoming = true; | |
| 1299 input.focus(); | |
| 1300 }); | |
| 1301 | |
| 1302 // Prevent normal selection in the editor (we handle our own) | |
| 1303 on(display.lineSpace, "selectstart", function(e) { | |
| 1304 if (!eventInWidget(display, e)) e_preventDefault(e); | |
| 1305 }); | |
| 1306 | |
| 1307 on(te, "compositionstart", function() { | |
| 1308 var start = cm.getCursor("from"); | |
| 1309 if (input.composing) input.composing.range.clear() | |
| 1310 input.composing = { | |
| 1311 start: start, | |
| 1312 range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-
composing"}) | |
| 1313 }; | |
| 1314 }); | |
| 1315 on(te, "compositionend", function() { | |
| 1316 if (input.composing) { | |
| 1317 input.poll(); | |
| 1318 input.composing.range.clear(); | |
| 1319 input.composing = null; | |
| 1320 } | |
| 1321 }); | |
| 1322 }, | |
| 1323 | |
| 1324 prepareSelection: function() { | |
| 1325 // Redraw the selection and/or cursor | |
| 1326 var cm = this.cm, display = cm.display, doc = cm.doc; | |
| 1327 var result = prepareSelection(cm); | |
| 1328 | |
| 1329 // Move the hidden textarea near the cursor to prevent scrolling artifacts | |
| 1330 if (cm.options.moveInputWithCursor) { | |
| 1331 var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); | |
| 1332 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display
.lineDiv.getBoundingClientRect(); | |
| 1333 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, | |
| 1334 headPos.top + lineOff.top - wrapOff.
top)); | |
| 1335 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, | |
| 1336 headPos.left + lineOff.left - wrapO
ff.left)); | |
| 1337 } | |
| 1338 | |
| 1339 return result; | |
| 1340 }, | |
| 1341 | |
| 1342 showSelection: function(drawn) { | |
| 1343 var cm = this.cm, display = cm.display; | |
| 1344 removeChildrenAndAdd(display.cursorDiv, drawn.cursors); | |
| 1345 removeChildrenAndAdd(display.selectionDiv, drawn.selection); | |
| 1346 if (drawn.teTop != null) { | |
| 1347 this.wrapper.style.top = drawn.teTop + "px"; | |
| 1348 this.wrapper.style.left = drawn.teLeft + "px"; | |
| 1349 } | |
| 1350 }, | |
| 1351 | |
| 1352 // Reset the input to correspond to the selection (or to be empty, | |
| 1353 // when not typing and nothing is selected) | |
| 1354 reset: function(typing) { | |
| 1355 if (this.contextMenuPending) return; | |
| 1356 var minimal, selected, cm = this.cm, doc = cm.doc; | |
| 1357 if (cm.somethingSelected()) { | |
| 1358 this.prevInput = ""; | |
| 1359 var range = doc.sel.primary(); | |
| 1360 minimal = hasCopyEvent && | |
| 1361 (range.to().line - range.from().line > 100 || (selected = cm.getSelect
ion()).length > 1000); | |
| 1362 var content = minimal ? "-" : selected || cm.getSelection(); | |
| 1363 this.textarea.value = content; | |
| 1364 if (cm.state.focused) selectInput(this.textarea); | |
| 1365 if (ie && ie_version >= 9) this.hasSelection = content; | |
| 1366 } else if (!typing) { | |
| 1367 this.prevInput = this.textarea.value = ""; | |
| 1368 if (ie && ie_version >= 9) this.hasSelection = null; | |
| 1369 } | |
| 1370 this.inaccurateSelection = minimal; | |
| 1371 }, | |
| 1372 | |
| 1373 getField: function() { return this.textarea; }, | |
| 1374 | |
| 1375 supportsTouch: function() { return false; }, | |
| 1376 | |
| 1377 focus: function() { | |
| 1378 if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != t
his.textarea)) { | |
| 1379 try { this.textarea.focus(); } | |
| 1380 catch (e) {} // IE8 will throw if the textarea is display: none or not i
n DOM | |
| 1381 } | |
| 1382 }, | |
| 1383 | |
| 1384 blur: function() { this.textarea.blur(); }, | |
| 1385 | |
| 1386 resetPosition: function() { | |
| 1387 this.wrapper.style.top = this.wrapper.style.left = 0; | |
| 1388 }, | |
| 1389 | |
| 1390 receivedFocus: function() { this.slowPoll(); }, | |
| 1391 | |
| 1392 // Poll for input changes, using the normal rate of polling. This | |
| 1393 // runs as long as the editor is focused. | |
| 1394 slowPoll: function() { | |
| 1395 var input = this; | |
| 1396 if (input.pollingFast) return; | |
| 1397 input.polling.set(this.cm.options.pollInterval, function() { | |
| 1398 input.poll(); | |
| 1399 if (input.cm.state.focused) input.slowPoll(); | |
| 1400 }); | |
| 1401 }, | |
| 1402 | |
| 1403 // When an event has just come in that is likely to add or change | |
| 1404 // something in the input textarea, we poll faster, to ensure that | |
| 1405 // the change appears on the screen quickly. | |
| 1406 fastPoll: function() { | |
| 1407 var missed = false, input = this; | |
| 1408 input.pollingFast = true; | |
| 1409 function p() { | |
| 1410 var changed = input.poll(); | |
| 1411 if (!changed && !missed) {missed = true; input.polling.set(60, p);} | |
| 1412 else {input.pollingFast = false; input.slowPoll();} | |
| 1413 } | |
| 1414 input.polling.set(20, p); | |
| 1415 }, | |
| 1416 | |
| 1417 // Read input from the textarea, and update the document to match. | |
| 1418 // When something is selected, it is present in the textarea, and | |
| 1419 // selected (unless it is huge, in which case a placeholder is | |
| 1420 // used). When nothing is selected, the cursor sits after previously | |
| 1421 // seen text (can be empty), which is stored in prevInput (we must | |
| 1422 // not reset the textarea when typing, because that breaks IME). | |
| 1423 poll: function() { | |
| 1424 var cm = this.cm, input = this.textarea, prevInput = this.prevInput; | |
| 1425 // Since this is called a *lot*, try to bail out as cheaply as | |
| 1426 // possible when it is clear that nothing happened. hasSelection | |
| 1427 // will be the case when there is a lot of text in the textarea, | |
| 1428 // in which case reading its value would be expensive. | |
| 1429 if (this.contextMenuPending || !cm.state.focused || | |
| 1430 (hasSelection(input) && !prevInput && !this.composing) || | |
| 1431 cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) | |
| 1432 return false; | |
| 1433 | |
| 1434 var text = input.value; | |
| 1435 // If nothing changed, bail. | |
| 1436 if (text == prevInput && !cm.somethingSelected()) return false; | |
| 1437 // Work around nonsensical selection resetting in IE9/10, and | |
| 1438 // inexplicable appearance of private area unicode characters on | |
| 1439 // some key combos in Mac (#2689). | |
| 1440 if (ie && ie_version >= 9 && this.hasSelection === text || | |
| 1441 mac && /[\uf700-\uf7ff]/.test(text)) { | |
| 1442 cm.display.input.reset(); | |
| 1443 return false; | |
| 1444 } | |
| 1445 | |
| 1446 if (cm.doc.sel == cm.display.selForContextMenu) { | |
| 1447 var first = text.charCodeAt(0); | |
| 1448 if (first == 0x200b && !prevInput) prevInput = "\u200b"; | |
| 1449 if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo");
} | |
| 1450 } | |
| 1451 // Find the part of the input that is actually new | |
| 1452 var same = 0, l = Math.min(prevInput.length, text.length); | |
| 1453 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++
same; | |
| 1454 | |
| 1455 var self = this; | |
| 1456 runInOp(cm, function() { | |
| 1457 applyTextInput(cm, text.slice(same), prevInput.length - same, | |
| 1458 null, self.composing ? "*compose" : null); | |
| 1459 | |
| 1460 // Don't leave long text in the textarea, since it makes further polling
slow | |
| 1461 if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.pr
evInput = ""; | |
| 1462 else self.prevInput = text; | |
| 1463 | |
| 1464 if (self.composing) { | |
| 1465 self.composing.range.clear(); | |
| 1466 self.composing.range = cm.markText(self.composing.start, cm.getCursor(
"to"), | |
| 1467 {className: "CodeMirror-composing"}
); | |
| 1468 } | |
| 1469 }); | |
| 1470 return true; | |
| 1471 }, | |
| 1472 | |
| 1473 ensurePolled: function() { | |
| 1474 if (this.pollingFast && this.poll()) this.pollingFast = false; | |
| 1475 }, | |
| 1476 | |
| 1477 onKeyPress: function() { | |
| 1478 if (ie && ie_version >= 9) this.hasSelection = null; | |
| 1479 this.fastPoll(); | |
| 1480 }, | |
| 1481 | |
| 1482 onContextMenu: function(e) { | |
| 1483 var input = this, cm = input.cm, display = cm.display, te = input.textarea
; | |
| 1484 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; | |
| 1485 if (!pos || presto) return; // Opera is difficult. | |
| 1486 | |
| 1487 // Reset the current text selection only if the click is done outside of t
he selection | |
| 1488 // and 'resetSelectionOnContextMenu' option is true. | |
| 1489 var reset = cm.options.resetSelectionOnContextMenu; | |
| 1490 if (reset && cm.doc.sel.contains(pos) == -1) | |
| 1491 operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll
); | |
| 1492 | |
| 1493 var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText
; | |
| 1494 input.wrapper.style.cssText = "position: absolute" | |
| 1495 var wrapperBox = input.wrapper.getBoundingClientRect() | |
| 1496 te.style.cssText = "position: absolute; width: 30px; height: 30px; top: "
+ (e.clientY - wrapperBox.top - 5) + | |
| 1497 "px; left: " + (e.clientX - wrapperBox.left - 5) + "px; z-index: 1000; b
ackground: " + | |
| 1498 (ie ? "rgba(255, 255, 255, .05)" : "transparent") + | |
| 1499 "; outline: none; border-width: 0; outline: none; overflow: hidden; opac
ity: .05; filter: alpha(opacity=5);"; | |
| 1500 if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (
#2712) | |
| 1501 display.input.focus(); | |
| 1502 if (webkit) window.scrollTo(null, oldScrollY); | |
| 1503 display.input.reset(); | |
| 1504 // Adds "Select all" to context menu in FF | |
| 1505 if (!cm.somethingSelected()) te.value = input.prevInput = " "; | |
| 1506 input.contextMenuPending = true; | |
| 1507 display.selForContextMenu = cm.doc.sel; | |
| 1508 clearTimeout(display.detectingSelectAll); | |
| 1509 | |
| 1510 // Select-all will be greyed out if there's nothing to select, so | |
| 1511 // this adds a zero-width space so that we can later check whether | |
| 1512 // it got selected. | |
| 1513 function prepareSelectAllHack() { | |
| 1514 if (te.selectionStart != null) { | |
| 1515 var selected = cm.somethingSelected(); | |
| 1516 var extval = "\u200b" + (selected ? te.value : ""); | |
| 1517 te.value = "\u21da"; // Used to catch context-menu undo | |
| 1518 te.value = extval; | |
| 1519 input.prevInput = selected ? "" : "\u200b"; | |
| 1520 te.selectionStart = 1; te.selectionEnd = extval.length; | |
| 1521 // Re-set this, in case some other handler touched the | |
| 1522 // selection in the meantime. | |
| 1523 display.selForContextMenu = cm.doc.sel; | |
| 1524 } | 1602 } |
| 1525 } | 1603 } |
| 1526 function rehide() { | 1604 }, lineClasses) |
| 1527 input.contextMenuPending = false; | 1605 }; |
| 1528 input.wrapper.style.cssText = oldWrapperCSS | 1606 |
| 1529 te.style.cssText = oldCSS; | 1607 for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); |
| 1530 if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroll
er.scrollTop = scrollPos); | 1608 |
| 1531 | 1609 return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? li
neClasses : null} |
| 1532 // Try to detect the user choosing select-all | 1610 } |
| 1533 if (te.selectionStart != null) { | 1611 |
| 1534 if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); | 1612 function getLineStyles(cm, line, updateFrontier) { |
| 1535 var i = 0, poll = function() { | 1613 if (!line.styles || line.styles[0] != cm.state.modeGen) { |
| 1536 if (display.selForContextMenu == cm.doc.sel && te.selectionStart ==
0 && | 1614 var state = getStateBefore(cm, lineNo(line)) |
| 1537 te.selectionEnd > 0 && input.prevInput == "\u200b") | 1615 var result = highlightLine(cm, line, line.text.length > cm.options.maxHighli
ghtLength ? copyState(cm.doc.mode, state) : state) |
| 1538 operation(cm, commands.selectAll)(cm); | 1616 line.stateAfter = state |
| 1539 else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500
); | 1617 line.styles = result.styles |
| 1540 else display.input.reset(); | 1618 if (result.classes) { line.styleClasses = result.classes } |
| 1541 }; | 1619 else if (line.styleClasses) { line.styleClasses = null } |
| 1542 display.detectingSelectAll = setTimeout(poll, 200); | 1620 if (updateFrontier === cm.doc.frontier) { cm.doc.frontier++ } |
| 1621 } |
| 1622 return line.styles |
| 1623 } |
| 1624 |
| 1625 function getStateBefore(cm, n, precise) { |
| 1626 var doc = cm.doc, display = cm.display |
| 1627 if (!doc.mode.startState) { return true } |
| 1628 var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(do
c, pos-1).stateAfter |
| 1629 if (!state) { state = startState(doc.mode) } |
| 1630 else { state = copyState(doc.mode, state) } |
| 1631 doc.iter(pos, n, function (line) { |
| 1632 processLine(cm, line.text, state) |
| 1633 var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos <
display.viewTo |
| 1634 line.stateAfter = save ? copyState(doc.mode, state) : null |
| 1635 ++pos |
| 1636 }) |
| 1637 if (precise) { doc.frontier = pos } |
| 1638 return state |
| 1639 } |
| 1640 |
| 1641 // Lightweight form of highlight -- proceed over this line and |
| 1642 // update state, but don't save a style array. Used for lines that |
| 1643 // aren't currently visible. |
| 1644 function processLine(cm, text, state, startAt) { |
| 1645 var mode = cm.doc.mode |
| 1646 var stream = new StringStream(text, cm.options.tabSize) |
| 1647 stream.start = stream.pos = startAt || 0 |
| 1648 if (text == "") { callBlankLine(mode, state) } |
| 1649 while (!stream.eol()) { |
| 1650 readToken(mode, stream, state) |
| 1651 stream.start = stream.pos |
| 1652 } |
| 1653 } |
| 1654 |
| 1655 function callBlankLine(mode, state) { |
| 1656 if (mode.blankLine) { return mode.blankLine(state) } |
| 1657 if (!mode.innerMode) { return } |
| 1658 var inner = innerMode(mode, state) |
| 1659 if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) } |
| 1660 } |
| 1661 |
| 1662 function readToken(mode, stream, state, inner) { |
| 1663 for (var i = 0; i < 10; i++) { |
| 1664 if (inner) { inner[0] = innerMode(mode, state).mode } |
| 1665 var style = mode.token(stream, state) |
| 1666 if (stream.pos > stream.start) { return style } |
| 1667 } |
| 1668 throw new Error("Mode " + mode.name + " failed to advance stream.") |
| 1669 } |
| 1670 |
| 1671 // Utility for getTokenAt and getLineTokens |
| 1672 function takeToken(cm, pos, precise, asArray) { |
| 1673 var getObj = function (copy) { return ({ |
| 1674 start: stream.start, end: stream.pos, |
| 1675 string: stream.current(), |
| 1676 type: style || null, |
| 1677 state: copy ? copyState(doc.mode, state) : state |
| 1678 }); } |
| 1679 |
| 1680 var doc = cm.doc, mode = doc.mode, style |
| 1681 pos = clipPos(doc, pos) |
| 1682 var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precis
e) |
| 1683 var stream = new StringStream(line.text, cm.options.tabSize), tokens |
| 1684 if (asArray) { tokens = [] } |
| 1685 while ((asArray || stream.pos < pos.ch) && !stream.eol()) { |
| 1686 stream.start = stream.pos |
| 1687 style = readToken(mode, stream, state) |
| 1688 if (asArray) { tokens.push(getObj(true)) } |
| 1689 } |
| 1690 return asArray ? tokens : getObj() |
| 1691 } |
| 1692 |
| 1693 function extractLineClasses(type, output) { |
| 1694 if (type) { for (;;) { |
| 1695 var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/) |
| 1696 if (!lineClass) { break } |
| 1697 type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineCla
ss[0].length) |
| 1698 var prop = lineClass[1] ? "bgClass" : "textClass" |
| 1699 if (output[prop] == null) |
| 1700 { output[prop] = lineClass[2] } |
| 1701 else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[p
rop])) |
| 1702 { output[prop] += " " + lineClass[2] } |
| 1703 } } |
| 1704 return type |
| 1705 } |
| 1706 |
| 1707 // Run the given mode's parser over a line, calling f for each token. |
| 1708 function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { |
| 1709 var flattenSpans = mode.flattenSpans |
| 1710 if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans } |
| 1711 var curStart = 0, curStyle = null |
| 1712 var stream = new StringStream(text, cm.options.tabSize), style |
| 1713 var inner = cm.options.addModeClass && [null] |
| 1714 if (text == "") { extractLineClasses(callBlankLine(mode, state), lineClasses)
} |
| 1715 while (!stream.eol()) { |
| 1716 if (stream.pos > cm.options.maxHighlightLength) { |
| 1717 flattenSpans = false |
| 1718 if (forceToEnd) { processLine(cm, text, state, stream.pos) } |
| 1719 stream.pos = text.length |
| 1720 style = null |
| 1721 } else { |
| 1722 style = extractLineClasses(readToken(mode, stream, state, inner), lineClas
ses) |
| 1723 } |
| 1724 if (inner) { |
| 1725 var mName = inner[0].name |
| 1726 if (mName) { style = "m-" + (style ? mName + " " + style : mName) } |
| 1727 } |
| 1728 if (!flattenSpans || curStyle != style) { |
| 1729 while (curStart < stream.start) { |
| 1730 curStart = Math.min(stream.start, curStart + 5000) |
| 1731 f(curStart, curStyle) |
| 1732 } |
| 1733 curStyle = style |
| 1734 } |
| 1735 stream.start = stream.pos |
| 1736 } |
| 1737 while (curStart < stream.pos) { |
| 1738 // Webkit seems to refuse to render text nodes longer than 57444 |
| 1739 // characters, and returns inaccurate measurements in nodes |
| 1740 // starting around 5000 chars. |
| 1741 var pos = Math.min(stream.pos, curStart + 5000) |
| 1742 f(pos, curStyle) |
| 1743 curStart = pos |
| 1744 } |
| 1745 } |
| 1746 |
| 1747 // Finds the line to start with when starting a parse. Tries to |
| 1748 // find a line with a stateAfter, so that it can start with a |
| 1749 // valid state. If that fails, it returns the line with the |
| 1750 // smallest indentation, which tends to need the least context to |
| 1751 // parse correctly. |
| 1752 function findStartLine(cm, n, precise) { |
| 1753 var minindent, minline, doc = cm.doc |
| 1754 var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100) |
| 1755 for (var search = n; search > lim; --search) { |
| 1756 if (search <= doc.first) { return doc.first } |
| 1757 var line = getLine(doc, search - 1) |
| 1758 if (line.stateAfter && (!precise || search <= doc.frontier)) { return search
} |
| 1759 var indented = countColumn(line.text, null, cm.options.tabSize) |
| 1760 if (minline == null || minindent > indented) { |
| 1761 minline = search - 1 |
| 1762 minindent = indented |
| 1763 } |
| 1764 } |
| 1765 return minline |
| 1766 } |
| 1767 |
| 1768 // LINE DATA STRUCTURE |
| 1769 |
| 1770 // Line objects. These hold state related to a line, including |
| 1771 // highlighting info (the styles array). |
| 1772 var Line = function Line(text, markedSpans, estimateHeight) { |
| 1773 this.text = text |
| 1774 attachMarkedSpans(this, markedSpans) |
| 1775 this.height = estimateHeight ? estimateHeight(this) : 1 |
| 1776 }; |
| 1777 |
| 1778 Line.prototype.lineNo = function lineNo$1 () { return lineNo(this) }; |
| 1779 eventMixin(Line) |
| 1780 |
| 1781 // Change the content (text, markers) of a line. Automatically |
| 1782 // invalidates cached information and tries to re-estimate the |
| 1783 // line's height. |
| 1784 function updateLine(line, text, markedSpans, estimateHeight) { |
| 1785 line.text = text |
| 1786 if (line.stateAfter) { line.stateAfter = null } |
| 1787 if (line.styles) { line.styles = null } |
| 1788 if (line.order != null) { line.order = null } |
| 1789 detachMarkedSpans(line) |
| 1790 attachMarkedSpans(line, markedSpans) |
| 1791 var estHeight = estimateHeight ? estimateHeight(line) : 1 |
| 1792 if (estHeight != line.height) { updateLineHeight(line, estHeight) } |
| 1793 } |
| 1794 |
| 1795 // Detach a line from the document tree and its markers. |
| 1796 function cleanUpLine(line) { |
| 1797 line.parent = null |
| 1798 detachMarkedSpans(line) |
| 1799 } |
| 1800 |
| 1801 // Convert a style as returned by a mode (either null, or a string |
| 1802 // containing one or more styles) to a CSS style. This is cached, |
| 1803 // and also looks for line-wide styles. |
| 1804 var styleToClassCache = {}; |
| 1805 var styleToClassCacheWithMode = {}; |
| 1806 function interpretTokenStyle(style, options) { |
| 1807 if (!style || /^\s*$/.test(style)) { return null } |
| 1808 var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCac
he |
| 1809 return cache[style] || |
| 1810 (cache[style] = style.replace(/\S+/g, "cm-$&")) |
| 1811 } |
| 1812 |
| 1813 // Render the DOM representation of the text of a line. Also builds |
| 1814 // up a 'line map', which points at the DOM nodes that represent |
| 1815 // specific stretches of text, and is used by the measuring code. |
| 1816 // The returned object contains the DOM node, this map, and |
| 1817 // information about line-wide styles that were set by the mode. |
| 1818 function buildLineContent(cm, lineView) { |
| 1819 // The padding-right forces the element to have a 'border', which |
| 1820 // is needed on Webkit to be able to get line-level bounding |
| 1821 // rectangles for it (in measureChar). |
| 1822 var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null) |
| 1823 var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: conten
t, |
| 1824 col: 0, pos: 0, cm: cm, |
| 1825 trailingSpace: false, |
| 1826 splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")} |
| 1827 lineView.measure = {} |
| 1828 |
| 1829 // Iterate over the logical lines that make up this visual line. |
| 1830 for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { |
| 1831 var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0) |
| 1832 builder.pos = 0 |
| 1833 builder.addToken = buildToken |
| 1834 // Optionally wire in some hacks into the token-rendering |
| 1835 // algorithm, to deal with browser quirks. |
| 1836 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.di
rection))) |
| 1837 { builder.addToken = buildTokenBadBidi(builder.addToken, order) } |
| 1838 builder.map = [] |
| 1839 var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(
line) |
| 1840 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate
)) |
| 1841 if (line.styleClasses) { |
| 1842 if (line.styleClasses.bgClass) |
| 1843 { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgCla
ss || "") } |
| 1844 if (line.styleClasses.textClass) |
| 1845 { builder.textClass = joinClasses(line.styleClasses.textClass, builder.t
extClass || "") } |
| 1846 } |
| 1847 |
| 1848 // Ensure at least a single node is present, for measuring. |
| 1849 if (builder.map.length == 0) |
| 1850 { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.d
isplay.measure))) } |
| 1851 |
| 1852 // Store the map and a cache object for the current logical line |
| 1853 if (i == 0) { |
| 1854 lineView.measure.map = builder.map |
| 1855 lineView.measure.cache = {} |
| 1856 } else { |
| 1857 ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) |
| 1858 ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}) |
| 1859 } |
| 1860 } |
| 1861 |
| 1862 // See issue #2901 |
| 1863 if (webkit) { |
| 1864 var last = builder.content.lastChild |
| 1865 if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySe
lector(".cm-tab"))) |
| 1866 { builder.content.className = "cm-tab-wrap-hack" } |
| 1867 } |
| 1868 |
| 1869 signal(cm, "renderLine", cm, lineView.line, builder.pre) |
| 1870 if (builder.pre.className) |
| 1871 { builder.textClass = joinClasses(builder.pre.className, builder.textClass |
| "") } |
| 1872 |
| 1873 return builder |
| 1874 } |
| 1875 |
| 1876 function defaultSpecialCharPlaceholder(ch) { |
| 1877 var token = elt("span", "\u2022", "cm-invalidchar") |
| 1878 token.title = "\\u" + ch.charCodeAt(0).toString(16) |
| 1879 token.setAttribute("aria-label", token.title) |
| 1880 return token |
| 1881 } |
| 1882 |
| 1883 // Build up the DOM representation for a single token, and add it to |
| 1884 // the line map. Takes care to render special characters separately. |
| 1885 function buildToken(builder, text, style, startStyle, endStyle, title, css) { |
| 1886 if (!text) { return } |
| 1887 var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpac
e) : text |
| 1888 var special = builder.cm.state.specialChars, mustWrap = false |
| 1889 var content |
| 1890 if (!special.test(text)) { |
| 1891 builder.col += text.length |
| 1892 content = document.createTextNode(displayText) |
| 1893 builder.map.push(builder.pos, builder.pos + text.length, content) |
| 1894 if (ie && ie_version < 9) { mustWrap = true } |
| 1895 builder.pos += text.length |
| 1896 } else { |
| 1897 content = document.createDocumentFragment() |
| 1898 var pos = 0 |
| 1899 while (true) { |
| 1900 special.lastIndex = pos |
| 1901 var m = special.exec(text) |
| 1902 var skipped = m ? m.index - pos : text.length - pos |
| 1903 if (skipped) { |
| 1904 var txt = document.createTextNode(displayText.slice(pos, pos + skipped)) |
| 1905 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])) } |
| 1906 else { content.appendChild(txt) } |
| 1907 builder.map.push(builder.pos, builder.pos + skipped, txt) |
| 1908 builder.col += skipped |
| 1909 builder.pos += skipped |
| 1910 } |
| 1911 if (!m) { break } |
| 1912 pos += skipped + 1 |
| 1913 var txt$1 = (void 0) |
| 1914 if (m[0] == "\t") { |
| 1915 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.c
ol % tabSize |
| 1916 txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")) |
| 1917 txt$1.setAttribute("role", "presentation") |
| 1918 txt$1.setAttribute("cm-text", "\t") |
| 1919 builder.col += tabWidth |
| 1920 } else if (m[0] == "\r" || m[0] == "\n") { |
| 1921 txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u242
4", "cm-invalidchar")) |
| 1922 txt$1.setAttribute("cm-text", m[0]) |
| 1923 builder.col += 1 |
| 1924 } else { |
| 1925 txt$1 = builder.cm.options.specialCharPlaceholder(m[0]) |
| 1926 txt$1.setAttribute("cm-text", m[0]) |
| 1927 if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])) } |
| 1928 else { content.appendChild(txt$1) } |
| 1929 builder.col += 1 |
| 1930 } |
| 1931 builder.map.push(builder.pos, builder.pos + 1, txt$1) |
| 1932 builder.pos++ |
| 1933 } |
| 1934 } |
| 1935 builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 |
| 1936 if (style || startStyle || endStyle || mustWrap || css) { |
| 1937 var fullStyle = style || "" |
| 1938 if (startStyle) { fullStyle += startStyle } |
| 1939 if (endStyle) { fullStyle += endStyle } |
| 1940 var token = elt("span", [content], fullStyle, css) |
| 1941 if (title) { token.title = title } |
| 1942 return builder.content.appendChild(token) |
| 1943 } |
| 1944 builder.content.appendChild(content) |
| 1945 } |
| 1946 |
| 1947 function splitSpaces(text, trailingBefore) { |
| 1948 if (text.length > 1 && !/ /.test(text)) { return text } |
| 1949 var spaceBefore = trailingBefore, result = "" |
| 1950 for (var i = 0; i < text.length; i++) { |
| 1951 var ch = text.charAt(i) |
| 1952 if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i +
1) == 32)) |
| 1953 { ch = "\u00a0" } |
| 1954 result += ch |
| 1955 spaceBefore = ch == " " |
| 1956 } |
| 1957 return result |
| 1958 } |
| 1959 |
| 1960 // Work around nonsense dimensions being reported for stretches of |
| 1961 // right-to-left text. |
| 1962 function buildTokenBadBidi(inner, order) { |
| 1963 return function (builder, text, style, startStyle, endStyle, title, css) { |
| 1964 style = style ? style + " cm-force-border" : "cm-force-border" |
| 1965 var start = builder.pos, end = start + text.length |
| 1966 for (;;) { |
| 1967 // Find the part that overlaps with the start of this text |
| 1968 var part = (void 0) |
| 1969 for (var i = 0; i < order.length; i++) { |
| 1970 part = order[i] |
| 1971 if (part.to > start && part.from <= start) { break } |
| 1972 } |
| 1973 if (part.to >= end) { return inner(builder, text, style, startStyle, endSt
yle, title, css) } |
| 1974 inner(builder, text.slice(0, part.to - start), style, startStyle, null, ti
tle, css) |
| 1975 startStyle = null |
| 1976 text = text.slice(part.to - start) |
| 1977 start = part.to |
| 1978 } |
| 1979 } |
| 1980 } |
| 1981 |
| 1982 function buildCollapsedSpan(builder, size, marker, ignoreWidget) { |
| 1983 var widget = !ignoreWidget && marker.widgetNode |
| 1984 if (widget) { builder.map.push(builder.pos, builder.pos + size, widget) } |
| 1985 if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { |
| 1986 if (!widget) |
| 1987 { widget = builder.content.appendChild(document.createElement("span")) } |
| 1988 widget.setAttribute("cm-marker", marker.id) |
| 1989 } |
| 1990 if (widget) { |
| 1991 builder.cm.display.input.setUneditable(widget) |
| 1992 builder.content.appendChild(widget) |
| 1993 } |
| 1994 builder.pos += size |
| 1995 builder.trailingSpace = false |
| 1996 } |
| 1997 |
| 1998 // Outputs a number of spans to make up a line, taking highlighting |
| 1999 // and marked text into account. |
| 2000 function insertLineContent(line, builder, styles) { |
| 2001 var spans = line.markedSpans, allText = line.text, at = 0 |
| 2002 if (!spans) { |
| 2003 for (var i$1 = 1; i$1 < styles.length; i$1+=2) |
| 2004 { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpret
TokenStyle(styles[i$1+1], builder.cm.options)) } |
| 2005 return |
| 2006 } |
| 2007 |
| 2008 var len = allText.length, pos = 0, i = 1, text = "", style, css |
| 2009 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed |
| 2010 for (;;) { |
| 2011 if (nextChange == pos) { // Update current marker set |
| 2012 spanStyle = spanEndStyle = spanStartStyle = title = css = "" |
| 2013 collapsed = null; nextChange = Infinity |
| 2014 var foundBookmarks = [], endStyles = (void 0) |
| 2015 for (var j = 0; j < spans.length; ++j) { |
| 2016 var sp = spans[j], m = sp.marker |
| 2017 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { |
| 2018 foundBookmarks.push(m) |
| 2019 } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapse
d && sp.to == pos && sp.from == pos)) { |
| 2020 if (sp.to != null && sp.to != pos && nextChange > sp.to) { |
| 2021 nextChange = sp.to |
| 2022 spanEndStyle = "" |
| 2023 } |
| 2024 if (m.className) { spanStyle += " " + m.className } |
| 2025 if (m.css) { css = (css ? css + ";" : "") + m.css } |
| 2026 if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startS
tyle } |
| 2027 if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = []
)).push(m.endStyle, sp.to) } |
| 2028 if (m.title && !title) { title = m.title } |
| 2029 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.ma
rker, m) < 0)) |
| 2030 { collapsed = sp } |
| 2031 } else if (sp.from > pos && nextChange > sp.from) { |
| 2032 nextChange = sp.from |
| 1543 } | 2033 } |
| 1544 } | 2034 } |
| 1545 | 2035 if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) |
| 1546 if (ie && ie_version >= 9) prepareSelectAllHack(); | 2036 { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyle
s[j$1] } } } |
| 1547 if (captureRightClick) { | 2037 |
| 1548 e_stop(e); | 2038 if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBo
okmarks.length; ++j$2) |
| 1549 var mouseup = function() { | 2039 { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]) } } |
| 1550 off(window, "mouseup", mouseup); | 2040 if (collapsed && (collapsed.from || 0) == pos) { |
| 1551 setTimeout(rehide, 20); | 2041 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.
to) - pos, |
| 1552 }; | 2042 collapsed.marker, collapsed.from == null) |
| 1553 on(window, "mouseup", mouseup); | 2043 if (collapsed.to == null) { return } |
| 2044 if (collapsed.to == pos) { collapsed = false } |
| 2045 } |
| 2046 } |
| 2047 if (pos >= len) { break } |
| 2048 |
| 2049 var upto = Math.min(len, nextChange) |
| 2050 while (true) { |
| 2051 if (text) { |
| 2052 var end = pos + text.length |
| 2053 if (!collapsed) { |
| 2054 var tokenText = end > upto ? text.slice(0, upto - pos) : text |
| 2055 builder.addToken(builder, tokenText, style ? style + spanStyle : spanS
tyle, |
| 2056 spanStartStyle, pos + tokenText.length == nextChange
? spanEndStyle : "", title, css) |
| 2057 } |
| 2058 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} |
| 2059 pos = end |
| 2060 spanStartStyle = "" |
| 2061 } |
| 2062 text = allText.slice(at, at = styles[i++]) |
| 2063 style = interpretTokenStyle(styles[i++], builder.cm.options) |
| 2064 } |
| 2065 } |
| 2066 } |
| 2067 |
| 2068 |
| 2069 // These objects are used to represent the visible (currently drawn) |
| 2070 // part of the document. A LineView may correspond to multiple |
| 2071 // logical lines, if those are connected by collapsed ranges. |
| 2072 function LineView(doc, line, lineN) { |
| 2073 // The starting line |
| 2074 this.line = line |
| 2075 // Continuing lines, if any |
| 2076 this.rest = visualLineContinued(line) |
| 2077 // Number of logical lines in this visual line |
| 2078 this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1 |
| 2079 this.node = this.text = null |
| 2080 this.hidden = lineIsHidden(doc, line) |
| 2081 } |
| 2082 |
| 2083 // Create a range of LineView objects for the given lines. |
| 2084 function buildViewArray(cm, from, to) { |
| 2085 var array = [], nextPos |
| 2086 for (var pos = from; pos < to; pos = nextPos) { |
| 2087 var view = new LineView(cm.doc, getLine(cm.doc, pos), pos) |
| 2088 nextPos = pos + view.size |
| 2089 array.push(view) |
| 2090 } |
| 2091 return array |
| 2092 } |
| 2093 |
| 2094 var operationGroup = null |
| 2095 |
| 2096 function pushOperation(op) { |
| 2097 if (operationGroup) { |
| 2098 operationGroup.ops.push(op) |
| 2099 } else { |
| 2100 op.ownsGroup = operationGroup = { |
| 2101 ops: [op], |
| 2102 delayedCallbacks: [] |
| 2103 } |
| 2104 } |
| 2105 } |
| 2106 |
| 2107 function fireCallbacksForOps(group) { |
| 2108 // Calls delayed callbacks and cursorActivity handlers until no |
| 2109 // new ones appear |
| 2110 var callbacks = group.delayedCallbacks, i = 0 |
| 2111 do { |
| 2112 for (; i < callbacks.length; i++) |
| 2113 { callbacks[i].call(null) } |
| 2114 for (var j = 0; j < group.ops.length; j++) { |
| 2115 var op = group.ops[j] |
| 2116 if (op.cursorActivityHandlers) |
| 2117 { while (op.cursorActivityCalled < op.cursorActivityHandlers.length) |
| 2118 { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.c
m) } } |
| 2119 } |
| 2120 } while (i < callbacks.length) |
| 2121 } |
| 2122 |
| 2123 function finishOperation(op, endCb) { |
| 2124 var group = op.ownsGroup |
| 2125 if (!group) { return } |
| 2126 |
| 2127 try { fireCallbacksForOps(group) } |
| 2128 finally { |
| 2129 operationGroup = null |
| 2130 endCb(group) |
| 2131 } |
| 2132 } |
| 2133 |
| 2134 var orphanDelayedCallbacks = null |
| 2135 |
| 2136 // Often, we want to signal events at a point where we are in the |
| 2137 // middle of some work, but don't want the handler to start calling |
| 2138 // other methods on the editor, which might be in an inconsistent |
| 2139 // state or simply not expect any other events to happen. |
| 2140 // signalLater looks whether there are any handlers, and schedules |
| 2141 // them to be executed when the last operation ends, or, if no |
| 2142 // operation is active, when a timeout fires. |
| 2143 function signalLater(emitter, type /*, values...*/) { |
| 2144 var arr = getHandlers(emitter, type) |
| 2145 if (!arr.length) { return } |
| 2146 var args = Array.prototype.slice.call(arguments, 2), list |
| 2147 if (operationGroup) { |
| 2148 list = operationGroup.delayedCallbacks |
| 2149 } else if (orphanDelayedCallbacks) { |
| 2150 list = orphanDelayedCallbacks |
| 2151 } else { |
| 2152 list = orphanDelayedCallbacks = [] |
| 2153 setTimeout(fireOrphanDelayed, 0) |
| 2154 } |
| 2155 var loop = function ( i ) { |
| 2156 list.push(function () { return arr[i].apply(null, args); }) |
| 2157 }; |
| 2158 |
| 2159 for (var i = 0; i < arr.length; ++i) |
| 2160 loop( i ); |
| 2161 } |
| 2162 |
| 2163 function fireOrphanDelayed() { |
| 2164 var delayed = orphanDelayedCallbacks |
| 2165 orphanDelayedCallbacks = null |
| 2166 for (var i = 0; i < delayed.length; ++i) { delayed[i]() } |
| 2167 } |
| 2168 |
| 2169 // When an aspect of a line changes, a string is added to |
| 2170 // lineView.changes. This updates the relevant part of the line's |
| 2171 // DOM structure. |
| 2172 function updateLineForChanges(cm, lineView, lineN, dims) { |
| 2173 for (var j = 0; j < lineView.changes.length; j++) { |
| 2174 var type = lineView.changes[j] |
| 2175 if (type == "text") { updateLineText(cm, lineView) } |
| 2176 else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims) } |
| 2177 else if (type == "class") { updateLineClasses(cm, lineView) } |
| 2178 else if (type == "widget") { updateLineWidgets(cm, lineView, dims) } |
| 2179 } |
| 2180 lineView.changes = null |
| 2181 } |
| 2182 |
| 2183 // Lines with gutter elements, widgets or a background class need to |
| 2184 // be wrapped, and have the extra elements added to the wrapper div |
| 2185 function ensureLineWrapped(lineView) { |
| 2186 if (lineView.node == lineView.text) { |
| 2187 lineView.node = elt("div", null, null, "position: relative") |
| 2188 if (lineView.text.parentNode) |
| 2189 { lineView.text.parentNode.replaceChild(lineView.node, lineView.text) } |
| 2190 lineView.node.appendChild(lineView.text) |
| 2191 if (ie && ie_version < 8) { lineView.node.style.zIndex = 2 } |
| 2192 } |
| 2193 return lineView.node |
| 2194 } |
| 2195 |
| 2196 function updateLineBackground(cm, lineView) { |
| 2197 var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass |
| "") : lineView.line.bgClass |
| 2198 if (cls) { cls += " CodeMirror-linebackground" } |
| 2199 if (lineView.background) { |
| 2200 if (cls) { lineView.background.className = cls } |
| 2201 else { lineView.background.parentNode.removeChild(lineView.background); line
View.background = null } |
| 2202 } else if (cls) { |
| 2203 var wrap = ensureLineWrapped(lineView) |
| 2204 lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChi
ld) |
| 2205 cm.display.input.setUneditable(lineView.background) |
| 2206 } |
| 2207 } |
| 2208 |
| 2209 // Wrapper around buildLineContent which will reuse the structure |
| 2210 // in display.externalMeasured when possible. |
| 2211 function getLineContent(cm, lineView) { |
| 2212 var ext = cm.display.externalMeasured |
| 2213 if (ext && ext.line == lineView.line) { |
| 2214 cm.display.externalMeasured = null |
| 2215 lineView.measure = ext.measure |
| 2216 return ext.built |
| 2217 } |
| 2218 return buildLineContent(cm, lineView) |
| 2219 } |
| 2220 |
| 2221 // Redraw the line's text. Interacts with the background and text |
| 2222 // classes because the mode may output tokens that influence these |
| 2223 // classes. |
| 2224 function updateLineText(cm, lineView) { |
| 2225 var cls = lineView.text.className |
| 2226 var built = getLineContent(cm, lineView) |
| 2227 if (lineView.text == lineView.node) { lineView.node = built.pre } |
| 2228 lineView.text.parentNode.replaceChild(built.pre, lineView.text) |
| 2229 lineView.text = built.pre |
| 2230 if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass
) { |
| 2231 lineView.bgClass = built.bgClass |
| 2232 lineView.textClass = built.textClass |
| 2233 updateLineClasses(cm, lineView) |
| 2234 } else if (cls) { |
| 2235 lineView.text.className = cls |
| 2236 } |
| 2237 } |
| 2238 |
| 2239 function updateLineClasses(cm, lineView) { |
| 2240 updateLineBackground(cm, lineView) |
| 2241 if (lineView.line.wrapClass) |
| 2242 { ensureLineWrapped(lineView).className = lineView.line.wrapClass } |
| 2243 else if (lineView.node != lineView.text) |
| 2244 { lineView.node.className = "" } |
| 2245 var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line
.textClass || "") : lineView.line.textClass |
| 2246 lineView.text.className = textClass || "" |
| 2247 } |
| 2248 |
| 2249 function updateLineGutter(cm, lineView, lineN, dims) { |
| 2250 if (lineView.gutter) { |
| 2251 lineView.node.removeChild(lineView.gutter) |
| 2252 lineView.gutter = null |
| 2253 } |
| 2254 if (lineView.gutterBackground) { |
| 2255 lineView.node.removeChild(lineView.gutterBackground) |
| 2256 lineView.gutterBackground = null |
| 2257 } |
| 2258 if (lineView.line.gutterClass) { |
| 2259 var wrap = ensureLineWrapped(lineView) |
| 2260 lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background "
+ lineView.line.gutterClass, |
| 2261 ("left: " + (cm.options.fixedGutter ? dims.f
ixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "p
x")) |
| 2262 cm.display.input.setUneditable(lineView.gutterBackground) |
| 2263 wrap.insertBefore(lineView.gutterBackground, lineView.text) |
| 2264 } |
| 2265 var markers = lineView.line.gutterMarkers |
| 2266 if (cm.options.lineNumbers || markers) { |
| 2267 var wrap$1 = ensureLineWrapped(lineView) |
| 2268 var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapp
er", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidt
h) + "px")) |
| 2269 cm.display.input.setUneditable(gutterWrap) |
| 2270 wrap$1.insertBefore(gutterWrap, lineView.text) |
| 2271 if (lineView.line.gutterClass) |
| 2272 { gutterWrap.className += " " + lineView.line.gutterClass } |
| 2273 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"
])) |
| 2274 { lineView.lineNumber = gutterWrap.appendChild( |
| 2275 elt("div", lineNumberFor(cm.options, lineN), |
| 2276 "CodeMirror-linenumber CodeMirror-gutter-elt", |
| 2277 ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width
: " + (cm.display.lineNumInnerWidth) + "px"))) } |
| 2278 if (markers) { for (var k = 0; k < cm.options.gutters.length; ++k) { |
| 2279 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && mark
ers[id] |
| 2280 if (found) |
| 2281 { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", |
| 2282 ("left: " + (dims.gutterLeft[id]) + "px; widt
h: " + (dims.gutterWidth[id]) + "px"))) } |
| 2283 } } |
| 2284 } |
| 2285 } |
| 2286 |
| 2287 function updateLineWidgets(cm, lineView, dims) { |
| 2288 if (lineView.alignable) { lineView.alignable = null } |
| 2289 for (var node = lineView.node.firstChild, next = (void 0); node; node = next)
{ |
| 2290 next = node.nextSibling |
| 2291 if (node.className == "CodeMirror-linewidget") |
| 2292 { lineView.node.removeChild(node) } |
| 2293 } |
| 2294 insertLineWidgets(cm, lineView, dims) |
| 2295 } |
| 2296 |
| 2297 // Build a line's DOM representation from scratch |
| 2298 function buildLineElement(cm, lineView, lineN, dims) { |
| 2299 var built = getLineContent(cm, lineView) |
| 2300 lineView.text = lineView.node = built.pre |
| 2301 if (built.bgClass) { lineView.bgClass = built.bgClass } |
| 2302 if (built.textClass) { lineView.textClass = built.textClass } |
| 2303 |
| 2304 updateLineClasses(cm, lineView) |
| 2305 updateLineGutter(cm, lineView, lineN, dims) |
| 2306 insertLineWidgets(cm, lineView, dims) |
| 2307 return lineView.node |
| 2308 } |
| 2309 |
| 2310 // A lineView may contain multiple logical lines (when merged by |
| 2311 // collapsed spans). The widgets for all of them need to be drawn. |
| 2312 function insertLineWidgets(cm, lineView, dims) { |
| 2313 insertLineWidgetsFor(cm, lineView.line, lineView, dims, true) |
| 2314 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) |
| 2315 { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false) } } |
| 2316 } |
| 2317 |
| 2318 function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { |
| 2319 if (!line.widgets) { return } |
| 2320 var wrap = ensureLineWrapped(lineView) |
| 2321 for (var i = 0, ws = line.widgets; i < ws.length; ++i) { |
| 2322 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"
) |
| 2323 if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true
") } |
| 2324 positionLineWidget(widget, node, lineView, dims) |
| 2325 cm.display.input.setUneditable(node) |
| 2326 if (allowAbove && widget.above) |
| 2327 { wrap.insertBefore(node, lineView.gutter || lineView.text) } |
| 2328 else |
| 2329 { wrap.appendChild(node) } |
| 2330 signalLater(widget, "redraw") |
| 2331 } |
| 2332 } |
| 2333 |
| 2334 function positionLineWidget(widget, node, lineView, dims) { |
| 2335 if (widget.noHScroll) { |
| 2336 ;(lineView.alignable || (lineView.alignable = [])).push(node) |
| 2337 var width = dims.wrapperWidth |
| 2338 node.style.left = dims.fixedPos + "px" |
| 2339 if (!widget.coverGutter) { |
| 2340 width -= dims.gutterTotalWidth |
| 2341 node.style.paddingLeft = dims.gutterTotalWidth + "px" |
| 2342 } |
| 2343 node.style.width = width + "px" |
| 2344 } |
| 2345 if (widget.coverGutter) { |
| 2346 node.style.zIndex = 5 |
| 2347 node.style.position = "relative" |
| 2348 if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "p
x" } |
| 2349 } |
| 2350 } |
| 2351 |
| 2352 function widgetHeight(widget) { |
| 2353 if (widget.height != null) { return widget.height } |
| 2354 var cm = widget.doc.cm |
| 2355 if (!cm) { return 0 } |
| 2356 if (!contains(document.body, widget.node)) { |
| 2357 var parentStyle = "position: relative;" |
| 2358 if (widget.coverGutter) |
| 2359 { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"
} |
| 2360 if (widget.noHScroll) |
| 2361 { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;" } |
| 2362 removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, par
entStyle)) |
| 2363 } |
| 2364 return widget.height = widget.node.parentNode.offsetHeight |
| 2365 } |
| 2366 |
| 2367 // Return true when the given mouse event happened in a widget |
| 2368 function eventInWidget(display, e) { |
| 2369 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { |
| 2370 if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true")
|| |
| 2371 (n.parentNode == display.sizer && n != display.mover)) |
| 2372 { return true } |
| 2373 } |
| 2374 } |
| 2375 |
| 2376 // POSITION MEASUREMENT |
| 2377 |
| 2378 function paddingTop(display) {return display.lineSpace.offsetTop} |
| 2379 function paddingVert(display) {return display.mover.offsetHeight - display.lineS
pace.offsetHeight} |
| 2380 function paddingH(display) { |
| 2381 if (display.cachedPaddingH) { return display.cachedPaddingH } |
| 2382 var e = removeChildrenAndAdd(display.measure, elt("pre", "x")) |
| 2383 var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentSt
yle |
| 2384 var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRi
ght)} |
| 2385 if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data } |
| 2386 return data |
| 2387 } |
| 2388 |
| 2389 function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } |
| 2390 function displayWidth(cm) { |
| 2391 return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth |
| 2392 } |
| 2393 function displayHeight(cm) { |
| 2394 return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight |
| 2395 } |
| 2396 |
| 2397 // Ensure the lineView.wrapping.heights array is populated. This is |
| 2398 // an array of bottom offsets for the lines that make up a drawn |
| 2399 // line. When lineWrapping is on, there might be more than one |
| 2400 // height. |
| 2401 function ensureLineHeights(cm, lineView, rect) { |
| 2402 var wrapping = cm.options.lineWrapping |
| 2403 var curWidth = wrapping && displayWidth(cm) |
| 2404 if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidt
h) { |
| 2405 var heights = lineView.measure.heights = [] |
| 2406 if (wrapping) { |
| 2407 lineView.measure.width = curWidth |
| 2408 var rects = lineView.text.firstChild.getClientRects() |
| 2409 for (var i = 0; i < rects.length - 1; i++) { |
| 2410 var cur = rects[i], next = rects[i + 1] |
| 2411 if (Math.abs(cur.bottom - next.bottom) > 2) |
| 2412 { heights.push((cur.bottom + next.top) / 2 - rect.top) } |
| 2413 } |
| 2414 } |
| 2415 heights.push(rect.bottom - rect.top) |
| 2416 } |
| 2417 } |
| 2418 |
| 2419 // Find a line map (mapping character offsets to text nodes) and a |
| 2420 // measurement cache for the given line number. (A line view might |
| 2421 // contain multiple lines when collapsed ranges are present.) |
| 2422 function mapFromLineView(lineView, line, lineN) { |
| 2423 if (lineView.line == line) |
| 2424 { return {map: lineView.measure.map, cache: lineView.measure.cache} } |
| 2425 for (var i = 0; i < lineView.rest.length; i++) |
| 2426 { if (lineView.rest[i] == line) |
| 2427 { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]
} } } |
| 2428 for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) |
| 2429 { if (lineNo(lineView.rest[i$1]) > lineN) |
| 2430 { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[
i$1], before: true} } } |
| 2431 } |
| 2432 |
| 2433 // Render a line into the hidden node display.externalMeasured. Used |
| 2434 // when measurement is needed for a line that's not in the viewport. |
| 2435 function updateExternalMeasurement(cm, line) { |
| 2436 line = visualLine(line) |
| 2437 var lineN = lineNo(line) |
| 2438 var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN) |
| 2439 view.lineN = lineN |
| 2440 var built = view.built = buildLineContent(cm, view) |
| 2441 view.text = built.pre |
| 2442 removeChildrenAndAdd(cm.display.lineMeasure, built.pre) |
| 2443 return view |
| 2444 } |
| 2445 |
| 2446 // Get a {top, bottom, left, right} box (in line-local coordinates) |
| 2447 // for a given character. |
| 2448 function measureChar(cm, line, ch, bias) { |
| 2449 return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) |
| 2450 } |
| 2451 |
| 2452 // Find a line view that corresponds to the given line number. |
| 2453 function findViewForLine(cm, lineN) { |
| 2454 if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) |
| 2455 { return cm.display.view[findViewIndex(cm, lineN)] } |
| 2456 var ext = cm.display.externalMeasured |
| 2457 if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) |
| 2458 { return ext } |
| 2459 } |
| 2460 |
| 2461 // Measurement can be split in two steps, the set-up work that |
| 2462 // applies to the whole line, and the measurement of the actual |
| 2463 // character. Functions like coordsChar, that need to do a lot of |
| 2464 // measurements in a row, can thus ensure that the set-up work is |
| 2465 // only done once. |
| 2466 function prepareMeasureForLine(cm, line) { |
| 2467 var lineN = lineNo(line) |
| 2468 var view = findViewForLine(cm, lineN) |
| 2469 if (view && !view.text) { |
| 2470 view = null |
| 2471 } else if (view && view.changes) { |
| 2472 updateLineForChanges(cm, view, lineN, getDimensions(cm)) |
| 2473 cm.curOp.forceUpdate = true |
| 2474 } |
| 2475 if (!view) |
| 2476 { view = updateExternalMeasurement(cm, line) } |
| 2477 |
| 2478 var info = mapFromLineView(view, line, lineN) |
| 2479 return { |
| 2480 line: line, view: view, rect: null, |
| 2481 map: info.map, cache: info.cache, before: info.before, |
| 2482 hasHeights: false |
| 2483 } |
| 2484 } |
| 2485 |
| 2486 // Given a prepared measurement object, measures the position of an |
| 2487 // actual character (or fetches it from the cache). |
| 2488 function measureCharPrepared(cm, prepared, ch, bias, varHeight) { |
| 2489 if (prepared.before) { ch = -1 } |
| 2490 var key = ch + (bias || ""), found |
| 2491 if (prepared.cache.hasOwnProperty(key)) { |
| 2492 found = prepared.cache[key] |
| 2493 } else { |
| 2494 if (!prepared.rect) |
| 2495 { prepared.rect = prepared.view.text.getBoundingClientRect() } |
| 2496 if (!prepared.hasHeights) { |
| 2497 ensureLineHeights(cm, prepared.view, prepared.rect) |
| 2498 prepared.hasHeights = true |
| 2499 } |
| 2500 found = measureCharInner(cm, prepared, ch, bias) |
| 2501 if (!found.bogus) { prepared.cache[key] = found } |
| 2502 } |
| 2503 return {left: found.left, right: found.right, |
| 2504 top: varHeight ? found.rtop : found.top, |
| 2505 bottom: varHeight ? found.rbottom : found.bottom} |
| 2506 } |
| 2507 |
| 2508 var nullRect = {left: 0, right: 0, top: 0, bottom: 0} |
| 2509 |
| 2510 function nodeAndOffsetInLineMap(map, ch, bias) { |
| 2511 var node, start, end, collapse, mStart, mEnd |
| 2512 // First, search the line map for the text node corresponding to, |
| 2513 // or closest to, the target character. |
| 2514 for (var i = 0; i < map.length; i += 3) { |
| 2515 mStart = map[i] |
| 2516 mEnd = map[i + 1] |
| 2517 if (ch < mStart) { |
| 2518 start = 0; end = 1 |
| 2519 collapse = "left" |
| 2520 } else if (ch < mEnd) { |
| 2521 start = ch - mStart |
| 2522 end = start + 1 |
| 2523 } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { |
| 2524 end = mEnd - mStart |
| 2525 start = end - 1 |
| 2526 if (ch >= mEnd) { collapse = "right" } |
| 2527 } |
| 2528 if (start != null) { |
| 2529 node = map[i + 2] |
| 2530 if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) |
| 2531 { collapse = bias } |
| 2532 if (bias == "left" && start == 0) |
| 2533 { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { |
| 2534 node = map[(i -= 3) + 2] |
| 2535 collapse = "left" |
| 2536 } } |
| 2537 if (bias == "right" && start == mEnd - mStart) |
| 2538 { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].i
nsertLeft) { |
| 2539 node = map[(i += 3) + 2] |
| 2540 collapse = "right" |
| 2541 } } |
| 2542 break |
| 2543 } |
| 2544 } |
| 2545 return {node: node, start: start, end: end, collapse: collapse, coverStart: mS
tart, coverEnd: mEnd} |
| 2546 } |
| 2547 |
| 2548 function getUsefulRect(rects, bias) { |
| 2549 var rect = nullRect |
| 2550 if (bias == "left") { for (var i = 0; i < rects.length; i++) { |
| 2551 if ((rect = rects[i]).left != rect.right) { break } |
| 2552 } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { |
| 2553 if ((rect = rects[i$1]).left != rect.right) { break } |
| 2554 } } |
| 2555 return rect |
| 2556 } |
| 2557 |
| 2558 function measureCharInner(cm, prepared, ch, bias) { |
| 2559 var place = nodeAndOffsetInLineMap(prepared.map, ch, bias) |
| 2560 var node = place.node, start = place.start, end = place.end, collapse = place.
collapse |
| 2561 |
| 2562 var rect |
| 2563 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the
coordinates. |
| 2564 for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonse
nse rectangles are returned |
| 2565 while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart
+ start))) { --start } |
| 2566 while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared
.line.text.charAt(place.coverStart + end))) { ++end } |
| 2567 if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.co
verStart) |
| 2568 { rect = node.parentNode.getBoundingClientRect() } |
| 2569 else |
| 2570 { rect = getUsefulRect(range(node, start, end).getClientRects(), bias) } |
| 2571 if (rect.left || rect.right || start == 0) { break } |
| 2572 end = start |
| 2573 start = start - 1 |
| 2574 collapse = "right" |
| 2575 } |
| 2576 if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.mea
sure, rect) } |
| 2577 } else { // If it is a widget, simply get the box for the whole widget. |
| 2578 if (start > 0) { collapse = bias = "right" } |
| 2579 var rects |
| 2580 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) |
| 2581 { rect = rects[bias == "right" ? rects.length - 1 : 0] } |
| 2582 else |
| 2583 { rect = node.getBoundingClientRect() } |
| 2584 } |
| 2585 if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { |
| 2586 var rSpan = node.parentNode.getClientRects()[0] |
| 2587 if (rSpan) |
| 2588 { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top
: rSpan.top, bottom: rSpan.bottom} } |
| 2589 else |
| 2590 { rect = nullRect } |
| 2591 } |
| 2592 |
| 2593 var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.to
p |
| 2594 var mid = (rtop + rbot) / 2 |
| 2595 var heights = prepared.view.measure.heights |
| 2596 var i = 0 |
| 2597 for (; i < heights.length - 1; i++) |
| 2598 { if (mid < heights[i]) { break } } |
| 2599 var top = i ? heights[i - 1] : 0, bot = heights[i] |
| 2600 var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.
rect.left, |
| 2601 right: (collapse == "left" ? rect.left : rect.right) - prepared.
rect.left, |
| 2602 top: top, bottom: bot} |
| 2603 if (!rect.left && !rect.right) { result.bogus = true } |
| 2604 if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbotto
m = rbot } |
| 2605 |
| 2606 return result |
| 2607 } |
| 2608 |
| 2609 // Work around problem with bounding client rects on ranges being |
| 2610 // returned incorrectly when zoomed on IE10 and below. |
| 2611 function maybeUpdateRectForZooming(measure, rect) { |
| 2612 if (!window.screen || screen.logicalXDPI == null || |
| 2613 screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) |
| 2614 { return rect } |
| 2615 var scaleX = screen.logicalXDPI / screen.deviceXDPI |
| 2616 var scaleY = screen.logicalYDPI / screen.deviceYDPI |
| 2617 return {left: rect.left * scaleX, right: rect.right * scaleX, |
| 2618 top: rect.top * scaleY, bottom: rect.bottom * scaleY} |
| 2619 } |
| 2620 |
| 2621 function clearLineMeasurementCacheFor(lineView) { |
| 2622 if (lineView.measure) { |
| 2623 lineView.measure.cache = {} |
| 2624 lineView.measure.heights = null |
| 2625 if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) |
| 2626 { lineView.measure.caches[i] = {} } } |
| 2627 } |
| 2628 } |
| 2629 |
| 2630 function clearLineMeasurementCache(cm) { |
| 2631 cm.display.externalMeasure = null |
| 2632 removeChildren(cm.display.lineMeasure) |
| 2633 for (var i = 0; i < cm.display.view.length; i++) |
| 2634 { clearLineMeasurementCacheFor(cm.display.view[i]) } |
| 2635 } |
| 2636 |
| 2637 function clearCaches(cm) { |
| 2638 clearLineMeasurementCache(cm) |
| 2639 cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPa
ddingH = null |
| 2640 if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true } |
| 2641 cm.display.lineNumChars = null |
| 2642 } |
| 2643 |
| 2644 function pageScrollX() { return window.pageXOffset || (document.documentElement
|| document.body).scrollLeft } |
| 2645 function pageScrollY() { return window.pageYOffset || (document.documentElement
|| document.body).scrollTop } |
| 2646 |
| 2647 // Converts a {top, bottom, left, right} box from line-local |
| 2648 // coordinates into another coordinate system. Context may be one of |
| 2649 // "line", "div" (display.lineDiv), "local"./null (editor), "window", |
| 2650 // or "page". |
| 2651 function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { |
| 2652 if (!includeWidgets && lineObj.widgets) { for (var i = 0; i < lineObj.widgets.
length; ++i) { if (lineObj.widgets[i].above) { |
| 2653 var size = widgetHeight(lineObj.widgets[i]) |
| 2654 rect.top += size; rect.bottom += size |
| 2655 } } } |
| 2656 if (context == "line") { return rect } |
| 2657 if (!context) { context = "local" } |
| 2658 var yOff = heightAtLine(lineObj) |
| 2659 if (context == "local") { yOff += paddingTop(cm.display) } |
| 2660 else { yOff -= cm.display.viewOffset } |
| 2661 if (context == "page" || context == "window") { |
| 2662 var lOff = cm.display.lineSpace.getBoundingClientRect() |
| 2663 yOff += lOff.top + (context == "window" ? 0 : pageScrollY()) |
| 2664 var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()) |
| 2665 rect.left += xOff; rect.right += xOff |
| 2666 } |
| 2667 rect.top += yOff; rect.bottom += yOff |
| 2668 return rect |
| 2669 } |
| 2670 |
| 2671 // Coverts a box from "div" coords to another coordinate system. |
| 2672 // Context may be "window", "page", "div", or "local"./null. |
| 2673 function fromCoordSystem(cm, coords, context) { |
| 2674 if (context == "div") { return coords } |
| 2675 var left = coords.left, top = coords.top |
| 2676 // First move into "page" coordinate system |
| 2677 if (context == "page") { |
| 2678 left -= pageScrollX() |
| 2679 top -= pageScrollY() |
| 2680 } else if (context == "local" || !context) { |
| 2681 var localBox = cm.display.sizer.getBoundingClientRect() |
| 2682 left += localBox.left |
| 2683 top += localBox.top |
| 2684 } |
| 2685 |
| 2686 var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect() |
| 2687 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} |
| 2688 } |
| 2689 |
| 2690 function charCoords(cm, pos, context, lineObj, bias) { |
| 2691 if (!lineObj) { lineObj = getLine(cm.doc, pos.line) } |
| 2692 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), co
ntext) |
| 2693 } |
| 2694 |
| 2695 // Returns a box for a given cursor position, which may have an |
| 2696 // 'other' property containing the position of the secondary cursor |
| 2697 // on a bidi boundary. |
| 2698 // A cursor Pos(line, char, "before") is on the same visual line as `char - 1` |
| 2699 // and after `char - 1` in writing order of `char - 1` |
| 2700 // A cursor Pos(line, char, "after") is on the same visual line as `char` |
| 2701 // and before `char` in writing order of `char` |
| 2702 // Examples (upper-case letters are RTL, lower-case are LTR): |
| 2703 // Pos(0, 1, ...) |
| 2704 // before after |
| 2705 // ab a|b a|b |
| 2706 // aB a|B aB| |
| 2707 // Ab |Ab A|b |
| 2708 // AB B|A B|A |
| 2709 // Every position after the last character on a line is considered to stick |
| 2710 // to the last character on the line. |
| 2711 function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { |
| 2712 lineObj = lineObj || getLine(cm.doc, pos.line) |
| 2713 if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) } |
| 2714 function get(ch, right) { |
| 2715 var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left
", varHeight) |
| 2716 if (right) { m.left = m.right; } else { m.right = m.left } |
| 2717 return intoCoordSystem(cm, lineObj, m, context) |
| 2718 } |
| 2719 var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sti
cky |
| 2720 if (ch >= lineObj.text.length) { |
| 2721 ch = lineObj.text.length |
| 2722 sticky = "before" |
| 2723 } else if (ch <= 0) { |
| 2724 ch = 0 |
| 2725 sticky = "after" |
| 2726 } |
| 2727 if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before")
} |
| 2728 |
| 2729 function getBidi(ch, partPos, invert) { |
| 2730 var part = order[partPos], right = (part.level % 2) != 0 |
| 2731 return get(invert ? ch - 1 : ch, right != invert) |
| 2732 } |
| 2733 var partPos = getBidiPartAt(order, ch, sticky) |
| 2734 var other = bidiOther |
| 2735 var val = getBidi(ch, partPos, sticky == "before") |
| 2736 if (other != null) { val.other = getBidi(ch, other, sticky != "before") } |
| 2737 return val |
| 2738 } |
| 2739 |
| 2740 // Used to cheaply estimate the coordinates for a position. Used for |
| 2741 // intermediate scroll updates. |
| 2742 function estimateCoords(cm, pos) { |
| 2743 var left = 0 |
| 2744 pos = clipPos(cm.doc, pos) |
| 2745 if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch } |
| 2746 var lineObj = getLine(cm.doc, pos.line) |
| 2747 var top = heightAtLine(lineObj) + paddingTop(cm.display) |
| 2748 return {left: left, right: left, top: top, bottom: top + lineObj.height} |
| 2749 } |
| 2750 |
| 2751 // Positions returned by coordsChar contain some extra information. |
| 2752 // xRel is the relative x position of the input coordinates compared |
| 2753 // to the found position (so xRel > 0 means the coordinates are to |
| 2754 // the right of the character position, for example). When outside |
| 2755 // is true, that means the coordinates lie outside the line's |
| 2756 // vertical range. |
| 2757 function PosWithInfo(line, ch, sticky, outside, xRel) { |
| 2758 var pos = Pos(line, ch, sticky) |
| 2759 pos.xRel = xRel |
| 2760 if (outside) { pos.outside = true } |
| 2761 return pos |
| 2762 } |
| 2763 |
| 2764 // Compute the character position closest to the given coordinates. |
| 2765 // Input must be lineSpace-local ("div" coordinate system). |
| 2766 function coordsChar(cm, x, y) { |
| 2767 var doc = cm.doc |
| 2768 y += cm.display.viewOffset |
| 2769 if (y < 0) { return PosWithInfo(doc.first, 0, null, true, -1) } |
| 2770 var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1 |
| 2771 if (lineN > last) |
| 2772 { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.lengt
h, null, true, 1) } |
| 2773 if (x < 0) { x = 0 } |
| 2774 |
| 2775 var lineObj = getLine(doc, lineN) |
| 2776 for (;;) { |
| 2777 var found = coordsCharInner(cm, lineObj, lineN, x, y) |
| 2778 var merged = collapsedSpanAtEnd(lineObj) |
| 2779 var mergedPos = merged && merged.find(0, true) |
| 2780 if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch
&& found.xRel > 0)) |
| 2781 { lineN = lineNo(lineObj = mergedPos.to.line) } |
| 2782 else |
| 2783 { return found } |
| 2784 } |
| 2785 } |
| 2786 |
| 2787 function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { |
| 2788 var measure = function (ch) { return intoCoordSystem(cm, lineObj, measureCharP
repared(cm, preparedMeasure, ch), "line"); } |
| 2789 var end = lineObj.text.length |
| 2790 var begin = findFirst(function (ch) { return measure(ch - 1).bottom <= y; }, e
nd, 0) |
| 2791 end = findFirst(function (ch) { return measure(ch).top > y; }, begin, end) |
| 2792 return {begin: begin, end: end} |
| 2793 } |
| 2794 |
| 2795 function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { |
| 2796 var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedM
easure, target), "line").top |
| 2797 return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) |
| 2798 } |
| 2799 |
| 2800 function coordsCharInner(cm, lineObj, lineNo, x, y) { |
| 2801 y -= heightAtLine(lineObj) |
| 2802 var begin = 0, end = lineObj.text.length |
| 2803 var preparedMeasure = prepareMeasureForLine(cm, lineObj) |
| 2804 var pos |
| 2805 var order = getOrder(lineObj, cm.doc.direction) |
| 2806 if (order) { |
| 2807 if (cm.options.lineWrapping) { |
| 2808 ;var assign; |
| 2809 ((assign = wrappedLineExtent(cm, lineObj, preparedMeasure, y), begin = ass
ign.begin, end = assign.end)) |
| 2810 } |
| 2811 pos = new Pos(lineNo, begin) |
| 2812 var beginLeft = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left |
| 2813 var dir = beginLeft < x ? 1 : -1 |
| 2814 var prevDiff, diff = beginLeft - x, prevPos |
| 2815 do { |
| 2816 prevDiff = diff |
| 2817 prevPos = pos |
| 2818 pos = moveVisually(cm, lineObj, pos, dir) |
| 2819 if (pos == null || pos.ch < begin || end <= (pos.sticky == "before" ? pos.
ch - 1 : pos.ch)) { |
| 2820 pos = prevPos |
| 2821 break |
| 2822 } |
| 2823 diff = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left - x |
| 2824 } while ((dir < 0) != (diff < 0) && (Math.abs(diff) <= Math.abs(prevDiff))) |
| 2825 if (Math.abs(diff) > Math.abs(prevDiff)) { |
| 2826 if ((diff < 0) == (prevDiff < 0)) { throw new Error("Broke out of infinite
loop in coordsCharInner") } |
| 2827 pos = prevPos |
| 2828 } |
| 2829 } else { |
| 2830 var ch = findFirst(function (ch) { |
| 2831 var box = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMea
sure, ch), "line") |
| 2832 if (box.top > y) { |
| 2833 // For the cursor stickiness |
| 2834 end = Math.min(ch, end) |
| 2835 return true |
| 2836 } |
| 2837 else if (box.bottom <= y) { return false } |
| 2838 else if (box.left > x) { return true } |
| 2839 else if (box.right < x) { return false } |
| 2840 else { return (x - box.left < box.right - x) } |
| 2841 }, begin, end) |
| 2842 ch = skipExtendingChars(lineObj.text, ch, 1) |
| 2843 pos = new Pos(lineNo, ch, ch == end ? "before" : "after") |
| 2844 } |
| 2845 var coords = cursorCoords(cm, pos, "line", lineObj, preparedMeasure) |
| 2846 if (y < coords.top || coords.bottom < y) { pos.outside = true } |
| 2847 pos.xRel = x < coords.left ? -1 : (x > coords.right ? 1 : 0) |
| 2848 return pos |
| 2849 } |
| 2850 |
| 2851 var measureText |
| 2852 // Compute the default text height. |
| 2853 function textHeight(display) { |
| 2854 if (display.cachedTextHeight != null) { return display.cachedTextHeight } |
| 2855 if (measureText == null) { |
| 2856 measureText = elt("pre") |
| 2857 // Measure a bunch of lines, for browsers that compute |
| 2858 // fractional heights. |
| 2859 for (var i = 0; i < 49; ++i) { |
| 2860 measureText.appendChild(document.createTextNode("x")) |
| 2861 measureText.appendChild(elt("br")) |
| 2862 } |
| 2863 measureText.appendChild(document.createTextNode("x")) |
| 2864 } |
| 2865 removeChildrenAndAdd(display.measure, measureText) |
| 2866 var height = measureText.offsetHeight / 50 |
| 2867 if (height > 3) { display.cachedTextHeight = height } |
| 2868 removeChildren(display.measure) |
| 2869 return height || 1 |
| 2870 } |
| 2871 |
| 2872 // Compute the default character width. |
| 2873 function charWidth(display) { |
| 2874 if (display.cachedCharWidth != null) { return display.cachedCharWidth } |
| 2875 var anchor = elt("span", "xxxxxxxxxx") |
| 2876 var pre = elt("pre", [anchor]) |
| 2877 removeChildrenAndAdd(display.measure, pre) |
| 2878 var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) /
10 |
| 2879 if (width > 2) { display.cachedCharWidth = width } |
| 2880 return width || 10 |
| 2881 } |
| 2882 |
| 2883 // Do a bulk-read of the DOM positions and sizes needed to draw the |
| 2884 // view, so that we don't interleave reading and writing to the DOM. |
| 2885 function getDimensions(cm) { |
| 2886 var d = cm.display, left = {}, width = {} |
| 2887 var gutterLeft = d.gutters.clientLeft |
| 2888 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { |
| 2889 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft |
| 2890 width[cm.options.gutters[i]] = n.clientWidth |
| 2891 } |
| 2892 return {fixedPos: compensateForHScroll(d), |
| 2893 gutterTotalWidth: d.gutters.offsetWidth, |
| 2894 gutterLeft: left, |
| 2895 gutterWidth: width, |
| 2896 wrapperWidth: d.wrapper.clientWidth} |
| 2897 } |
| 2898 |
| 2899 // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, |
| 2900 // but using getBoundingClientRect to get a sub-pixel-accurate |
| 2901 // result. |
| 2902 function compensateForHScroll(display) { |
| 2903 return display.scroller.getBoundingClientRect().left - display.sizer.getBoundi
ngClientRect().left |
| 2904 } |
| 2905 |
| 2906 // Returns a function that estimates the height of a line, to use as |
| 2907 // first approximation until the line becomes visible (and is thus |
| 2908 // properly measurable). |
| 2909 function estimateHeight(cm) { |
| 2910 var th = textHeight(cm.display), wrapping = cm.options.lineWrapping |
| 2911 var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWi
dth(cm.display) - 3) |
| 2912 return function (line) { |
| 2913 if (lineIsHidden(cm.doc, line)) { return 0 } |
| 2914 |
| 2915 var widgetsHeight = 0 |
| 2916 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { |
| 2917 if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height } |
| 2918 } } |
| 2919 |
| 2920 if (wrapping) |
| 2921 { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th
} |
| 2922 else |
| 2923 { return widgetsHeight + th } |
| 2924 } |
| 2925 } |
| 2926 |
| 2927 function estimateLineHeights(cm) { |
| 2928 var doc = cm.doc, est = estimateHeight(cm) |
| 2929 doc.iter(function (line) { |
| 2930 var estHeight = est(line) |
| 2931 if (estHeight != line.height) { updateLineHeight(line, estHeight) } |
| 2932 }) |
| 2933 } |
| 2934 |
| 2935 // Given a mouse event, find the corresponding position. If liberal |
| 2936 // is false, it checks whether a gutter or scrollbar was clicked, |
| 2937 // and returns null if it was. forRect is used by rectangular |
| 2938 // selections, and tries to estimate a character position even for |
| 2939 // coordinates beyond the right of the text. |
| 2940 function posFromMouse(cm, e, liberal, forRect) { |
| 2941 var display = cm.display |
| 2942 if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return
null } |
| 2943 |
| 2944 var x, y, space = display.lineSpace.getBoundingClientRect() |
| 2945 // Fails unpredictably on IE[67] when mouse is dragged around quickly. |
| 2946 try { x = e.clientX - space.left; y = e.clientY - space.top } |
| 2947 catch (e) { return null } |
| 2948 var coords = coordsChar(cm, x, y), line |
| 2949 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).
length == coords.ch) { |
| 2950 var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.leng
th |
| 2951 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).l
eft) / charWidth(cm.display)) - colDiff)) |
| 2952 } |
| 2953 return coords |
| 2954 } |
| 2955 |
| 2956 // Find the view element corresponding to a given line. Return null |
| 2957 // when the line isn't visible. |
| 2958 function findViewIndex(cm, n) { |
| 2959 if (n >= cm.display.viewTo) { return null } |
| 2960 n -= cm.display.viewFrom |
| 2961 if (n < 0) { return null } |
| 2962 var view = cm.display.view |
| 2963 for (var i = 0; i < view.length; i++) { |
| 2964 n -= view[i].size |
| 2965 if (n < 0) { return i } |
| 2966 } |
| 2967 } |
| 2968 |
| 2969 function updateSelection(cm) { |
| 2970 cm.display.input.showSelection(cm.display.input.prepareSelection()) |
| 2971 } |
| 2972 |
| 2973 function prepareSelection(cm, primary) { |
| 2974 var doc = cm.doc, result = {} |
| 2975 var curFragment = result.cursors = document.createDocumentFragment() |
| 2976 var selFragment = result.selection = document.createDocumentFragment() |
| 2977 |
| 2978 for (var i = 0; i < doc.sel.ranges.length; i++) { |
| 2979 if (primary === false && i == doc.sel.primIndex) { continue } |
| 2980 var range = doc.sel.ranges[i] |
| 2981 if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.v
iewFrom) { continue } |
| 2982 var collapsed = range.empty() |
| 2983 if (collapsed || cm.options.showCursorWhenSelecting) |
| 2984 { drawSelectionCursor(cm, range.head, curFragment) } |
| 2985 if (!collapsed) |
| 2986 { drawSelectionRange(cm, range, selFragment) } |
| 2987 } |
| 2988 return result |
| 2989 } |
| 2990 |
| 2991 // Draws a cursor for the given range |
| 2992 function drawSelectionCursor(cm, head, output) { |
| 2993 var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHe
ightPerLine) |
| 2994 |
| 2995 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")) |
| 2996 cursor.style.left = pos.left + "px" |
| 2997 cursor.style.top = pos.top + "px" |
| 2998 cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHei
ght + "px" |
| 2999 |
| 3000 if (pos.other) { |
| 3001 // Secondary cursor, shown when on a 'jump' in bi-directional text |
| 3002 var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor
CodeMirror-secondarycursor")) |
| 3003 otherCursor.style.display = "" |
| 3004 otherCursor.style.left = pos.other.left + "px" |
| 3005 otherCursor.style.top = pos.other.top + "px" |
| 3006 otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px" |
| 3007 } |
| 3008 } |
| 3009 |
| 3010 // Draws the given range as a highlighted selection |
| 3011 function drawSelectionRange(cm, range, output) { |
| 3012 var display = cm.display, doc = cm.doc |
| 3013 var fragment = document.createDocumentFragment() |
| 3014 var padding = paddingH(cm.display), leftSide = padding.left |
| 3015 var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.
offsetLeft) - padding.right |
| 3016 |
| 3017 function add(left, top, width, bottom) { |
| 3018 if (top < 0) { top = 0 } |
| 3019 top = Math.round(top) |
| 3020 bottom = Math.round(bottom) |
| 3021 fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: abs
olute; left: " + left + "px;\n top: " + top + "px; w
idth: " + (width == null ? rightSide - left : width) + "px;\n
height: " + (bottom - top) + "px"))) |
| 3022 } |
| 3023 |
| 3024 function drawForLine(line, fromArg, toArg) { |
| 3025 var lineObj = getLine(doc, line) |
| 3026 var lineLen = lineObj.text.length |
| 3027 var start, end |
| 3028 function coords(ch, bias) { |
| 3029 return charCoords(cm, Pos(line, ch), "div", lineObj, bias) |
| 3030 } |
| 3031 |
| 3032 iterateBidiSections(getOrder(lineObj, doc.direction), fromArg || 0, toArg ==
null ? lineLen : toArg, function (from, to, dir) { |
| 3033 var leftPos = coords(from, "left"), rightPos, left, right |
| 3034 if (from == to) { |
| 3035 rightPos = leftPos |
| 3036 left = right = leftPos.left |
| 1554 } else { | 3037 } else { |
| 1555 setTimeout(rehide, 50); | 3038 rightPos = coords(to - 1, "right") |
| 3039 if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tm
p } |
| 3040 left = leftPos.left |
| 3041 right = rightPos.right |
| 1556 } | 3042 } |
| 1557 }, | 3043 if (fromArg == null && from == 0) { left = leftSide } |
| 1558 | 3044 if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part |
| 1559 readOnlyChanged: function(val) { | 3045 add(left, leftPos.top, null, leftPos.bottom) |
| 1560 if (!val) this.reset(); | 3046 left = leftSide |
| 1561 }, | 3047 if (leftPos.bottom < rightPos.top) { add(left, leftPos.bottom, null, rig
htPos.top) } |
| 1562 | 3048 } |
| 1563 setUneditable: nothing, | 3049 if (toArg == null && to == lineLen) { right = rightSide } |
| 1564 | 3050 if (!start || leftPos.top < start.top || leftPos.top == start.top && leftP
os.left < start.left) |
| 1565 needsContentAttribute: false | 3051 { start = leftPos } |
| 1566 }, TextareaInput.prototype); | 3052 if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom
&& rightPos.right > end.right) |
| 1567 | 3053 { end = rightPos } |
| 1568 // CONTENTEDITABLE INPUT STYLE | 3054 if (left < leftSide + 1) { left = leftSide } |
| 1569 | 3055 add(left, rightPos.top, right - left, rightPos.bottom) |
| 1570 function ContentEditableInput(cm) { | 3056 }) |
| 1571 this.cm = cm; | 3057 return {start: start, end: end} |
| 1572 this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.last
FocusOffset = null; | 3058 } |
| 1573 this.polling = new Delayed(); | 3059 |
| 1574 this.gracePeriod = false; | 3060 var sFrom = range.from(), sTo = range.to() |
| 1575 } | 3061 if (sFrom.line == sTo.line) { |
| 1576 | 3062 drawForLine(sFrom.line, sFrom.ch, sTo.ch) |
| 1577 ContentEditableInput.prototype = copyObj({ | 3063 } else { |
| 1578 init: function(display) { | 3064 var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line) |
| 1579 var input = this, cm = input.cm; | 3065 var singleVLine = visualLine(fromLine) == visualLine(toLine) |
| 1580 var div = input.div = display.lineDiv; | 3066 var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.
length + 1 : null).end |
| 1581 disableBrowserMagic(div); | 3067 var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start |
| 1582 | 3068 if (singleVLine) { |
| 1583 on(div, "paste", function(e) { | 3069 if (leftEnd.top < rightStart.top - 2) { |
| 1584 if (!signalDOMEvent(cm, e)) handlePaste(e, cm); | 3070 add(leftEnd.right, leftEnd.top, null, leftEnd.bottom) |
| 1585 }) | 3071 add(leftSide, rightStart.top, rightStart.left, rightStart.bottom) |
| 1586 | 3072 } else { |
| 1587 on(div, "compositionstart", function(e) { | 3073 add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd
.bottom) |
| 1588 var data = e.data; | 3074 } |
| 1589 input.composing = {sel: cm.doc.sel, data: data, startData: data}; | 3075 } |
| 1590 if (!data) return; | 3076 if (leftEnd.bottom < rightStart.top) |
| 1591 var prim = cm.doc.sel.primary(); | 3077 { add(leftSide, leftEnd.bottom, null, rightStart.top) } |
| 1592 var line = cm.getLine(prim.head.line); | 3078 } |
| 1593 var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length)); | 3079 |
| 1594 if (found > -1 && found <= prim.head.ch) | 3080 output.appendChild(fragment) |
| 1595 input.composing.sel = simpleSelection(Pos(prim.head.line, found), | 3081 } |
| 1596 Pos(prim.head.line, found + data
.length)); | 3082 |
| 1597 }); | 3083 // Cursor-blinking |
| 1598 on(div, "compositionupdate", function(e) { | 3084 function restartBlink(cm) { |
| 1599 input.composing.data = e.data; | 3085 if (!cm.state.focused) { return } |
| 1600 }); | 3086 var display = cm.display |
| 1601 on(div, "compositionend", function(e) { | 3087 clearInterval(display.blinker) |
| 1602 var ours = input.composing; | 3088 var on = true |
| 1603 if (!ours) return; | 3089 display.cursorDiv.style.visibility = "" |
| 1604 if (e.data != ours.startData && !/\u200b/.test(e.data)) | 3090 if (cm.options.cursorBlinkRate > 0) |
| 1605 ours.data = e.data; | 3091 { display.blinker = setInterval(function () { return display.cursorDiv.style
.visibility = (on = !on) ? "" : "hidden"; }, |
| 1606 // Need a small delay to prevent other code (input event, | 3092 cm.options.cursorBlinkRate) } |
| 1607 // selection polling) from doing damage when fired right after | 3093 else if (cm.options.cursorBlinkRate < 0) |
| 1608 // compositionend. | 3094 { display.cursorDiv.style.visibility = "hidden" } |
| 1609 setTimeout(function() { | 3095 } |
| 1610 if (!ours.handled) | 3096 |
| 1611 input.applyComposition(ours); | 3097 function ensureFocus(cm) { |
| 1612 if (input.composing == ours) | 3098 if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm) } |
| 1613 input.composing = null; | 3099 } |
| 1614 }, 50); | 3100 |
| 1615 }); | 3101 function delayBlurEvent(cm) { |
| 1616 | 3102 cm.state.delayingBlurEvent = true |
| 1617 on(div, "touchstart", function() { | 3103 setTimeout(function () { if (cm.state.delayingBlurEvent) { |
| 1618 input.forceCompositionEnd(); | 3104 cm.state.delayingBlurEvent = false |
| 1619 }); | 3105 onBlur(cm) |
| 1620 | 3106 } }, 100) |
| 1621 on(div, "input", function() { | 3107 } |
| 1622 if (input.composing) return; | 3108 |
| 1623 if (cm.isReadOnly() || !input.pollContent()) | 3109 function onFocus(cm, e) { |
| 1624 runInOp(input.cm, function() {regChange(cm);}); | 3110 if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false } |
| 1625 }); | 3111 |
| 1626 | 3112 if (cm.options.readOnly == "nocursor") { return } |
| 1627 function onCopyCut(e) { | 3113 if (!cm.state.focused) { |
| 1628 if (signalDOMEvent(cm, e)) return | 3114 signal(cm, "focus", cm, e) |
| 1629 if (cm.somethingSelected()) { | 3115 cm.state.focused = true |
| 1630 lastCopied = {lineWise: false, text: cm.getSelections()}; | 3116 addClass(cm.display.wrapper, "CodeMirror-focused") |
| 1631 if (e.type == "cut") cm.replaceSelection("", null, "cut"); | 3117 // This test prevents this from firing when a context |
| 1632 } else if (!cm.options.lineWiseCopyCut) { | 3118 // menu is closed (since the input reset would kill the |
| 1633 return; | 3119 // select-all detection hack) |
| 1634 } else { | 3120 if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { |
| 1635 var ranges = copyableRanges(cm); | 3121 cm.display.input.reset() |
| 1636 lastCopied = {lineWise: true, text: ranges.text}; | 3122 if (webkit) { setTimeout(function () { return cm.display.input.reset(true)
; }, 20) } // Issue #1730 |
| 1637 if (e.type == "cut") { | 3123 } |
| 1638 cm.operation(function() { | 3124 cm.display.input.receivedFocus() |
| 1639 cm.setSelections(ranges.ranges, 0, sel_dontScroll); | 3125 } |
| 1640 cm.replaceSelection("", null, "cut"); | 3126 restartBlink(cm) |
| 1641 }); | 3127 } |
| 1642 } | 3128 function onBlur(cm, e) { |
| 1643 } | 3129 if (cm.state.delayingBlurEvent) { return } |
| 1644 // iOS exposes the clipboard API, but seems to discard content inserted
into it | 3130 |
| 1645 if (e.clipboardData && !ios) { | 3131 if (cm.state.focused) { |
| 1646 e.preventDefault(); | 3132 signal(cm, "blur", cm, e) |
| 1647 e.clipboardData.clearData(); | 3133 cm.state.focused = false |
| 1648 e.clipboardData.setData("text/plain", lastCopied.text.join("\n")); | 3134 rmClass(cm.display.wrapper, "CodeMirror-focused") |
| 1649 } else { | 3135 } |
| 1650 // Old-fashioned briefly-focus-a-textarea hack | 3136 clearInterval(cm.display.blinker) |
| 1651 var kludge = hiddenTextarea(), te = kludge.firstChild; | 3137 setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false } }
, 150) |
| 1652 cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstCh
ild); | 3138 } |
| 1653 te.value = lastCopied.text.join("\n"); | 3139 |
| 1654 var hadFocus = document.activeElement; | 3140 // Re-align line numbers and gutter marks to compensate for |
| 1655 selectInput(te); | 3141 // horizontal scrolling. |
| 1656 setTimeout(function() { | 3142 function alignHorizontally(cm) { |
| 1657 cm.display.lineSpace.removeChild(kludge); | 3143 var display = cm.display, view = display.view |
| 1658 hadFocus.focus(); | 3144 if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixed
Gutter)) { return } |
| 1659 }, 50); | 3145 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.do
c.scrollLeft |
| 3146 var gutterW = display.gutters.offsetWidth, left = comp + "px" |
| 3147 for (var i = 0; i < view.length; i++) { if (!view[i].hidden) { |
| 3148 if (cm.options.fixedGutter) { |
| 3149 if (view[i].gutter) |
| 3150 { view[i].gutter.style.left = left } |
| 3151 if (view[i].gutterBackground) |
| 3152 { view[i].gutterBackground.style.left = left } |
| 3153 } |
| 3154 var align = view[i].alignable |
| 3155 if (align) { for (var j = 0; j < align.length; j++) |
| 3156 { align[j].style.left = left } } |
| 3157 } } |
| 3158 if (cm.options.fixedGutter) |
| 3159 { display.gutters.style.left = (comp + gutterW) + "px" } |
| 3160 } |
| 3161 |
| 3162 // Used to ensure that the line number gutter is still the right |
| 3163 // size for the current document size. Returns true when an update |
| 3164 // is needed. |
| 3165 function maybeUpdateLineNumberWidth(cm) { |
| 3166 if (!cm.options.lineNumbers) { return false } |
| 3167 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1),
display = cm.display |
| 3168 if (last.length != display.lineNumChars) { |
| 3169 var test = display.measure.appendChild(elt("div", [elt("div", last)], |
| 3170 "CodeMirror-linenumber CodeMirror
-gutter-elt")) |
| 3171 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - inner
W |
| 3172 display.lineGutter.style.width = "" |
| 3173 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth
- padding) + 1 |
| 3174 display.lineNumWidth = display.lineNumInnerWidth + padding |
| 3175 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1 |
| 3176 display.lineGutter.style.width = display.lineNumWidth + "px" |
| 3177 updateGutterSpace(cm) |
| 3178 return true |
| 3179 } |
| 3180 return false |
| 3181 } |
| 3182 |
| 3183 // Read the actual heights of the rendered lines, and update their |
| 3184 // stored heights to match. |
| 3185 function updateHeightsInViewport(cm) { |
| 3186 var display = cm.display |
| 3187 var prevBottom = display.lineDiv.offsetTop |
| 3188 for (var i = 0; i < display.view.length; i++) { |
| 3189 var cur = display.view[i], height = (void 0) |
| 3190 if (cur.hidden) { continue } |
| 3191 if (ie && ie_version < 8) { |
| 3192 var bot = cur.node.offsetTop + cur.node.offsetHeight |
| 3193 height = bot - prevBottom |
| 3194 prevBottom = bot |
| 3195 } else { |
| 3196 var box = cur.node.getBoundingClientRect() |
| 3197 height = box.bottom - box.top |
| 3198 } |
| 3199 var diff = cur.line.height - height |
| 3200 if (height < 2) { height = textHeight(display) } |
| 3201 if (diff > .001 || diff < -.001) { |
| 3202 updateLineHeight(cur.line, height) |
| 3203 updateWidgetHeight(cur.line) |
| 3204 if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) |
| 3205 { updateWidgetHeight(cur.rest[j]) } } |
| 3206 } |
| 3207 } |
| 3208 } |
| 3209 |
| 3210 // Read and store the height of line widgets associated with the |
| 3211 // given line. |
| 3212 function updateWidgetHeight(line) { |
| 3213 if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) |
| 3214 { line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight } } |
| 3215 } |
| 3216 |
| 3217 // Compute the lines that are visible in a given viewport (defaults |
| 3218 // the the current scroll position). viewport may contain top, |
| 3219 // height, and ensure (see op.scrollToPos) properties. |
| 3220 function visibleLines(display, doc, viewport) { |
| 3221 var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : displ
ay.scroller.scrollTop |
| 3222 top = Math.floor(top - paddingTop(display)) |
| 3223 var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + dis
play.wrapper.clientHeight |
| 3224 |
| 3225 var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom) |
| 3226 // Ensure is a {from: {line, ch}, to: {line, ch}} object, and |
| 3227 // forces those lines into the viewport (if possible). |
| 3228 if (viewport && viewport.ensure) { |
| 3229 var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.li
ne |
| 3230 if (ensureFrom < from) { |
| 3231 from = ensureFrom |
| 3232 to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wr
apper.clientHeight) |
| 3233 } else if (Math.min(ensureTo, doc.lastLine()) >= to) { |
| 3234 from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wr
apper.clientHeight) |
| 3235 to = ensureTo |
| 3236 } |
| 3237 } |
| 3238 return {from: from, to: Math.max(to, from + 1)} |
| 3239 } |
| 3240 |
| 3241 // Sync the scrollable area and scrollbars, ensure the viewport |
| 3242 // covers the visible area. |
| 3243 function setScrollTop(cm, val) { |
| 3244 if (Math.abs(cm.doc.scrollTop - val) < 2) { return } |
| 3245 cm.doc.scrollTop = val |
| 3246 if (!gecko) { updateDisplaySimple(cm, {top: val}) } |
| 3247 if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = va
l } |
| 3248 cm.display.scrollbars.setScrollTop(val) |
| 3249 if (gecko) { updateDisplaySimple(cm) } |
| 3250 startWorker(cm, 100) |
| 3251 } |
| 3252 // Sync scroller and scrollbar, ensure the gutter elements are |
| 3253 // aligned. |
| 3254 function setScrollLeft(cm, val, isScroller) { |
| 3255 if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val)
< 2) { return } |
| 3256 val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clie
ntWidth) |
| 3257 cm.doc.scrollLeft = val |
| 3258 alignHorizontally(cm) |
| 3259 if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft =
val } |
| 3260 cm.display.scrollbars.setScrollLeft(val) |
| 3261 } |
| 3262 |
| 3263 // Since the delta values reported on mouse wheel events are |
| 3264 // unstandardized between browsers and even browser versions, and |
| 3265 // generally horribly unpredictable, this code starts by measuring |
| 3266 // the scroll effect that the first few mouse wheel events have, |
| 3267 // and, from that, detects the way it can convert deltas to pixel |
| 3268 // offsets afterwards. |
| 3269 // |
| 3270 // The reason we want to know the amount a wheel event will scroll |
| 3271 // is that it gives us a chance to update the display before the |
| 3272 // actual scrolling happens, reducing flickering. |
| 3273 |
| 3274 var wheelSamples = 0; |
| 3275 var wheelPixelsPerUnit = null; |
| 3276 // Fill in a browser-detected starting value on browsers where we |
| 3277 // know one. These don't have to be accurate -- the result of them |
| 3278 // being wrong would just be a slight flicker on the first wheel |
| 3279 // scroll (if it is large enough). |
| 3280 if (ie) { wheelPixelsPerUnit = -.53 } |
| 3281 else if (gecko) { wheelPixelsPerUnit = 15 } |
| 3282 else if (chrome) { wheelPixelsPerUnit = -.7 } |
| 3283 else if (safari) { wheelPixelsPerUnit = -1/3 } |
| 3284 |
| 3285 function wheelEventDelta(e) { |
| 3286 var dx = e.wheelDeltaX, dy = e.wheelDeltaY |
| 3287 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail } |
| 3288 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail } |
| 3289 else if (dy == null) { dy = e.wheelDelta } |
| 3290 return {x: dx, y: dy} |
| 3291 } |
| 3292 function wheelEventPixels(e) { |
| 3293 var delta = wheelEventDelta(e) |
| 3294 delta.x *= wheelPixelsPerUnit |
| 3295 delta.y *= wheelPixelsPerUnit |
| 3296 return delta |
| 3297 } |
| 3298 |
| 3299 function onScrollWheel(cm, e) { |
| 3300 var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y |
| 3301 |
| 3302 var display = cm.display, scroll = display.scroller |
| 3303 // Quit if there's nothing to scroll here |
| 3304 var canScrollX = scroll.scrollWidth > scroll.clientWidth |
| 3305 var canScrollY = scroll.scrollHeight > scroll.clientHeight |
| 3306 if (!(dx && canScrollX || dy && canScrollY)) { return } |
| 3307 |
| 3308 // Webkit browsers on OS X abort momentum scrolls when the target |
| 3309 // of the scroll event is removed from the scrollable element. |
| 3310 // This hack (see related code in patchDisplay) makes sure the |
| 3311 // element is kept around. |
| 3312 if (dy && mac && webkit) { |
| 3313 outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cu
r.parentNode) { |
| 3314 for (var i = 0; i < view.length; i++) { |
| 3315 if (view[i].node == cur) { |
| 3316 cm.display.currentWheelTarget = cur |
| 3317 break outer |
| 1660 } | 3318 } |
| 1661 } | 3319 } |
| 1662 on(div, "copy", onCopyCut); | 3320 } |
| 1663 on(div, "cut", onCopyCut); | 3321 } |
| 3322 |
| 3323 // On some browsers, horizontal scrolling will cause redraws to |
| 3324 // happen before the gutter has been realigned, causing it to |
| 3325 // wriggle around in a most unseemly way. When we have an |
| 3326 // estimated pixels/delta value, we just handle horizontal |
| 3327 // scrolling entirely here. It'll be slightly off from native, but |
| 3328 // better than glitching out. |
| 3329 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { |
| 3330 if (dy && canScrollY) |
| 3331 { setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixel
sPerUnit, scroll.scrollHeight - scroll.clientHeight))) } |
| 3332 setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsP
erUnit, scroll.scrollWidth - scroll.clientWidth))) |
| 3333 // Only prevent default scrolling if vertical scrolling is |
| 3334 // actually possible. Otherwise, it causes vertical scroll |
| 3335 // jitter on OSX trackpads when deltaX is small and deltaY |
| 3336 // is large (issue #3579) |
| 3337 if (!dy || (dy && canScrollY)) |
| 3338 { e_preventDefault(e) } |
| 3339 display.wheelStartX = null // Abort measurement, if in progress |
| 3340 return |
| 3341 } |
| 3342 |
| 3343 // 'Project' the visible viewport to cover the area that is being |
| 3344 // scrolled into view (if we know enough to estimate it). |
| 3345 if (dy && wheelPixelsPerUnit != null) { |
| 3346 var pixels = dy * wheelPixelsPerUnit |
| 3347 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight |
| 3348 if (pixels < 0) { top = Math.max(0, top + pixels - 50) } |
| 3349 else { bot = Math.min(cm.doc.height, bot + pixels + 50) } |
| 3350 updateDisplaySimple(cm, {top: top, bottom: bot}) |
| 3351 } |
| 3352 |
| 3353 if (wheelSamples < 20) { |
| 3354 if (display.wheelStartX == null) { |
| 3355 display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scro
llTop |
| 3356 display.wheelDX = dx; display.wheelDY = dy |
| 3357 setTimeout(function () { |
| 3358 if (display.wheelStartX == null) { return } |
| 3359 var movedX = scroll.scrollLeft - display.wheelStartX |
| 3360 var movedY = scroll.scrollTop - display.wheelStartY |
| 3361 var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || |
| 3362 (movedX && display.wheelDX && movedX / display.wheelDX) |
| 3363 display.wheelStartX = display.wheelStartY = null |
| 3364 if (!sample) { return } |
| 3365 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (whe
elSamples + 1) |
| 3366 ++wheelSamples |
| 3367 }, 200) |
| 3368 } else { |
| 3369 display.wheelDX += dx; display.wheelDY += dy |
| 3370 } |
| 3371 } |
| 3372 } |
| 3373 |
| 3374 // SCROLLBARS |
| 3375 |
| 3376 // Prepare DOM reads needed to update the scrollbars. Done in one |
| 3377 // shot to minimize update/measure roundtrips. |
| 3378 function measureForScrollbars(cm) { |
| 3379 var d = cm.display, gutterW = d.gutters.offsetWidth |
| 3380 var docH = Math.round(cm.doc.height + paddingVert(cm.display)) |
| 3381 return { |
| 3382 clientHeight: d.scroller.clientHeight, |
| 3383 viewHeight: d.wrapper.clientHeight, |
| 3384 scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, |
| 3385 viewWidth: d.wrapper.clientWidth, |
| 3386 barLeft: cm.options.fixedGutter ? gutterW : 0, |
| 3387 docHeight: docH, |
| 3388 scrollHeight: docH + scrollGap(cm) + d.barHeight, |
| 3389 nativeBarWidth: d.nativeBarWidth, |
| 3390 gutterWidth: gutterW |
| 3391 } |
| 3392 } |
| 3393 |
| 3394 var NativeScrollbars = function NativeScrollbars(place, scroll, cm) { |
| 3395 this.cm = cm |
| 3396 var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")],
"CodeMirror-vscrollbar") |
| 3397 var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min
-height: 1px")], "CodeMirror-hscrollbar") |
| 3398 place(vert); place(horiz) |
| 3399 |
| 3400 on(vert, "scroll", function () { |
| 3401 if (vert.clientHeight) { scroll(vert.scrollTop, "vertical") } |
| 3402 }) |
| 3403 on(horiz, "scroll", function () { |
| 3404 if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal") } |
| 3405 }) |
| 3406 |
| 3407 this.checkedZeroWidth = false |
| 3408 // Need to set a minimum width to see the scrollbar on IE7 (but must not set i
t on IE8). |
| 3409 if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWi
dth = "18px" } |
| 3410 }; |
| 3411 |
| 3412 NativeScrollbars.prototype.update = function update (measure) { |
| 3413 var needsH = measure.scrollWidth > measure.clientWidth + 1 |
| 3414 var needsV = measure.scrollHeight > measure.clientHeight + 1 |
| 3415 var sWidth = measure.nativeBarWidth |
| 3416 |
| 3417 if (needsV) { |
| 3418 this.vert.style.display = "block" |
| 3419 this.vert.style.bottom = needsH ? sWidth + "px" : "0" |
| 3420 var totalHeight = measure.viewHeight - (needsH ? sWidth : 0) |
| 3421 // A bug in IE8 can cause this value to be negative, so guard it. |
| 3422 this.vert.firstChild.style.height = |
| 3423 Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "
px" |
| 3424 } else { |
| 3425 this.vert.style.display = "" |
| 3426 this.vert.firstChild.style.height = "0" |
| 3427 } |
| 3428 |
| 3429 if (needsH) { |
| 3430 this.horiz.style.display = "block" |
| 3431 this.horiz.style.right = needsV ? sWidth + "px" : "0" |
| 3432 this.horiz.style.left = measure.barLeft + "px" |
| 3433 var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0) |
| 3434 this.horiz.firstChild.style.width = |
| 3435 Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px" |
| 3436 } else { |
| 3437 this.horiz.style.display = "" |
| 3438 this.horiz.firstChild.style.width = "0" |
| 3439 } |
| 3440 |
| 3441 if (!this.checkedZeroWidth && measure.clientHeight > 0) { |
| 3442 if (sWidth == 0) { this.zeroWidthHack() } |
| 3443 this.checkedZeroWidth = true |
| 3444 } |
| 3445 |
| 3446 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} |
| 3447 }; |
| 3448 |
| 3449 NativeScrollbars.prototype.setScrollLeft = function setScrollLeft$1 (pos) { |
| 3450 if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos } |
| 3451 if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz
) } |
| 3452 }; |
| 3453 |
| 3454 NativeScrollbars.prototype.setScrollTop = function setScrollTop$1 (pos) { |
| 3455 if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos } |
| 3456 if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert) } |
| 3457 }; |
| 3458 |
| 3459 NativeScrollbars.prototype.zeroWidthHack = function zeroWidthHack () { |
| 3460 var w = mac && !mac_geMountainLion ? "12px" : "18px" |
| 3461 this.horiz.style.height = this.vert.style.width = w |
| 3462 this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none" |
| 3463 this.disableHoriz = new Delayed |
| 3464 this.disableVert = new Delayed |
| 3465 }; |
| 3466 |
| 3467 NativeScrollbars.prototype.enableZeroWidthBar = function enableZeroWidthBar (bar
, delay) { |
| 3468 bar.style.pointerEvents = "auto" |
| 3469 function maybeDisable() { |
| 3470 // To find out whether the scrollbar is still visible, we |
| 3471 // check whether the element under the pixel in the bottom |
| 3472 // left corner of the scrollbar box is the scrollbar box |
| 3473 // itself (when the bar is still visible) or its filler child |
| 3474 // (when the bar is hidden). If it is still visible, we keep |
| 3475 // it enabled, if it's hidden, we disable pointer events. |
| 3476 var box = bar.getBoundingClientRect() |
| 3477 var elt = document.elementFromPoint(box.left + 1, box.bottom - 1) |
| 3478 if (elt != bar) { bar.style.pointerEvents = "none" } |
| 3479 else { delay.set(1000, maybeDisable) } |
| 3480 } |
| 3481 delay.set(1000, maybeDisable) |
| 3482 }; |
| 3483 |
| 3484 NativeScrollbars.prototype.clear = function clear () { |
| 3485 var parent = this.horiz.parentNode |
| 3486 parent.removeChild(this.horiz) |
| 3487 parent.removeChild(this.vert) |
| 3488 }; |
| 3489 |
| 3490 var NullScrollbars = function NullScrollbars () {}; |
| 3491 |
| 3492 NullScrollbars.prototype.update = function update () { return {bottom: 0, right:
0} }; |
| 3493 NullScrollbars.prototype.setScrollLeft = function setScrollLeft$2 () {}; |
| 3494 NullScrollbars.prototype.setScrollTop = function setScrollTop$2 () {}; |
| 3495 NullScrollbars.prototype.clear = function clear () {}; |
| 3496 |
| 3497 function updateScrollbars(cm, measure) { |
| 3498 if (!measure) { measure = measureForScrollbars(cm) } |
| 3499 var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight |
| 3500 updateScrollbarsInner(cm, measure) |
| 3501 for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != c
m.display.barHeight; i++) { |
| 3502 if (startWidth != cm.display.barWidth && cm.options.lineWrapping) |
| 3503 { updateHeightsInViewport(cm) } |
| 3504 updateScrollbarsInner(cm, measureForScrollbars(cm)) |
| 3505 startWidth = cm.display.barWidth; startHeight = cm.display.barHeight |
| 3506 } |
| 3507 } |
| 3508 |
| 3509 // Re-synchronize the fake scrollbars with the actual size of the |
| 3510 // content. |
| 3511 function updateScrollbarsInner(cm, measure) { |
| 3512 var d = cm.display |
| 3513 var sizes = d.scrollbars.update(measure) |
| 3514 |
| 3515 d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px" |
| 3516 d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px" |
| 3517 d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" |
| 3518 |
| 3519 if (sizes.right && sizes.bottom) { |
| 3520 d.scrollbarFiller.style.display = "block" |
| 3521 d.scrollbarFiller.style.height = sizes.bottom + "px" |
| 3522 d.scrollbarFiller.style.width = sizes.right + "px" |
| 3523 } else { d.scrollbarFiller.style.display = "" } |
| 3524 if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedG
utter) { |
| 3525 d.gutterFiller.style.display = "block" |
| 3526 d.gutterFiller.style.height = sizes.bottom + "px" |
| 3527 d.gutterFiller.style.width = measure.gutterWidth + "px" |
| 3528 } else { d.gutterFiller.style.display = "" } |
| 3529 } |
| 3530 |
| 3531 var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars} |
| 3532 |
| 3533 function initScrollbars(cm) { |
| 3534 if (cm.display.scrollbars) { |
| 3535 cm.display.scrollbars.clear() |
| 3536 if (cm.display.scrollbars.addClass) |
| 3537 { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass) } |
| 3538 } |
| 3539 |
| 3540 cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function
(node) { |
| 3541 cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller) |
| 3542 // Prevent clicks in the scrollbars from killing focus |
| 3543 on(node, "mousedown", function () { |
| 3544 if (cm.state.focused) { setTimeout(function () { return cm.display.input.f
ocus(); }, 0) } |
| 3545 }) |
| 3546 node.setAttribute("cm-not-content", "true") |
| 3547 }, function (pos, axis) { |
| 3548 if (axis == "horizontal") { setScrollLeft(cm, pos) } |
| 3549 else { setScrollTop(cm, pos) } |
| 3550 }, cm) |
| 3551 if (cm.display.scrollbars.addClass) |
| 3552 { addClass(cm.display.wrapper, cm.display.scrollbars.addClass) } |
| 3553 } |
| 3554 |
| 3555 // SCROLLING THINGS INTO VIEW |
| 3556 |
| 3557 // If an editor sits on the top or bottom of the window, partially |
| 3558 // scrolled out of view, this ensures that the cursor is visible. |
| 3559 function maybeScrollWindow(cm, rect) { |
| 3560 if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } |
| 3561 |
| 3562 var display = cm.display, box = display.sizer.getBoundingClientRect(), doScrol
l = null |
| 3563 if (rect.top + box.top < 0) { doScroll = true } |
| 3564 else if (rect.bottom + box.top > (window.innerHeight || document.documentEleme
nt.clientHeight)) { doScroll = false } |
| 3565 if (doScroll != null && !phantom) { |
| 3566 var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n
top: " + (rect.top - display.viewOffset - paddingTop(cm.display)
) + "px;\n height: " + (rect.bottom - rect.top + scrollG
ap(cm) + display.barHeight) + "px;\n left: " + (rect.lef
t) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;")) |
| 3567 cm.display.lineSpace.appendChild(scrollNode) |
| 3568 scrollNode.scrollIntoView(doScroll) |
| 3569 cm.display.lineSpace.removeChild(scrollNode) |
| 3570 } |
| 3571 } |
| 3572 |
| 3573 // Scroll a given position into view (immediately), verifying that |
| 3574 // it actually became visible (as line heights are accurately |
| 3575 // measured, the position of something may 'drift' during drawing). |
| 3576 function scrollPosIntoView(cm, pos, end, margin) { |
| 3577 if (margin == null) { margin = 0 } |
| 3578 var rect |
| 3579 for (var limit = 0; limit < 5; limit++) { |
| 3580 var changed = false |
| 3581 var coords = cursorCoords(cm, pos) |
| 3582 var endCoords = !end || end == pos ? coords : cursorCoords(cm, end) |
| 3583 rect = {left: Math.min(coords.left, endCoords.left), |
| 3584 top: Math.min(coords.top, endCoords.top) - margin, |
| 3585 right: Math.max(coords.left, endCoords.left), |
| 3586 bottom: Math.max(coords.bottom, endCoords.bottom) + margin} |
| 3587 var scrollPos = calculateScrollPos(cm, rect) |
| 3588 var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft |
| 3589 if (scrollPos.scrollTop != null) { |
| 3590 setScrollTop(cm, scrollPos.scrollTop) |
| 3591 if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true } |
| 3592 } |
| 3593 if (scrollPos.scrollLeft != null) { |
| 3594 setScrollLeft(cm, scrollPos.scrollLeft) |
| 3595 if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true } |
| 3596 } |
| 3597 if (!changed) { break } |
| 3598 } |
| 3599 return rect |
| 3600 } |
| 3601 |
| 3602 // Scroll a given set of coordinates into view (immediately). |
| 3603 function scrollIntoView(cm, rect) { |
| 3604 var scrollPos = calculateScrollPos(cm, rect) |
| 3605 if (scrollPos.scrollTop != null) { setScrollTop(cm, scrollPos.scrollTop) } |
| 3606 if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft) } |
| 3607 } |
| 3608 |
| 3609 // Calculate a new scroll position needed to scroll the given |
| 3610 // rectangle into view. Returns an object with scrollTop and |
| 3611 // scrollLeft properties. When these are undefined, the |
| 3612 // vertical/horizontal position does not need to be adjusted. |
| 3613 function calculateScrollPos(cm, rect) { |
| 3614 var display = cm.display, snapMargin = textHeight(cm.display) |
| 3615 if (rect.top < 0) { rect.top = 0 } |
| 3616 var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop :
display.scroller.scrollTop |
| 3617 var screen = displayHeight(cm), result = {} |
| 3618 if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen } |
| 3619 var docBottom = cm.doc.height + paddingVert(display) |
| 3620 var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMa
rgin |
| 3621 if (rect.top < screentop) { |
| 3622 result.scrollTop = atTop ? 0 : rect.top |
| 3623 } else if (rect.bottom > screentop + screen) { |
| 3624 var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - scree
n) |
| 3625 if (newTop != screentop) { result.scrollTop = newTop } |
| 3626 } |
| 3627 |
| 3628 var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft
: display.scroller.scrollLeft |
| 3629 var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.off
setWidth : 0) |
| 3630 var tooWide = rect.right - rect.left > screenw |
| 3631 if (tooWide) { rect.right = rect.left + screenw } |
| 3632 if (rect.left < 10) |
| 3633 { result.scrollLeft = 0 } |
| 3634 else if (rect.left < screenleft) |
| 3635 { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)) } |
| 3636 else if (rect.right > screenw + screenleft - 3) |
| 3637 { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw } |
| 3638 return result |
| 3639 } |
| 3640 |
| 3641 // Store a relative adjustment to the scroll position in the current |
| 3642 // operation (to be applied when the operation finishes). |
| 3643 function addToScrollPos(cm, left, top) { |
| 3644 if (left != null || top != null) { resolveScrollToPos(cm) } |
| 3645 if (left != null) |
| 3646 { cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : c
m.curOp.scrollLeft) + left } |
| 3647 if (top != null) |
| 3648 { cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.c
urOp.scrollTop) + top } |
| 3649 } |
| 3650 |
| 3651 // Make sure that at the end of the operation the current cursor is |
| 3652 // shown. |
| 3653 function ensureCursorVisible(cm) { |
| 3654 resolveScrollToPos(cm) |
| 3655 var cur = cm.getCursor(), from = cur, to = cur |
| 3656 if (!cm.options.lineWrapping) { |
| 3657 from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur |
| 3658 to = Pos(cur.line, cur.ch + 1) |
| 3659 } |
| 3660 cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMar
gin} |
| 3661 } |
| 3662 |
| 3663 // When an operation has its scrollToPos property set, and another |
| 3664 // scroll action is applied before the end of the operation, this |
| 3665 // 'simulates' scrolling that position into view in a cheap way, so |
| 3666 // that the effect of intermediate scroll commands is not ignored. |
| 3667 function resolveScrollToPos(cm) { |
| 3668 var range = cm.curOp.scrollToPos |
| 3669 if (range) { |
| 3670 cm.curOp.scrollToPos = null |
| 3671 var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to) |
| 3672 var sPos = calculateScrollPos(cm, { |
| 3673 left: Math.min(from.left, to.left), |
| 3674 top: Math.min(from.top, to.top) - range.margin, |
| 3675 right: Math.max(from.right, to.right), |
| 3676 bottom: Math.max(from.bottom, to.bottom) + range.margin |
| 3677 }) |
| 3678 cm.scrollTo(sPos.scrollLeft, sPos.scrollTop) |
| 3679 } |
| 3680 } |
| 3681 |
| 3682 // Operations are used to wrap a series of changes to the editor |
| 3683 // state in such a way that each change won't have to update the |
| 3684 // cursor and display (which would be awkward, slow, and |
| 3685 // error-prone). Instead, display updates are batched and then all |
| 3686 // combined and executed at once. |
| 3687 |
| 3688 var nextOpId = 0 |
| 3689 // Start a new operation. |
| 3690 function startOperation(cm) { |
| 3691 cm.curOp = { |
| 3692 cm: cm, |
| 3693 viewChanged: false, // Flag that indicates that lines might need to be
redrawn |
| 3694 startHeight: cm.doc.height, // Used to detect need to update scrollbar |
| 3695 forceUpdate: false, // Used to force a redraw |
| 3696 updateInput: null, // Whether to reset the input textarea |
| 3697 typing: false, // Whether this reset should be careful to leave ex
isting text (for compositing) |
| 3698 changeObjs: null, // Accumulated changes, for firing change events |
| 3699 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on |
| 3700 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been c
alled already |
| 3701 selectionChanged: false, // Whether the selection needs to be redrawn |
| 3702 updateMaxLine: false, // Set when the widest line needs to be determined
anew |
| 3703 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not push
ed to DOM yet |
| 3704 scrollToPos: null, // Used to scroll to a specific position |
| 3705 focus: false, |
| 3706 id: ++nextOpId // Unique ID |
| 3707 } |
| 3708 pushOperation(cm.curOp) |
| 3709 } |
| 3710 |
| 3711 // Finish an operation, updating the display and signalling delayed events |
| 3712 function endOperation(cm) { |
| 3713 var op = cm.curOp |
| 3714 finishOperation(op, function (group) { |
| 3715 for (var i = 0; i < group.ops.length; i++) |
| 3716 { group.ops[i].cm.curOp = null } |
| 3717 endOperations(group) |
| 3718 }) |
| 3719 } |
| 3720 |
| 3721 // The DOM updates done when an operation finishes are batched so |
| 3722 // that the minimum number of relayouts are required. |
| 3723 function endOperations(group) { |
| 3724 var ops = group.ops |
| 3725 for (var i = 0; i < ops.length; i++) // Read DOM |
| 3726 { endOperation_R1(ops[i]) } |
| 3727 for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe) |
| 3728 { endOperation_W1(ops[i$1]) } |
| 3729 for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM |
| 3730 { endOperation_R2(ops[i$2]) } |
| 3731 for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe) |
| 3732 { endOperation_W2(ops[i$3]) } |
| 3733 for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM |
| 3734 { endOperation_finish(ops[i$4]) } |
| 3735 } |
| 3736 |
| 3737 function endOperation_R1(op) { |
| 3738 var cm = op.cm, display = cm.display |
| 3739 maybeClipScrollbars(cm) |
| 3740 if (op.updateMaxLine) { findMaxLine(cm) } |
| 3741 |
| 3742 op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || |
| 3743 op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || |
| 3744 op.scrollToPos.to.line >= display.viewTo) || |
| 3745 display.maxLineChanged && cm.options.lineWrapping |
| 3746 op.update = op.mustUpdate && |
| 3747 new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scroll
ToPos}, op.forceUpdate) |
| 3748 } |
| 3749 |
| 3750 function endOperation_W1(op) { |
| 3751 op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update) |
| 3752 } |
| 3753 |
| 3754 function endOperation_R2(op) { |
| 3755 var cm = op.cm, display = cm.display |
| 3756 if (op.updatedDisplay) { updateHeightsInViewport(cm) } |
| 3757 |
| 3758 op.barMeasure = measureForScrollbars(cm) |
| 3759 |
| 3760 // If the max line changed since it was last measured, measure it, |
| 3761 // and ensure the document's width matches it. |
| 3762 // updateDisplay_W2 will use these properties to do the actual resizing |
| 3763 if (display.maxLineChanged && !cm.options.lineWrapping) { |
| 3764 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.len
gth).left + 3 |
| 3765 cm.display.sizerWidth = op.adjustWidthTo |
| 3766 op.barMeasure.scrollWidth = |
| 3767 Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjus
tWidthTo + scrollGap(cm) + cm.display.barWidth) |
| 3768 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo -
displayWidth(cm)) |
| 3769 } |
| 3770 |
| 3771 if (op.updatedDisplay || op.selectionChanged) |
| 3772 { op.preparedSelection = display.input.prepareSelection(op.focus) } |
| 3773 } |
| 3774 |
| 3775 function endOperation_W2(op) { |
| 3776 var cm = op.cm |
| 3777 |
| 3778 if (op.adjustWidthTo != null) { |
| 3779 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px" |
| 3780 if (op.maxScrollLeft < cm.doc.scrollLeft) |
| 3781 { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollL
eft), true) } |
| 3782 cm.display.maxLineChanged = false |
| 3783 } |
| 3784 |
| 3785 var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus ||
document.hasFocus()) |
| 3786 if (op.preparedSelection) |
| 3787 { cm.display.input.showSelection(op.preparedSelection, takeFocus) } |
| 3788 if (op.updatedDisplay || op.startHeight != cm.doc.height) |
| 3789 { updateScrollbars(cm, op.barMeasure) } |
| 3790 if (op.updatedDisplay) |
| 3791 { setDocumentHeight(cm, op.barMeasure) } |
| 3792 |
| 3793 if (op.selectionChanged) { restartBlink(cm) } |
| 3794 |
| 3795 if (cm.state.focused && op.updateInput) |
| 3796 { cm.display.input.reset(op.typing) } |
| 3797 if (takeFocus) { ensureFocus(op.cm) } |
| 3798 } |
| 3799 |
| 3800 function endOperation_finish(op) { |
| 3801 var cm = op.cm, display = cm.display, doc = cm.doc |
| 3802 |
| 3803 if (op.updatedDisplay) { postUpdateDisplay(cm, op.update) } |
| 3804 |
| 3805 // Abort mouse wheel delta measurement, when scrolling explicitly |
| 3806 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != n
ull || op.scrollToPos)) |
| 3807 { display.wheelStartX = display.wheelStartY = null } |
| 3808 |
| 3809 // Propagate the scroll position to the actual DOM scroller |
| 3810 if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.
forceScroll)) { |
| 3811 doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display
.scroller.clientHeight, op.scrollTop)) |
| 3812 display.scrollbars.setScrollTop(doc.scrollTop) |
| 3813 display.scroller.scrollTop = doc.scrollTop |
| 3814 } |
| 3815 if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft ||
op.forceScroll)) { |
| 3816 doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display
.scroller.clientWidth, op.scrollLeft)) |
| 3817 display.scrollbars.setScrollLeft(doc.scrollLeft) |
| 3818 display.scroller.scrollLeft = doc.scrollLeft |
| 3819 alignHorizontally(cm) |
| 3820 } |
| 3821 // If we need to scroll a specific position into view, do so. |
| 3822 if (op.scrollToPos) { |
| 3823 var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), |
| 3824 clipPos(doc, op.scrollToPos.to), op.scrollToPos
.margin) |
| 3825 maybeScrollWindow(cm, rect) |
| 3826 } |
| 3827 |
| 3828 // Fire events for markers that are hidden/unidden by editing or |
| 3829 // undoing |
| 3830 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers |
| 3831 if (hidden) { for (var i = 0; i < hidden.length; ++i) |
| 3832 { if (!hidden[i].lines.length) { signal(hidden[i], "hide") } } } |
| 3833 if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1) |
| 3834 { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide") } } } |
| 3835 |
| 3836 if (display.wrapper.offsetHeight) |
| 3837 { doc.scrollTop = cm.display.scroller.scrollTop } |
| 3838 |
| 3839 // Fire change events, and delayed event handlers |
| 3840 if (op.changeObjs) |
| 3841 { signal(cm, "changes", cm, op.changeObjs) } |
| 3842 if (op.update) |
| 3843 { op.update.finish() } |
| 3844 } |
| 3845 |
| 3846 // Run the given function in an operation |
| 3847 function runInOp(cm, f) { |
| 3848 if (cm.curOp) { return f() } |
| 3849 startOperation(cm) |
| 3850 try { return f() } |
| 3851 finally { endOperation(cm) } |
| 3852 } |
| 3853 // Wraps a function in an operation. Returns the wrapped function. |
| 3854 function operation(cm, f) { |
| 3855 return function() { |
| 3856 if (cm.curOp) { return f.apply(cm, arguments) } |
| 3857 startOperation(cm) |
| 3858 try { return f.apply(cm, arguments) } |
| 3859 finally { endOperation(cm) } |
| 3860 } |
| 3861 } |
| 3862 // Used to add methods to editor and doc instances, wrapping them in |
| 3863 // operations. |
| 3864 function methodOp(f) { |
| 3865 return function() { |
| 3866 if (this.curOp) { return f.apply(this, arguments) } |
| 3867 startOperation(this) |
| 3868 try { return f.apply(this, arguments) } |
| 3869 finally { endOperation(this) } |
| 3870 } |
| 3871 } |
| 3872 function docMethodOp(f) { |
| 3873 return function() { |
| 3874 var cm = this.cm |
| 3875 if (!cm || cm.curOp) { return f.apply(this, arguments) } |
| 3876 startOperation(cm) |
| 3877 try { return f.apply(this, arguments) } |
| 3878 finally { endOperation(cm) } |
| 3879 } |
| 3880 } |
| 3881 |
| 3882 // Updates the display.view data structure for a given change to the |
| 3883 // document. From and to are in pre-change coordinates. Lendiff is |
| 3884 // the amount of lines added or subtracted by the change. This is |
| 3885 // used for changes that span multiple lines, or change the way |
| 3886 // lines are divided into visual lines. regLineChange (below) |
| 3887 // registers single-line changes. |
| 3888 function regChange(cm, from, to, lendiff) { |
| 3889 if (from == null) { from = cm.doc.first } |
| 3890 if (to == null) { to = cm.doc.first + cm.doc.size } |
| 3891 if (!lendiff) { lendiff = 0 } |
| 3892 |
| 3893 var display = cm.display |
| 3894 if (lendiff && to < display.viewTo && |
| 3895 (display.updateLineNumbers == null || display.updateLineNumbers > from)) |
| 3896 { display.updateLineNumbers = from } |
| 3897 |
| 3898 cm.curOp.viewChanged = true |
| 3899 |
| 3900 if (from >= display.viewTo) { // Change after |
| 3901 if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) |
| 3902 { resetView(cm) } |
| 3903 } else if (to <= display.viewFrom) { // Change before |
| 3904 if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.vie
wFrom) { |
| 3905 resetView(cm) |
| 3906 } else { |
| 3907 display.viewFrom += lendiff |
| 3908 display.viewTo += lendiff |
| 3909 } |
| 3910 } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap |
| 3911 resetView(cm) |
| 3912 } else if (from <= display.viewFrom) { // Top overlap |
| 3913 var cut = viewCuttingPoint(cm, to, to + lendiff, 1) |
| 3914 if (cut) { |
| 3915 display.view = display.view.slice(cut.index) |
| 3916 display.viewFrom = cut.lineN |
| 3917 display.viewTo += lendiff |
| 3918 } else { |
| 3919 resetView(cm) |
| 3920 } |
| 3921 } else if (to >= display.viewTo) { // Bottom overlap |
| 3922 var cut$1 = viewCuttingPoint(cm, from, from, -1) |
| 3923 if (cut$1) { |
| 3924 display.view = display.view.slice(0, cut$1.index) |
| 3925 display.viewTo = cut$1.lineN |
| 3926 } else { |
| 3927 resetView(cm) |
| 3928 } |
| 3929 } else { // Gap in the middle |
| 3930 var cutTop = viewCuttingPoint(cm, from, from, -1) |
| 3931 var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1) |
| 3932 if (cutTop && cutBot) { |
| 3933 display.view = display.view.slice(0, cutTop.index) |
| 3934 .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) |
| 3935 .concat(display.view.slice(cutBot.index)) |
| 3936 display.viewTo += lendiff |
| 3937 } else { |
| 3938 resetView(cm) |
| 3939 } |
| 3940 } |
| 3941 |
| 3942 var ext = display.externalMeasured |
| 3943 if (ext) { |
| 3944 if (to < ext.lineN) |
| 3945 { ext.lineN += lendiff } |
| 3946 else if (from < ext.lineN + ext.size) |
| 3947 { display.externalMeasured = null } |
| 3948 } |
| 3949 } |
| 3950 |
| 3951 // Register a change to a single line. Type must be one of "text", |
| 3952 // "gutter", "class", "widget" |
| 3953 function regLineChange(cm, line, type) { |
| 3954 cm.curOp.viewChanged = true |
| 3955 var display = cm.display, ext = cm.display.externalMeasured |
| 3956 if (ext && line >= ext.lineN && line < ext.lineN + ext.size) |
| 3957 { display.externalMeasured = null } |
| 3958 |
| 3959 if (line < display.viewFrom || line >= display.viewTo) { return } |
| 3960 var lineView = display.view[findViewIndex(cm, line)] |
| 3961 if (lineView.node == null) { return } |
| 3962 var arr = lineView.changes || (lineView.changes = []) |
| 3963 if (indexOf(arr, type) == -1) { arr.push(type) } |
| 3964 } |
| 3965 |
| 3966 // Clear the view. |
| 3967 function resetView(cm) { |
| 3968 cm.display.viewFrom = cm.display.viewTo = cm.doc.first |
| 3969 cm.display.view = [] |
| 3970 cm.display.viewOffset = 0 |
| 3971 } |
| 3972 |
| 3973 function viewCuttingPoint(cm, oldN, newN, dir) { |
| 3974 var index = findViewIndex(cm, oldN), diff, view = cm.display.view |
| 3975 if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) |
| 3976 { return {index: index, lineN: newN} } |
| 3977 var n = cm.display.viewFrom |
| 3978 for (var i = 0; i < index; i++) |
| 3979 { n += view[i].size } |
| 3980 if (n != oldN) { |
| 3981 if (dir > 0) { |
| 3982 if (index == view.length - 1) { return null } |
| 3983 diff = (n + view[index].size) - oldN |
| 3984 index++ |
| 3985 } else { |
| 3986 diff = n - oldN |
| 3987 } |
| 3988 oldN += diff; newN += diff |
| 3989 } |
| 3990 while (visualLineNo(cm.doc, newN) != newN) { |
| 3991 if (index == (dir < 0 ? 0 : view.length - 1)) { return null } |
| 3992 newN += dir * view[index - (dir < 0 ? 1 : 0)].size |
| 3993 index += dir |
| 3994 } |
| 3995 return {index: index, lineN: newN} |
| 3996 } |
| 3997 |
| 3998 // Force the view to cover a given range, adding empty view element |
| 3999 // or clipping off existing ones as needed. |
| 4000 function adjustView(cm, from, to) { |
| 4001 var display = cm.display, view = display.view |
| 4002 if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { |
| 4003 display.view = buildViewArray(cm, from, to) |
| 4004 display.viewFrom = from |
| 4005 } else { |
| 4006 if (display.viewFrom > from) |
| 4007 { display.view = buildViewArray(cm, from, display.viewFrom).concat(display
.view) } |
| 4008 else if (display.viewFrom < from) |
| 4009 { display.view = display.view.slice(findViewIndex(cm, from)) } |
| 4010 display.viewFrom = from |
| 4011 if (display.viewTo < to) |
| 4012 { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to
)) } |
| 4013 else if (display.viewTo > to) |
| 4014 { display.view = display.view.slice(0, findViewIndex(cm, to)) } |
| 4015 } |
| 4016 display.viewTo = to |
| 4017 } |
| 4018 |
| 4019 // Count the number of lines in the view whose DOM representation is |
| 4020 // out of date (or nonexistent). |
| 4021 function countDirtyView(cm) { |
| 4022 var view = cm.display.view, dirty = 0 |
| 4023 for (var i = 0; i < view.length; i++) { |
| 4024 var lineView = view[i] |
| 4025 if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty } |
| 4026 } |
| 4027 return dirty |
| 4028 } |
| 4029 |
| 4030 // HIGHLIGHT WORKER |
| 4031 |
| 4032 function startWorker(cm, time) { |
| 4033 if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) |
| 4034 { cm.state.highlight.set(time, bind(highlightWorker, cm)) } |
| 4035 } |
| 4036 |
| 4037 function highlightWorker(cm) { |
| 4038 var doc = cm.doc |
| 4039 if (doc.frontier < doc.first) { doc.frontier = doc.first } |
| 4040 if (doc.frontier >= cm.display.viewTo) { return } |
| 4041 var end = +new Date + cm.options.workTime |
| 4042 var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)) |
| 4043 var changedLines = [] |
| 4044 |
| 4045 doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500)
, function (line) { |
| 4046 if (doc.frontier >= cm.display.viewFrom) { // Visible |
| 4047 var oldStyles = line.styles, tooLong = line.text.length > cm.options.maxHi
ghlightLength |
| 4048 var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, st
ate) : state, true) |
| 4049 line.styles = highlighted.styles |
| 4050 var oldCls = line.styleClasses, newCls = highlighted.classes |
| 4051 if (newCls) { line.styleClasses = newCls } |
| 4052 else if (oldCls) { line.styleClasses = null } |
| 4053 var ischange = !oldStyles || oldStyles.length != line.styles.length || |
| 4054 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgCl
ass || oldCls.textClass != newCls.textClass) |
| 4055 for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldSt
yles[i] != line.styles[i] } |
| 4056 if (ischange) { changedLines.push(doc.frontier) } |
| 4057 line.stateAfter = tooLong ? state : copyState(doc.mode, state) |
| 4058 } else { |
| 4059 if (line.text.length <= cm.options.maxHighlightLength) |
| 4060 { processLine(cm, line.text, state) } |
| 4061 line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : nul
l |
| 4062 } |
| 4063 ++doc.frontier |
| 4064 if (+new Date > end) { |
| 4065 startWorker(cm, cm.options.workDelay) |
| 4066 return true |
| 4067 } |
| 4068 }) |
| 4069 if (changedLines.length) { runInOp(cm, function () { |
| 4070 for (var i = 0; i < changedLines.length; i++) |
| 4071 { regLineChange(cm, changedLines[i], "text") } |
| 4072 }) } |
| 4073 } |
| 4074 |
| 4075 // DISPLAY DRAWING |
| 4076 |
| 4077 var DisplayUpdate = function DisplayUpdate(cm, viewport, force) { |
| 4078 var display = cm.display |
| 4079 |
| 4080 this.viewport = viewport |
| 4081 // Store some values that we'll need later (but don't want to force a relayout
for) |
| 4082 this.visible = visibleLines(display, cm.doc, viewport) |
| 4083 this.editorIsHidden = !display.wrapper.offsetWidth |
| 4084 this.wrapperHeight = display.wrapper.clientHeight |
| 4085 this.wrapperWidth = display.wrapper.clientWidth |
| 4086 this.oldDisplayWidth = displayWidth(cm) |
| 4087 this.force = force |
| 4088 this.dims = getDimensions(cm) |
| 4089 this.events = [] |
| 4090 }; |
| 4091 |
| 4092 DisplayUpdate.prototype.signal = function signal$1 (emitter, type) { |
| 4093 if (hasHandler(emitter, type)) |
| 4094 { this.events.push(arguments) } |
| 4095 }; |
| 4096 DisplayUpdate.prototype.finish = function finish () { |
| 4097 var this$1 = this; |
| 4098 |
| 4099 for (var i = 0; i < this.events.length; i++) |
| 4100 { signal.apply(null, this$1.events[i]) } |
| 4101 }; |
| 4102 |
| 4103 function maybeClipScrollbars(cm) { |
| 4104 var display = cm.display |
| 4105 if (!display.scrollbarsClipped && display.scroller.offsetWidth) { |
| 4106 display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.cli
entWidth |
| 4107 display.heightForcer.style.height = scrollGap(cm) + "px" |
| 4108 display.sizer.style.marginBottom = -display.nativeBarWidth + "px" |
| 4109 display.sizer.style.borderRightWidth = scrollGap(cm) + "px" |
| 4110 display.scrollbarsClipped = true |
| 4111 } |
| 4112 } |
| 4113 |
| 4114 // Does the actual updating of the line display. Bails out |
| 4115 // (returning false) when there is nothing to be done and forced is |
| 4116 // false. |
| 4117 function updateDisplayIfNeeded(cm, update) { |
| 4118 var display = cm.display, doc = cm.doc |
| 4119 |
| 4120 if (update.editorIsHidden) { |
| 4121 resetView(cm) |
| 4122 return false |
| 4123 } |
| 4124 |
| 4125 // Bail out if the visible area is already rendered and nothing changed. |
| 4126 if (!update.force && |
| 4127 update.visible.from >= display.viewFrom && update.visible.to <= display.vi
ewTo && |
| 4128 (display.updateLineNumbers == null || display.updateLineNumbers >= display
.viewTo) && |
| 4129 display.renderedView == display.view && countDirtyView(cm) == 0) |
| 4130 { return false } |
| 4131 |
| 4132 if (maybeUpdateLineNumberWidth(cm)) { |
| 4133 resetView(cm) |
| 4134 update.dims = getDimensions(cm) |
| 4135 } |
| 4136 |
| 4137 // Compute a suitable new viewport (from & to) |
| 4138 var end = doc.first + doc.size |
| 4139 var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first
) |
| 4140 var to = Math.min(end, update.visible.to + cm.options.viewportMargin) |
| 4141 if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max
(doc.first, display.viewFrom) } |
| 4142 if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, disp
lay.viewTo) } |
| 4143 if (sawCollapsedSpans) { |
| 4144 from = visualLineNo(cm.doc, from) |
| 4145 to = visualLineEndNo(cm.doc, to) |
| 4146 } |
| 4147 |
| 4148 var different = from != display.viewFrom || to != display.viewTo || |
| 4149 display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != u
pdate.wrapperWidth |
| 4150 adjustView(cm, from, to) |
| 4151 |
| 4152 display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)) |
| 4153 // Position the mover div to align with the current scroll position |
| 4154 cm.display.mover.style.top = display.viewOffset + "px" |
| 4155 |
| 4156 var toUpdate = countDirtyView(cm) |
| 4157 if (!different && toUpdate == 0 && !update.force && display.renderedView == di
splay.view && |
| 4158 (display.updateLineNumbers == null || display.updateLineNumbers >= display
.viewTo)) |
| 4159 { return false } |
| 4160 |
| 4161 // For big changes, we hide the enclosing element during the |
| 4162 // update, since that speeds up the operations on most browsers. |
| 4163 var focused = activeElt() |
| 4164 if (toUpdate > 4) { display.lineDiv.style.display = "none" } |
| 4165 patchDisplay(cm, display.updateLineNumbers, update.dims) |
| 4166 if (toUpdate > 4) { display.lineDiv.style.display = "" } |
| 4167 display.renderedView = display.view |
| 4168 // There might have been a widget with a focused element that got |
| 4169 // hidden or updated, if so re-focus it. |
| 4170 if (focused && activeElt() != focused && focused.offsetHeight) { focused.focus
() } |
| 4171 |
| 4172 // Prevent selection and cursors from interfering with the scroll |
| 4173 // width and height. |
| 4174 removeChildren(display.cursorDiv) |
| 4175 removeChildren(display.selectionDiv) |
| 4176 display.gutters.style.height = display.sizer.style.minHeight = 0 |
| 4177 |
| 4178 if (different) { |
| 4179 display.lastWrapHeight = update.wrapperHeight |
| 4180 display.lastWrapWidth = update.wrapperWidth |
| 4181 startWorker(cm, 400) |
| 4182 } |
| 4183 |
| 4184 display.updateLineNumbers = null |
| 4185 |
| 4186 return true |
| 4187 } |
| 4188 |
| 4189 function postUpdateDisplay(cm, update) { |
| 4190 var viewport = update.viewport |
| 4191 |
| 4192 for (var first = true;; first = false) { |
| 4193 if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayW
idth(cm)) { |
| 4194 // Clip forced viewport to actual scrollable area. |
| 4195 if (viewport && viewport.top != null) |
| 4196 { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - di
splayHeight(cm), viewport.top)} } |
| 4197 // Updated line heights might result in the drawn area not |
| 4198 // actually covering the viewport. Keep looping until it does. |
| 4199 update.visible = visibleLines(cm.display, cm.doc, viewport) |
| 4200 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.
display.viewTo) |
| 4201 { break } |
| 4202 } |
| 4203 if (!updateDisplayIfNeeded(cm, update)) { break } |
| 4204 updateHeightsInViewport(cm) |
| 4205 var barMeasure = measureForScrollbars(cm) |
| 4206 updateSelection(cm) |
| 4207 updateScrollbars(cm, barMeasure) |
| 4208 setDocumentHeight(cm, barMeasure) |
| 4209 } |
| 4210 |
| 4211 update.signal(cm, "update", cm) |
| 4212 if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo !=
cm.display.reportedViewTo) { |
| 4213 update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.view
To) |
| 4214 cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo
= cm.display.viewTo |
| 4215 } |
| 4216 } |
| 4217 |
| 4218 function updateDisplaySimple(cm, viewport) { |
| 4219 var update = new DisplayUpdate(cm, viewport) |
| 4220 if (updateDisplayIfNeeded(cm, update)) { |
| 4221 updateHeightsInViewport(cm) |
| 4222 postUpdateDisplay(cm, update) |
| 4223 var barMeasure = measureForScrollbars(cm) |
| 4224 updateSelection(cm) |
| 4225 updateScrollbars(cm, barMeasure) |
| 4226 setDocumentHeight(cm, barMeasure) |
| 4227 update.finish() |
| 4228 } |
| 4229 } |
| 4230 |
| 4231 // Sync the actual display DOM structure with display.view, removing |
| 4232 // nodes for lines that are no longer in view, and creating the ones |
| 4233 // that are not there yet, and updating the ones that are out of |
| 4234 // date. |
| 4235 function patchDisplay(cm, updateNumbersFrom, dims) { |
| 4236 var display = cm.display, lineNumbers = cm.options.lineNumbers |
| 4237 var container = display.lineDiv, cur = container.firstChild |
| 4238 |
| 4239 function rm(node) { |
| 4240 var next = node.nextSibling |
| 4241 // Works around a throw-scroll bug in OS X Webkit |
| 4242 if (webkit && mac && cm.display.currentWheelTarget == node) |
| 4243 { node.style.display = "none" } |
| 4244 else |
| 4245 { node.parentNode.removeChild(node) } |
| 4246 return next |
| 4247 } |
| 4248 |
| 4249 var view = display.view, lineN = display.viewFrom |
| 4250 // Loop over the elements in the view, syncing cur (the DOM nodes |
| 4251 // in display.lineDiv) with the view as we go. |
| 4252 for (var i = 0; i < view.length; i++) { |
| 4253 var lineView = view[i] |
| 4254 if (lineView.hidden) { |
| 4255 } else if (!lineView.node || lineView.node.parentNode != container) { // Not
drawn yet |
| 4256 var node = buildLineElement(cm, lineView, lineN, dims) |
| 4257 container.insertBefore(node, cur) |
| 4258 } else { // Already drawn |
| 4259 while (cur != lineView.node) { cur = rm(cur) } |
| 4260 var updateNumber = lineNumbers && updateNumbersFrom != null && |
| 4261 updateNumbersFrom <= lineN && lineView.lineNumber |
| 4262 if (lineView.changes) { |
| 4263 if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false } |
| 4264 updateLineForChanges(cm, lineView, lineN, dims) |
| 4265 } |
| 4266 if (updateNumber) { |
| 4267 removeChildren(lineView.lineNumber) |
| 4268 lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm
.options, lineN))) |
| 4269 } |
| 4270 cur = lineView.node.nextSibling |
| 4271 } |
| 4272 lineN += lineView.size |
| 4273 } |
| 4274 while (cur) { cur = rm(cur) } |
| 4275 } |
| 4276 |
| 4277 function updateGutterSpace(cm) { |
| 4278 var width = cm.display.gutters.offsetWidth |
| 4279 cm.display.sizer.style.marginLeft = width + "px" |
| 4280 } |
| 4281 |
| 4282 function setDocumentHeight(cm, measure) { |
| 4283 cm.display.sizer.style.minHeight = measure.docHeight + "px" |
| 4284 cm.display.heightForcer.style.top = measure.docHeight + "px" |
| 4285 cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight +
scrollGap(cm)) + "px" |
| 4286 } |
| 4287 |
| 4288 // Rebuild the gutter elements, ensure the margin to the left of the |
| 4289 // code matches their width. |
| 4290 function updateGutters(cm) { |
| 4291 var gutters = cm.display.gutters, specs = cm.options.gutters |
| 4292 removeChildren(gutters) |
| 4293 var i = 0 |
| 4294 for (; i < specs.length; ++i) { |
| 4295 var gutterClass = specs[i] |
| 4296 var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutte
rClass)) |
| 4297 if (gutterClass == "CodeMirror-linenumbers") { |
| 4298 cm.display.lineGutter = gElt |
| 4299 gElt.style.width = (cm.display.lineNumWidth || 1) + "px" |
| 4300 } |
| 4301 } |
| 4302 gutters.style.display = i ? "" : "none" |
| 4303 updateGutterSpace(cm) |
| 4304 } |
| 4305 |
| 4306 // Make sure the gutters options contains the element |
| 4307 // "CodeMirror-linenumbers" when the lineNumbers option is true. |
| 4308 function setGuttersForLineNumbers(options) { |
| 4309 var found = indexOf(options.gutters, "CodeMirror-linenumbers") |
| 4310 if (found == -1 && options.lineNumbers) { |
| 4311 options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]) |
| 4312 } else if (found > -1 && !options.lineNumbers) { |
| 4313 options.gutters = options.gutters.slice(0) |
| 4314 options.gutters.splice(found, 1) |
| 4315 } |
| 4316 } |
| 4317 |
| 4318 // Selection objects are immutable. A new one is created every time |
| 4319 // the selection changes. A selection is one or more non-overlapping |
| 4320 // (and non-touching) ranges, sorted, and an integer that indicates |
| 4321 // which one is the primary selection (the one that's scrolled into |
| 4322 // view, that getCursor returns, etc). |
| 4323 var Selection = function Selection(ranges, primIndex) { |
| 4324 this.ranges = ranges |
| 4325 this.primIndex = primIndex |
| 4326 }; |
| 4327 |
| 4328 Selection.prototype.primary = function primary () { return this.ranges[this.prim
Index] }; |
| 4329 |
| 4330 Selection.prototype.equals = function equals (other) { |
| 4331 var this$1 = this; |
| 4332 |
| 4333 if (other == this) { return true } |
| 4334 if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.le
ngth) { return false } |
| 4335 for (var i = 0; i < this.ranges.length; i++) { |
| 4336 var here = this$1.ranges[i], there = other.ranges[i] |
| 4337 if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head,
there.head)) { return false } |
| 4338 } |
| 4339 return true |
| 4340 }; |
| 4341 |
| 4342 Selection.prototype.deepCopy = function deepCopy () { |
| 4343 var this$1 = this; |
| 4344 |
| 4345 var out = [] |
| 4346 for (var i = 0; i < this.ranges.length; i++) |
| 4347 { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges
[i].head)) } |
| 4348 return new Selection(out, this.primIndex) |
| 4349 }; |
| 4350 |
| 4351 Selection.prototype.somethingSelected = function somethingSelected () { |
| 4352 var this$1 = this; |
| 4353 |
| 4354 for (var i = 0; i < this.ranges.length; i++) |
| 4355 { if (!this$1.ranges[i].empty()) { return true } } |
| 4356 return false |
| 4357 }; |
| 4358 |
| 4359 Selection.prototype.contains = function contains (pos, end) { |
| 4360 var this$1 = this; |
| 4361 |
| 4362 if (!end) { end = pos } |
| 4363 for (var i = 0; i < this.ranges.length; i++) { |
| 4364 var range = this$1.ranges[i] |
| 4365 if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) |
| 4366 { return i } |
| 4367 } |
| 4368 return -1 |
| 4369 }; |
| 4370 |
| 4371 var Range = function Range(anchor, head) { |
| 4372 this.anchor = anchor; this.head = head |
| 4373 }; |
| 4374 |
| 4375 Range.prototype.from = function from () { return minPos(this.anchor, this.head)
}; |
| 4376 Range.prototype.to = function to () { return maxPos(this.anchor, this.head) }; |
| 4377 Range.prototype.empty = function empty () { return this.head.line == this.anchor
.line && this.head.ch == this.anchor.ch }; |
| 4378 |
| 4379 // Take an unsorted, potentially overlapping set of ranges, and |
| 4380 // build a selection out of it. 'Consumes' ranges array (modifying |
| 4381 // it). |
| 4382 function normalizeSelection(ranges, primIndex) { |
| 4383 var prim = ranges[primIndex] |
| 4384 ranges.sort(function (a, b) { return cmp(a.from(), b.from()); }) |
| 4385 primIndex = indexOf(ranges, prim) |
| 4386 for (var i = 1; i < ranges.length; i++) { |
| 4387 var cur = ranges[i], prev = ranges[i - 1] |
| 4388 if (cmp(prev.to(), cur.from()) >= 0) { |
| 4389 var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to(
)) |
| 4390 var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head |
| 4391 if (i <= primIndex) { --primIndex } |
| 4392 ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)) |
| 4393 } |
| 4394 } |
| 4395 return new Selection(ranges, primIndex) |
| 4396 } |
| 4397 |
| 4398 function simpleSelection(anchor, head) { |
| 4399 return new Selection([new Range(anchor, head || anchor)], 0) |
| 4400 } |
| 4401 |
| 4402 // Compute the position of the end of a change (its 'to' property |
| 4403 // refers to the pre-change end). |
| 4404 function changeEnd(change) { |
| 4405 if (!change.text) { return change.to } |
| 4406 return Pos(change.from.line + change.text.length - 1, |
| 4407 lst(change.text).length + (change.text.length == 1 ? change.from.ch
: 0)) |
| 4408 } |
| 4409 |
| 4410 // Adjust a position to refer to the post-change position of the |
| 4411 // same text, or the end of the change if the change covers it. |
| 4412 function adjustForChange(pos, change) { |
| 4413 if (cmp(pos, change.from) < 0) { return pos } |
| 4414 if (cmp(pos, change.to) <= 0) { return changeEnd(change) } |
| 4415 |
| 4416 var line = pos.line + change.text.length - (change.to.line - change.from.line)
- 1, ch = pos.ch |
| 4417 if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch } |
| 4418 return Pos(line, ch) |
| 4419 } |
| 4420 |
| 4421 function computeSelAfterChange(doc, change) { |
| 4422 var out = [] |
| 4423 for (var i = 0; i < doc.sel.ranges.length; i++) { |
| 4424 var range = doc.sel.ranges[i] |
| 4425 out.push(new Range(adjustForChange(range.anchor, change), |
| 4426 adjustForChange(range.head, change))) |
| 4427 } |
| 4428 return normalizeSelection(out, doc.sel.primIndex) |
| 4429 } |
| 4430 |
| 4431 function offsetPos(pos, old, nw) { |
| 4432 if (pos.line == old.line) |
| 4433 { return Pos(nw.line, pos.ch - old.ch + nw.ch) } |
| 4434 else |
| 4435 { return Pos(nw.line + (pos.line - old.line), pos.ch) } |
| 4436 } |
| 4437 |
| 4438 // Used by replaceSelections to allow moving the selection to the |
| 4439 // start or around the replaced test. Hint may be "start" or "around". |
| 4440 function computeReplacedSel(doc, changes, hint) { |
| 4441 var out = [] |
| 4442 var oldPrev = Pos(doc.first, 0), newPrev = oldPrev |
| 4443 for (var i = 0; i < changes.length; i++) { |
| 4444 var change = changes[i] |
| 4445 var from = offsetPos(change.from, oldPrev, newPrev) |
| 4446 var to = offsetPos(changeEnd(change), oldPrev, newPrev) |
| 4447 oldPrev = change.to |
| 4448 newPrev = to |
| 4449 if (hint == "around") { |
| 4450 var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0 |
| 4451 out[i] = new Range(inv ? to : from, inv ? from : to) |
| 4452 } else { |
| 4453 out[i] = new Range(from, from) |
| 4454 } |
| 4455 } |
| 4456 return new Selection(out, doc.sel.primIndex) |
| 4457 } |
| 4458 |
| 4459 // Used to get the editor into a consistent state again when options change. |
| 4460 |
| 4461 function loadMode(cm) { |
| 4462 cm.doc.mode = getMode(cm.options, cm.doc.modeOption) |
| 4463 resetModeState(cm) |
| 4464 } |
| 4465 |
| 4466 function resetModeState(cm) { |
| 4467 cm.doc.iter(function (line) { |
| 4468 if (line.stateAfter) { line.stateAfter = null } |
| 4469 if (line.styles) { line.styles = null } |
| 4470 }) |
| 4471 cm.doc.frontier = cm.doc.first |
| 4472 startWorker(cm, 100) |
| 4473 cm.state.modeGen++ |
| 4474 if (cm.curOp) { regChange(cm) } |
| 4475 } |
| 4476 |
| 4477 // DOCUMENT DATA STRUCTURE |
| 4478 |
| 4479 // By default, updates that start and end at the beginning of a line |
| 4480 // are treated specially, in order to make the association of line |
| 4481 // widgets and marker elements with the text behave more intuitive. |
| 4482 function isWholeLineUpdate(doc, change) { |
| 4483 return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && |
| 4484 (!doc.cm || doc.cm.options.wholeLineUpdateBefore) |
| 4485 } |
| 4486 |
| 4487 // Perform a change on the document data structure. |
| 4488 function updateDoc(doc, change, markedSpans, estimateHeight) { |
| 4489 function spansFor(n) {return markedSpans ? markedSpans[n] : null} |
| 4490 function update(line, text, spans) { |
| 4491 updateLine(line, text, spans, estimateHeight) |
| 4492 signalLater(line, "change", line, change) |
| 4493 } |
| 4494 function linesFor(start, end) { |
| 4495 var result = [] |
| 4496 for (var i = start; i < end; ++i) |
| 4497 { result.push(new Line(text[i], spansFor(i), estimateHeight)) } |
| 4498 return result |
| 4499 } |
| 4500 |
| 4501 var from = change.from, to = change.to, text = change.text |
| 4502 var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line) |
| 4503 var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.l
ine - from.line |
| 4504 |
| 4505 // Adjust the line structure |
| 4506 if (change.full) { |
| 4507 doc.insert(0, linesFor(0, text.length)) |
| 4508 doc.remove(text.length, doc.size - text.length) |
| 4509 } else if (isWholeLineUpdate(doc, change)) { |
| 4510 // This is a whole-line replace. Treated specially to make |
| 4511 // sure line objects move the way they are supposed to. |
| 4512 var added = linesFor(0, text.length - 1) |
| 4513 update(lastLine, lastLine.text, lastSpans) |
| 4514 if (nlines) { doc.remove(from.line, nlines) } |
| 4515 if (added.length) { doc.insert(from.line, added) } |
| 4516 } else if (firstLine == lastLine) { |
| 4517 if (text.length == 1) { |
| 4518 update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.
text.slice(to.ch), lastSpans) |
| 4519 } else { |
| 4520 var added$1 = linesFor(1, text.length - 1) |
| 4521 added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, e
stimateHeight)) |
| 4522 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) |
| 4523 doc.insert(from.line + 1, added$1) |
| 4524 } |
| 4525 } else if (text.length == 1) { |
| 4526 update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text
.slice(to.ch), spansFor(0)) |
| 4527 doc.remove(from.line + 1, nlines) |
| 4528 } else { |
| 4529 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) |
| 4530 update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans) |
| 4531 var added$2 = linesFor(1, text.length - 1) |
| 4532 if (nlines > 1) { doc.remove(from.line + 1, nlines - 1) } |
| 4533 doc.insert(from.line + 1, added$2) |
| 4534 } |
| 4535 |
| 4536 signalLater(doc, "change", doc, change) |
| 4537 } |
| 4538 |
| 4539 // Call f for all linked documents. |
| 4540 function linkedDocs(doc, f, sharedHistOnly) { |
| 4541 function propagate(doc, skip, sharedHist) { |
| 4542 if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) { |
| 4543 var rel = doc.linked[i] |
| 4544 if (rel.doc == skip) { continue } |
| 4545 var shared = sharedHist && rel.sharedHist |
| 4546 if (sharedHistOnly && !shared) { continue } |
| 4547 f(rel.doc, shared) |
| 4548 propagate(rel.doc, doc, shared) |
| 4549 } } |
| 4550 } |
| 4551 propagate(doc, null, true) |
| 4552 } |
| 4553 |
| 4554 // Attach a document to an editor. |
| 4555 function attachDoc(cm, doc) { |
| 4556 if (doc.cm) { throw new Error("This document is already in use.") } |
| 4557 cm.doc = doc |
| 4558 doc.cm = cm |
| 4559 estimateLineHeights(cm) |
| 4560 loadMode(cm) |
| 4561 setDirectionClass(cm) |
| 4562 if (!cm.options.lineWrapping) { findMaxLine(cm) } |
| 4563 cm.options.mode = doc.modeOption |
| 4564 regChange(cm) |
| 4565 } |
| 4566 |
| 4567 function setDirectionClass(cm) { |
| 4568 ;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMir
ror-rtl") |
| 4569 } |
| 4570 |
| 4571 function directionChanged(cm) { |
| 4572 runInOp(cm, function () { |
| 4573 setDirectionClass(cm) |
| 4574 regChange(cm) |
| 4575 }) |
| 4576 } |
| 4577 |
| 4578 function History(startGen) { |
| 4579 // Arrays of change events and selections. Doing something adds an |
| 4580 // event to done and clears undo. Undoing moves events from done |
| 4581 // to undone, redoing moves them in the other direction. |
| 4582 this.done = []; this.undone = [] |
| 4583 this.undoDepth = Infinity |
| 4584 // Used to track when changes can be merged into a single undo |
| 4585 // event |
| 4586 this.lastModTime = this.lastSelTime = 0 |
| 4587 this.lastOp = this.lastSelOp = null |
| 4588 this.lastOrigin = this.lastSelOrigin = null |
| 4589 // Used by the isClean() method |
| 4590 this.generation = this.maxGeneration = startGen || 1 |
| 4591 } |
| 4592 |
| 4593 // Create a history change event from an updateDoc-style change |
| 4594 // object. |
| 4595 function historyChangeFromChange(doc, change) { |
| 4596 var histChange = {from: copyPos(change.from), to: changeEnd(change), text: get
Between(doc, change.from, change.to)} |
| 4597 attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1) |
| 4598 linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, chan
ge.from.line, change.to.line + 1); }, true) |
| 4599 return histChange |
| 4600 } |
| 4601 |
| 4602 // Pop all selection events off the end of a history array. Stop at |
| 4603 // a change event. |
| 4604 function clearSelectionEvents(array) { |
| 4605 while (array.length) { |
| 4606 var last = lst(array) |
| 4607 if (last.ranges) { array.pop() } |
| 4608 else { break } |
| 4609 } |
| 4610 } |
| 4611 |
| 4612 // Find the top change event in the history. Pop off selection |
| 4613 // events that are in the way. |
| 4614 function lastChangeEvent(hist, force) { |
| 4615 if (force) { |
| 4616 clearSelectionEvents(hist.done) |
| 4617 return lst(hist.done) |
| 4618 } else if (hist.done.length && !lst(hist.done).ranges) { |
| 4619 return lst(hist.done) |
| 4620 } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { |
| 4621 hist.done.pop() |
| 4622 return lst(hist.done) |
| 4623 } |
| 4624 } |
| 4625 |
| 4626 // Register a change in the history. Merges changes that are within |
| 4627 // a single operation, or are close together with an origin that |
| 4628 // allows merging (starting with "+") into a single event. |
| 4629 function addChangeToHistory(doc, change, selAfter, opId) { |
| 4630 var hist = doc.history |
| 4631 hist.undone.length = 0 |
| 4632 var time = +new Date, cur |
| 4633 var last |
| 4634 |
| 4635 if ((hist.lastOp == opId || |
| 4636 hist.lastOrigin == change.origin && change.origin && |
| 4637 ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - d
oc.cm.options.historyEventDelay) || |
| 4638 change.origin.charAt(0) == "*")) && |
| 4639 (cur = lastChangeEvent(hist, hist.lastOp == opId))) { |
| 4640 // Merge this change into the last event |
| 4641 last = lst(cur.changes) |
| 4642 if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { |
| 4643 // Optimized case for simple insertion -- don't want to add |
| 4644 // new changesets for every character typed |
| 4645 last.to = changeEnd(change) |
| 4646 } else { |
| 4647 // Add new sub-event |
| 4648 cur.changes.push(historyChangeFromChange(doc, change)) |
| 4649 } |
| 4650 } else { |
| 4651 // Can not be merged, start a new event. |
| 4652 var before = lst(hist.done) |
| 4653 if (!before || !before.ranges) |
| 4654 { pushSelectionToHistory(doc.sel, hist.done) } |
| 4655 cur = {changes: [historyChangeFromChange(doc, change)], |
| 4656 generation: hist.generation} |
| 4657 hist.done.push(cur) |
| 4658 while (hist.done.length > hist.undoDepth) { |
| 4659 hist.done.shift() |
| 4660 if (!hist.done[0].ranges) { hist.done.shift() } |
| 4661 } |
| 4662 } |
| 4663 hist.done.push(selAfter) |
| 4664 hist.generation = ++hist.maxGeneration |
| 4665 hist.lastModTime = hist.lastSelTime = time |
| 4666 hist.lastOp = hist.lastSelOp = opId |
| 4667 hist.lastOrigin = hist.lastSelOrigin = change.origin |
| 4668 |
| 4669 if (!last) { signal(doc, "historyAdded") } |
| 4670 } |
| 4671 |
| 4672 function selectionEventCanBeMerged(doc, origin, prev, sel) { |
| 4673 var ch = origin.charAt(0) |
| 4674 return ch == "*" || |
| 4675 ch == "+" && |
| 4676 prev.ranges.length == sel.ranges.length && |
| 4677 prev.somethingSelected() == sel.somethingSelected() && |
| 4678 new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventD
elay : 500) |
| 4679 } |
| 4680 |
| 4681 // Called whenever the selection changes, sets the new selection as |
| 4682 // the pending selection in the history, and pushes the old pending |
| 4683 // selection into the 'done' array when it was significantly |
| 4684 // different (in number of selected ranges, emptiness, or time). |
| 4685 function addSelectionToHistory(doc, sel, opId, options) { |
| 4686 var hist = doc.history, origin = options && options.origin |
| 4687 |
| 4688 // A new event is started when the previous origin does not match |
| 4689 // the current, or the origins don't allow matching. Origins |
| 4690 // starting with * are always merged, those starting with + are |
| 4691 // merged when similar and close together in time. |
| 4692 if (opId == hist.lastSelOp || |
| 4693 (origin && hist.lastSelOrigin == origin && |
| 4694 (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || |
| 4695 selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) |
| 4696 { hist.done[hist.done.length - 1] = sel } |
| 4697 else |
| 4698 { pushSelectionToHistory(sel, hist.done) } |
| 4699 |
| 4700 hist.lastSelTime = +new Date |
| 4701 hist.lastSelOrigin = origin |
| 4702 hist.lastSelOp = opId |
| 4703 if (options && options.clearRedo !== false) |
| 4704 { clearSelectionEvents(hist.undone) } |
| 4705 } |
| 4706 |
| 4707 function pushSelectionToHistory(sel, dest) { |
| 4708 var top = lst(dest) |
| 4709 if (!(top && top.ranges && top.equals(sel))) |
| 4710 { dest.push(sel) } |
| 4711 } |
| 4712 |
| 4713 // Used to store marked span information in the history. |
| 4714 function attachLocalSpans(doc, change, from, to) { |
| 4715 var existing = change["spans_" + doc.id], n = 0 |
| 4716 doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), functi
on (line) { |
| 4717 if (line.markedSpans) |
| 4718 { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.mark
edSpans } |
| 4719 ++n |
| 4720 }) |
| 4721 } |
| 4722 |
| 4723 // When un/re-doing restores text containing marked spans, those |
| 4724 // that have been explicitly cleared should not be restored. |
| 4725 function removeClearedSpans(spans) { |
| 4726 if (!spans) { return null } |
| 4727 var out |
| 4728 for (var i = 0; i < spans.length; ++i) { |
| 4729 if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i)
} } |
| 4730 else if (out) { out.push(spans[i]) } |
| 4731 } |
| 4732 return !out ? spans : out.length ? out : null |
| 4733 } |
| 4734 |
| 4735 // Retrieve and filter the old marked spans stored in a change event. |
| 4736 function getOldSpans(doc, change) { |
| 4737 var found = change["spans_" + doc.id] |
| 4738 if (!found) { return null } |
| 4739 var nw = [] |
| 4740 for (var i = 0; i < change.text.length; ++i) |
| 4741 { nw.push(removeClearedSpans(found[i])) } |
| 4742 return nw |
| 4743 } |
| 4744 |
| 4745 // Used for un/re-doing changes from the history. Combines the |
| 4746 // result of computing the existing spans with the set of spans that |
| 4747 // existed in the history (so that deleting around a span and then |
| 4748 // undoing brings back the span). |
| 4749 function mergeOldSpans(doc, change) { |
| 4750 var old = getOldSpans(doc, change) |
| 4751 var stretched = stretchSpansOverChange(doc, change) |
| 4752 if (!old) { return stretched } |
| 4753 if (!stretched) { return old } |
| 4754 |
| 4755 for (var i = 0; i < old.length; ++i) { |
| 4756 var oldCur = old[i], stretchCur = stretched[i] |
| 4757 if (oldCur && stretchCur) { |
| 4758 spans: for (var j = 0; j < stretchCur.length; ++j) { |
| 4759 var span = stretchCur[j] |
| 4760 for (var k = 0; k < oldCur.length; ++k) |
| 4761 { if (oldCur[k].marker == span.marker) { continue spans } } |
| 4762 oldCur.push(span) |
| 4763 } |
| 4764 } else if (stretchCur) { |
| 4765 old[i] = stretchCur |
| 4766 } |
| 4767 } |
| 4768 return old |
| 4769 } |
| 4770 |
| 4771 // Used both to provide a JSON-safe object in .getHistory, and, when |
| 4772 // detaching a document, to split the history in two |
| 4773 function copyHistoryArray(events, newGroup, instantiateSel) { |
| 4774 var copy = [] |
| 4775 for (var i = 0; i < events.length; ++i) { |
| 4776 var event = events[i] |
| 4777 if (event.ranges) { |
| 4778 copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : even
t) |
| 4779 continue |
| 4780 } |
| 4781 var changes = event.changes, newChanges = [] |
| 4782 copy.push({changes: newChanges}) |
| 4783 for (var j = 0; j < changes.length; ++j) { |
| 4784 var change = changes[j], m = (void 0) |
| 4785 newChanges.push({from: change.from, to: change.to, text: change.text}) |
| 4786 if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+
)$/)) { |
| 4787 if (indexOf(newGroup, Number(m[1])) > -1) { |
| 4788 lst(newChanges)[prop] = change[prop] |
| 4789 delete change[prop] |
| 4790 } |
| 4791 } } } |
| 4792 } |
| 4793 } |
| 4794 return copy |
| 4795 } |
| 4796 |
| 4797 // The 'scroll' parameter given to many of these indicated whether |
| 4798 // the new cursor position should be scrolled into view after |
| 4799 // modifying the selection. |
| 4800 |
| 4801 // If shift is held or the extend flag is set, extends a range to |
| 4802 // include a given position (and optionally a second position). |
| 4803 // Otherwise, simply returns the range between the given positions. |
| 4804 // Used for cursor motion and such. |
| 4805 function extendRange(doc, range, head, other) { |
| 4806 if (doc.cm && doc.cm.display.shift || doc.extend) { |
| 4807 var anchor = range.anchor |
| 4808 if (other) { |
| 4809 var posBefore = cmp(head, anchor) < 0 |
| 4810 if (posBefore != (cmp(other, anchor) < 0)) { |
| 4811 anchor = head |
| 4812 head = other |
| 4813 } else if (posBefore != (cmp(head, other) < 0)) { |
| 4814 head = other |
| 4815 } |
| 4816 } |
| 4817 return new Range(anchor, head) |
| 4818 } else { |
| 4819 return new Range(other || head, head) |
| 4820 } |
| 4821 } |
| 4822 |
| 4823 // Extend the primary selection range, discard the rest. |
| 4824 function extendSelection(doc, head, other, options) { |
| 4825 setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, oth
er)], 0), options) |
| 4826 } |
| 4827 |
| 4828 // Extend all selections (pos is an array of selections with length |
| 4829 // equal the number of selections) |
| 4830 function extendSelections(doc, heads, options) { |
| 4831 var out = [] |
| 4832 for (var i = 0; i < doc.sel.ranges.length; i++) |
| 4833 { out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null) } |
| 4834 var newSel = normalizeSelection(out, doc.sel.primIndex) |
| 4835 setSelection(doc, newSel, options) |
| 4836 } |
| 4837 |
| 4838 // Updates a single range in the selection. |
| 4839 function replaceOneSelection(doc, i, range, options) { |
| 4840 var ranges = doc.sel.ranges.slice(0) |
| 4841 ranges[i] = range |
| 4842 setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options) |
| 4843 } |
| 4844 |
| 4845 // Reset the selection to a single range. |
| 4846 function setSimpleSelection(doc, anchor, head, options) { |
| 4847 setSelection(doc, simpleSelection(anchor, head), options) |
| 4848 } |
| 4849 |
| 4850 // Give beforeSelectionChange handlers a change to influence a |
| 4851 // selection update. |
| 4852 function filterSelectionChange(doc, sel, options) { |
| 4853 var obj = { |
| 4854 ranges: sel.ranges, |
| 4855 update: function(ranges) { |
| 4856 var this$1 = this; |
| 4857 |
| 4858 this.ranges = [] |
| 4859 for (var i = 0; i < ranges.length; i++) |
| 4860 { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), |
| 4861 clipPos(doc, ranges[i].head)) } |
| 1664 }, | 4862 }, |
| 1665 | 4863 origin: options && options.origin |
| 1666 prepareSelection: function() { | 4864 } |
| 1667 var result = prepareSelection(this.cm, false); | 4865 signal(doc, "beforeSelectionChange", doc, obj) |
| 1668 result.focus = this.cm.state.focused; | 4866 if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj) } |
| 1669 return result; | 4867 if (obj.ranges != sel.ranges) { return normalizeSelection(obj.ranges, obj.rang
es.length - 1) } |
| 1670 }, | 4868 else { return sel } |
| 1671 | 4869 } |
| 1672 showSelection: function(info, takeFocus) { | 4870 |
| 1673 if (!info || !this.cm.display.view.length) return; | 4871 function setSelectionReplaceHistory(doc, sel, options) { |
| 1674 if (info.focus || takeFocus) this.showPrimarySelection(); | 4872 var done = doc.history.done, last = lst(done) |
| 1675 this.showMultipleSelections(info); | 4873 if (last && last.ranges) { |
| 1676 }, | 4874 done[done.length - 1] = sel |
| 1677 | 4875 setSelectionNoUndo(doc, sel, options) |
| 1678 showPrimarySelection: function() { | 4876 } else { |
| 1679 var sel = window.getSelection(), prim = this.cm.doc.sel.primary(); | 4877 setSelection(doc, sel, options) |
| 1680 var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset); | 4878 } |
| 1681 var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset); | 4879 } |
| 1682 if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && | 4880 |
| 1683 cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && | 4881 // Set a new selection. |
| 1684 cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) | 4882 function setSelection(doc, sel, options) { |
| 1685 return; | 4883 setSelectionNoUndo(doc, sel, options) |
| 1686 | 4884 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options) |
| 1687 var start = posToDOM(this.cm, prim.from()); | 4885 } |
| 1688 var end = posToDOM(this.cm, prim.to()); | 4886 |
| 1689 if (!start && !end) return; | 4887 function setSelectionNoUndo(doc, sel, options) { |
| 1690 | 4888 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "
beforeSelectionChange")) |
| 1691 var view = this.cm.display.view; | 4889 { sel = filterSelectionChange(doc, sel, options) } |
| 1692 var old = sel.rangeCount && sel.getRangeAt(0); | 4890 |
| 1693 if (!start) { | 4891 var bias = options && options.bias || |
| 1694 start = {node: view[0].measure.map[2], offset: 0}; | 4892 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1) |
| 1695 } else if (!end) { // FIXME dangerously hacky | 4893 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)) |
| 1696 var measure = view[view.length - 1].measure; | 4894 |
| 1697 var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure
.map; | 4895 if (!(options && options.scroll === false) && doc.cm) |
| 1698 end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.
length - 3]}; | 4896 { ensureCursorVisible(doc.cm) } |
| 1699 } | 4897 } |
| 1700 | 4898 |
| 1701 try { var rng = range(start.node, start.offset, end.offset, end.node); } | 4899 function setSelectionInner(doc, sel) { |
| 1702 catch(e) {} // Our model of the DOM might be outdated, in which case the r
ange we try to set can be impossible | 4900 if (sel.equals(doc.sel)) { return } |
| 1703 if (rng) { | 4901 |
| 1704 if (!gecko && this.cm.state.focused) { | 4902 doc.sel = sel |
| 1705 sel.collapse(start.node, start.offset); | 4903 |
| 1706 if (!rng.collapsed) sel.addRange(rng); | 4904 if (doc.cm) { |
| 1707 } else { | 4905 doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true |
| 1708 sel.removeAllRanges(); | 4906 signalCursorActivity(doc.cm) |
| 1709 sel.addRange(rng); | 4907 } |
| 1710 } | 4908 signalLater(doc, "cursorActivity", doc) |
| 1711 if (old && sel.anchorNode == null) sel.addRange(old); | 4909 } |
| 1712 else if (gecko) this.startGracePeriod(); | 4910 |
| 1713 } | 4911 // Verify that the selection does not partially select any atomic |
| 1714 this.rememberSelection(); | 4912 // marked ranges. |
| 1715 }, | 4913 function reCheckSelection(doc) { |
| 1716 | 4914 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_d
ontScroll) |
| 1717 startGracePeriod: function() { | 4915 } |
| 1718 var input = this; | 4916 |
| 1719 clearTimeout(this.gracePeriod); | 4917 // Return a selection that does not partially select any atomic |
| 1720 this.gracePeriod = setTimeout(function() { | 4918 // ranges. |
| 1721 input.gracePeriod = false; | 4919 function skipAtomicInSelection(doc, sel, bias, mayClear) { |
| 1722 if (input.selectionChanged()) | 4920 var out |
| 1723 input.cm.operation(function() { input.cm.curOp.selectionChanged = true
; }); | 4921 for (var i = 0; i < sel.ranges.length; i++) { |
| 1724 }, 20); | 4922 var range = sel.ranges[i] |
| 1725 }, | 4923 var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i] |
| 1726 | 4924 var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayCl
ear) |
| 1727 showMultipleSelections: function(info) { | 4925 var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear) |
| 1728 removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); | 4926 if (out || newAnchor != range.anchor || newHead != range.head) { |
| 1729 removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); | 4927 if (!out) { out = sel.ranges.slice(0, i) } |
| 1730 }, | 4928 out[i] = new Range(newAnchor, newHead) |
| 1731 | 4929 } |
| 1732 rememberSelection: function() { | 4930 } |
| 1733 var sel = window.getSelection(); | 4931 return out ? normalizeSelection(out, sel.primIndex) : sel |
| 1734 this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOf
fset; | 4932 } |
| 1735 this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset
; | 4933 |
| 1736 }, | 4934 function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { |
| 1737 | 4935 var line = getLine(doc, pos.line) |
| 1738 selectionInEditor: function() { | 4936 if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { |
| 1739 var sel = window.getSelection(); | 4937 var sp = line.markedSpans[i], m = sp.marker |
| 1740 if (!sel.rangeCount) return false; | 4938 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos
.ch)) && |
| 1741 var node = sel.getRangeAt(0).commonAncestorContainer; | 4939 (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))
) { |
| 1742 return contains(this.div, node); | 4940 if (mayClear) { |
| 1743 }, | 4941 signal(m, "beforeCursorEnter") |
| 1744 | 4942 if (m.explicitlyCleared) { |
| 1745 focus: function() { | 4943 if (!line.markedSpans) { break } |
| 1746 if (this.cm.options.readOnly != "nocursor") this.div.focus(); | 4944 else {--i; continue} |
| 1747 }, | |
| 1748 blur: function() { this.div.blur(); }, | |
| 1749 getField: function() { return this.div; }, | |
| 1750 | |
| 1751 supportsTouch: function() { return true; }, | |
| 1752 | |
| 1753 receivedFocus: function() { | |
| 1754 var input = this; | |
| 1755 if (this.selectionInEditor()) | |
| 1756 this.pollSelection(); | |
| 1757 else | |
| 1758 runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; })
; | |
| 1759 | |
| 1760 function poll() { | |
| 1761 if (input.cm.state.focused) { | |
| 1762 input.pollSelection(); | |
| 1763 input.polling.set(input.cm.options.pollInterval, poll); | |
| 1764 } | 4945 } |
| 1765 } | 4946 } |
| 1766 this.polling.set(this.cm.options.pollInterval, poll); | 4947 if (!m.atomic) { continue } |
| 1767 }, | 4948 |
| 1768 | 4949 if (oldPos) { |
| 1769 selectionChanged: function() { | 4950 var near = m.find(dir < 0 ? 1 : -1), diff = (void 0) |
| 1770 var sel = window.getSelection(); | 4951 if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) |
| 1771 return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.l
astAnchorOffset || | 4952 { near = movePos(doc, near, -dir, near && near.line == pos.line ? line
: null) } |
| 1772 sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocus
Offset; | 4953 if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir
< 0 ? diff < 0 : diff > 0)) |
| 1773 }, | 4954 { return skipAtomicInner(doc, near, pos, dir, mayClear) } |
| 1774 | |
| 1775 pollSelection: function() { | |
| 1776 if (!this.composing && !this.gracePeriod && this.selectionChanged()) { | |
| 1777 var sel = window.getSelection(), cm = this.cm; | |
| 1778 this.rememberSelection(); | |
| 1779 var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); | |
| 1780 var head = domToPos(cm, sel.focusNode, sel.focusOffset); | |
| 1781 if (anchor && head) runInOp(cm, function() { | |
| 1782 setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); | |
| 1783 if (anchor.bad || head.bad) cm.curOp.selectionChanged = true; | |
| 1784 }); | |
| 1785 } | 4955 } |
| 1786 }, | 4956 |
| 1787 | 4957 var far = m.find(dir < 0 ? -1 : 1) |
| 1788 pollContent: function() { | 4958 if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) |
| 1789 var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); | 4959 { far = movePos(doc, far, dir, far.line == pos.line ? line : null) } |
| 1790 var from = sel.from(), to = sel.to(); | 4960 return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null |
| 1791 if (from.line < display.viewFrom || to.line > display.viewTo - 1) return f
alse; | 4961 } |
| 1792 | 4962 } } |
| 1793 var fromIndex; | 4963 return pos |
| 1794 if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.l
ine)) == 0) { | 4964 } |
| 1795 var fromLine = lineNo(display.view[0].line); | 4965 |
| 1796 var fromNode = display.view[0].node; | 4966 // Ensure a given position is not inside an atomic range. |
| 4967 function skipAtomic(doc, pos, oldPos, bias, mayClear) { |
| 4968 var dir = bias || 1 |
| 4969 var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || |
| 4970 (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || |
| 4971 skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || |
| 4972 (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)) |
| 4973 if (!found) { |
| 4974 doc.cantEdit = true |
| 4975 return Pos(doc.first, 0) |
| 4976 } |
| 4977 return found |
| 4978 } |
| 4979 |
| 4980 function movePos(doc, pos, dir, line) { |
| 4981 if (dir < 0 && pos.ch == 0) { |
| 4982 if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) } |
| 4983 else { return null } |
| 4984 } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length)
{ |
| 4985 if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) } |
| 4986 else { return null } |
| 4987 } else { |
| 4988 return new Pos(pos.line, pos.ch + dir) |
| 4989 } |
| 4990 } |
| 4991 |
| 4992 function selectAll(cm) { |
| 4993 cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll) |
| 4994 } |
| 4995 |
| 4996 // UPDATING |
| 4997 |
| 4998 // Allow "beforeChange" event handlers to influence a change |
| 4999 function filterChange(doc, change, update) { |
| 5000 var obj = { |
| 5001 canceled: false, |
| 5002 from: change.from, |
| 5003 to: change.to, |
| 5004 text: change.text, |
| 5005 origin: change.origin, |
| 5006 cancel: function () { return obj.canceled = true; } |
| 5007 } |
| 5008 if (update) { obj.update = function (from, to, text, origin) { |
| 5009 if (from) { obj.from = clipPos(doc, from) } |
| 5010 if (to) { obj.to = clipPos(doc, to) } |
| 5011 if (text) { obj.text = text } |
| 5012 if (origin !== undefined) { obj.origin = origin } |
| 5013 } } |
| 5014 signal(doc, "beforeChange", doc, obj) |
| 5015 if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj) } |
| 5016 |
| 5017 if (obj.canceled) { return null } |
| 5018 return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} |
| 5019 } |
| 5020 |
| 5021 // Apply a change to a document, and add it to the document's |
| 5022 // history, and propagating it to all linked documents. |
| 5023 function makeChange(doc, change, ignoreReadOnly) { |
| 5024 if (doc.cm) { |
| 5025 if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignor
eReadOnly) } |
| 5026 if (doc.cm.state.suppressEdits) { return } |
| 5027 } |
| 5028 |
| 5029 if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeCha
nge")) { |
| 5030 change = filterChange(doc, change, true) |
| 5031 if (!change) { return } |
| 5032 } |
| 5033 |
| 5034 // Possibly split or suppress the update based on the presence |
| 5035 // of read-only spans in its range. |
| 5036 var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, c
hange.from, change.to) |
| 5037 if (split) { |
| 5038 for (var i = split.length - 1; i >= 0; --i) |
| 5039 { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? ["
"] : change.text}) } |
| 5040 } else { |
| 5041 makeChangeInner(doc, change) |
| 5042 } |
| 5043 } |
| 5044 |
| 5045 function makeChangeInner(doc, change) { |
| 5046 if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change
.to) == 0) { return } |
| 5047 var selAfter = computeSelAfterChange(doc, change) |
| 5048 addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN) |
| 5049 |
| 5050 makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)
) |
| 5051 var rebased = [] |
| 5052 |
| 5053 linkedDocs(doc, function (doc, sharedHist) { |
| 5054 if (!sharedHist && indexOf(rebased, doc.history) == -1) { |
| 5055 rebaseHist(doc.history, change) |
| 5056 rebased.push(doc.history) |
| 5057 } |
| 5058 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)) |
| 5059 }) |
| 5060 } |
| 5061 |
| 5062 // Revert a change stored in a document's history. |
| 5063 function makeChangeFromHistory(doc, type, allowSelectionOnly) { |
| 5064 if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) { return } |
| 5065 |
| 5066 var hist = doc.history, event, selAfter = doc.sel |
| 5067 var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ?
hist.undone : hist.done |
| 5068 |
| 5069 // Verify that there is a useable event (so that ctrl-z won't |
| 5070 // needlessly clear selection events) |
| 5071 var i = 0 |
| 5072 for (; i < source.length; i++) { |
| 5073 event = source[i] |
| 5074 if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ran
ges) |
| 5075 { break } |
| 5076 } |
| 5077 if (i == source.length) { return } |
| 5078 hist.lastOrigin = hist.lastSelOrigin = null |
| 5079 |
| 5080 for (;;) { |
| 5081 event = source.pop() |
| 5082 if (event.ranges) { |
| 5083 pushSelectionToHistory(event, dest) |
| 5084 if (allowSelectionOnly && !event.equals(doc.sel)) { |
| 5085 setSelection(doc, event, {clearRedo: false}) |
| 5086 return |
| 5087 } |
| 5088 selAfter = event |
| 5089 } |
| 5090 else { break } |
| 5091 } |
| 5092 |
| 5093 // Build up a reverse change object to add to the opposite history |
| 5094 // stack (redo when undoing, and vice versa). |
| 5095 var antiChanges = [] |
| 5096 pushSelectionToHistory(selAfter, dest) |
| 5097 dest.push({changes: antiChanges, generation: hist.generation}) |
| 5098 hist.generation = event.generation || ++hist.maxGeneration |
| 5099 |
| 5100 var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "
beforeChange") |
| 5101 |
| 5102 var loop = function ( i ) { |
| 5103 var change = event.changes[i] |
| 5104 change.origin = type |
| 5105 if (filter && !filterChange(doc, change, false)) { |
| 5106 source.length = 0 |
| 5107 return {} |
| 5108 } |
| 5109 |
| 5110 antiChanges.push(historyChangeFromChange(doc, change)) |
| 5111 |
| 5112 var after = i ? computeSelAfterChange(doc, change) : lst(source) |
| 5113 makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)) |
| 5114 if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(
change)}) } |
| 5115 var rebased = [] |
| 5116 |
| 5117 // Propagate to the linked documents |
| 5118 linkedDocs(doc, function (doc, sharedHist) { |
| 5119 if (!sharedHist && indexOf(rebased, doc.history) == -1) { |
| 5120 rebaseHist(doc.history, change) |
| 5121 rebased.push(doc.history) |
| 5122 } |
| 5123 makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)) |
| 5124 }) |
| 5125 }; |
| 5126 |
| 5127 for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { |
| 5128 var returned = loop( i$1 ); |
| 5129 |
| 5130 if ( returned ) return returned.v; |
| 5131 } |
| 5132 } |
| 5133 |
| 5134 // Sub-views need their line numbers shifted when text is added |
| 5135 // above or below them in the parent document. |
| 5136 function shiftDoc(doc, distance) { |
| 5137 if (distance == 0) { return } |
| 5138 doc.first += distance |
| 5139 doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Rang
e( |
| 5140 Pos(range.anchor.line + distance, range.anchor.ch), |
| 5141 Pos(range.head.line + distance, range.head.ch) |
| 5142 ); }), doc.sel.primIndex) |
| 5143 if (doc.cm) { |
| 5144 regChange(doc.cm, doc.first, doc.first - distance, distance) |
| 5145 for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) |
| 5146 { regLineChange(doc.cm, l, "gutter") } |
| 5147 } |
| 5148 } |
| 5149 |
| 5150 // More lower-level change function, handling only a single document |
| 5151 // (not linked ones). |
| 5152 function makeChangeSingleDoc(doc, change, selAfter, spans) { |
| 5153 if (doc.cm && !doc.cm.curOp) |
| 5154 { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans
) } |
| 5155 |
| 5156 if (change.to.line < doc.first) { |
| 5157 shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)) |
| 5158 return |
| 5159 } |
| 5160 if (change.from.line > doc.lastLine()) { return } |
| 5161 |
| 5162 // Clip the change to the size of this doc |
| 5163 if (change.from.line < doc.first) { |
| 5164 var shift = change.text.length - 1 - (doc.first - change.from.line) |
| 5165 shiftDoc(doc, shift) |
| 5166 change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to
.ch), |
| 5167 text: [lst(change.text)], origin: change.origin} |
| 5168 } |
| 5169 var last = doc.lastLine() |
| 5170 if (change.to.line > last) { |
| 5171 change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), |
| 5172 text: [change.text[0]], origin: change.origin} |
| 5173 } |
| 5174 |
| 5175 change.removed = getBetween(doc, change.from, change.to) |
| 5176 |
| 5177 if (!selAfter) { selAfter = computeSelAfterChange(doc, change) } |
| 5178 if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans) } |
| 5179 else { updateDoc(doc, change, spans) } |
| 5180 setSelectionNoUndo(doc, selAfter, sel_dontScroll) |
| 5181 } |
| 5182 |
| 5183 // Handle the interaction of a change to a document with the editor |
| 5184 // that this document is part of. |
| 5185 function makeChangeSingleDocInEditor(cm, change, spans) { |
| 5186 var doc = cm.doc, display = cm.display, from = change.from, to = change.to |
| 5187 |
| 5188 var recomputeMaxLength = false, checkWidthStart = from.line |
| 5189 if (!cm.options.lineWrapping) { |
| 5190 checkWidthStart = lineNo(visualLine(getLine(doc, from.line))) |
| 5191 doc.iter(checkWidthStart, to.line + 1, function (line) { |
| 5192 if (line == display.maxLine) { |
| 5193 recomputeMaxLength = true |
| 5194 return true |
| 5195 } |
| 5196 }) |
| 5197 } |
| 5198 |
| 5199 if (doc.sel.contains(change.from, change.to) > -1) |
| 5200 { signalCursorActivity(cm) } |
| 5201 |
| 5202 updateDoc(doc, change, spans, estimateHeight(cm)) |
| 5203 |
| 5204 if (!cm.options.lineWrapping) { |
| 5205 doc.iter(checkWidthStart, from.line + change.text.length, function (line) { |
| 5206 var len = lineLength(line) |
| 5207 if (len > display.maxLineLength) { |
| 5208 display.maxLine = line |
| 5209 display.maxLineLength = len |
| 5210 display.maxLineChanged = true |
| 5211 recomputeMaxLength = false |
| 5212 } |
| 5213 }) |
| 5214 if (recomputeMaxLength) { cm.curOp.updateMaxLine = true } |
| 5215 } |
| 5216 |
| 5217 // Adjust frontier, schedule worker |
| 5218 doc.frontier = Math.min(doc.frontier, from.line) |
| 5219 startWorker(cm, 400) |
| 5220 |
| 5221 var lendiff = change.text.length - (to.line - from.line) - 1 |
| 5222 // Remember that these lines changed, for updating the display |
| 5223 if (change.full) |
| 5224 { regChange(cm) } |
| 5225 else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate
(cm.doc, change)) |
| 5226 { regLineChange(cm, from.line, "text") } |
| 5227 else |
| 5228 { regChange(cm, from.line, to.line + 1, lendiff) } |
| 5229 |
| 5230 var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm,
"change") |
| 5231 if (changeHandler || changesHandler) { |
| 5232 var obj = { |
| 5233 from: from, to: to, |
| 5234 text: change.text, |
| 5235 removed: change.removed, |
| 5236 origin: change.origin |
| 5237 } |
| 5238 if (changeHandler) { signalLater(cm, "change", cm, obj) } |
| 5239 if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).pu
sh(obj) } |
| 5240 } |
| 5241 cm.display.selForContextMenu = null |
| 5242 } |
| 5243 |
| 5244 function replaceRange(doc, code, from, to, origin) { |
| 5245 if (!to) { to = from } |
| 5246 if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp } |
| 5247 if (typeof code == "string") { code = doc.splitLines(code) } |
| 5248 makeChange(doc, {from: from, to: to, text: code, origin: origin}) |
| 5249 } |
| 5250 |
| 5251 // Rebasing/resetting history to deal with externally-sourced changes |
| 5252 |
| 5253 function rebaseHistSelSingle(pos, from, to, diff) { |
| 5254 if (to < pos.line) { |
| 5255 pos.line += diff |
| 5256 } else if (from < pos.line) { |
| 5257 pos.line = from |
| 5258 pos.ch = 0 |
| 5259 } |
| 5260 } |
| 5261 |
| 5262 // Tries to rebase an array of history events given a change in the |
| 5263 // document. If the change touches the same lines as the event, the |
| 5264 // event, and everything 'behind' it, is discarded. If the change is |
| 5265 // before the event, the event's positions are updated. Uses a |
| 5266 // copy-on-write scheme for the positions, to avoid having to |
| 5267 // reallocate them all on every rebase, but also avoid problems with |
| 5268 // shared position objects being unsafely updated. |
| 5269 function rebaseHistArray(array, from, to, diff) { |
| 5270 for (var i = 0; i < array.length; ++i) { |
| 5271 var sub = array[i], ok = true |
| 5272 if (sub.ranges) { |
| 5273 if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true } |
| 5274 for (var j = 0; j < sub.ranges.length; j++) { |
| 5275 rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff) |
| 5276 rebaseHistSelSingle(sub.ranges[j].head, from, to, diff) |
| 5277 } |
| 5278 continue |
| 5279 } |
| 5280 for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { |
| 5281 var cur = sub.changes[j$1] |
| 5282 if (to < cur.from.line) { |
| 5283 cur.from = Pos(cur.from.line + diff, cur.from.ch) |
| 5284 cur.to = Pos(cur.to.line + diff, cur.to.ch) |
| 5285 } else if (from <= cur.to.line) { |
| 5286 ok = false |
| 5287 break |
| 5288 } |
| 5289 } |
| 5290 if (!ok) { |
| 5291 array.splice(0, i + 1) |
| 5292 i = 0 |
| 5293 } |
| 5294 } |
| 5295 } |
| 5296 |
| 5297 function rebaseHist(hist, change) { |
| 5298 var from = change.from.line, to = change.to.line, diff = change.text.length -
(to - from) - 1 |
| 5299 rebaseHistArray(hist.done, from, to, diff) |
| 5300 rebaseHistArray(hist.undone, from, to, diff) |
| 5301 } |
| 5302 |
| 5303 // Utility for applying a change to a line by handle or number, |
| 5304 // returning the number and optionally registering the line as |
| 5305 // changed. |
| 5306 function changeLine(doc, handle, changeType, op) { |
| 5307 var no = handle, line = handle |
| 5308 if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)) } |
| 5309 else { no = lineNo(handle) } |
| 5310 if (no == null) { return null } |
| 5311 if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType) } |
| 5312 return line |
| 5313 } |
| 5314 |
| 5315 // The document is represented as a BTree consisting of leaves, with |
| 5316 // chunk of lines in them, and branches, with up to ten leaves or |
| 5317 // other branch nodes below them. The top node is always a branch |
| 5318 // node, and is the document object itself (meaning it has |
| 5319 // additional methods and properties). |
| 5320 // |
| 5321 // All nodes have parent links. The tree is used both to go from |
| 5322 // line numbers to line objects, and to go from objects to numbers. |
| 5323 // It also indexes by height, and is used to convert between height |
| 5324 // and line object, and to find the total height of the document. |
| 5325 // |
| 5326 // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html |
| 5327 |
| 5328 var LeafChunk = function LeafChunk(lines) { |
| 5329 var this$1 = this; |
| 5330 |
| 5331 this.lines = lines |
| 5332 this.parent = null |
| 5333 var height = 0 |
| 5334 for (var i = 0; i < lines.length; ++i) { |
| 5335 lines[i].parent = this$1 |
| 5336 height += lines[i].height |
| 5337 } |
| 5338 this.height = height |
| 5339 }; |
| 5340 |
| 5341 LeafChunk.prototype.chunkSize = function chunkSize () { return this.lines.length
}; |
| 5342 |
| 5343 // Remove the n lines at offset 'at'. |
| 5344 LeafChunk.prototype.removeInner = function removeInner (at, n) { |
| 5345 var this$1 = this; |
| 5346 |
| 5347 for (var i = at, e = at + n; i < e; ++i) { |
| 5348 var line = this$1.lines[i] |
| 5349 this$1.height -= line.height |
| 5350 cleanUpLine(line) |
| 5351 signalLater(line, "delete") |
| 5352 } |
| 5353 this.lines.splice(at, n) |
| 5354 }; |
| 5355 |
| 5356 // Helper used to collapse a small branch into a single leaf. |
| 5357 LeafChunk.prototype.collapse = function collapse (lines) { |
| 5358 lines.push.apply(lines, this.lines) |
| 5359 }; |
| 5360 |
| 5361 // Insert the given array of lines at offset 'at', count them as |
| 5362 // having the given height. |
| 5363 LeafChunk.prototype.insertInner = function insertInner (at, lines, height) { |
| 5364 var this$1 = this; |
| 5365 |
| 5366 this.height += height |
| 5367 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)
) |
| 5368 for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1 } |
| 5369 }; |
| 5370 |
| 5371 // Used to iterate over a part of the tree. |
| 5372 LeafChunk.prototype.iterN = function iterN (at, n, op) { |
| 5373 var this$1 = this; |
| 5374 |
| 5375 for (var e = at + n; at < e; ++at) |
| 5376 { if (op(this$1.lines[at])) { return true } } |
| 5377 }; |
| 5378 |
| 5379 var BranchChunk = function BranchChunk(children) { |
| 5380 var this$1 = this; |
| 5381 |
| 5382 this.children = children |
| 5383 var size = 0, height = 0 |
| 5384 for (var i = 0; i < children.length; ++i) { |
| 5385 var ch = children[i] |
| 5386 size += ch.chunkSize(); height += ch.height |
| 5387 ch.parent = this$1 |
| 5388 } |
| 5389 this.size = size |
| 5390 this.height = height |
| 5391 this.parent = null |
| 5392 }; |
| 5393 |
| 5394 BranchChunk.prototype.chunkSize = function chunkSize () { return this.size }; |
| 5395 |
| 5396 BranchChunk.prototype.removeInner = function removeInner (at, n) { |
| 5397 var this$1 = this; |
| 5398 |
| 5399 this.size -= n |
| 5400 for (var i = 0; i < this.children.length; ++i) { |
| 5401 var child = this$1.children[i], sz = child.chunkSize() |
| 5402 if (at < sz) { |
| 5403 var rm = Math.min(n, sz - at), oldHeight = child.height |
| 5404 child.removeInner(at, rm) |
| 5405 this$1.height -= oldHeight - child.height |
| 5406 if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null } |
| 5407 if ((n -= rm) == 0) { break } |
| 5408 at = 0 |
| 5409 } else { at -= sz } |
| 5410 } |
| 5411 // If the result is smaller than 25 lines, ensure that it is a |
| 5412 // single leaf node. |
| 5413 if (this.size - n < 25 && |
| 5414 (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { |
| 5415 var lines = [] |
| 5416 this.collapse(lines) |
| 5417 this.children = [new LeafChunk(lines)] |
| 5418 this.children[0].parent = this |
| 5419 } |
| 5420 }; |
| 5421 |
| 5422 BranchChunk.prototype.collapse = function collapse (lines) { |
| 5423 var this$1 = this; |
| 5424 |
| 5425 for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(l
ines) } |
| 5426 }; |
| 5427 |
| 5428 BranchChunk.prototype.insertInner = function insertInner (at, lines, height) { |
| 5429 var this$1 = this; |
| 5430 |
| 5431 this.size += lines.length |
| 5432 this.height += height |
| 5433 for (var i = 0; i < this.children.length; ++i) { |
| 5434 var child = this$1.children[i], sz = child.chunkSize() |
| 5435 if (at <= sz) { |
| 5436 child.insertInner(at, lines, height) |
| 5437 if (child.lines && child.lines.length > 50) { |
| 5438 // To avoid memory thrashing when child.lines is huge (e.g. first view o
f a large file), it's never spliced. |
| 5439 // Instead, small slices are taken. They're taken in order because seque
ntial memory accesses are fastest. |
| 5440 var remaining = child.lines.length % 25 + 25 |
| 5441 for (var pos = remaining; pos < child.lines.length;) { |
| 5442 var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) |
| 5443 child.height -= leaf.height |
| 5444 this$1.children.splice(++i, 0, leaf) |
| 5445 leaf.parent = this$1 |
| 5446 } |
| 5447 child.lines = child.lines.slice(0, remaining) |
| 5448 this$1.maybeSpill() |
| 5449 } |
| 5450 break |
| 5451 } |
| 5452 at -= sz |
| 5453 } |
| 5454 }; |
| 5455 |
| 5456 // When a node has grown, check whether it should be split. |
| 5457 BranchChunk.prototype.maybeSpill = function maybeSpill () { |
| 5458 if (this.children.length <= 10) { return } |
| 5459 var me = this |
| 5460 do { |
| 5461 var spilled = me.children.splice(me.children.length - 5, 5) |
| 5462 var sibling = new BranchChunk(spilled) |
| 5463 if (!me.parent) { // Become the parent node |
| 5464 var copy = new BranchChunk(me.children) |
| 5465 copy.parent = me |
| 5466 me.children = [copy, sibling] |
| 5467 me = copy |
| 5468 } else { |
| 5469 me.size -= sibling.size |
| 5470 me.height -= sibling.height |
| 5471 var myIndex = indexOf(me.parent.children, me) |
| 5472 me.parent.children.splice(myIndex + 1, 0, sibling) |
| 5473 } |
| 5474 sibling.parent = me.parent |
| 5475 } while (me.children.length > 10) |
| 5476 me.parent.maybeSpill() |
| 5477 }; |
| 5478 |
| 5479 BranchChunk.prototype.iterN = function iterN (at, n, op) { |
| 5480 var this$1 = this; |
| 5481 |
| 5482 for (var i = 0; i < this.children.length; ++i) { |
| 5483 var child = this$1.children[i], sz = child.chunkSize() |
| 5484 if (at < sz) { |
| 5485 var used = Math.min(n, sz - at) |
| 5486 if (child.iterN(at, used, op)) { return true } |
| 5487 if ((n -= used) == 0) { break } |
| 5488 at = 0 |
| 5489 } else { at -= sz } |
| 5490 } |
| 5491 }; |
| 5492 |
| 5493 // Line widgets are block elements displayed above or below a line. |
| 5494 |
| 5495 var LineWidget = function LineWidget(doc, node, options) { |
| 5496 var this$1 = this; |
| 5497 |
| 5498 if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) |
| 5499 { this$1[opt] = options[opt] } } } |
| 5500 this.doc = doc |
| 5501 this.node = node |
| 5502 }; |
| 5503 |
| 5504 LineWidget.prototype.clear = function clear () { |
| 5505 var this$1 = this; |
| 5506 |
| 5507 var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(li
ne) |
| 5508 if (no == null || !ws) { return } |
| 5509 for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1)
} } |
| 5510 if (!ws.length) { line.widgets = null } |
| 5511 var height = widgetHeight(this) |
| 5512 updateLineHeight(line, Math.max(0, line.height - height)) |
| 5513 if (cm) { |
| 5514 runInOp(cm, function () { |
| 5515 adjustScrollWhenAboveVisible(cm, line, -height) |
| 5516 regLineChange(cm, no, "widget") |
| 5517 }) |
| 5518 signalLater(cm, "lineWidgetCleared", cm, this, no) |
| 5519 } |
| 5520 }; |
| 5521 |
| 5522 LineWidget.prototype.changed = function changed () { |
| 5523 var this$1 = this; |
| 5524 |
| 5525 var oldH = this.height, cm = this.doc.cm, line = this.line |
| 5526 this.height = null |
| 5527 var diff = widgetHeight(this) - oldH |
| 5528 if (!diff) { return } |
| 5529 updateLineHeight(line, line.height + diff) |
| 5530 if (cm) { |
| 5531 runInOp(cm, function () { |
| 5532 cm.curOp.forceUpdate = true |
| 5533 adjustScrollWhenAboveVisible(cm, line, diff) |
| 5534 signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line)) |
| 5535 }) |
| 5536 } |
| 5537 }; |
| 5538 eventMixin(LineWidget) |
| 5539 |
| 5540 function adjustScrollWhenAboveVisible(cm, line, diff) { |
| 5541 if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop
)) |
| 5542 { addToScrollPos(cm, null, diff) } |
| 5543 } |
| 5544 |
| 5545 function addLineWidget(doc, handle, node, options) { |
| 5546 var widget = new LineWidget(doc, node, options) |
| 5547 var cm = doc.cm |
| 5548 if (cm && widget.noHScroll) { cm.display.alignWidgets = true } |
| 5549 changeLine(doc, handle, "widget", function (line) { |
| 5550 var widgets = line.widgets || (line.widgets = []) |
| 5551 if (widget.insertAt == null) { widgets.push(widget) } |
| 5552 else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insert
At)), 0, widget) } |
| 5553 widget.line = line |
| 5554 if (cm && !lineIsHidden(doc, line)) { |
| 5555 var aboveVisible = heightAtLine(line) < doc.scrollTop |
| 5556 updateLineHeight(line, line.height + widgetHeight(widget)) |
| 5557 if (aboveVisible) { addToScrollPos(cm, null, widget.height) } |
| 5558 cm.curOp.forceUpdate = true |
| 5559 } |
| 5560 return true |
| 5561 }) |
| 5562 signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? han
dle : lineNo(handle)) |
| 5563 return widget |
| 5564 } |
| 5565 |
| 5566 // TEXTMARKERS |
| 5567 |
| 5568 // Created with markText and setBookmark methods. A TextMarker is a |
| 5569 // handle that can be used to clear or find a marked position in the |
| 5570 // document. Line objects hold arrays (markedSpans) containing |
| 5571 // {from, to, marker} object pointing to such marker objects, and |
| 5572 // indicating that such a marker is present on that line. Multiple |
| 5573 // lines may point to the same marker when it spans across lines. |
| 5574 // The spans will have null for their from/to properties when the |
| 5575 // marker continues beyond the start/end of the line. Markers have |
| 5576 // links back to the lines they currently touch. |
| 5577 |
| 5578 // Collapsed markers have unique ids, in order to be able to order |
| 5579 // them, which is needed for uniquely determining an outer marker |
| 5580 // when they overlap (they may nest, but not partially overlap). |
| 5581 var nextMarkerId = 0 |
| 5582 |
| 5583 var TextMarker = function TextMarker(doc, type) { |
| 5584 this.lines = [] |
| 5585 this.type = type |
| 5586 this.doc = doc |
| 5587 this.id = ++nextMarkerId |
| 5588 }; |
| 5589 |
| 5590 // Clear the marker. |
| 5591 TextMarker.prototype.clear = function clear () { |
| 5592 var this$1 = this; |
| 5593 |
| 5594 if (this.explicitlyCleared) { return } |
| 5595 var cm = this.doc.cm, withOp = cm && !cm.curOp |
| 5596 if (withOp) { startOperation(cm) } |
| 5597 if (hasHandler(this, "clear")) { |
| 5598 var found = this.find() |
| 5599 if (found) { signalLater(this, "clear", found.from, found.to) } |
| 5600 } |
| 5601 var min = null, max = null |
| 5602 for (var i = 0; i < this.lines.length; ++i) { |
| 5603 var line = this$1.lines[i] |
| 5604 var span = getMarkedSpanFor(line.markedSpans, this$1) |
| 5605 if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), "text") } |
| 5606 else if (cm) { |
| 5607 if (span.to != null) { max = lineNo(line) } |
| 5608 if (span.from != null) { min = lineNo(line) } |
| 5609 } |
| 5610 line.markedSpans = removeMarkedSpan(line.markedSpans, span) |
| 5611 if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line)
&& cm) |
| 5612 { updateLineHeight(line, textHeight(cm.display)) } |
| 5613 } |
| 5614 if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1
< this.lines.length; ++i$1) { |
| 5615 var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual) |
| 5616 if (len > cm.display.maxLineLength) { |
| 5617 cm.display.maxLine = visual |
| 5618 cm.display.maxLineLength = len |
| 5619 cm.display.maxLineChanged = true |
| 5620 } |
| 5621 } } |
| 5622 |
| 5623 if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1) } |
| 5624 this.lines.length = 0 |
| 5625 this.explicitlyCleared = true |
| 5626 if (this.atomic && this.doc.cantEdit) { |
| 5627 this.doc.cantEdit = false |
| 5628 if (cm) { reCheckSelection(cm.doc) } |
| 5629 } |
| 5630 if (cm) { signalLater(cm, "markerCleared", cm, this, min, max) } |
| 5631 if (withOp) { endOperation(cm) } |
| 5632 if (this.parent) { this.parent.clear() } |
| 5633 }; |
| 5634 |
| 5635 // Find the position of the marker in the document. Returns a {from, |
| 5636 // to} object by default. Side can be passed to get a specific side |
| 5637 // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the |
| 5638 // Pos objects returned contain a line object, rather than a line |
| 5639 // number (used to prevent looking up the same line twice). |
| 5640 TextMarker.prototype.find = function find (side, lineObj) { |
| 5641 var this$1 = this; |
| 5642 |
| 5643 if (side == null && this.type == "bookmark") { side = 1 } |
| 5644 var from, to |
| 5645 for (var i = 0; i < this.lines.length; ++i) { |
| 5646 var line = this$1.lines[i] |
| 5647 var span = getMarkedSpanFor(line.markedSpans, this$1) |
| 5648 if (span.from != null) { |
| 5649 from = Pos(lineObj ? line : lineNo(line), span.from) |
| 5650 if (side == -1) { return from } |
| 5651 } |
| 5652 if (span.to != null) { |
| 5653 to = Pos(lineObj ? line : lineNo(line), span.to) |
| 5654 if (side == 1) { return to } |
| 5655 } |
| 5656 } |
| 5657 return from && {from: from, to: to} |
| 5658 }; |
| 5659 |
| 5660 // Signals that the marker's widget changed, and surrounding layout |
| 5661 // should be recomputed. |
| 5662 TextMarker.prototype.changed = function changed () { |
| 5663 var this$1 = this; |
| 5664 |
| 5665 var pos = this.find(-1, true), widget = this, cm = this.doc.cm |
| 5666 if (!pos || !cm) { return } |
| 5667 runInOp(cm, function () { |
| 5668 var line = pos.line, lineN = lineNo(pos.line) |
| 5669 var view = findViewForLine(cm, lineN) |
| 5670 if (view) { |
| 5671 clearLineMeasurementCacheFor(view) |
| 5672 cm.curOp.selectionChanged = cm.curOp.forceUpdate = true |
| 5673 } |
| 5674 cm.curOp.updateMaxLine = true |
| 5675 if (!lineIsHidden(widget.doc, line) && widget.height != null) { |
| 5676 var oldHeight = widget.height |
| 5677 widget.height = null |
| 5678 var dHeight = widgetHeight(widget) - oldHeight |
| 5679 if (dHeight) |
| 5680 { updateLineHeight(line, line.height + dHeight) } |
| 5681 } |
| 5682 signalLater(cm, "markerChanged", cm, this$1) |
| 5683 }) |
| 5684 }; |
| 5685 |
| 5686 TextMarker.prototype.attachLine = function attachLine (line) { |
| 5687 if (!this.lines.length && this.doc.cm) { |
| 5688 var op = this.doc.cm.curOp |
| 5689 if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) |
| 5690 { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) } |
| 5691 } |
| 5692 this.lines.push(line) |
| 5693 }; |
| 5694 |
| 5695 TextMarker.prototype.detachLine = function detachLine (line) { |
| 5696 this.lines.splice(indexOf(this.lines, line), 1) |
| 5697 if (!this.lines.length && this.doc.cm) { |
| 5698 var op = this.doc.cm.curOp |
| 5699 ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this) |
| 5700 } |
| 5701 }; |
| 5702 eventMixin(TextMarker) |
| 5703 |
| 5704 // Create a marker, wire it up to the right lines, and |
| 5705 function markText(doc, from, to, options, type) { |
| 5706 // Shared markers (across linked documents) are handled separately |
| 5707 // (markTextShared will call out to this again, once per |
| 5708 // document). |
| 5709 if (options && options.shared) { return markTextShared(doc, from, to, options,
type) } |
| 5710 // Ensure we are in an operation. |
| 5711 if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, t
o, options, type) } |
| 5712 |
| 5713 var marker = new TextMarker(doc, type), diff = cmp(from, to) |
| 5714 if (options) { copyObj(options, marker, false) } |
| 5715 // Don't connect empty markers unless clearWhenEmpty is false |
| 5716 if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) |
| 5717 { return marker } |
| 5718 if (marker.replacedWith) { |
| 5719 // Showing up as a widget implies collapsed (widget replaces text) |
| 5720 marker.collapsed = true |
| 5721 marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget") |
| 5722 if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-
events", "true") } |
| 5723 if (options.insertLeft) { marker.widgetNode.insertLeft = true } |
| 5724 } |
| 5725 if (marker.collapsed) { |
| 5726 if (conflictingCollapsedRange(doc, from.line, from, to, marker) || |
| 5727 from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to
, marker)) |
| 5728 { throw new Error("Inserting collapsed marker partially overlapping an exi
sting one") } |
| 5729 seeCollapsedSpans() |
| 5730 } |
| 5731 |
| 5732 if (marker.addToHistory) |
| 5733 { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel,
NaN) } |
| 5734 |
| 5735 var curLine = from.line, cm = doc.cm, updateMaxLine |
| 5736 doc.iter(curLine, to.line + 1, function (line) { |
| 5737 if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) =
= cm.display.maxLine) |
| 5738 { updateMaxLine = true } |
| 5739 if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0) } |
| 5740 addMarkedSpan(line, new MarkedSpan(marker, |
| 5741 curLine == from.line ? from.ch : null, |
| 5742 curLine == to.line ? to.ch : null)) |
| 5743 ++curLine |
| 5744 }) |
| 5745 // lineIsHidden depends on the presence of the spans, so needs a second pass |
| 5746 if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) { |
| 5747 if (lineIsHidden(doc, line)) { updateLineHeight(line, 0) } |
| 5748 }) } |
| 5749 |
| 5750 if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { retur
n marker.clear(); }) } |
| 5751 |
| 5752 if (marker.readOnly) { |
| 5753 seeReadOnlySpans() |
| 5754 if (doc.history.done.length || doc.history.undone.length) |
| 5755 { doc.clearHistory() } |
| 5756 } |
| 5757 if (marker.collapsed) { |
| 5758 marker.id = ++nextMarkerId |
| 5759 marker.atomic = true |
| 5760 } |
| 5761 if (cm) { |
| 5762 // Sync editor state |
| 5763 if (updateMaxLine) { cm.curOp.updateMaxLine = true } |
| 5764 if (marker.collapsed) |
| 5765 { regChange(cm, from.line, to.line + 1) } |
| 5766 else if (marker.className || marker.title || marker.startStyle || marker.end
Style || marker.css) |
| 5767 { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text"
) } } |
| 5768 if (marker.atomic) { reCheckSelection(cm.doc) } |
| 5769 signalLater(cm, "markerAdded", cm, marker) |
| 5770 } |
| 5771 return marker |
| 5772 } |
| 5773 |
| 5774 // SHARED TEXTMARKERS |
| 5775 |
| 5776 // A shared marker spans multiple linked documents. It is |
| 5777 // implemented as a meta-marker-object controlling multiple normal |
| 5778 // markers. |
| 5779 var SharedTextMarker = function SharedTextMarker(markers, primary) { |
| 5780 var this$1 = this; |
| 5781 |
| 5782 this.markers = markers |
| 5783 this.primary = primary |
| 5784 for (var i = 0; i < markers.length; ++i) |
| 5785 { markers[i].parent = this$1 } |
| 5786 }; |
| 5787 |
| 5788 SharedTextMarker.prototype.clear = function clear () { |
| 5789 var this$1 = this; |
| 5790 |
| 5791 if (this.explicitlyCleared) { return } |
| 5792 this.explicitlyCleared = true |
| 5793 for (var i = 0; i < this.markers.length; ++i) |
| 5794 { this$1.markers[i].clear() } |
| 5795 signalLater(this, "clear") |
| 5796 }; |
| 5797 |
| 5798 SharedTextMarker.prototype.find = function find (side, lineObj) { |
| 5799 return this.primary.find(side, lineObj) |
| 5800 }; |
| 5801 eventMixin(SharedTextMarker) |
| 5802 |
| 5803 function markTextShared(doc, from, to, options, type) { |
| 5804 options = copyObj(options) |
| 5805 options.shared = false |
| 5806 var markers = [markText(doc, from, to, options, type)], primary = markers[0] |
| 5807 var widget = options.widgetNode |
| 5808 linkedDocs(doc, function (doc) { |
| 5809 if (widget) { options.widgetNode = widget.cloneNode(true) } |
| 5810 markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, ty
pe)) |
| 5811 for (var i = 0; i < doc.linked.length; ++i) |
| 5812 { if (doc.linked[i].isParent) { return } } |
| 5813 primary = lst(markers) |
| 5814 }) |
| 5815 return new SharedTextMarker(markers, primary) |
| 5816 } |
| 5817 |
| 5818 function findSharedMarkers(doc) { |
| 5819 return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), func
tion (m) { return m.parent; }) |
| 5820 } |
| 5821 |
| 5822 function copySharedMarkers(doc, markers) { |
| 5823 for (var i = 0; i < markers.length; i++) { |
| 5824 var marker = markers[i], pos = marker.find() |
| 5825 var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to) |
| 5826 if (cmp(mFrom, mTo)) { |
| 5827 var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.typ
e) |
| 5828 marker.markers.push(subMark) |
| 5829 subMark.parent = marker |
| 5830 } |
| 5831 } |
| 5832 } |
| 5833 |
| 5834 function detachSharedMarkers(markers) { |
| 5835 var loop = function ( i ) { |
| 5836 var marker = markers[i], linked = [marker.primary.doc] |
| 5837 linkedDocs(marker.primary.doc, function (d) { return linked.push(d); }) |
| 5838 for (var j = 0; j < marker.markers.length; j++) { |
| 5839 var subMarker = marker.markers[j] |
| 5840 if (indexOf(linked, subMarker.doc) == -1) { |
| 5841 subMarker.parent = null |
| 5842 marker.markers.splice(j--, 1) |
| 5843 } |
| 5844 } |
| 5845 }; |
| 5846 |
| 5847 for (var i = 0; i < markers.length; i++) loop( i ); |
| 5848 } |
| 5849 |
| 5850 var nextDocId = 0 |
| 5851 var Doc = function(text, mode, firstLine, lineSep, direction) { |
| 5852 if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, d
irection) } |
| 5853 if (firstLine == null) { firstLine = 0 } |
| 5854 |
| 5855 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]) |
| 5856 this.first = firstLine |
| 5857 this.scrollTop = this.scrollLeft = 0 |
| 5858 this.cantEdit = false |
| 5859 this.cleanGeneration = 1 |
| 5860 this.frontier = firstLine |
| 5861 var start = Pos(firstLine, 0) |
| 5862 this.sel = simpleSelection(start) |
| 5863 this.history = new History(null) |
| 5864 this.id = ++nextDocId |
| 5865 this.modeOption = mode |
| 5866 this.lineSep = lineSep |
| 5867 this.direction = (direction == "rtl") ? "rtl" : "ltr" |
| 5868 this.extend = false |
| 5869 |
| 5870 if (typeof text == "string") { text = this.splitLines(text) } |
| 5871 updateDoc(this, {from: start, to: start, text: text}) |
| 5872 setSelection(this, simpleSelection(start), sel_dontScroll) |
| 5873 } |
| 5874 |
| 5875 Doc.prototype = createObj(BranchChunk.prototype, { |
| 5876 constructor: Doc, |
| 5877 // Iterate over the document. Supports two forms -- with only one |
| 5878 // argument, it calls that for each line in the document. With |
| 5879 // three, it iterates over the range given by the first two (with |
| 5880 // the second being non-inclusive). |
| 5881 iter: function(from, to, op) { |
| 5882 if (op) { this.iterN(from - this.first, to - from, op) } |
| 5883 else { this.iterN(this.first, this.first + this.size, from) } |
| 5884 }, |
| 5885 |
| 5886 // Non-public interface for adding and removing lines. |
| 5887 insert: function(at, lines) { |
| 5888 var height = 0 |
| 5889 for (var i = 0; i < lines.length; ++i) { height += lines[i].height } |
| 5890 this.insertInner(at - this.first, lines, height) |
| 5891 }, |
| 5892 remove: function(at, n) { this.removeInner(at - this.first, n) }, |
| 5893 |
| 5894 // From here, the methods are part of the public interface. Most |
| 5895 // are also available from CodeMirror (editor) instances. |
| 5896 |
| 5897 getValue: function(lineSep) { |
| 5898 var lines = getLines(this, this.first, this.first + this.size) |
| 5899 if (lineSep === false) { return lines } |
| 5900 return lines.join(lineSep || this.lineSeparator()) |
| 5901 }, |
| 5902 setValue: docMethodOp(function(code) { |
| 5903 var top = Pos(this.first, 0), last = this.first + this.size - 1 |
| 5904 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), |
| 5905 text: this.splitLines(code), origin: "setValue", full: tru
e}, true) |
| 5906 setSelection(this, simpleSelection(top)) |
| 5907 }), |
| 5908 replaceRange: function(code, from, to, origin) { |
| 5909 from = clipPos(this, from) |
| 5910 to = to ? clipPos(this, to) : from |
| 5911 replaceRange(this, code, from, to, origin) |
| 5912 }, |
| 5913 getRange: function(from, to, lineSep) { |
| 5914 var lines = getBetween(this, clipPos(this, from), clipPos(this, to)) |
| 5915 if (lineSep === false) { return lines } |
| 5916 return lines.join(lineSep || this.lineSeparator()) |
| 5917 }, |
| 5918 |
| 5919 getLine: function(line) {var l = this.getLineHandle(line); return l && l.text}
, |
| 5920 |
| 5921 getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this,
line) }}, |
| 5922 getLineNumber: function(line) {return lineNo(line)}, |
| 5923 |
| 5924 getLineHandleVisualStart: function(line) { |
| 5925 if (typeof line == "number") { line = getLine(this, line) } |
| 5926 return visualLine(line) |
| 5927 }, |
| 5928 |
| 5929 lineCount: function() {return this.size}, |
| 5930 firstLine: function() {return this.first}, |
| 5931 lastLine: function() {return this.first + this.size - 1}, |
| 5932 |
| 5933 clipPos: function(pos) {return clipPos(this, pos)}, |
| 5934 |
| 5935 getCursor: function(start) { |
| 5936 var range = this.sel.primary(), pos |
| 5937 if (start == null || start == "head") { pos = range.head } |
| 5938 else if (start == "anchor") { pos = range.anchor } |
| 5939 else if (start == "end" || start == "to" || start === false) { pos = range.t
o() } |
| 5940 else { pos = range.from() } |
| 5941 return pos |
| 5942 }, |
| 5943 listSelections: function() { return this.sel.ranges }, |
| 5944 somethingSelected: function() {return this.sel.somethingSelected()}, |
| 5945 |
| 5946 setCursor: docMethodOp(function(line, ch, options) { |
| 5947 setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, c
h || 0) : line), null, options) |
| 5948 }), |
| 5949 setSelection: docMethodOp(function(anchor, head, options) { |
| 5950 setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor
), options) |
| 5951 }), |
| 5952 extendSelection: docMethodOp(function(head, other, options) { |
| 5953 extendSelection(this, clipPos(this, head), other && clipPos(this, other), op
tions) |
| 5954 }), |
| 5955 extendSelections: docMethodOp(function(heads, options) { |
| 5956 extendSelections(this, clipPosArray(this, heads), options) |
| 5957 }), |
| 5958 extendSelectionsBy: docMethodOp(function(f, options) { |
| 5959 var heads = map(this.sel.ranges, f) |
| 5960 extendSelections(this, clipPosArray(this, heads), options) |
| 5961 }), |
| 5962 setSelections: docMethodOp(function(ranges, primary, options) { |
| 5963 var this$1 = this; |
| 5964 |
| 5965 if (!ranges.length) { return } |
| 5966 var out = [] |
| 5967 for (var i = 0; i < ranges.length; i++) |
| 5968 { out[i] = new Range(clipPos(this$1, ranges[i].anchor), |
| 5969 clipPos(this$1, ranges[i].head)) } |
| 5970 if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIn
dex) } |
| 5971 setSelection(this, normalizeSelection(out, primary), options) |
| 5972 }), |
| 5973 addSelection: docMethodOp(function(anchor, head, options) { |
| 5974 var ranges = this.sel.ranges.slice(0) |
| 5975 ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))) |
| 5976 setSelection(this, normalizeSelection(ranges, ranges.length - 1), options) |
| 5977 }), |
| 5978 |
| 5979 getSelection: function(lineSep) { |
| 5980 var this$1 = this; |
| 5981 |
| 5982 var ranges = this.sel.ranges, lines |
| 5983 for (var i = 0; i < ranges.length; i++) { |
| 5984 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) |
| 5985 lines = lines ? lines.concat(sel) : sel |
| 5986 } |
| 5987 if (lineSep === false) { return lines } |
| 5988 else { return lines.join(lineSep || this.lineSeparator()) } |
| 5989 }, |
| 5990 getSelections: function(lineSep) { |
| 5991 var this$1 = this; |
| 5992 |
| 5993 var parts = [], ranges = this.sel.ranges |
| 5994 for (var i = 0; i < ranges.length; i++) { |
| 5995 var sel = getBetween(this$1, ranges[i].from(), ranges[i].to()) |
| 5996 if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator())
} |
| 5997 parts[i] = sel |
| 5998 } |
| 5999 return parts |
| 6000 }, |
| 6001 replaceSelection: function(code, collapse, origin) { |
| 6002 var dup = [] |
| 6003 for (var i = 0; i < this.sel.ranges.length; i++) |
| 6004 { dup[i] = code } |
| 6005 this.replaceSelections(dup, collapse, origin || "+input") |
| 6006 }, |
| 6007 replaceSelections: docMethodOp(function(code, collapse, origin) { |
| 6008 var this$1 = this; |
| 6009 |
| 6010 var changes = [], sel = this.sel |
| 6011 for (var i = 0; i < sel.ranges.length; i++) { |
| 6012 var range = sel.ranges[i] |
| 6013 changes[i] = {from: range.from(), to: range.to(), text: this$1.splitLines(
code[i]), origin: origin} |
| 6014 } |
| 6015 var newSel = collapse && collapse != "end" && computeReplacedSel(this, chang
es, collapse) |
| 6016 for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) |
| 6017 { makeChange(this$1, changes[i$1]) } |
| 6018 if (newSel) { setSelectionReplaceHistory(this, newSel) } |
| 6019 else if (this.cm) { ensureCursorVisible(this.cm) } |
| 6020 }), |
| 6021 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}), |
| 6022 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}), |
| 6023 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", tru
e)}), |
| 6024 redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", tru
e)}), |
| 6025 |
| 6026 setExtending: function(val) {this.extend = val}, |
| 6027 getExtending: function() {return this.extend}, |
| 6028 |
| 6029 historySize: function() { |
| 6030 var hist = this.history, done = 0, undone = 0 |
| 6031 for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++d
one } } |
| 6032 for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].r
anges) { ++undone } } |
| 6033 return {undo: done, redo: undone} |
| 6034 }, |
| 6035 clearHistory: function() {this.history = new History(this.history.maxGeneratio
n)}, |
| 6036 |
| 6037 markClean: function() { |
| 6038 this.cleanGeneration = this.changeGeneration(true) |
| 6039 }, |
| 6040 changeGeneration: function(forceSplit) { |
| 6041 if (forceSplit) |
| 6042 { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin =
null } |
| 6043 return this.history.generation |
| 6044 }, |
| 6045 isClean: function (gen) { |
| 6046 return this.history.generation == (gen || this.cleanGeneration) |
| 6047 }, |
| 6048 |
| 6049 getHistory: function() { |
| 6050 return {done: copyHistoryArray(this.history.done), |
| 6051 undone: copyHistoryArray(this.history.undone)} |
| 6052 }, |
| 6053 setHistory: function(histData) { |
| 6054 var hist = this.history = new History(this.history.maxGeneration) |
| 6055 hist.done = copyHistoryArray(histData.done.slice(0), null, true) |
| 6056 hist.undone = copyHistoryArray(histData.undone.slice(0), null, true) |
| 6057 }, |
| 6058 |
| 6059 setGutterMarker: docMethodOp(function(line, gutterID, value) { |
| 6060 return changeLine(this, line, "gutter", function (line) { |
| 6061 var markers = line.gutterMarkers || (line.gutterMarkers = {}) |
| 6062 markers[gutterID] = value |
| 6063 if (!value && isEmpty(markers)) { line.gutterMarkers = null } |
| 6064 return true |
| 6065 }) |
| 6066 }), |
| 6067 |
| 6068 clearGutter: docMethodOp(function(gutterID) { |
| 6069 var this$1 = this; |
| 6070 |
| 6071 this.iter(function (line) { |
| 6072 if (line.gutterMarkers && line.gutterMarkers[gutterID]) { |
| 6073 changeLine(this$1, line, "gutter", function () { |
| 6074 line.gutterMarkers[gutterID] = null |
| 6075 if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null } |
| 6076 return true |
| 6077 }) |
| 6078 } |
| 6079 }) |
| 6080 }), |
| 6081 |
| 6082 lineInfo: function(line) { |
| 6083 var n |
| 6084 if (typeof line == "number") { |
| 6085 if (!isLine(this, line)) { return null } |
| 6086 n = line |
| 6087 line = getLine(this, line) |
| 6088 if (!line) { return null } |
| 6089 } else { |
| 6090 n = lineNo(line) |
| 6091 if (n == null) { return null } |
| 6092 } |
| 6093 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMa
rkers, |
| 6094 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wr
apClass, |
| 6095 widgets: line.widgets} |
| 6096 }, |
| 6097 |
| 6098 addLineClass: docMethodOp(function(handle, where, cls) { |
| 6099 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", func
tion (line) { |
| 6100 var prop = where == "text" ? "textClass" |
| 6101 : where == "background" ? "bgClass" |
| 6102 : where == "gutter" ? "gutterClass" : "wrapClass" |
| 6103 if (!line[prop]) { line[prop] = cls } |
| 6104 else if (classTest(cls).test(line[prop])) { return false } |
| 6105 else { line[prop] += " " + cls } |
| 6106 return true |
| 6107 }) |
| 6108 }), |
| 6109 removeLineClass: docMethodOp(function(handle, where, cls) { |
| 6110 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", func
tion (line) { |
| 6111 var prop = where == "text" ? "textClass" |
| 6112 : where == "background" ? "bgClass" |
| 6113 : where == "gutter" ? "gutterClass" : "wrapClass" |
| 6114 var cur = line[prop] |
| 6115 if (!cur) { return false } |
| 6116 else if (cls == null) { line[prop] = null } |
| 6117 else { |
| 6118 var found = cur.match(classTest(cls)) |
| 6119 if (!found) { return false } |
| 6120 var end = found.index + found[0].length |
| 6121 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.len
gth ? "" : " ") + cur.slice(end) || null |
| 6122 } |
| 6123 return true |
| 6124 }) |
| 6125 }), |
| 6126 |
| 6127 addLineWidget: docMethodOp(function(handle, node, options) { |
| 6128 return addLineWidget(this, handle, node, options) |
| 6129 }), |
| 6130 removeLineWidget: function(widget) { widget.clear() }, |
| 6131 |
| 6132 markText: function(from, to, options) { |
| 6133 return markText(this, clipPos(this, from), clipPos(this, to), options, optio
ns && options.type || "range") |
| 6134 }, |
| 6135 setBookmark: function(pos, options) { |
| 6136 var realOpts = {replacedWith: options && (options.nodeType == null ? options
.widget : options), |
| 6137 insertLeft: options && options.insertLeft, |
| 6138 clearWhenEmpty: false, shared: options && options.shared, |
| 6139 handleMouseEvents: options && options.handleMouseEvents} |
| 6140 pos = clipPos(this, pos) |
| 6141 return markText(this, pos, pos, realOpts, "bookmark") |
| 6142 }, |
| 6143 findMarksAt: function(pos) { |
| 6144 pos = clipPos(this, pos) |
| 6145 var markers = [], spans = getLine(this, pos.line).markedSpans |
| 6146 if (spans) { for (var i = 0; i < spans.length; ++i) { |
| 6147 var span = spans[i] |
| 6148 if ((span.from == null || span.from <= pos.ch) && |
| 6149 (span.to == null || span.to >= pos.ch)) |
| 6150 { markers.push(span.marker.parent || span.marker) } |
| 6151 } } |
| 6152 return markers |
| 6153 }, |
| 6154 findMarks: function(from, to, filter) { |
| 6155 from = clipPos(this, from); to = clipPos(this, to) |
| 6156 var found = [], lineNo = from.line |
| 6157 this.iter(from.line, to.line + 1, function (line) { |
| 6158 var spans = line.markedSpans |
| 6159 if (spans) { for (var i = 0; i < spans.length; i++) { |
| 6160 var span = spans[i] |
| 6161 if (!(span.to != null && lineNo == from.line && from.ch >= span.to || |
| 6162 span.from == null && lineNo != from.line || |
| 6163 span.from != null && lineNo == to.line && span.from >= to.ch) && |
| 6164 (!filter || filter(span.marker))) |
| 6165 { found.push(span.marker.parent || span.marker) } |
| 6166 } } |
| 6167 ++lineNo |
| 6168 }) |
| 6169 return found |
| 6170 }, |
| 6171 getAllMarks: function() { |
| 6172 var markers = [] |
| 6173 this.iter(function (line) { |
| 6174 var sps = line.markedSpans |
| 6175 if (sps) { for (var i = 0; i < sps.length; ++i) |
| 6176 { if (sps[i].from != null) { markers.push(sps[i].marker) } } } |
| 6177 }) |
| 6178 return markers |
| 6179 }, |
| 6180 |
| 6181 posFromIndex: function(off) { |
| 6182 var ch, lineNo = this.first, sepSize = this.lineSeparator().length |
| 6183 this.iter(function (line) { |
| 6184 var sz = line.text.length + sepSize |
| 6185 if (sz > off) { ch = off; return true } |
| 6186 off -= sz |
| 6187 ++lineNo |
| 6188 }) |
| 6189 return clipPos(this, Pos(lineNo, ch)) |
| 6190 }, |
| 6191 indexFromPos: function (coords) { |
| 6192 coords = clipPos(this, coords) |
| 6193 var index = coords.ch |
| 6194 if (coords.line < this.first || coords.ch < 0) { return 0 } |
| 6195 var sepSize = this.lineSeparator().length |
| 6196 this.iter(this.first, coords.line, function (line) { // iter aborts when cal
lback returns a truthy value |
| 6197 index += line.text.length + sepSize |
| 6198 }) |
| 6199 return index |
| 6200 }, |
| 6201 |
| 6202 copy: function(copyHistory) { |
| 6203 var doc = new Doc(getLines(this, this.first, this.first + this.size), |
| 6204 this.modeOption, this.first, this.lineSep, this.direction) |
| 6205 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft |
| 6206 doc.sel = this.sel |
| 6207 doc.extend = false |
| 6208 if (copyHistory) { |
| 6209 doc.history.undoDepth = this.history.undoDepth |
| 6210 doc.setHistory(this.getHistory()) |
| 6211 } |
| 6212 return doc |
| 6213 }, |
| 6214 |
| 6215 linkedDoc: function(options) { |
| 6216 if (!options) { options = {} } |
| 6217 var from = this.first, to = this.first + this.size |
| 6218 if (options.from != null && options.from > from) { from = options.from } |
| 6219 if (options.to != null && options.to < to) { to = options.to } |
| 6220 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption
, from, this.lineSep, this.direction) |
| 6221 if (options.sharedHist) { copy.history = this.history |
| 6222 ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.
sharedHist}) |
| 6223 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}] |
| 6224 copySharedMarkers(copy, findSharedMarkers(this)) |
| 6225 return copy |
| 6226 }, |
| 6227 unlinkDoc: function(other) { |
| 6228 var this$1 = this; |
| 6229 |
| 6230 if (other instanceof CodeMirror) { other = other.doc } |
| 6231 if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { |
| 6232 var link = this$1.linked[i] |
| 6233 if (link.doc != other) { continue } |
| 6234 this$1.linked.splice(i, 1) |
| 6235 other.unlinkDoc(this$1) |
| 6236 detachSharedMarkers(findSharedMarkers(this$1)) |
| 6237 break |
| 6238 } } |
| 6239 // If the histories were shared, split them again |
| 6240 if (other.history == this.history) { |
| 6241 var splitIds = [other.id] |
| 6242 linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true) |
| 6243 other.history = new History(null) |
| 6244 other.history.done = copyHistoryArray(this.history.done, splitIds) |
| 6245 other.history.undone = copyHistoryArray(this.history.undone, splitIds) |
| 6246 } |
| 6247 }, |
| 6248 iterLinkedDocs: function(f) {linkedDocs(this, f)}, |
| 6249 |
| 6250 getMode: function() {return this.mode}, |
| 6251 getEditor: function() {return this.cm}, |
| 6252 |
| 6253 splitLines: function(str) { |
| 6254 if (this.lineSep) { return str.split(this.lineSep) } |
| 6255 return splitLinesAuto(str) |
| 6256 }, |
| 6257 lineSeparator: function() { return this.lineSep || "\n" }, |
| 6258 |
| 6259 setDirection: docMethodOp(function (dir) { |
| 6260 if (dir != "rtl") { dir = "ltr" } |
| 6261 if (dir == this.direction) { return } |
| 6262 this.direction = dir |
| 6263 this.iter(function (line) { return line.order = null; }) |
| 6264 if (this.cm) { directionChanged(this.cm) } |
| 6265 }) |
| 6266 }) |
| 6267 |
| 6268 // Public alias. |
| 6269 Doc.prototype.eachLine = Doc.prototype.iter |
| 6270 |
| 6271 // Kludge to work around strange IE behavior where it'll sometimes |
| 6272 // re-fire a series of drag-related events right after the drop (#1551) |
| 6273 var lastDrop = 0 |
| 6274 |
| 6275 function onDrop(e) { |
| 6276 var cm = this |
| 6277 clearDragCursor(cm) |
| 6278 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) |
| 6279 { return } |
| 6280 e_preventDefault(e) |
| 6281 if (ie) { lastDrop = +new Date } |
| 6282 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files |
| 6283 if (!pos || cm.isReadOnly()) { return } |
| 6284 // Might be a file drop, in which case we simply extract the text |
| 6285 // and insert it. |
| 6286 if (files && files.length && window.FileReader && window.File) { |
| 6287 var n = files.length, text = Array(n), read = 0 |
| 6288 var loadFile = function (file, i) { |
| 6289 if (cm.options.allowDropFileTypes && |
| 6290 indexOf(cm.options.allowDropFileTypes, file.type) == -1) |
| 6291 { return } |
| 6292 |
| 6293 var reader = new FileReader |
| 6294 reader.onload = operation(cm, function () { |
| 6295 var content = reader.result |
| 6296 if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { content = "" } |
| 6297 text[i] = content |
| 6298 if (++read == n) { |
| 6299 pos = clipPos(cm.doc, pos) |
| 6300 var change = {from: pos, to: pos, |
| 6301 text: cm.doc.splitLines(text.join(cm.doc.lineSeparator()
)), |
| 6302 origin: "paste"} |
| 6303 makeChange(cm.doc, change) |
| 6304 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(chan
ge))) |
| 6305 } |
| 6306 }) |
| 6307 reader.readAsText(file) |
| 6308 } |
| 6309 for (var i = 0; i < n; ++i) { loadFile(files[i], i) } |
| 6310 } else { // Normal drop |
| 6311 // Don't do a replace if the drop happened inside of the selected text. |
| 6312 if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { |
| 6313 cm.state.draggingText(e) |
| 6314 // Ensure the editor is re-focused |
| 6315 setTimeout(function () { return cm.display.input.focus(); }, 20) |
| 6316 return |
| 6317 } |
| 6318 try { |
| 6319 var text$1 = e.dataTransfer.getData("Text") |
| 6320 if (text$1) { |
| 6321 var selected |
| 6322 if (cm.state.draggingText && !cm.state.draggingText.copy) |
| 6323 { selected = cm.listSelections() } |
| 6324 setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)) |
| 6325 if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1) |
| 6326 { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "
drag") } } |
| 6327 cm.replaceSelection(text$1, "around", "paste") |
| 6328 cm.display.input.focus() |
| 6329 } |
| 6330 } |
| 6331 catch(e){} |
| 6332 } |
| 6333 } |
| 6334 |
| 6335 function onDragStart(cm, e) { |
| 6336 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e);
return } |
| 6337 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } |
| 6338 |
| 6339 e.dataTransfer.setData("Text", cm.getSelection()) |
| 6340 e.dataTransfer.effectAllowed = "copyMove" |
| 6341 |
| 6342 // Use dummy image instead of default browsers image. |
| 6343 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we
don't do it there. |
| 6344 if (e.dataTransfer.setDragImage && !safari) { |
| 6345 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;") |
| 6346 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAI
CTAEAOw==" |
| 6347 if (presto) { |
| 6348 img.width = img.height = 1 |
| 6349 cm.display.wrapper.appendChild(img) |
| 6350 // Force a relayout, or Opera won't use our image for some obscure reason |
| 6351 img._top = img.offsetTop |
| 6352 } |
| 6353 e.dataTransfer.setDragImage(img, 0, 0) |
| 6354 if (presto) { img.parentNode.removeChild(img) } |
| 6355 } |
| 6356 } |
| 6357 |
| 6358 function onDragOver(cm, e) { |
| 6359 var pos = posFromMouse(cm, e) |
| 6360 if (!pos) { return } |
| 6361 var frag = document.createDocumentFragment() |
| 6362 drawSelectionCursor(cm, pos, frag) |
| 6363 if (!cm.display.dragCursor) { |
| 6364 cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-drag
cursors") |
| 6365 cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDi
v) |
| 6366 } |
| 6367 removeChildrenAndAdd(cm.display.dragCursor, frag) |
| 6368 } |
| 6369 |
| 6370 function clearDragCursor(cm) { |
| 6371 if (cm.display.dragCursor) { |
| 6372 cm.display.lineSpace.removeChild(cm.display.dragCursor) |
| 6373 cm.display.dragCursor = null |
| 6374 } |
| 6375 } |
| 6376 |
| 6377 // These must be handled carefully, because naively registering a |
| 6378 // handler for each editor will cause the editors to never be |
| 6379 // garbage collected. |
| 6380 |
| 6381 function forEachCodeMirror(f) { |
| 6382 if (!document.body.getElementsByClassName) { return } |
| 6383 var byClass = document.body.getElementsByClassName("CodeMirror") |
| 6384 for (var i = 0; i < byClass.length; i++) { |
| 6385 var cm = byClass[i].CodeMirror |
| 6386 if (cm) { f(cm) } |
| 6387 } |
| 6388 } |
| 6389 |
| 6390 var globalsRegistered = false |
| 6391 function ensureGlobalHandlers() { |
| 6392 if (globalsRegistered) { return } |
| 6393 registerGlobalHandlers() |
| 6394 globalsRegistered = true |
| 6395 } |
| 6396 function registerGlobalHandlers() { |
| 6397 // When the window resizes, we need to refresh active editors. |
| 6398 var resizeTimer |
| 6399 on(window, "resize", function () { |
| 6400 if (resizeTimer == null) { resizeTimer = setTimeout(function () { |
| 6401 resizeTimer = null |
| 6402 forEachCodeMirror(onResize) |
| 6403 }, 100) } |
| 6404 }) |
| 6405 // When the window loses focus, we want to show the editor as blurred |
| 6406 on(window, "blur", function () { return forEachCodeMirror(onBlur); }) |
| 6407 } |
| 6408 // Called when the window resizes |
| 6409 function onResize(cm) { |
| 6410 var d = cm.display |
| 6411 if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper
.clientWidth) |
| 6412 { return } |
| 6413 // Might be a text scaling operation, clear size caches. |
| 6414 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null |
| 6415 d.scrollbarsClipped = false |
| 6416 cm.setSize() |
| 6417 } |
| 6418 |
| 6419 var keyNames = { |
| 6420 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18
: "Alt", |
| 6421 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDo
wn", 35: "End", |
| 6422 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45
: "Insert", |
| 6423 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", |
| 6424 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", |
| 6425 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`"
, 219: "[", 220: "\\", |
| 6426 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right",
63272: "Delete", |
| 6427 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Inser
t" |
| 6428 } |
| 6429 |
| 6430 // Number keys |
| 6431 for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i) } |
| 6432 // Alphabetic keys |
| 6433 for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1)
} |
| 6434 // Function keys |
| 6435 for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235
] = "F" + i$2 } |
| 6436 |
| 6437 var keyMap = {} |
| 6438 |
| 6439 keyMap.basic = { |
| 6440 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLin
eDown", |
| 6441 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDow
n": "goPageDown", |
| 6442 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "de
lCharBefore", |
| 6443 "Tab": "defaultTab", "Shift-Tab": "indentAuto", |
| 6444 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", |
| 6445 "Esc": "singleSelection" |
| 6446 } |
| 6447 // Note that the save and find-related commands aren't defined by |
| 6448 // default. User code or addons can define them. Unknown commands |
| 6449 // are simply ignored. |
| 6450 keyMap.pcDefault = { |
| 6451 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z
": "redo", "Ctrl-Y": "redo", |
| 6452 "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctr
l-Down": "goLineDown", |
| 6453 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineS
tart", "Alt-Right": "goLineEnd", |
| 6454 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S":
"save", "Ctrl-F": "find", |
| 6455 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "
Shift-Ctrl-R": "replaceAll", |
| 6456 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", |
| 6457 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSele
ction", |
| 6458 fallthrough: "basic" |
| 6459 } |
| 6460 // Very basic readline/emacs-style bindings, which are standard on Mac. |
| 6461 keyMap.emacsy = { |
| 6462 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N
": "goLineDown", |
| 6463 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-
E": "goLineEnd", |
| 6464 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter",
"Ctrl-H": "delCharBefore", |
| 6465 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine
", "Ctrl-T": "transposeChars", |
| 6466 "Ctrl-O": "openLine" |
| 6467 } |
| 6468 keyMap.macDefault = { |
| 6469 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "
redo", "Cmd-Y": "redo", |
| 6470 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-
Down": "goDocEnd", "Alt-Left": "goGroupLeft", |
| 6471 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRig
ht", "Alt-Backspace": "delGroupBefore", |
| 6472 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S":
"save", "Cmd-F": "find", |
| 6473 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift
-Cmd-Alt-F": "replaceAll", |
| 6474 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLine
Left", "Cmd-Delete": "delWrappedLineRight", |
| 6475 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocSta
rt", "Ctrl-Down": "goDocEnd", |
| 6476 fallthrough: ["basic", "emacsy"] |
| 6477 } |
| 6478 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault |
| 6479 |
| 6480 // KEYMAP DISPATCH |
| 6481 |
| 6482 function normalizeKeyName(name) { |
| 6483 var parts = name.split(/-(?!$)/) |
| 6484 name = parts[parts.length - 1] |
| 6485 var alt, ctrl, shift, cmd |
| 6486 for (var i = 0; i < parts.length - 1; i++) { |
| 6487 var mod = parts[i] |
| 6488 if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true } |
| 6489 else if (/^a(lt)?$/i.test(mod)) { alt = true } |
| 6490 else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true } |
| 6491 else if (/^s(hift)?$/i.test(mod)) { shift = true } |
| 6492 else { throw new Error("Unrecognized modifier name: " + mod) } |
| 6493 } |
| 6494 if (alt) { name = "Alt-" + name } |
| 6495 if (ctrl) { name = "Ctrl-" + name } |
| 6496 if (cmd) { name = "Cmd-" + name } |
| 6497 if (shift) { name = "Shift-" + name } |
| 6498 return name |
| 6499 } |
| 6500 |
| 6501 // This is a kludge to keep keymaps mostly working as raw objects |
| 6502 // (backwards compatibility) while at the same time support features |
| 6503 // like normalization and multi-stroke key bindings. It compiles a |
| 6504 // new normalized keymap, and then updates the old object to reflect |
| 6505 // this. |
| 6506 function normalizeKeyMap(keymap) { |
| 6507 var copy = {} |
| 6508 for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) { |
| 6509 var value = keymap[keyname] |
| 6510 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue } |
| 6511 if (value == "...") { delete keymap[keyname]; continue } |
| 6512 |
| 6513 var keys = map(keyname.split(" "), normalizeKeyName) |
| 6514 for (var i = 0; i < keys.length; i++) { |
| 6515 var val = (void 0), name = (void 0) |
| 6516 if (i == keys.length - 1) { |
| 6517 name = keys.join(" ") |
| 6518 val = value |
| 1797 } else { | 6519 } else { |
| 1798 var fromLine = lineNo(display.view[fromIndex].line); | 6520 name = keys.slice(0, i + 1).join(" ") |
| 1799 var fromNode = display.view[fromIndex - 1].node.nextSibling; | 6521 val = "..." |
| 1800 } | 6522 } |
| 1801 var toIndex = findViewIndex(cm, to.line); | 6523 var prev = copy[name] |
| 1802 if (toIndex == display.view.length - 1) { | 6524 if (!prev) { copy[name] = val } |
| 1803 var toLine = display.viewTo - 1; | 6525 else if (prev != val) { throw new Error("Inconsistent bindings for " + nam
e) } |
| 1804 var toNode = display.lineDiv.lastChild; | 6526 } |
| 1805 } else { | 6527 delete keymap[keyname] |
| 1806 var toLine = lineNo(display.view[toIndex + 1].line) - 1; | 6528 } } |
| 1807 var toNode = display.view[toIndex + 1].node.previousSibling; | 6529 for (var prop in copy) { keymap[prop] = copy[prop] } |
| 6530 return keymap |
| 6531 } |
| 6532 |
| 6533 function lookupKey(key, map, handle, context) { |
| 6534 map = getKeyMap(map) |
| 6535 var found = map.call ? map.call(key, context) : map[key] |
| 6536 if (found === false) { return "nothing" } |
| 6537 if (found === "...") { return "multi" } |
| 6538 if (found != null && handle(found)) { return "handled" } |
| 6539 |
| 6540 if (map.fallthrough) { |
| 6541 if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") |
| 6542 { return lookupKey(key, map.fallthrough, handle, context) } |
| 6543 for (var i = 0; i < map.fallthrough.length; i++) { |
| 6544 var result = lookupKey(key, map.fallthrough[i], handle, context) |
| 6545 if (result) { return result } |
| 6546 } |
| 6547 } |
| 6548 } |
| 6549 |
| 6550 // Modifier key presses don't count as 'real' key presses for the |
| 6551 // purpose of keymap fallthrough. |
| 6552 function isModifierKey(value) { |
| 6553 var name = typeof value == "string" ? value : keyNames[value.keyCode] |
| 6554 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" |
| 6555 } |
| 6556 |
| 6557 // Look up the name of a key as indicated by an event object. |
| 6558 function keyName(event, noShift) { |
| 6559 if (presto && event.keyCode == 34 && event["char"]) { return false } |
| 6560 var base = keyNames[event.keyCode], name = base |
| 6561 if (name == null || event.altGraphKey) { return false } |
| 6562 if (event.altKey && base != "Alt") { name = "Alt-" + name } |
| 6563 if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name =
"Ctrl-" + name } |
| 6564 if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "
Cmd-" + name } |
| 6565 if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name } |
| 6566 return name |
| 6567 } |
| 6568 |
| 6569 function getKeyMap(val) { |
| 6570 return typeof val == "string" ? keyMap[val] : val |
| 6571 } |
| 6572 |
| 6573 // Helper for deleting text near the selection(s), used to implement |
| 6574 // backspace, delete, and similar functionality. |
| 6575 function deleteNearSelection(cm, compute) { |
| 6576 var ranges = cm.doc.sel.ranges, kill = [] |
| 6577 // Build up a set of ranges to kill first, merging overlapping |
| 6578 // ranges. |
| 6579 for (var i = 0; i < ranges.length; i++) { |
| 6580 var toKill = compute(ranges[i]) |
| 6581 while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { |
| 6582 var replaced = kill.pop() |
| 6583 if (cmp(replaced.from, toKill.from) < 0) { |
| 6584 toKill.from = replaced.from |
| 6585 break |
| 1808 } | 6586 } |
| 1809 | 6587 } |
| 1810 var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromL
ine, toLine)); | 6588 kill.push(toKill) |
| 1811 var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.
doc, toLine).text.length)); | 6589 } |
| 1812 while (newText.length > 1 && oldText.length > 1) { | 6590 // Next, remove those actual ranges. |
| 1813 if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine
--; } | 6591 runInOp(cm, function () { |
| 1814 else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); f
romLine++; } | 6592 for (var i = kill.length - 1; i >= 0; i--) |
| 1815 else break; | 6593 { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete") } |
| 1816 } | 6594 ensureCursorVisible(cm) |
| 1817 | 6595 }) |
| 1818 var cutFront = 0, cutEnd = 0; | 6596 } |
| 1819 var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTo
p.length, oldTop.length); | 6597 |
| 1820 while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.cha
rCodeAt(cutFront)) | 6598 // Commands are parameter-less actions that can be performed on an |
| 1821 ++cutFront; | 6599 // editor, mostly used for keybindings. |
| 1822 var newBot = lst(newText), oldBot = lst(oldText); | 6600 var commands = { |
| 1823 var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront :
0), | 6601 selectAll: selectAll, |
| 1824 oldBot.length - (oldText.length == 1 ? cutFront :
0)); | 6602 singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor")
, cm.getCursor("head"), sel_dontScroll); }, |
| 1825 while (cutEnd < maxCutEnd && | 6603 killLine: function (cm) { return deleteNearSelection(cm, function (range) { |
| 1826 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(
oldBot.length - cutEnd - 1)) | 6604 if (range.empty()) { |
| 1827 ++cutEnd; | 6605 var len = getLine(cm.doc, range.head.line).text.length |
| 1828 | 6606 if (range.head.ch == len && range.head.line < cm.lastLine()) |
| 1829 newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd); | 6607 { return {from: range.head, to: Pos(range.head.line + 1, 0)} } |
| 1830 newText[0] = newText[0].slice(cutFront); | 6608 else |
| 1831 | 6609 { return {from: range.head, to: Pos(range.head.line, len)} } |
| 1832 var chFrom = Pos(fromLine, cutFront); | |
| 1833 var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); | |
| 1834 if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { | |
| 1835 replaceRange(cm.doc, newText, chFrom, chTo, "+input"); | |
| 1836 return true; | |
| 1837 } | |
| 1838 }, | |
| 1839 | |
| 1840 ensurePolled: function() { | |
| 1841 this.forceCompositionEnd(); | |
| 1842 }, | |
| 1843 reset: function() { | |
| 1844 this.forceCompositionEnd(); | |
| 1845 }, | |
| 1846 forceCompositionEnd: function() { | |
| 1847 if (!this.composing || this.composing.handled) return; | |
| 1848 this.applyComposition(this.composing); | |
| 1849 this.composing.handled = true; | |
| 1850 this.div.blur(); | |
| 1851 this.div.focus(); | |
| 1852 }, | |
| 1853 applyComposition: function(composing) { | |
| 1854 if (this.cm.isReadOnly()) | |
| 1855 operation(this.cm, regChange)(this.cm) | |
| 1856 else if (composing.data && composing.data != composing.startData) | |
| 1857 operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing
.sel); | |
| 1858 }, | |
| 1859 | |
| 1860 setUneditable: function(node) { | |
| 1861 node.contentEditable = "false" | |
| 1862 }, | |
| 1863 | |
| 1864 onKeyPress: function(e) { | |
| 1865 e.preventDefault(); | |
| 1866 if (!this.cm.isReadOnly()) | |
| 1867 operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCo
de == null ? e.keyCode : e.charCode), 0); | |
| 1868 }, | |
| 1869 | |
| 1870 readOnlyChanged: function(val) { | |
| 1871 this.div.contentEditable = String(val != "nocursor") | |
| 1872 }, | |
| 1873 | |
| 1874 onContextMenu: nothing, | |
| 1875 resetPosition: nothing, | |
| 1876 | |
| 1877 needsContentAttribute: true | |
| 1878 }, ContentEditableInput.prototype); | |
| 1879 | |
| 1880 function posToDOM(cm, pos) { | |
| 1881 var view = findViewForLine(cm, pos.line); | |
| 1882 if (!view || view.hidden) return null; | |
| 1883 var line = getLine(cm.doc, pos.line); | |
| 1884 var info = mapFromLineView(view, line, pos.line); | |
| 1885 | |
| 1886 var order = getOrder(line), side = "left"; | |
| 1887 if (order) { | |
| 1888 var partPos = getBidiPartAt(order, pos.ch); | |
| 1889 side = partPos % 2 ? "right" : "left"; | |
| 1890 } | |
| 1891 var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); | |
| 1892 result.offset = result.collapse == "right" ? result.end : result.start; | |
| 1893 return result; | |
| 1894 } | |
| 1895 | |
| 1896 function badPos(pos, bad) { if (bad) pos.bad = true; return pos; } | |
| 1897 | |
| 1898 function domToPos(cm, node, offset) { | |
| 1899 var lineNode; | |
| 1900 if (node == cm.display.lineDiv) { | |
| 1901 lineNode = cm.display.lineDiv.childNodes[offset]; | |
| 1902 if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true)
; | |
| 1903 node = null; offset = 0; | |
| 1904 } else { | 6610 } else { |
| 1905 for (lineNode = node;; lineNode = lineNode.parentNode) { | 6611 return {from: range.from(), to: range.to()} |
| 1906 if (!lineNode || lineNode == cm.display.lineDiv) return null; | 6612 } |
| 1907 if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) br
eak; | 6613 }); }, |
| 1908 } | 6614 deleteLine: function (cm) { return deleteNearSelection(cm, function (range) {
return ({ |
| 1909 } | 6615 from: Pos(range.from().line, 0), |
| 1910 for (var i = 0; i < cm.display.view.length; i++) { | 6616 to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) |
| 1911 var lineView = cm.display.view[i]; | 6617 }); }); }, |
| 1912 if (lineView.node == lineNode) | 6618 delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) {
return ({ |
| 1913 return locateNodeInLineView(lineView, node, offset); | 6619 from: Pos(range.from().line, 0), to: range.from() |
| 1914 } | 6620 }); }); }, |
| 1915 } | 6621 delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (r
ange) { |
| 1916 | 6622 var top = cm.charCoords(range.head, "div").top + 5 |
| 1917 function locateNodeInLineView(lineView, node, offset) { | 6623 var leftPos = cm.coordsChar({left: 0, top: top}, "div") |
| 1918 var wrapper = lineView.text.firstChild, bad = false; | 6624 return {from: leftPos, to: range.from()} |
| 1919 if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.lin
e), 0), true); | 6625 }); }, |
| 1920 if (node == wrapper) { | 6626 delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (
range) { |
| 1921 bad = true; | 6627 var top = cm.charCoords(range.head, "div").top + 5 |
| 1922 node = wrapper.childNodes[offset]; | 6628 var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, to
p: top}, "div") |
| 1923 offset = 0; | 6629 return {from: range.from(), to: rightPos } |
| 1924 if (!node) { | 6630 }); }, |
| 1925 var line = lineView.rest ? lst(lineView.rest) : lineView.line; | 6631 undo: function (cm) { return cm.undo(); }, |
| 1926 return badPos(Pos(lineNo(line), line.text.length), bad); | 6632 redo: function (cm) { return cm.redo(); }, |
| 1927 } | 6633 undoSelection: function (cm) { return cm.undoSelection(); }, |
| 1928 } | 6634 redoSelection: function (cm) { return cm.redoSelection(); }, |
| 1929 | 6635 goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0));
}, |
| 1930 var textNode = node.nodeType == 3 ? node : null, topNode = node; | 6636 goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); }, |
| 1931 if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType ==
3) { | 6637 goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { r
eturn lineStart(cm, range.head.line); }, |
| 1932 textNode = node.firstChild; | 6638 {origin: "+move", bias: 1} |
| 1933 if (offset) offset = textNode.nodeValue.length; | 6639 ); }, |
| 1934 } | 6640 goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range
) { return lineStartSmart(cm, range.head); }, |
| 1935 while (topNode.parentNode != wrapper) topNode = topNode.parentNode; | 6641 {origin: "+move", bias: 1} |
| 1936 var measure = lineView.measure, maps = measure.maps; | 6642 ); }, |
| 1937 | 6643 goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { ret
urn lineEnd(cm, range.head.line); }, |
| 1938 function find(textNode, topNode, offset) { | 6644 {origin: "+move", bias: -1} |
| 1939 for (var i = -1; i < (maps ? maps.length : 0); i++) { | 6645 ); }, |
| 1940 var map = i < 0 ? measure.map : maps[i]; | 6646 goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { |
| 1941 for (var j = 0; j < map.length; j += 3) { | 6647 var top = cm.charCoords(range.head, "div").top + 5 |
| 1942 var curNode = map[j + 2]; | 6648 return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top},
"div") |
| 1943 if (curNode == textNode || curNode == topNode) { | 6649 }, sel_move); }, |
| 1944 var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); | 6650 goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { |
| 1945 var ch = map[j] + offset; | 6651 var top = cm.charCoords(range.head, "div").top + 5 |
| 1946 if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)
]; | 6652 return cm.coordsChar({left: 0, top: top}, "div") |
| 1947 return Pos(line, ch); | 6653 }, sel_move); }, |
| 6654 goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range)
{ |
| 6655 var top = cm.charCoords(range.head, "div").top + 5 |
| 6656 var pos = cm.coordsChar({left: 0, top: top}, "div") |
| 6657 if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm,
range.head) } |
| 6658 return pos |
| 6659 }, sel_move); }, |
| 6660 goLineUp: function (cm) { return cm.moveV(-1, "line"); }, |
| 6661 goLineDown: function (cm) { return cm.moveV(1, "line"); }, |
| 6662 goPageUp: function (cm) { return cm.moveV(-1, "page"); }, |
| 6663 goPageDown: function (cm) { return cm.moveV(1, "page"); }, |
| 6664 goCharLeft: function (cm) { return cm.moveH(-1, "char"); }, |
| 6665 goCharRight: function (cm) { return cm.moveH(1, "char"); }, |
| 6666 goColumnLeft: function (cm) { return cm.moveH(-1, "column"); }, |
| 6667 goColumnRight: function (cm) { return cm.moveH(1, "column"); }, |
| 6668 goWordLeft: function (cm) { return cm.moveH(-1, "word"); }, |
| 6669 goGroupRight: function (cm) { return cm.moveH(1, "group"); }, |
| 6670 goGroupLeft: function (cm) { return cm.moveH(-1, "group"); }, |
| 6671 goWordRight: function (cm) { return cm.moveH(1, "word"); }, |
| 6672 delCharBefore: function (cm) { return cm.deleteH(-1, "char"); }, |
| 6673 delCharAfter: function (cm) { return cm.deleteH(1, "char"); }, |
| 6674 delWordBefore: function (cm) { return cm.deleteH(-1, "word"); }, |
| 6675 delWordAfter: function (cm) { return cm.deleteH(1, "word"); }, |
| 6676 delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); }, |
| 6677 delGroupAfter: function (cm) { return cm.deleteH(1, "group"); }, |
| 6678 indentAuto: function (cm) { return cm.indentSelection("smart"); }, |
| 6679 indentMore: function (cm) { return cm.indentSelection("add"); }, |
| 6680 indentLess: function (cm) { return cm.indentSelection("subtract"); }, |
| 6681 insertTab: function (cm) { return cm.replaceSelection("\t"); }, |
| 6682 insertSoftTab: function (cm) { |
| 6683 var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize |
| 6684 for (var i = 0; i < ranges.length; i++) { |
| 6685 var pos = ranges[i].from() |
| 6686 var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize) |
| 6687 spaces.push(spaceStr(tabSize - col % tabSize)) |
| 6688 } |
| 6689 cm.replaceSelections(spaces) |
| 6690 }, |
| 6691 defaultTab: function (cm) { |
| 6692 if (cm.somethingSelected()) { cm.indentSelection("add") } |
| 6693 else { cm.execCommand("insertTab") } |
| 6694 }, |
| 6695 // Swap the two chars left and right of each selection's head. |
| 6696 // Move cursor behind the two swapped characters afterwards. |
| 6697 // |
| 6698 // Doesn't consider line feeds a character. |
| 6699 // Doesn't scan more than one line above to find a character. |
| 6700 // Doesn't do anything on an empty line. |
| 6701 // Doesn't do anything with non-empty selections. |
| 6702 transposeChars: function (cm) { return runInOp(cm, function () { |
| 6703 var ranges = cm.listSelections(), newSel = [] |
| 6704 for (var i = 0; i < ranges.length; i++) { |
| 6705 if (!ranges[i].empty()) { continue } |
| 6706 var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text |
| 6707 if (line) { |
| 6708 if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1) } |
| 6709 if (cur.ch > 0) { |
| 6710 cur = new Pos(cur.line, cur.ch + 1) |
| 6711 cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), |
| 6712 Pos(cur.line, cur.ch - 2), cur, "+transpose") |
| 6713 } else if (cur.line > cm.doc.first) { |
| 6714 var prev = getLine(cm.doc, cur.line - 1).text |
| 6715 if (prev) { |
| 6716 cur = new Pos(cur.line, 1) |
| 6717 cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + |
| 6718 prev.charAt(prev.length - 1), |
| 6719 Pos(cur.line - 1, prev.length - 1), cur, "+transpose
") |
| 1948 } | 6720 } |
| 1949 } | 6721 } |
| 1950 } | 6722 } |
| 1951 } | 6723 newSel.push(new Range(cur, cur)) |
| 1952 var found = find(textNode, topNode, offset); | 6724 } |
| 1953 if (found) return badPos(found, bad); | 6725 cm.setSelections(newSel) |
| 1954 | 6726 }); }, |
| 1955 // FIXME this is all really shaky. might handle the few cases it needs to ha
ndle, but likely to cause problems | 6727 newlineAndIndent: function (cm) { return runInOp(cm, function () { |
| 1956 for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.l
ength - offset : 0; after; after = after.nextSibling) { | 6728 var sels = cm.listSelections() |
| 1957 found = find(after, after.firstChild, 0); | 6729 for (var i = sels.length - 1; i >= 0; i--) |
| 1958 if (found) | 6730 { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+
input") } |
| 1959 return badPos(Pos(found.line, found.ch - dist), bad); | 6731 sels = cm.listSelections() |
| 6732 for (var i$1 = 0; i$1 < sels.length; i$1++) |
| 6733 { cm.indentLine(sels[i$1].from().line, null, true) } |
| 6734 ensureCursorVisible(cm) |
| 6735 }); }, |
| 6736 openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, |
| 6737 toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } |
| 6738 } |
| 6739 |
| 6740 |
| 6741 function lineStart(cm, lineN) { |
| 6742 var line = getLine(cm.doc, lineN) |
| 6743 var visual = visualLine(line) |
| 6744 if (visual != line) { lineN = lineNo(visual) } |
| 6745 return endOfLine(true, cm, visual, lineN, 1) |
| 6746 } |
| 6747 function lineEnd(cm, lineN) { |
| 6748 var line = getLine(cm.doc, lineN) |
| 6749 var visual = visualLineEnd(line) |
| 6750 if (visual != line) { lineN = lineNo(visual) } |
| 6751 return endOfLine(true, cm, line, lineN, -1) |
| 6752 } |
| 6753 function lineStartSmart(cm, pos) { |
| 6754 var start = lineStart(cm, pos.line) |
| 6755 var line = getLine(cm.doc, start.line) |
| 6756 var order = getOrder(line, cm.doc.direction) |
| 6757 if (!order || order[0].level == 0) { |
| 6758 var firstNonWS = Math.max(0, line.text.search(/\S/)) |
| 6759 var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch |
| 6760 return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) |
| 6761 } |
| 6762 return start |
| 6763 } |
| 6764 |
| 6765 // Run a handler that was bound to a key. |
| 6766 function doHandleBinding(cm, bound, dropShift) { |
| 6767 if (typeof bound == "string") { |
| 6768 bound = commands[bound] |
| 6769 if (!bound) { return false } |
| 6770 } |
| 6771 // Ensure previous input has been read, so that the handler sees a |
| 6772 // consistent view of the document |
| 6773 cm.display.input.ensurePolled() |
| 6774 var prevShift = cm.display.shift, done = false |
| 6775 try { |
| 6776 if (cm.isReadOnly()) { cm.state.suppressEdits = true } |
| 6777 if (dropShift) { cm.display.shift = false } |
| 6778 done = bound(cm) != Pass |
| 6779 } finally { |
| 6780 cm.display.shift = prevShift |
| 6781 cm.state.suppressEdits = false |
| 6782 } |
| 6783 return done |
| 6784 } |
| 6785 |
| 6786 function lookupKeyForEditor(cm, name, handle) { |
| 6787 for (var i = 0; i < cm.state.keyMaps.length; i++) { |
| 6788 var result = lookupKey(name, cm.state.keyMaps[i], handle, cm) |
| 6789 if (result) { return result } |
| 6790 } |
| 6791 return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle,
cm)) |
| 6792 || lookupKey(name, cm.options.keyMap, handle, cm) |
| 6793 } |
| 6794 |
| 6795 var stopSeq = new Delayed |
| 6796 function dispatchKey(cm, name, e, handle) { |
| 6797 var seq = cm.state.keySeq |
| 6798 if (seq) { |
| 6799 if (isModifierKey(name)) { return "handled" } |
| 6800 stopSeq.set(50, function () { |
| 6801 if (cm.state.keySeq == seq) { |
| 6802 cm.state.keySeq = null |
| 6803 cm.display.input.reset() |
| 6804 } |
| 6805 }) |
| 6806 name = seq + " " + name |
| 6807 } |
| 6808 var result = lookupKeyForEditor(cm, name, handle) |
| 6809 |
| 6810 if (result == "multi") |
| 6811 { cm.state.keySeq = name } |
| 6812 if (result == "handled") |
| 6813 { signalLater(cm, "keyHandled", cm, name, e) } |
| 6814 |
| 6815 if (result == "handled" || result == "multi") { |
| 6816 e_preventDefault(e) |
| 6817 restartBlink(cm) |
| 6818 } |
| 6819 |
| 6820 if (seq && !result && /\'$/.test(name)) { |
| 6821 e_preventDefault(e) |
| 6822 return true |
| 6823 } |
| 6824 return !!result |
| 6825 } |
| 6826 |
| 6827 // Handle a key from the keydown event. |
| 6828 function handleKeyBinding(cm, e) { |
| 6829 var name = keyName(e, true) |
| 6830 if (!name) { return false } |
| 6831 |
| 6832 if (e.shiftKey && !cm.state.keySeq) { |
| 6833 // First try to resolve full name (including 'Shift-'). Failing |
| 6834 // that, see if there is a cursor-motion command (starting with |
| 6835 // 'go') bound to the keyname without 'Shift-'. |
| 6836 return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBin
ding(cm, b, true); }) |
| 6837 || dispatchKey(cm, name, e, function (b) { |
| 6838 if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) |
| 6839 { return doHandleBinding(cm, b) } |
| 6840 }) |
| 6841 } else { |
| 6842 return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b)
; }) |
| 6843 } |
| 6844 } |
| 6845 |
| 6846 // Handle a key from the keypress event |
| 6847 function handleCharBinding(cm, e, ch) { |
| 6848 return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBindin
g(cm, b, true); }) |
| 6849 } |
| 6850 |
| 6851 var lastStoppedKey = null |
| 6852 function onKeyDown(e) { |
| 6853 var cm = this |
| 6854 cm.curOp.focus = activeElt() |
| 6855 if (signalDOMEvent(cm, e)) { return } |
| 6856 // IE does strange things with escape. |
| 6857 if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false } |
| 6858 var code = e.keyCode |
| 6859 cm.display.shift = code == 16 || e.shiftKey |
| 6860 var handled = handleKeyBinding(cm, e) |
| 6861 if (presto) { |
| 6862 lastStoppedKey = handled ? code : null |
| 6863 // Opera has no cut event... we try to at least catch the key combo |
| 6864 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)
) |
| 6865 { cm.replaceSelection("", null, "cut") } |
| 6866 } |
| 6867 |
| 6868 // Turn mouse into crosshair when Alt is held on Mac. |
| 6869 if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.classNam
e)) |
| 6870 { showCrossHair(cm) } |
| 6871 } |
| 6872 |
| 6873 function showCrossHair(cm) { |
| 6874 var lineDiv = cm.display.lineDiv |
| 6875 addClass(lineDiv, "CodeMirror-crosshair") |
| 6876 |
| 6877 function up(e) { |
| 6878 if (e.keyCode == 18 || !e.altKey) { |
| 6879 rmClass(lineDiv, "CodeMirror-crosshair") |
| 6880 off(document, "keyup", up) |
| 6881 off(document, "mouseover", up) |
| 6882 } |
| 6883 } |
| 6884 on(document, "keyup", up) |
| 6885 on(document, "mouseover", up) |
| 6886 } |
| 6887 |
| 6888 function onKeyUp(e) { |
| 6889 if (e.keyCode == 16) { this.doc.sel.shift = false } |
| 6890 signalDOMEvent(this, e) |
| 6891 } |
| 6892 |
| 6893 function onKeyPress(e) { |
| 6894 var cm = this |
| 6895 if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.a
ltKey || mac && e.metaKey) { return } |
| 6896 var keyCode = e.keyCode, charCode = e.charCode |
| 6897 if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefa
ult(e); return} |
| 6898 if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { retur
n } |
| 6899 var ch = String.fromCharCode(charCode == null ? keyCode : charCode) |
| 6900 // Some browsers fire keypress events for backspace |
| 6901 if (ch == "\x08") { return } |
| 6902 if (handleCharBinding(cm, e, ch)) { return } |
| 6903 cm.display.input.onKeyPress(e) |
| 6904 } |
| 6905 |
| 6906 // A mouse down can be a single click, double click, triple click, |
| 6907 // start of selection drag, start of text drag, new cursor |
| 6908 // (ctrl-click), rectangle drag (alt-drag), or xwin |
| 6909 // middle-click-paste. Or it might be a click on something we should |
| 6910 // not interfere with, such as a scrollbar or widget. |
| 6911 function onMouseDown(e) { |
| 6912 var cm = this, display = cm.display |
| 6913 if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouc
h()) { return } |
| 6914 display.input.ensurePolled() |
| 6915 display.shift = e.shiftKey |
| 6916 |
| 6917 if (eventInWidget(display, e)) { |
| 6918 if (!webkit) { |
| 6919 // Briefly turn off draggability, to allow widgets to do |
| 6920 // normal dragging things. |
| 6921 display.scroller.draggable = false |
| 6922 setTimeout(function () { return display.scroller.draggable = true; }, 100) |
| 6923 } |
| 6924 return |
| 6925 } |
| 6926 if (clickInGutter(cm, e)) { return } |
| 6927 var start = posFromMouse(cm, e) |
| 6928 window.focus() |
| 6929 |
| 6930 switch (e_button(e)) { |
| 6931 case 1: |
| 6932 // #3261: make sure, that we're not starting a second selection |
| 6933 if (cm.state.selectingText) |
| 6934 { cm.state.selectingText(e) } |
| 6935 else if (start) |
| 6936 { leftButtonDown(cm, e, start) } |
| 6937 else if (e_target(e) == display.scroller) |
| 6938 { e_preventDefault(e) } |
| 6939 break |
| 6940 case 2: |
| 6941 if (webkit) { cm.state.lastMiddleDown = +new Date } |
| 6942 if (start) { extendSelection(cm.doc, start) } |
| 6943 setTimeout(function () { return display.input.focus(); }, 20) |
| 6944 e_preventDefault(e) |
| 6945 break |
| 6946 case 3: |
| 6947 if (captureRightClick) { onContextMenu(cm, e) } |
| 6948 else { delayBlurEvent(cm) } |
| 6949 break |
| 6950 } |
| 6951 } |
| 6952 |
| 6953 var lastClick; |
| 6954 var lastDoubleClick; |
| 6955 function leftButtonDown(cm, e, start) { |
| 6956 if (ie) { setTimeout(bind(ensureFocus, cm), 0) } |
| 6957 else { cm.curOp.focus = activeElt() } |
| 6958 |
| 6959 var now = +new Date, type |
| 6960 if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick
.pos, start) == 0) { |
| 6961 type = "triple" |
| 6962 } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start
) == 0) { |
| 6963 type = "double" |
| 6964 lastDoubleClick = {time: now, pos: start} |
| 6965 } else { |
| 6966 type = "single" |
| 6967 lastClick = {time: now, pos: start} |
| 6968 } |
| 6969 |
| 6970 var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained |
| 6971 if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && |
| 6972 type == "single" && (contained = sel.contains(start)) > -1 && |
| 6973 (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel
> 0) && |
| 6974 (cmp(contained.to(), start) > 0 || start.xRel < 0)) |
| 6975 { leftButtonStartDrag(cm, e, start, modifier) } |
| 6976 else |
| 6977 { leftButtonSelect(cm, e, start, type, modifier) } |
| 6978 } |
| 6979 |
| 6980 // Start a text drag. When it ends, see if any dragging actually |
| 6981 // happen, and treat as a click if it didn't. |
| 6982 function leftButtonStartDrag(cm, e, start, modifier) { |
| 6983 var display = cm.display, startTime = +new Date |
| 6984 var dragEnd = operation(cm, function (e2) { |
| 6985 if (webkit) { display.scroller.draggable = false } |
| 6986 cm.state.draggingText = false |
| 6987 off(document, "mouseup", dragEnd) |
| 6988 off(display.scroller, "drop", dragEnd) |
| 6989 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10
) { |
| 6990 e_preventDefault(e2) |
| 6991 if (!modifier && +new Date - 200 < startTime) |
| 6992 { extendSelection(cm.doc, start) } |
| 6993 // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#308
1) |
| 6994 if (webkit || ie && ie_version == 9) |
| 6995 { setTimeout(function () {document.body.focus(); display.input.focus()},
20) } |
| 1960 else | 6996 else |
| 1961 dist += after.textContent.length; | 6997 { display.input.focus() } |
| 1962 } | 6998 } |
| 1963 for (var before = topNode.previousSibling, dist = offset; before; before = b
efore.previousSibling) { | 6999 }) |
| 1964 found = find(before, before.firstChild, -1); | 7000 // Let the drag handler handle this. |
| 1965 if (found) | 7001 if (webkit) { display.scroller.draggable = true } |
| 1966 return badPos(Pos(found.line, found.ch + dist), bad); | 7002 cm.state.draggingText = dragEnd |
| 1967 else | 7003 dragEnd.copy = mac ? e.altKey : e.ctrlKey |
| 1968 dist += after.textContent.length; | 7004 // IE's approach to draggable |
| 1969 } | 7005 if (display.scroller.dragDrop) { display.scroller.dragDrop() } |
| 1970 } | 7006 on(document, "mouseup", dragEnd) |
| 1971 | 7007 on(display.scroller, "drop", dragEnd) |
| 1972 function domTextBetween(cm, from, to, fromLine, toLine) { | 7008 } |
| 1973 var text = "", closing = false, lineSep = cm.doc.lineSeparator(); | 7009 |
| 1974 function recognizeMarker(id) { return function(marker) { return marker.id ==
id; }; } | 7010 // Normal selection, as opposed to text dragging. |
| 1975 function walk(node) { | 7011 function leftButtonSelect(cm, e, start, type, addNew) { |
| 1976 if (node.nodeType == 1) { | 7012 var display = cm.display, doc = cm.doc |
| 1977 var cmText = node.getAttribute("cm-text"); | 7013 e_preventDefault(e) |
| 1978 if (cmText != null) { | 7014 |
| 1979 if (cmText == "") cmText = node.textContent.replace(/\u200b/g, ""); | 7015 var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges |
| 1980 text += cmText; | 7016 if (addNew && !e.shiftKey) { |
| 1981 return; | 7017 ourIndex = doc.sel.contains(start) |
| 1982 } | 7018 if (ourIndex > -1) |
| 1983 var markerID = node.getAttribute("cm-marker"), range; | 7019 { ourRange = ranges[ourIndex] } |
| 1984 if (markerID) { | 7020 else |
| 1985 var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recogni
zeMarker(+markerID)); | 7021 { ourRange = new Range(start, start) } |
| 1986 if (found.length && (range = found[0].find())) | 7022 } else { |
| 1987 text += getBetween(cm.doc, range.from, range.to).join(lineSep); | 7023 ourRange = doc.sel.primary() |
| 1988 return; | 7024 ourIndex = doc.sel.primIndex |
| 1989 } | 7025 } |
| 1990 if (node.getAttribute("contenteditable") == "false") return; | 7026 |
| 1991 for (var i = 0; i < node.childNodes.length; i++) | 7027 if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) { |
| 1992 walk(node.childNodes[i]); | 7028 type = "rect" |
| 1993 if (/^(pre|div|p)$/i.test(node.nodeName)) | 7029 if (!addNew) { ourRange = new Range(start, start) } |
| 1994 closing = true; | 7030 start = posFromMouse(cm, e, true, true) |
| 1995 } else if (node.nodeType == 3) { | 7031 ourIndex = -1 |
| 1996 var val = node.nodeValue; | 7032 } else if (type == "double") { |
| 1997 if (!val) return; | 7033 var word = cm.findWordAt(start) |
| 1998 if (closing) { | 7034 if (cm.display.shift || doc.extend) |
| 1999 text += lineSep; | 7035 { ourRange = extendRange(doc, ourRange, word.anchor, word.head) } |
| 2000 closing = false; | 7036 else |
| 2001 } | 7037 { ourRange = word } |
| 2002 text += val; | 7038 } else if (type == "triple") { |
| 7039 var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0)
)) |
| 7040 if (cm.display.shift || doc.extend) |
| 7041 { ourRange = extendRange(doc, ourRange, line.anchor, line.head) } |
| 7042 else |
| 7043 { ourRange = line } |
| 7044 } else { |
| 7045 ourRange = extendRange(doc, ourRange, start) |
| 7046 } |
| 7047 |
| 7048 if (!addNew) { |
| 7049 ourIndex = 0 |
| 7050 setSelection(doc, new Selection([ourRange], 0), sel_mouse) |
| 7051 startSel = doc.sel |
| 7052 } else if (ourIndex == -1) { |
| 7053 ourIndex = ranges.length |
| 7054 setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), |
| 7055 {scroll: false, origin: "*mouse"}) |
| 7056 } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" &
& !e.shiftKey) { |
| 7057 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges
.slice(ourIndex + 1)), 0), |
| 7058 {scroll: false, origin: "*mouse"}) |
| 7059 startSel = doc.sel |
| 7060 } else { |
| 7061 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse) |
| 7062 } |
| 7063 |
| 7064 var lastPos = start |
| 7065 function extendTo(pos) { |
| 7066 if (cmp(lastPos, pos) == 0) { return } |
| 7067 lastPos = pos |
| 7068 |
| 7069 if (type == "rect") { |
| 7070 var ranges = [], tabSize = cm.options.tabSize |
| 7071 var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSiz
e) |
| 7072 var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize) |
| 7073 var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol) |
| 7074 for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine
(), Math.max(start.line, pos.line)); |
| 7075 line <= end; line++) { |
| 7076 var text = getLine(doc, line).text, leftPos = findColumn(text, left, tab
Size) |
| 7077 if (left == right) |
| 7078 { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))) } |
| 7079 else if (text.length > leftPos) |
| 7080 { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text,
right, tabSize)))) } |
| 2003 } | 7081 } |
| 2004 } | 7082 if (!ranges.length) { ranges.push(new Range(start, start)) } |
| 2005 for (;;) { | 7083 setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).co
ncat(ranges), ourIndex), |
| 2006 walk(from); | 7084 {origin: "*mouse", scroll: false}) |
| 2007 if (from == to) break; | 7085 cm.scrollIntoView(pos) |
| 2008 from = from.nextSibling; | 7086 } else { |
| 2009 } | 7087 var oldRange = ourRange |
| 2010 return text; | 7088 var anchor = oldRange.anchor, head = pos |
| 2011 } | 7089 if (type != "single") { |
| 2012 | 7090 var range |
| 2013 CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": Conten
tEditableInput}; | 7091 if (type == "double") |
| 2014 | 7092 { range = cm.findWordAt(pos) } |
| 2015 // SELECTION / CURSOR | 7093 else |
| 2016 | 7094 { range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0
))) } |
| 2017 // Selection objects are immutable. A new one is created every time | 7095 if (cmp(range.anchor, anchor) > 0) { |
| 2018 // the selection changes. A selection is one or more non-overlapping | 7096 head = range.head |
| 2019 // (and non-touching) ranges, sorted, and an integer that indicates | 7097 anchor = minPos(oldRange.from(), range.anchor) |
| 2020 // which one is the primary selection (the one that's scrolled into | 7098 } else { |
| 2021 // view, that getCursor returns, etc). | 7099 head = range.anchor |
| 2022 function Selection(ranges, primIndex) { | 7100 anchor = maxPos(oldRange.to(), range.head) |
| 2023 this.ranges = ranges; | |
| 2024 this.primIndex = primIndex; | |
| 2025 } | |
| 2026 | |
| 2027 Selection.prototype = { | |
| 2028 primary: function() { return this.ranges[this.primIndex]; }, | |
| 2029 equals: function(other) { | |
| 2030 if (other == this) return true; | |
| 2031 if (other.primIndex != this.primIndex || other.ranges.length != this.range
s.length) return false; | |
| 2032 for (var i = 0; i < this.ranges.length; i++) { | |
| 2033 var here = this.ranges[i], there = other.ranges[i]; | |
| 2034 if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) !=
0) return false; | |
| 2035 } | |
| 2036 return true; | |
| 2037 }, | |
| 2038 deepCopy: function() { | |
| 2039 for (var out = [], i = 0; i < this.ranges.length; i++) | |
| 2040 out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i
].head)); | |
| 2041 return new Selection(out, this.primIndex); | |
| 2042 }, | |
| 2043 somethingSelected: function() { | |
| 2044 for (var i = 0; i < this.ranges.length; i++) | |
| 2045 if (!this.ranges[i].empty()) return true; | |
| 2046 return false; | |
| 2047 }, | |
| 2048 contains: function(pos, end) { | |
| 2049 if (!end) end = pos; | |
| 2050 for (var i = 0; i < this.ranges.length; i++) { | |
| 2051 var range = this.ranges[i]; | |
| 2052 if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) | |
| 2053 return i; | |
| 2054 } | |
| 2055 return -1; | |
| 2056 } | |
| 2057 }; | |
| 2058 | |
| 2059 function Range(anchor, head) { | |
| 2060 this.anchor = anchor; this.head = head; | |
| 2061 } | |
| 2062 | |
| 2063 Range.prototype = { | |
| 2064 from: function() { return minPos(this.anchor, this.head); }, | |
| 2065 to: function() { return maxPos(this.anchor, this.head); }, | |
| 2066 empty: function() { | |
| 2067 return this.head.line == this.anchor.line && this.head.ch == this.anchor.c
h; | |
| 2068 } | |
| 2069 }; | |
| 2070 | |
| 2071 // Take an unsorted, potentially overlapping set of ranges, and | |
| 2072 // build a selection out of it. 'Consumes' ranges array (modifying | |
| 2073 // it). | |
| 2074 function normalizeSelection(ranges, primIndex) { | |
| 2075 var prim = ranges[primIndex]; | |
| 2076 ranges.sort(function(a, b) { return cmp(a.from(), b.from()); }); | |
| 2077 primIndex = indexOf(ranges, prim); | |
| 2078 for (var i = 1; i < ranges.length; i++) { | |
| 2079 var cur = ranges[i], prev = ranges[i - 1]; | |
| 2080 if (cmp(prev.to(), cur.from()) >= 0) { | |
| 2081 var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.t
o()); | |
| 2082 var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.he
ad; | |
| 2083 if (i <= primIndex) --primIndex; | |
| 2084 ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); | |
| 2085 } | |
| 2086 } | |
| 2087 return new Selection(ranges, primIndex); | |
| 2088 } | |
| 2089 | |
| 2090 function simpleSelection(anchor, head) { | |
| 2091 return new Selection([new Range(anchor, head || anchor)], 0); | |
| 2092 } | |
| 2093 | |
| 2094 // Most of the external API clips given positions to make sure they | |
| 2095 // actually exist within the document. | |
| 2096 function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first +
doc.size - 1));} | |
| 2097 function clipPos(doc, pos) { | |
| 2098 if (pos.line < doc.first) return Pos(doc.first, 0); | |
| 2099 var last = doc.first + doc.size - 1; | |
| 2100 if (pos.line > last) return Pos(last, getLine(doc, last).text.length); | |
| 2101 return clipToLen(pos, getLine(doc, pos.line).text.length); | |
| 2102 } | |
| 2103 function clipToLen(pos, linelen) { | |
| 2104 var ch = pos.ch; | |
| 2105 if (ch == null || ch > linelen) return Pos(pos.line, linelen); | |
| 2106 else if (ch < 0) return Pos(pos.line, 0); | |
| 2107 else return pos; | |
| 2108 } | |
| 2109 function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} | |
| 2110 function clipPosArray(doc, array) { | |
| 2111 for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array
[i]); | |
| 2112 return out; | |
| 2113 } | |
| 2114 | |
| 2115 // SELECTION UPDATES | |
| 2116 | |
| 2117 // The 'scroll' parameter given to many of these indicated whether | |
| 2118 // the new cursor position should be scrolled into view after | |
| 2119 // modifying the selection. | |
| 2120 | |
| 2121 // If shift is held or the extend flag is set, extends a range to | |
| 2122 // include a given position (and optionally a second position). | |
| 2123 // Otherwise, simply returns the range between the given positions. | |
| 2124 // Used for cursor motion and such. | |
| 2125 function extendRange(doc, range, head, other) { | |
| 2126 if (doc.cm && doc.cm.display.shift || doc.extend) { | |
| 2127 var anchor = range.anchor; | |
| 2128 if (other) { | |
| 2129 var posBefore = cmp(head, anchor) < 0; | |
| 2130 if (posBefore != (cmp(other, anchor) < 0)) { | |
| 2131 anchor = head; | |
| 2132 head = other; | |
| 2133 } else if (posBefore != (cmp(head, other) < 0)) { | |
| 2134 head = other; | |
| 2135 } | 7101 } |
| 2136 } | 7102 } |
| 2137 return new Range(anchor, head); | 7103 var ranges$1 = startSel.ranges.slice(0) |
| 7104 ranges$1[ourIndex] = new Range(clipPos(doc, anchor), head) |
| 7105 setSelection(doc, normalizeSelection(ranges$1, ourIndex), sel_mouse) |
| 7106 } |
| 7107 } |
| 7108 |
| 7109 var editorSize = display.wrapper.getBoundingClientRect() |
| 7110 // Used to ensure timeout re-tries don't fire when another extend |
| 7111 // happened in the meantime (clearTimeout isn't reliable -- at |
| 7112 // least on Chrome, the timeouts still happen even when cleared, |
| 7113 // if the clear happens after their scheduled firing time). |
| 7114 var counter = 0 |
| 7115 |
| 7116 function extend(e) { |
| 7117 var curCount = ++counter |
| 7118 var cur = posFromMouse(cm, e, true, type == "rect") |
| 7119 if (!cur) { return } |
| 7120 if (cmp(cur, lastPos) != 0) { |
| 7121 cm.curOp.focus = activeElt() |
| 7122 extendTo(cur) |
| 7123 var visible = visibleLines(display, doc) |
| 7124 if (cur.line >= visible.to || cur.line < visible.from) |
| 7125 { setTimeout(operation(cm, function () {if (counter == curCount) { exten
d(e) }}), 150) } |
| 2138 } else { | 7126 } else { |
| 2139 return new Range(other || head, head); | 7127 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bo
ttom ? 20 : 0 |
| 2140 } | 7128 if (outside) { setTimeout(operation(cm, function () { |
| 2141 } | 7129 if (counter != curCount) { return } |
| 2142 | 7130 display.scroller.scrollTop += outside |
| 2143 // Extend the primary selection range, discard the rest. | 7131 extend(e) |
| 2144 function extendSelection(doc, head, other, options) { | 7132 }), 50) } |
| 2145 setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, o
ther)], 0), options); | 7133 } |
| 2146 } | 7134 } |
| 2147 | 7135 |
| 2148 // Extend all selections (pos is an array of selections with length | 7136 function done(e) { |
| 2149 // equal the number of selections) | 7137 cm.state.selectingText = false |
| 2150 function extendSelections(doc, heads, options) { | 7138 counter = Infinity |
| 2151 for (var out = [], i = 0; i < doc.sel.ranges.length; i++) | 7139 e_preventDefault(e) |
| 2152 out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null); | 7140 display.input.focus() |
| 2153 var newSel = normalizeSelection(out, doc.sel.primIndex); | 7141 off(document, "mousemove", move) |
| 2154 setSelection(doc, newSel, options); | 7142 off(document, "mouseup", up) |
| 2155 } | 7143 doc.history.lastSelOrigin = null |
| 2156 | 7144 } |
| 2157 // Updates a single range in the selection. | 7145 |
| 2158 function replaceOneSelection(doc, i, range, options) { | 7146 var move = operation(cm, function (e) { |
| 2159 var ranges = doc.sel.ranges.slice(0); | 7147 if (!e_button(e)) { done(e) } |
| 2160 ranges[i] = range; | 7148 else { extend(e) } |
| 2161 setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); | 7149 }) |
| 2162 } | 7150 var up = operation(cm, done) |
| 2163 | 7151 cm.state.selectingText = up |
| 2164 // Reset the selection to a single range. | 7152 on(document, "mousemove", move) |
| 2165 function setSimpleSelection(doc, anchor, head, options) { | 7153 on(document, "mouseup", up) |
| 2166 setSelection(doc, simpleSelection(anchor, head), options); | 7154 } |
| 2167 } | 7155 |
| 2168 | 7156 |
| 2169 // Give beforeSelectionChange handlers a change to influence a | 7157 // Determines whether an event happened in the gutter, and fires the |
| 2170 // selection update. | 7158 // handlers for the corresponding event. |
| 2171 function filterSelectionChange(doc, sel, options) { | 7159 function gutterEvent(cm, e, type, prevent) { |
| 2172 var obj = { | 7160 var mX, mY |
| 2173 ranges: sel.ranges, | 7161 try { mX = e.clientX; mY = e.clientY } |
| 2174 update: function(ranges) { | 7162 catch(e) { return false } |
| 2175 this.ranges = []; | 7163 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { retu
rn false } |
| 2176 for (var i = 0; i < ranges.length; i++) | 7164 if (prevent) { e_preventDefault(e) } |
| 2177 this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), | 7165 |
| 2178 clipPos(doc, ranges[i].head)); | 7166 var display = cm.display |
| 2179 }, | 7167 var lineBox = display.lineDiv.getBoundingClientRect() |
| 2180 origin: options && options.origin | 7168 |
| 2181 }; | 7169 if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(
e) } |
| 2182 signal(doc, "beforeSelectionChange", doc, obj); | 7170 mY -= lineBox.top - display.viewOffset |
| 2183 if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); | 7171 |
| 2184 if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.rang
es.length - 1); | 7172 for (var i = 0; i < cm.options.gutters.length; ++i) { |
| 2185 else return sel; | 7173 var g = display.gutters.childNodes[i] |
| 2186 } | 7174 if (g && g.getBoundingClientRect().right >= mX) { |
| 2187 | 7175 var line = lineAtHeight(cm.doc, mY) |
| 2188 function setSelectionReplaceHistory(doc, sel, options) { | 7176 var gutter = cm.options.gutters[i] |
| 2189 var done = doc.history.done, last = lst(done); | 7177 signal(cm, type, cm, line, gutter, e) |
| 2190 if (last && last.ranges) { | 7178 return e_defaultPrevented(e) |
| 2191 done[done.length - 1] = sel; | 7179 } |
| 2192 setSelectionNoUndo(doc, sel, options); | 7180 } |
| 7181 } |
| 7182 |
| 7183 function clickInGutter(cm, e) { |
| 7184 return gutterEvent(cm, e, "gutterClick", true) |
| 7185 } |
| 7186 |
| 7187 // CONTEXT MENU HANDLING |
| 7188 |
| 7189 // To make the context menu work, we need to briefly unhide the |
| 7190 // textarea (making it as unobtrusive as possible) to let the |
| 7191 // right-click take effect on it. |
| 7192 function onContextMenu(cm, e) { |
| 7193 if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } |
| 7194 if (signalDOMEvent(cm, e, "contextmenu")) { return } |
| 7195 cm.display.input.onContextMenu(e) |
| 7196 } |
| 7197 |
| 7198 function contextMenuInGutter(cm, e) { |
| 7199 if (!hasHandler(cm, "gutterContextMenu")) { return false } |
| 7200 return gutterEvent(cm, e, "gutterContextMenu", false) |
| 7201 } |
| 7202 |
| 7203 function themeChanged(cm) { |
| 7204 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\
S+/g, "") + |
| 7205 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-") |
| 7206 clearCaches(cm) |
| 7207 } |
| 7208 |
| 7209 var Init = {toString: function(){return "CodeMirror.Init"}} |
| 7210 |
| 7211 var defaults = {} |
| 7212 var optionHandlers = {} |
| 7213 |
| 7214 function defineOptions(CodeMirror) { |
| 7215 var optionHandlers = CodeMirror.optionHandlers |
| 7216 |
| 7217 function option(name, deflt, handle, notOnInit) { |
| 7218 CodeMirror.defaults[name] = deflt |
| 7219 if (handle) { optionHandlers[name] = |
| 7220 notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, ol
d) }} : handle } |
| 7221 } |
| 7222 |
| 7223 CodeMirror.defineOption = option |
| 7224 |
| 7225 // Passed to option handlers when there is no old value. |
| 7226 CodeMirror.Init = Init |
| 7227 |
| 7228 // These two are, on init, called from the constructor because they |
| 7229 // have to be initialized before the editor can start at all. |
| 7230 option("value", "", function (cm, val) { return cm.setValue(val); }, true) |
| 7231 option("mode", null, function (cm, val) { |
| 7232 cm.doc.modeOption = val |
| 7233 loadMode(cm) |
| 7234 }, true) |
| 7235 |
| 7236 option("indentUnit", 2, loadMode, true) |
| 7237 option("indentWithTabs", false) |
| 7238 option("smartIndent", true) |
| 7239 option("tabSize", 4, function (cm) { |
| 7240 resetModeState(cm) |
| 7241 clearCaches(cm) |
| 7242 regChange(cm) |
| 7243 }, true) |
| 7244 option("lineSeparator", null, function (cm, val) { |
| 7245 cm.doc.lineSep = val |
| 7246 if (!val) { return } |
| 7247 var newBreaks = [], lineNo = cm.doc.first |
| 7248 cm.doc.iter(function (line) { |
| 7249 for (var pos = 0;;) { |
| 7250 var found = line.text.indexOf(val, pos) |
| 7251 if (found == -1) { break } |
| 7252 pos = found + val.length |
| 7253 newBreaks.push(Pos(lineNo, found)) |
| 7254 } |
| 7255 lineNo++ |
| 7256 }) |
| 7257 for (var i = newBreaks.length - 1; i >= 0; i--) |
| 7258 { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks
[i].ch + val.length)) } |
| 7259 }) |
| 7260 option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u
2028\u2029\ufeff]/g, function (cm, val, old) { |
| 7261 cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"
), "g") |
| 7262 if (old != Init) { cm.refresh() } |
| 7263 }) |
| 7264 option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm)
{ return cm.refresh(); }, true) |
| 7265 option("electricChars", true) |
| 7266 option("inputStyle", mobile ? "contenteditable" : "textarea", function () { |
| 7267 throw new Error("inputStyle can not (yet) be changed in a running editor") /
/ FIXME |
| 7268 }, true) |
| 7269 option("spellcheck", false, function (cm, val) { return cm.getInputField().spe
llcheck = val; }, true) |
| 7270 option("rtlMoveVisually", !windows) |
| 7271 option("wholeLineUpdateBefore", true) |
| 7272 |
| 7273 option("theme", "default", function (cm) { |
| 7274 themeChanged(cm) |
| 7275 guttersChanged(cm) |
| 7276 }, true) |
| 7277 option("keyMap", "default", function (cm, val, old) { |
| 7278 var next = getKeyMap(val) |
| 7279 var prev = old != Init && getKeyMap(old) |
| 7280 if (prev && prev.detach) { prev.detach(cm, next) } |
| 7281 if (next.attach) { next.attach(cm, prev || null) } |
| 7282 }) |
| 7283 option("extraKeys", null) |
| 7284 |
| 7285 option("lineWrapping", false, wrappingChanged, true) |
| 7286 option("gutters", [], function (cm) { |
| 7287 setGuttersForLineNumbers(cm.options) |
| 7288 guttersChanged(cm) |
| 7289 }, true) |
| 7290 option("fixedGutter", true, function (cm, val) { |
| 7291 cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px
" : "0" |
| 7292 cm.refresh() |
| 7293 }, true) |
| 7294 option("coverGutterNextToScrollbar", false, function (cm) { return updateScrol
lbars(cm); }, true) |
| 7295 option("scrollbarStyle", "native", function (cm) { |
| 7296 initScrollbars(cm) |
| 7297 updateScrollbars(cm) |
| 7298 cm.display.scrollbars.setScrollTop(cm.doc.scrollTop) |
| 7299 cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft) |
| 7300 }, true) |
| 7301 option("lineNumbers", false, function (cm) { |
| 7302 setGuttersForLineNumbers(cm.options) |
| 7303 guttersChanged(cm) |
| 7304 }, true) |
| 7305 option("firstLineNumber", 1, guttersChanged, true) |
| 7306 option("lineNumberFormatter", function (integer) { return integer; }, guttersC
hanged, true) |
| 7307 option("showCursorWhenSelecting", false, updateSelection, true) |
| 7308 |
| 7309 option("resetSelectionOnContextMenu", true) |
| 7310 option("lineWiseCopyCut", true) |
| 7311 |
| 7312 option("readOnly", false, function (cm, val) { |
| 7313 if (val == "nocursor") { |
| 7314 onBlur(cm) |
| 7315 cm.display.input.blur() |
| 7316 cm.display.disabled = true |
| 2193 } else { | 7317 } else { |
| 2194 setSelection(doc, sel, options); | 7318 cm.display.disabled = false |
| 2195 } | 7319 } |
| 2196 } | 7320 cm.display.input.readOnlyChanged(val) |
| 2197 | 7321 }) |
| 2198 // Set a new selection. | 7322 option("disableInput", false, function (cm, val) {if (!val) { cm.display.input
.reset() }}, true) |
| 2199 function setSelection(doc, sel, options) { | 7323 option("dragDrop", true, dragDropChanged) |
| 2200 setSelectionNoUndo(doc, sel, options); | 7324 option("allowDropFileTypes", null) |
| 2201 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options)
; | 7325 |
| 2202 } | 7326 option("cursorBlinkRate", 530) |
| 2203 | 7327 option("cursorScrollMargin", 0) |
| 2204 function setSelectionNoUndo(doc, sel, options) { | 7328 option("cursorHeight", 1, updateSelection, true) |
| 2205 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm,
"beforeSelectionChange")) | 7329 option("singleCursorHeightPerLine", true, updateSelection, true) |
| 2206 sel = filterSelectionChange(doc, sel, options); | 7330 option("workTime", 100) |
| 2207 | 7331 option("workDelay", 100) |
| 2208 var bias = options && options.bias || | 7332 option("flattenSpans", true, resetModeState, true) |
| 2209 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); | 7333 option("addModeClass", false, resetModeState, true) |
| 2210 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); | 7334 option("pollInterval", 100) |
| 2211 | 7335 option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth
= val; }) |
| 2212 if (!(options && options.scroll === false) && doc.cm) | 7336 option("historyEventDelay", 1250) |
| 2213 ensureCursorVisible(doc.cm); | 7337 option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true) |
| 2214 } | 7338 option("maxHighlightLength", 10000, resetModeState, true) |
| 2215 | 7339 option("moveInputWithCursor", true, function (cm, val) { |
| 2216 function setSelectionInner(doc, sel) { | 7340 if (!val) { cm.display.input.resetPosition() } |
| 2217 if (sel.equals(doc.sel)) return; | 7341 }) |
| 2218 | 7342 |
| 2219 doc.sel = sel; | 7343 option("tabindex", null, function (cm, val) { return cm.display.input.getField
().tabIndex = val || ""; }) |
| 2220 | 7344 option("autofocus", null) |
| 2221 if (doc.cm) { | 7345 option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val
); }, true) |
| 2222 doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; | 7346 } |
| 2223 signalCursorActivity(doc.cm); | 7347 |
| 2224 } | 7348 function guttersChanged(cm) { |
| 2225 signalLater(doc, "cursorActivity", doc); | 7349 updateGutters(cm) |
| 2226 } | 7350 regChange(cm) |
| 2227 | 7351 alignHorizontally(cm) |
| 2228 // Verify that the selection does not partially select any atomic | 7352 } |
| 2229 // marked ranges. | 7353 |
| 2230 function reCheckSelection(doc) { | 7354 function dragDropChanged(cm, value, old) { |
| 2231 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel
_dontScroll); | 7355 var wasOn = old && old != Init |
| 2232 } | 7356 if (!value != !wasOn) { |
| 2233 | 7357 var funcs = cm.display.dragFunctions |
| 2234 // Return a selection that does not partially select any atomic | 7358 var toggle = value ? on : off |
| 2235 // ranges. | 7359 toggle(cm.display.scroller, "dragstart", funcs.start) |
| 2236 function skipAtomicInSelection(doc, sel, bias, mayClear) { | 7360 toggle(cm.display.scroller, "dragenter", funcs.enter) |
| 2237 var out; | 7361 toggle(cm.display.scroller, "dragover", funcs.over) |
| 2238 for (var i = 0; i < sel.ranges.length; i++) { | 7362 toggle(cm.display.scroller, "dragleave", funcs.leave) |
| 2239 var range = sel.ranges[i]; | 7363 toggle(cm.display.scroller, "drop", funcs.drop) |
| 2240 var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; | 7364 } |
| 2241 var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, may
Clear); | 7365 } |
| 2242 var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear)
; | 7366 |
| 2243 if (out || newAnchor != range.anchor || newHead != range.head) { | 7367 function wrappingChanged(cm) { |
| 2244 if (!out) out = sel.ranges.slice(0, i); | 7368 if (cm.options.lineWrapping) { |
| 2245 out[i] = new Range(newAnchor, newHead); | 7369 addClass(cm.display.wrapper, "CodeMirror-wrap") |
| 7370 cm.display.sizer.style.minWidth = "" |
| 7371 cm.display.sizerWidth = null |
| 7372 } else { |
| 7373 rmClass(cm.display.wrapper, "CodeMirror-wrap") |
| 7374 findMaxLine(cm) |
| 7375 } |
| 7376 estimateLineHeights(cm) |
| 7377 regChange(cm) |
| 7378 clearCaches(cm) |
| 7379 setTimeout(function () { return updateScrollbars(cm); }, 100) |
| 7380 } |
| 7381 |
| 7382 // A CodeMirror instance represents an editor. This is the object |
| 7383 // that user code is usually dealing with. |
| 7384 |
| 7385 function CodeMirror(place, options) { |
| 7386 var this$1 = this; |
| 7387 |
| 7388 if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) } |
| 7389 |
| 7390 this.options = options = options ? copyObj(options) : {} |
| 7391 // Determine effective options based on given values and defaults. |
| 7392 copyObj(defaults, options, false) |
| 7393 setGuttersForLineNumbers(options) |
| 7394 |
| 7395 var doc = options.value |
| 7396 if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.l
ineSeparator, options.direction) } |
| 7397 this.doc = doc |
| 7398 |
| 7399 var input = new CodeMirror.inputStyles[options.inputStyle](this) |
| 7400 var display = this.display = new Display(place, doc, input) |
| 7401 display.wrapper.CodeMirror = this |
| 7402 updateGutters(this) |
| 7403 themeChanged(this) |
| 7404 if (options.lineWrapping) |
| 7405 { this.display.wrapper.className += " CodeMirror-wrap" } |
| 7406 initScrollbars(this) |
| 7407 |
| 7408 this.state = { |
| 7409 keyMaps: [], // stores maps added by addKeyMap |
| 7410 overlays: [], // highlighting overlays, as added by addOverlay |
| 7411 modeGen: 0, // bumped when mode/overlay changes, used to invalidate highli
ghting info |
| 7412 overwrite: false, |
| 7413 delayingBlurEvent: false, |
| 7414 focused: false, |
| 7415 suppressEdits: false, // used to disable editing during key handlers when in
readOnly mode |
| 7416 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits
in input.poll |
| 7417 selectingText: false, |
| 7418 draggingText: false, |
| 7419 highlight: new Delayed(), // stores highlight worker timeout |
| 7420 keySeq: null, // Unfinished key sequence |
| 7421 specialChars: null |
| 7422 } |
| 7423 |
| 7424 if (options.autofocus && !mobile) { display.input.focus() } |
| 7425 |
| 7426 // Override magic textarea content restore that IE sometimes does |
| 7427 // on our hidden textarea on reload |
| 7428 if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.in
put.reset(true); }, 20) } |
| 7429 |
| 7430 registerEventHandlers(this) |
| 7431 ensureGlobalHandlers() |
| 7432 |
| 7433 startOperation(this) |
| 7434 this.curOp.forceUpdate = true |
| 7435 attachDoc(this, doc) |
| 7436 |
| 7437 if ((options.autofocus && !mobile) || this.hasFocus()) |
| 7438 { setTimeout(bind(onFocus, this), 20) } |
| 7439 else |
| 7440 { onBlur(this) } |
| 7441 |
| 7442 for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) |
| 7443 { optionHandlers[opt](this$1, options[opt], Init) } } |
| 7444 maybeUpdateLineNumberWidth(this) |
| 7445 if (options.finishInit) { options.finishInit(this) } |
| 7446 for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1) } |
| 7447 endOperation(this) |
| 7448 // Suppress optimizelegibility in Webkit, since it breaks text |
| 7449 // measuring on line wrapping boundaries. |
| 7450 if (webkit && options.lineWrapping && |
| 7451 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") |
| 7452 { display.lineDiv.style.textRendering = "auto" } |
| 7453 } |
| 7454 |
| 7455 // The default configuration options. |
| 7456 CodeMirror.defaults = defaults |
| 7457 // Functions to run when options are changed. |
| 7458 CodeMirror.optionHandlers = optionHandlers |
| 7459 |
| 7460 // Attach the necessary event handlers when initializing the editor |
| 7461 function registerEventHandlers(cm) { |
| 7462 var d = cm.display |
| 7463 on(d.scroller, "mousedown", operation(cm, onMouseDown)) |
| 7464 // Older IE's will not fire a second mousedown for a double click |
| 7465 if (ie && ie_version < 11) |
| 7466 { on(d.scroller, "dblclick", operation(cm, function (e) { |
| 7467 if (signalDOMEvent(cm, e)) { return } |
| 7468 var pos = posFromMouse(cm, e) |
| 7469 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return
} |
| 7470 e_preventDefault(e) |
| 7471 var word = cm.findWordAt(pos) |
| 7472 extendSelection(cm.doc, word.anchor, word.head) |
| 7473 })) } |
| 7474 else |
| 7475 { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) ||
e_preventDefault(e); }) } |
| 7476 // Some browsers fire contextmenu *after* opening the menu, at |
| 7477 // which point we can't mess with it anymore. Context menu is |
| 7478 // handled in onMouseDown for these browsers. |
| 7479 if (!captureRightClick) { on(d.scroller, "contextmenu", function (e) { return
onContextMenu(cm, e); }) } |
| 7480 |
| 7481 // Used to suppress mouse event handling when a touch happens |
| 7482 var touchFinished, prevTouch = {end: 0} |
| 7483 function finishTouch() { |
| 7484 if (d.activeTouch) { |
| 7485 touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1
000) |
| 7486 prevTouch = d.activeTouch |
| 7487 prevTouch.end = +new Date |
| 7488 } |
| 7489 } |
| 7490 function isMouseLikeTouchEvent(e) { |
| 7491 if (e.touches.length != 1) { return false } |
| 7492 var touch = e.touches[0] |
| 7493 return touch.radiusX <= 1 && touch.radiusY <= 1 |
| 7494 } |
| 7495 function farAway(touch, other) { |
| 7496 if (other.left == null) { return true } |
| 7497 var dx = other.left - touch.left, dy = other.top - touch.top |
| 7498 return dx * dx + dy * dy > 20 * 20 |
| 7499 } |
| 7500 on(d.scroller, "touchstart", function (e) { |
| 7501 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { |
| 7502 d.input.ensurePolled() |
| 7503 clearTimeout(touchFinished) |
| 7504 var now = +new Date |
| 7505 d.activeTouch = {start: now, moved: false, |
| 7506 prev: now - prevTouch.end <= 300 ? prevTouch : null} |
| 7507 if (e.touches.length == 1) { |
| 7508 d.activeTouch.left = e.touches[0].pageX |
| 7509 d.activeTouch.top = e.touches[0].pageY |
| 2246 } | 7510 } |
| 2247 } | 7511 } |
| 2248 return out ? normalizeSelection(out, sel.primIndex) : sel; | 7512 }) |
| 2249 } | 7513 on(d.scroller, "touchmove", function () { |
| 2250 | 7514 if (d.activeTouch) { d.activeTouch.moved = true } |
| 2251 function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { | 7515 }) |
| 2252 var line = getLine(doc, pos.line); | 7516 on(d.scroller, "touchend", function (e) { |
| 2253 if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { | 7517 var touch = d.activeTouch |
| 2254 var sp = line.markedSpans[i], m = sp.marker; | 7518 if (touch && !eventInWidget(d, e) && touch.left != null && |
| 2255 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < p
os.ch)) && | 7519 !touch.moved && new Date - touch.start < 300) { |
| 2256 (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch
))) { | 7520 var pos = cm.coordsChar(d.activeTouch, "page"), range |
| 2257 if (mayClear) { | 7521 if (!touch.prev || farAway(touch, touch.prev)) // Single tap |
| 2258 signal(m, "beforeCursorEnter"); | 7522 { range = new Range(pos, pos) } |
| 2259 if (m.explicitlyCleared) { | 7523 else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double ta
p |
| 2260 if (!line.markedSpans) break; | 7524 { range = cm.findWordAt(pos) } |
| 2261 else {--i; continue;} | 7525 else // Triple tap |
| 2262 } | 7526 { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1,
0))) } |
| 2263 } | 7527 cm.setSelection(range.anchor, range.head) |
| 2264 if (!m.atomic) continue; | 7528 cm.focus() |
| 2265 | 7529 e_preventDefault(e) |
| 2266 if (oldPos) { | 7530 } |
| 2267 var near = m.find(dir < 0 ? 1 : -1), diff; | 7531 finishTouch() |
| 2268 if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) | 7532 }) |
| 2269 near = movePos(doc, near, -dir, near && near.line == pos.line ? line
: null); | 7533 on(d.scroller, "touchcancel", finishTouch) |
| 2270 if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (di
r < 0 ? diff < 0 : diff > 0)) | 7534 |
| 2271 return skipAtomicInner(doc, near, pos, dir, mayClear); | 7535 // Sync scrolling between fake scrollbars and real scrollable |
| 2272 } | 7536 // area, ensure viewport is updated when scrolling. |
| 2273 | 7537 on(d.scroller, "scroll", function () { |
| 2274 var far = m.find(dir < 0 ? -1 : 1); | 7538 if (d.scroller.clientHeight) { |
| 2275 if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) | 7539 setScrollTop(cm, d.scroller.scrollTop) |
| 2276 far = movePos(doc, far, dir, far.line == pos.line ? line : null); | 7540 setScrollLeft(cm, d.scroller.scrollLeft, true) |
| 2277 return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null; | 7541 signal(cm, "scroll", cm) |
| 7542 } |
| 7543 }) |
| 7544 |
| 7545 // Listen to wheel events in order to try and update the viewport on time. |
| 7546 on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); }) |
| 7547 on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); }
) |
| 7548 |
| 7549 // Prevent wrapper from ever scrolling |
| 7550 on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.s
crollLeft = 0; }) |
| 7551 |
| 7552 d.dragFunctions = { |
| 7553 enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e) }}, |
| 7554 over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(
e) }}, |
| 7555 start: function (e) { return onDragStart(cm, e); }, |
| 7556 drop: operation(cm, onDrop), |
| 7557 leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }} |
| 7558 } |
| 7559 |
| 7560 var inp = d.input.getField() |
| 7561 on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); }) |
| 7562 on(inp, "keydown", operation(cm, onKeyDown)) |
| 7563 on(inp, "keypress", operation(cm, onKeyPress)) |
| 7564 on(inp, "focus", function (e) { return onFocus(cm, e); }) |
| 7565 on(inp, "blur", function (e) { return onBlur(cm, e); }) |
| 7566 } |
| 7567 |
| 7568 var initHooks = [] |
| 7569 CodeMirror.defineInitHook = function (f) { return initHooks.push(f); } |
| 7570 |
| 7571 // Indent the given line. The how parameter can be "smart", |
| 7572 // "add"/null, "subtract", or "prev". When aggressive is false |
| 7573 // (typically set to true for forced single-line indents), empty |
| 7574 // lines are not indented, and places where the mode returns Pass |
| 7575 // are left alone. |
| 7576 function indentLine(cm, n, how, aggressive) { |
| 7577 var doc = cm.doc, state |
| 7578 if (how == null) { how = "add" } |
| 7579 if (how == "smart") { |
| 7580 // Fall back to "prev" when the mode doesn't have an indentation |
| 7581 // method. |
| 7582 if (!doc.mode.indent) { how = "prev" } |
| 7583 else { state = getStateBefore(cm, n) } |
| 7584 } |
| 7585 |
| 7586 var tabSize = cm.options.tabSize |
| 7587 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize) |
| 7588 if (line.stateAfter) { line.stateAfter = null } |
| 7589 var curSpaceString = line.text.match(/^\s*/)[0], indentation |
| 7590 if (!aggressive && !/\S/.test(line.text)) { |
| 7591 indentation = 0 |
| 7592 how = "not" |
| 7593 } else if (how == "smart") { |
| 7594 indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length),
line.text) |
| 7595 if (indentation == Pass || indentation > 150) { |
| 7596 if (!aggressive) { return } |
| 7597 how = "prev" |
| 7598 } |
| 7599 } |
| 7600 if (how == "prev") { |
| 7601 if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null,
tabSize) } |
| 7602 else { indentation = 0 } |
| 7603 } else if (how == "add") { |
| 7604 indentation = curSpace + cm.options.indentUnit |
| 7605 } else if (how == "subtract") { |
| 7606 indentation = curSpace - cm.options.indentUnit |
| 7607 } else if (typeof how == "number") { |
| 7608 indentation = curSpace + how |
| 7609 } |
| 7610 indentation = Math.max(0, indentation) |
| 7611 |
| 7612 var indentString = "", pos = 0 |
| 7613 if (cm.options.indentWithTabs) |
| 7614 { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; i
ndentString += "\t"} } |
| 7615 if (pos < indentation) { indentString += spaceStr(indentation - pos) } |
| 7616 |
| 7617 if (indentString != curSpaceString) { |
| 7618 replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+
input") |
| 7619 line.stateAfter = null |
| 7620 return true |
| 7621 } else { |
| 7622 // Ensure that, if the cursor was in the whitespace at the start |
| 7623 // of the line, it is moved to the end of that space. |
| 7624 for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { |
| 7625 var range = doc.sel.ranges[i$1] |
| 7626 if (range.head.line == n && range.head.ch < curSpaceString.length) { |
| 7627 var pos$1 = Pos(n, curSpaceString.length) |
| 7628 replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)) |
| 7629 break |
| 2278 } | 7630 } |
| 2279 } | 7631 } |
| 2280 return pos; | 7632 } |
| 2281 } | 7633 } |
| 2282 | 7634 |
| 2283 // Ensure a given position is not inside an atomic range. | 7635 // This will be set to a {lineWise: bool, text: [string]} object, so |
| 2284 function skipAtomic(doc, pos, oldPos, bias, mayClear) { | 7636 // that, when pasting, we know what kind of selections the copied |
| 2285 var dir = bias || 1; | 7637 // text was made out of. |
| 2286 var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || | 7638 var lastCopied = null |
| 2287 (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || | 7639 |
| 2288 skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || | 7640 function setLastCopied(newLastCopied) { |
| 2289 (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)); | 7641 lastCopied = newLastCopied |
| 2290 if (!found) { | 7642 } |
| 2291 doc.cantEdit = true; | 7643 |
| 2292 return Pos(doc.first, 0); | 7644 function applyTextInput(cm, inserted, deleted, sel, origin) { |
| 2293 } | 7645 var doc = cm.doc |
| 2294 return found; | 7646 cm.display.shift = false |
| 2295 } | 7647 if (!sel) { sel = doc.sel } |
| 2296 | 7648 |
| 2297 function movePos(doc, pos, dir, line) { | 7649 var paste = cm.state.pasteIncoming || origin == "paste" |
| 2298 if (dir < 0 && pos.ch == 0) { | 7650 var textLines = splitLinesAuto(inserted), multiPaste = null |
| 2299 if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1)); | 7651 // When pasing N lines into N selections, insert one line per selection |
| 2300 else return null; | 7652 if (paste && sel.ranges.length > 1) { |
| 2301 } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length
) { | 7653 if (lastCopied && lastCopied.text.join("\n") == inserted) { |
| 2302 if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0); | 7654 if (sel.ranges.length % lastCopied.text.length == 0) { |
| 2303 else return null; | 7655 multiPaste = [] |
| 2304 } else { | 7656 for (var i = 0; i < lastCopied.text.length; i++) |
| 2305 return new Pos(pos.line, pos.ch + dir); | 7657 { multiPaste.push(doc.splitLines(lastCopied.text[i])) } |
| 2306 } | |
| 2307 } | |
| 2308 | |
| 2309 // SELECTION DRAWING | |
| 2310 | |
| 2311 function updateSelection(cm) { | |
| 2312 cm.display.input.showSelection(cm.display.input.prepareSelection()); | |
| 2313 } | |
| 2314 | |
| 2315 function prepareSelection(cm, primary) { | |
| 2316 var doc = cm.doc, result = {}; | |
| 2317 var curFragment = result.cursors = document.createDocumentFragment(); | |
| 2318 var selFragment = result.selection = document.createDocumentFragment(); | |
| 2319 | |
| 2320 for (var i = 0; i < doc.sel.ranges.length; i++) { | |
| 2321 if (primary === false && i == doc.sel.primIndex) continue; | |
| 2322 var range = doc.sel.ranges[i]; | |
| 2323 if (range.from().line >= cm.display.viewTo || range.to().line < cm.display
.viewFrom) continue; | |
| 2324 var collapsed = range.empty(); | |
| 2325 if (collapsed || cm.options.showCursorWhenSelecting) | |
| 2326 drawSelectionCursor(cm, range.head, curFragment); | |
| 2327 if (!collapsed) | |
| 2328 drawSelectionRange(cm, range, selFragment); | |
| 2329 } | |
| 2330 return result; | |
| 2331 } | |
| 2332 | |
| 2333 // Draws a cursor for the given range | |
| 2334 function drawSelectionCursor(cm, head, output) { | |
| 2335 var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursor
HeightPerLine); | |
| 2336 | |
| 2337 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); | |
| 2338 cursor.style.left = pos.left + "px"; | |
| 2339 cursor.style.top = pos.top + "px"; | |
| 2340 cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorH
eight + "px"; | |
| 2341 | |
| 2342 if (pos.other) { | |
| 2343 // Secondary cursor, shown when on a 'jump' in bi-directional text | |
| 2344 var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-curs
or CodeMirror-secondarycursor")); | |
| 2345 otherCursor.style.display = ""; | |
| 2346 otherCursor.style.left = pos.other.left + "px"; | |
| 2347 otherCursor.style.top = pos.other.top + "px"; | |
| 2348 otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"
; | |
| 2349 } | |
| 2350 } | |
| 2351 | |
| 2352 // Draws the given range as a highlighted selection | |
| 2353 function drawSelectionRange(cm, range, output) { | |
| 2354 var display = cm.display, doc = cm.doc; | |
| 2355 var fragment = document.createDocumentFragment(); | |
| 2356 var padding = paddingH(cm.display), leftSide = padding.left; | |
| 2357 var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.size
r.offsetLeft) - padding.right; | |
| 2358 | |
| 2359 function add(left, top, width, bottom) { | |
| 2360 if (top < 0) top = 0; | |
| 2361 top = Math.round(top); | |
| 2362 bottom = Math.round(bottom); | |
| 2363 fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: ab
solute; left: " + left + | |
| 2364 "px; top: " + top + "px; width: " + (width == nul
l ? rightSide - left : width) + | |
| 2365 "px; height: " + (bottom - top) + "px")); | |
| 2366 } | |
| 2367 | |
| 2368 function drawForLine(line, fromArg, toArg) { | |
| 2369 var lineObj = getLine(doc, line); | |
| 2370 var lineLen = lineObj.text.length; | |
| 2371 var start, end; | |
| 2372 function coords(ch, bias) { | |
| 2373 return charCoords(cm, Pos(line, ch), "div", lineObj, bias); | |
| 2374 } | 7658 } |
| 2375 | 7659 } else if (textLines.length == sel.ranges.length) { |
| 2376 iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineL
en : toArg, function(from, to, dir) { | 7660 multiPaste = map(textLines, function (l) { return [l]; }) |
| 2377 var leftPos = coords(from, "left"), rightPos, left, right; | 7661 } |
| 2378 if (from == to) { | 7662 } |
| 2379 rightPos = leftPos; | 7663 |
| 2380 left = right = leftPos.left; | 7664 var updateInput |
| 2381 } else { | 7665 // Normal behavior is to insert the new text into every selection |
| 2382 rightPos = coords(to - 1, "right"); | 7666 for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { |
| 2383 if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos =
tmp; } | 7667 var range = sel.ranges[i$1] |
| 2384 left = leftPos.left; | 7668 var from = range.from(), to = range.to() |
| 2385 right = rightPos.right; | 7669 if (range.empty()) { |
| 2386 } | 7670 if (deleted && deleted > 0) // Handle deletion |
| 2387 if (fromArg == null && from == 0) left = leftSide; | 7671 { from = Pos(from.line, from.ch - deleted) } |
| 2388 if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part | 7672 else if (cm.state.overwrite && !paste) // Handle overwrite |
| 2389 add(left, leftPos.top, null, leftPos.bottom); | 7673 { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch +
lst(textLines).length)) } |
| 2390 left = leftSide; | 7674 else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") =
= inserted) |
| 2391 if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rig
htPos.top); | 7675 { from = to = Pos(from.line, 0) } |
| 2392 } | 7676 } |
| 2393 if (toArg == null && to == lineLen) right = rightSide; | 7677 updateInput = cm.curOp.updateInput |
| 2394 if (!start || leftPos.top < start.top || leftPos.top == start.top && lef
tPos.left < start.left) | 7678 var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % m
ultiPaste.length] : textLines, |
| 2395 start = leftPos; | 7679 origin: origin || (paste ? "paste" : cm.state.cutIncoming
? "cut" : "+input")} |
| 2396 if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.botto
m && rightPos.right > end.right) | 7680 makeChange(cm.doc, changeEvent) |
| 2397 end = rightPos; | 7681 signalLater(cm, "inputRead", cm, changeEvent) |
| 2398 if (left < leftSide + 1) left = leftSide; | 7682 } |
| 2399 add(left, rightPos.top, right - left, rightPos.bottom); | 7683 if (inserted && !paste) |
| 2400 }); | 7684 { triggerElectric(cm, inserted) } |
| 2401 return {start: start, end: end}; | 7685 |
| 2402 } | 7686 ensureCursorVisible(cm) |
| 2403 | 7687 cm.curOp.updateInput = updateInput |
| 2404 var sFrom = range.from(), sTo = range.to(); | 7688 cm.curOp.typing = true |
| 2405 if (sFrom.line == sTo.line) { | 7689 cm.state.pasteIncoming = cm.state.cutIncoming = false |
| 2406 drawForLine(sFrom.line, sFrom.ch, sTo.ch); | 7690 } |
| 2407 } else { | 7691 |
| 2408 var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); | 7692 function handlePaste(e, cm) { |
| 2409 var singleVLine = visualLine(fromLine) == visualLine(toLine); | 7693 var pasted = e.clipboardData && e.clipboardData.getData("Text") |
| 2410 var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.tex
t.length + 1 : null).end; | 7694 if (pasted) { |
| 2411 var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).sta
rt; | 7695 e.preventDefault() |
| 2412 if (singleVLine) { | 7696 if (!cm.isReadOnly() && !cm.options.disableInput) |
| 2413 if (leftEnd.top < rightStart.top - 2) { | 7697 { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "pa
ste"); }) } |
| 2414 add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); | 7698 return true |
| 2415 add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); | 7699 } |
| 2416 } else { | 7700 } |
| 2417 add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftE
nd.bottom); | 7701 |
| 2418 } | 7702 function triggerElectric(cm, inserted) { |
| 2419 } | 7703 // When an 'electric' character is inserted, immediately trigger a reindent |
| 2420 if (leftEnd.bottom < rightStart.top) | 7704 if (!cm.options.electricChars || !cm.options.smartIndent) { return } |
| 2421 add(leftSide, leftEnd.bottom, null, rightStart.top); | 7705 var sel = cm.doc.sel |
| 2422 } | 7706 |
| 2423 | 7707 for (var i = sel.ranges.length - 1; i >= 0; i--) { |
| 2424 output.appendChild(fragment); | 7708 var range = sel.ranges[i] |
| 2425 } | 7709 if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.l
ine)) { continue } |
| 2426 | 7710 var mode = cm.getModeAt(range.head) |
| 2427 // Cursor-blinking | 7711 var indented = false |
| 2428 function restartBlink(cm) { | 7712 if (mode.electricChars) { |
| 2429 if (!cm.state.focused) return; | 7713 for (var j = 0; j < mode.electricChars.length; j++) |
| 2430 var display = cm.display; | 7714 { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { |
| 2431 clearInterval(display.blinker); | 7715 indented = indentLine(cm, range.head.line, "smart") |
| 2432 var on = true; | 7716 break |
| 2433 display.cursorDiv.style.visibility = ""; | 7717 } } |
| 2434 if (cm.options.cursorBlinkRate > 0) | 7718 } else if (mode.electricInput) { |
| 2435 display.blinker = setInterval(function() { | 7719 if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0,
range.head.ch))) |
| 2436 display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; | 7720 { indented = indentLine(cm, range.head.line, "smart") } |
| 2437 }, cm.options.cursorBlinkRate); | 7721 } |
| 2438 else if (cm.options.cursorBlinkRate < 0) | 7722 if (indented) { signalLater(cm, "electricInput", cm, range.head.line) } |
| 2439 display.cursorDiv.style.visibility = "hidden"; | 7723 } |
| 2440 } | 7724 } |
| 2441 | 7725 |
| 2442 // HIGHLIGHT WORKER | 7726 function copyableRanges(cm) { |
| 2443 | 7727 var text = [], ranges = [] |
| 2444 function startWorker(cm, time) { | 7728 for (var i = 0; i < cm.doc.sel.ranges.length; i++) { |
| 2445 if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) | 7729 var line = cm.doc.sel.ranges[i].head.line |
| 2446 cm.state.highlight.set(time, bind(highlightWorker, cm)); | 7730 var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)} |
| 2447 } | 7731 ranges.push(lineRange) |
| 2448 | 7732 text.push(cm.getRange(lineRange.anchor, lineRange.head)) |
| 2449 function highlightWorker(cm) { | 7733 } |
| 2450 var doc = cm.doc; | 7734 return {text: text, ranges: ranges} |
| 2451 if (doc.frontier < doc.first) doc.frontier = doc.first; | 7735 } |
| 2452 if (doc.frontier >= cm.display.viewTo) return; | 7736 |
| 2453 var end = +new Date + cm.options.workTime; | 7737 function disableBrowserMagic(field, spellcheck) { |
| 2454 var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); | 7738 field.setAttribute("autocorrect", "off") |
| 2455 var changedLines = []; | 7739 field.setAttribute("autocapitalize", "off") |
| 2456 | 7740 field.setAttribute("spellcheck", !!spellcheck) |
| 2457 doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 50
0), function(line) { | 7741 } |
| 2458 if (doc.frontier >= cm.display.viewFrom) { // Visible | 7742 |
| 2459 var oldStyles = line.styles, tooLong = line.text.length > cm.options.max
HighlightLength; | 7743 function hiddenTextarea() { |
| 2460 var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode,
state) : state, true); | 7744 var te = elt("textarea", null, null, "position: absolute; bottom: -1em; paddin
g: 0; width: 1px; height: 1em; outline: none") |
| 2461 line.styles = highlighted.styles; | 7745 var div = elt("div", [te], null, "overflow: hidden; position: relative; width:
3px; height: 0px;") |
| 2462 var oldCls = line.styleClasses, newCls = highlighted.classes; | 7746 // The textarea is kept positioned near the cursor to prevent the |
| 2463 if (newCls) line.styleClasses = newCls; | 7747 // fact that it'll be scrolled into view on input from scrolling |
| 2464 else if (oldCls) line.styleClasses = null; | 7748 // our fake cursor out of view. On webkit, when wrap=off, paste is |
| 2465 var ischange = !oldStyles || oldStyles.length != line.styles.length || | 7749 // very slow. So make the area wide instead. |
| 2466 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bg
Class || oldCls.textClass != newCls.textClass); | 7750 if (webkit) { te.style.width = "1000px" } |
| 2467 for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldSt
yles[i] != line.styles[i]; | 7751 else { te.setAttribute("wrap", "off") } |
| 2468 if (ischange) changedLines.push(doc.frontier); | 7752 // If border: 0; -- iOS fails to open keyboard (issue #1287) |
| 2469 line.stateAfter = tooLong ? state : copyState(doc.mode, state); | 7753 if (ios) { te.style.border = "1px solid black" } |
| 2470 } else { | 7754 disableBrowserMagic(te) |
| 2471 if (line.text.length <= cm.options.maxHighlightLength) | 7755 return div |
| 2472 processLine(cm, line.text, state); | 7756 } |
| 2473 line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : n
ull; | 7757 |
| 2474 } | 7758 // The publicly visible API. Note that methodOp(f) means |
| 2475 ++doc.frontier; | 7759 // 'wrap f in an operation, performed on its `this` parameter'. |
| 2476 if (+new Date > end) { | 7760 |
| 2477 startWorker(cm, cm.options.workDelay); | 7761 // This is not the complete set of editor methods. Most of the |
| 2478 return true; | 7762 // methods defined on the Doc type are also injected into |
| 2479 } | 7763 // CodeMirror.prototype, for backwards compatibility and |
| 2480 }); | 7764 // convenience. |
| 2481 if (changedLines.length) runInOp(cm, function() { | 7765 |
| 2482 for (var i = 0; i < changedLines.length; i++) | 7766 function addEditorMethods(CodeMirror) { |
| 2483 regLineChange(cm, changedLines[i], "text"); | 7767 var optionHandlers = CodeMirror.optionHandlers |
| 2484 }); | 7768 |
| 2485 } | 7769 var helpers = CodeMirror.helpers = {} |
| 2486 | |
| 2487 // Finds the line to start with when starting a parse. Tries to | |
| 2488 // find a line with a stateAfter, so that it can start with a | |
| 2489 // valid state. If that fails, it returns the line with the | |
| 2490 // smallest indentation, which tends to need the least context to | |
| 2491 // parse correctly. | |
| 2492 function findStartLine(cm, n, precise) { | |
| 2493 var minindent, minline, doc = cm.doc; | |
| 2494 var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); | |
| 2495 for (var search = n; search > lim; --search) { | |
| 2496 if (search <= doc.first) return doc.first; | |
| 2497 var line = getLine(doc, search - 1); | |
| 2498 if (line.stateAfter && (!precise || search <= doc.frontier)) return search
; | |
| 2499 var indented = countColumn(line.text, null, cm.options.tabSize); | |
| 2500 if (minline == null || minindent > indented) { | |
| 2501 minline = search - 1; | |
| 2502 minindent = indented; | |
| 2503 } | |
| 2504 } | |
| 2505 return minline; | |
| 2506 } | |
| 2507 | |
| 2508 function getStateBefore(cm, n, precise) { | |
| 2509 var doc = cm.doc, display = cm.display; | |
| 2510 if (!doc.mode.startState) return true; | |
| 2511 var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(
doc, pos-1).stateAfter; | |
| 2512 if (!state) state = startState(doc.mode); | |
| 2513 else state = copyState(doc.mode, state); | |
| 2514 doc.iter(pos, n, function(line) { | |
| 2515 processLine(cm, line.text, state); | |
| 2516 var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos
< display.viewTo; | |
| 2517 line.stateAfter = save ? copyState(doc.mode, state) : null; | |
| 2518 ++pos; | |
| 2519 }); | |
| 2520 if (precise) doc.frontier = pos; | |
| 2521 return state; | |
| 2522 } | |
| 2523 | |
| 2524 // POSITION MEASUREMENT | |
| 2525 | |
| 2526 function paddingTop(display) {return display.lineSpace.offsetTop;} | |
| 2527 function paddingVert(display) {return display.mover.offsetHeight - display.lin
eSpace.offsetHeight;} | |
| 2528 function paddingH(display) { | |
| 2529 if (display.cachedPaddingH) return display.cachedPaddingH; | |
| 2530 var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); | |
| 2531 var style = window.getComputedStyle ? window.getComputedStyle(e) : e.current
Style; | |
| 2532 var data = {left: parseInt(style.paddingLeft), right: parseInt(style.padding
Right)}; | |
| 2533 if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data; | |
| 2534 return data; | |
| 2535 } | |
| 2536 | |
| 2537 function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; } | |
| 2538 function displayWidth(cm) { | |
| 2539 return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth
; | |
| 2540 } | |
| 2541 function displayHeight(cm) { | |
| 2542 return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeig
ht; | |
| 2543 } | |
| 2544 | |
| 2545 // Ensure the lineView.wrapping.heights array is populated. This is | |
| 2546 // an array of bottom offsets for the lines that make up a drawn | |
| 2547 // line. When lineWrapping is on, there might be more than one | |
| 2548 // height. | |
| 2549 function ensureLineHeights(cm, lineView, rect) { | |
| 2550 var wrapping = cm.options.lineWrapping; | |
| 2551 var curWidth = wrapping && displayWidth(cm); | |
| 2552 if (!lineView.measure.heights || wrapping && lineView.measure.width != curWi
dth) { | |
| 2553 var heights = lineView.measure.heights = []; | |
| 2554 if (wrapping) { | |
| 2555 lineView.measure.width = curWidth; | |
| 2556 var rects = lineView.text.firstChild.getClientRects(); | |
| 2557 for (var i = 0; i < rects.length - 1; i++) { | |
| 2558 var cur = rects[i], next = rects[i + 1]; | |
| 2559 if (Math.abs(cur.bottom - next.bottom) > 2) | |
| 2560 heights.push((cur.bottom + next.top) / 2 - rect.top); | |
| 2561 } | |
| 2562 } | |
| 2563 heights.push(rect.bottom - rect.top); | |
| 2564 } | |
| 2565 } | |
| 2566 | |
| 2567 // Find a line map (mapping character offsets to text nodes) and a | |
| 2568 // measurement cache for the given line number. (A line view might | |
| 2569 // contain multiple lines when collapsed ranges are present.) | |
| 2570 function mapFromLineView(lineView, line, lineN) { | |
| 2571 if (lineView.line == line) | |
| 2572 return {map: lineView.measure.map, cache: lineView.measure.cache}; | |
| 2573 for (var i = 0; i < lineView.rest.length; i++) | |
| 2574 if (lineView.rest[i] == line) | |
| 2575 return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]
}; | |
| 2576 for (var i = 0; i < lineView.rest.length; i++) | |
| 2577 if (lineNo(lineView.rest[i]) > lineN) | |
| 2578 return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]
, before: true}; | |
| 2579 } | |
| 2580 | |
| 2581 // Render a line into the hidden node display.externalMeasured. Used | |
| 2582 // when measurement is needed for a line that's not in the viewport. | |
| 2583 function updateExternalMeasurement(cm, line) { | |
| 2584 line = visualLine(line); | |
| 2585 var lineN = lineNo(line); | |
| 2586 var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); | |
| 2587 view.lineN = lineN; | |
| 2588 var built = view.built = buildLineContent(cm, view); | |
| 2589 view.text = built.pre; | |
| 2590 removeChildrenAndAdd(cm.display.lineMeasure, built.pre); | |
| 2591 return view; | |
| 2592 } | |
| 2593 | |
| 2594 // Get a {top, bottom, left, right} box (in line-local coordinates) | |
| 2595 // for a given character. | |
| 2596 function measureChar(cm, line, ch, bias) { | |
| 2597 return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias); | |
| 2598 } | |
| 2599 | |
| 2600 // Find a line view that corresponds to the given line number. | |
| 2601 function findViewForLine(cm, lineN) { | |
| 2602 if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) | |
| 2603 return cm.display.view[findViewIndex(cm, lineN)]; | |
| 2604 var ext = cm.display.externalMeasured; | |
| 2605 if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) | |
| 2606 return ext; | |
| 2607 } | |
| 2608 | |
| 2609 // Measurement can be split in two steps, the set-up work that | |
| 2610 // applies to the whole line, and the measurement of the actual | |
| 2611 // character. Functions like coordsChar, that need to do a lot of | |
| 2612 // measurements in a row, can thus ensure that the set-up work is | |
| 2613 // only done once. | |
| 2614 function prepareMeasureForLine(cm, line) { | |
| 2615 var lineN = lineNo(line); | |
| 2616 var view = findViewForLine(cm, lineN); | |
| 2617 if (view && !view.text) { | |
| 2618 view = null; | |
| 2619 } else if (view && view.changes) { | |
| 2620 updateLineForChanges(cm, view, lineN, getDimensions(cm)); | |
| 2621 cm.curOp.forceUpdate = true; | |
| 2622 } | |
| 2623 if (!view) | |
| 2624 view = updateExternalMeasurement(cm, line); | |
| 2625 | |
| 2626 var info = mapFromLineView(view, line, lineN); | |
| 2627 return { | |
| 2628 line: line, view: view, rect: null, | |
| 2629 map: info.map, cache: info.cache, before: info.before, | |
| 2630 hasHeights: false | |
| 2631 }; | |
| 2632 } | |
| 2633 | |
| 2634 // Given a prepared measurement object, measures the position of an | |
| 2635 // actual character (or fetches it from the cache). | |
| 2636 function measureCharPrepared(cm, prepared, ch, bias, varHeight) { | |
| 2637 if (prepared.before) ch = -1; | |
| 2638 var key = ch + (bias || ""), found; | |
| 2639 if (prepared.cache.hasOwnProperty(key)) { | |
| 2640 found = prepared.cache[key]; | |
| 2641 } else { | |
| 2642 if (!prepared.rect) | |
| 2643 prepared.rect = prepared.view.text.getBoundingClientRect(); | |
| 2644 if (!prepared.hasHeights) { | |
| 2645 ensureLineHeights(cm, prepared.view, prepared.rect); | |
| 2646 prepared.hasHeights = true; | |
| 2647 } | |
| 2648 found = measureCharInner(cm, prepared, ch, bias); | |
| 2649 if (!found.bogus) prepared.cache[key] = found; | |
| 2650 } | |
| 2651 return {left: found.left, right: found.right, | |
| 2652 top: varHeight ? found.rtop : found.top, | |
| 2653 bottom: varHeight ? found.rbottom : found.bottom}; | |
| 2654 } | |
| 2655 | |
| 2656 var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; | |
| 2657 | |
| 2658 function nodeAndOffsetInLineMap(map, ch, bias) { | |
| 2659 var node, start, end, collapse; | |
| 2660 // First, search the line map for the text node corresponding to, | |
| 2661 // or closest to, the target character. | |
| 2662 for (var i = 0; i < map.length; i += 3) { | |
| 2663 var mStart = map[i], mEnd = map[i + 1]; | |
| 2664 if (ch < mStart) { | |
| 2665 start = 0; end = 1; | |
| 2666 collapse = "left"; | |
| 2667 } else if (ch < mEnd) { | |
| 2668 start = ch - mStart; | |
| 2669 end = start + 1; | |
| 2670 } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { | |
| 2671 end = mEnd - mStart; | |
| 2672 start = end - 1; | |
| 2673 if (ch >= mEnd) collapse = "right"; | |
| 2674 } | |
| 2675 if (start != null) { | |
| 2676 node = map[i + 2]; | |
| 2677 if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) | |
| 2678 collapse = bias; | |
| 2679 if (bias == "left" && start == 0) | |
| 2680 while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { | |
| 2681 node = map[(i -= 3) + 2]; | |
| 2682 collapse = "left"; | |
| 2683 } | |
| 2684 if (bias == "right" && start == mEnd - mStart) | |
| 2685 while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].i
nsertLeft) { | |
| 2686 node = map[(i += 3) + 2]; | |
| 2687 collapse = "right"; | |
| 2688 } | |
| 2689 break; | |
| 2690 } | |
| 2691 } | |
| 2692 return {node: node, start: start, end: end, collapse: collapse, coverStart:
mStart, coverEnd: mEnd}; | |
| 2693 } | |
| 2694 | |
| 2695 function getUsefulRect(rects, bias) { | |
| 2696 var rect = nullRect | |
| 2697 if (bias == "left") for (var i = 0; i < rects.length; i++) { | |
| 2698 if ((rect = rects[i]).left != rect.right) break | |
| 2699 } else for (var i = rects.length - 1; i >= 0; i--) { | |
| 2700 if ((rect = rects[i]).left != rect.right) break | |
| 2701 } | |
| 2702 return rect | |
| 2703 } | |
| 2704 | |
| 2705 function measureCharInner(cm, prepared, ch, bias) { | |
| 2706 var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); | |
| 2707 var node = place.node, start = place.start, end = place.end, collapse = plac
e.collapse; | |
| 2708 | |
| 2709 var rect; | |
| 2710 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve t
he coordinates. | |
| 2711 for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense
rectangles are returned | |
| 2712 while (start && isExtendingChar(prepared.line.text.charAt(place.coverSta
rt + start))) --start; | |
| 2713 while (place.coverStart + end < place.coverEnd && isExtendingChar(prepar
ed.line.text.charAt(place.coverStart + end))) ++end; | |
| 2714 if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.
coverStart) | |
| 2715 rect = node.parentNode.getBoundingClientRect(); | |
| 2716 else | |
| 2717 rect = getUsefulRect(range(node, start, end).getClientRects(), bias) | |
| 2718 if (rect.left || rect.right || start == 0) break; | |
| 2719 end = start; | |
| 2720 start = start - 1; | |
| 2721 collapse = "right"; | |
| 2722 } | |
| 2723 if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.mea
sure, rect); | |
| 2724 } else { // If it is a widget, simply get the box for the whole widget. | |
| 2725 if (start > 0) collapse = bias = "right"; | |
| 2726 var rects; | |
| 2727 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) | |
| 2728 rect = rects[bias == "right" ? rects.length - 1 : 0]; | |
| 2729 else | |
| 2730 rect = node.getBoundingClientRect(); | |
| 2731 } | |
| 2732 if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right))
{ | |
| 2733 var rSpan = node.parentNode.getClientRects()[0]; | |
| 2734 if (rSpan) | |
| 2735 rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top
: rSpan.top, bottom: rSpan.bottom}; | |
| 2736 else | |
| 2737 rect = nullRect; | |
| 2738 } | |
| 2739 | |
| 2740 var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.
top; | |
| 2741 var mid = (rtop + rbot) / 2; | |
| 2742 var heights = prepared.view.measure.heights; | |
| 2743 for (var i = 0; i < heights.length - 1; i++) | |
| 2744 if (mid < heights[i]) break; | |
| 2745 var top = i ? heights[i - 1] : 0, bot = heights[i]; | |
| 2746 var result = {left: (collapse == "right" ? rect.right : rect.left) - prepare
d.rect.left, | |
| 2747 right: (collapse == "left" ? rect.left : rect.right) - prepare
d.rect.left, | |
| 2748 top: top, bottom: bot}; | |
| 2749 if (!rect.left && !rect.right) result.bogus = true; | |
| 2750 if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbot
tom = rbot; } | |
| 2751 | |
| 2752 return result; | |
| 2753 } | |
| 2754 | |
| 2755 // Work around problem with bounding client rects on ranges being | |
| 2756 // returned incorrectly when zoomed on IE10 and below. | |
| 2757 function maybeUpdateRectForZooming(measure, rect) { | |
| 2758 if (!window.screen || screen.logicalXDPI == null || | |
| 2759 screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) | |
| 2760 return rect; | |
| 2761 var scaleX = screen.logicalXDPI / screen.deviceXDPI; | |
| 2762 var scaleY = screen.logicalYDPI / screen.deviceYDPI; | |
| 2763 return {left: rect.left * scaleX, right: rect.right * scaleX, | |
| 2764 top: rect.top * scaleY, bottom: rect.bottom * scaleY}; | |
| 2765 } | |
| 2766 | |
| 2767 function clearLineMeasurementCacheFor(lineView) { | |
| 2768 if (lineView.measure) { | |
| 2769 lineView.measure.cache = {}; | |
| 2770 lineView.measure.heights = null; | |
| 2771 if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) | |
| 2772 lineView.measure.caches[i] = {}; | |
| 2773 } | |
| 2774 } | |
| 2775 | |
| 2776 function clearLineMeasurementCache(cm) { | |
| 2777 cm.display.externalMeasure = null; | |
| 2778 removeChildren(cm.display.lineMeasure); | |
| 2779 for (var i = 0; i < cm.display.view.length; i++) | |
| 2780 clearLineMeasurementCacheFor(cm.display.view[i]); | |
| 2781 } | |
| 2782 | |
| 2783 function clearCaches(cm) { | |
| 2784 clearLineMeasurementCache(cm); | |
| 2785 cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cached
PaddingH = null; | |
| 2786 if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; | |
| 2787 cm.display.lineNumChars = null; | |
| 2788 } | |
| 2789 | |
| 2790 function pageScrollX() { return window.pageXOffset || (document.documentElemen
t || document.body).scrollLeft; } | |
| 2791 function pageScrollY() { return window.pageYOffset || (document.documentElemen
t || document.body).scrollTop; } | |
| 2792 | |
| 2793 // Converts a {top, bottom, left, right} box from line-local | |
| 2794 // coordinates into another coordinate system. Context may be one of | |
| 2795 // "line", "div" (display.lineDiv), "local"/null (editor), "window", | |
| 2796 // or "page". | |
| 2797 function intoCoordSystem(cm, lineObj, rect, context) { | |
| 2798 if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (li
neObj.widgets[i].above) { | |
| 2799 var size = widgetHeight(lineObj.widgets[i]); | |
| 2800 rect.top += size; rect.bottom += size; | |
| 2801 } | |
| 2802 if (context == "line") return rect; | |
| 2803 if (!context) context = "local"; | |
| 2804 var yOff = heightAtLine(lineObj); | |
| 2805 if (context == "local") yOff += paddingTop(cm.display); | |
| 2806 else yOff -= cm.display.viewOffset; | |
| 2807 if (context == "page" || context == "window") { | |
| 2808 var lOff = cm.display.lineSpace.getBoundingClientRect(); | |
| 2809 yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); | |
| 2810 var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); | |
| 2811 rect.left += xOff; rect.right += xOff; | |
| 2812 } | |
| 2813 rect.top += yOff; rect.bottom += yOff; | |
| 2814 return rect; | |
| 2815 } | |
| 2816 | |
| 2817 // Coverts a box from "div" coords to another coordinate system. | |
| 2818 // Context may be "window", "page", "div", or "local"/null. | |
| 2819 function fromCoordSystem(cm, coords, context) { | |
| 2820 if (context == "div") return coords; | |
| 2821 var left = coords.left, top = coords.top; | |
| 2822 // First move into "page" coordinate system | |
| 2823 if (context == "page") { | |
| 2824 left -= pageScrollX(); | |
| 2825 top -= pageScrollY(); | |
| 2826 } else if (context == "local" || !context) { | |
| 2827 var localBox = cm.display.sizer.getBoundingClientRect(); | |
| 2828 left += localBox.left; | |
| 2829 top += localBox.top; | |
| 2830 } | |
| 2831 | |
| 2832 var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); | |
| 2833 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; | |
| 2834 } | |
| 2835 | |
| 2836 function charCoords(cm, pos, context, lineObj, bias) { | |
| 2837 if (!lineObj) lineObj = getLine(cm.doc, pos.line); | |
| 2838 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias),
context); | |
| 2839 } | |
| 2840 | |
| 2841 // Returns a box for a given cursor position, which may have an | |
| 2842 // 'other' property containing the position of the secondary cursor | |
| 2843 // on a bidi boundary. | |
| 2844 function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { | |
| 2845 lineObj = lineObj || getLine(cm.doc, pos.line); | |
| 2846 if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj); | |
| 2847 function get(ch, right) { | |
| 2848 var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "le
ft", varHeight); | |
| 2849 if (right) m.left = m.right; else m.right = m.left; | |
| 2850 return intoCoordSystem(cm, lineObj, m, context); | |
| 2851 } | |
| 2852 function getBidi(ch, partPos) { | |
| 2853 var part = order[partPos], right = part.level % 2; | |
| 2854 if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].lev
el) { | |
| 2855 part = order[--partPos]; | |
| 2856 ch = bidiRight(part) - (part.level % 2 ? 0 : 1); | |
| 2857 right = true; | |
| 2858 } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.lev
el < order[partPos + 1].level) { | |
| 2859 part = order[++partPos]; | |
| 2860 ch = bidiLeft(part) - part.level % 2; | |
| 2861 right = false; | |
| 2862 } | |
| 2863 if (right && ch == part.to && ch > part.from) return get(ch - 1); | |
| 2864 return get(ch, right); | |
| 2865 } | |
| 2866 var order = getOrder(lineObj), ch = pos.ch; | |
| 2867 if (!order) return get(ch); | |
| 2868 var partPos = getBidiPartAt(order, ch); | |
| 2869 var val = getBidi(ch, partPos); | |
| 2870 if (bidiOther != null) val.other = getBidi(ch, bidiOther); | |
| 2871 return val; | |
| 2872 } | |
| 2873 | |
| 2874 // Used to cheaply estimate the coordinates for a position. Used for | |
| 2875 // intermediate scroll updates. | |
| 2876 function estimateCoords(cm, pos) { | |
| 2877 var left = 0, pos = clipPos(cm.doc, pos); | |
| 2878 if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch; | |
| 2879 var lineObj = getLine(cm.doc, pos.line); | |
| 2880 var top = heightAtLine(lineObj) + paddingTop(cm.display); | |
| 2881 return {left: left, right: left, top: top, bottom: top + lineObj.height}; | |
| 2882 } | |
| 2883 | |
| 2884 // Positions returned by coordsChar contain some extra information. | |
| 2885 // xRel is the relative x position of the input coordinates compared | |
| 2886 // to the found position (so xRel > 0 means the coordinates are to | |
| 2887 // the right of the character position, for example). When outside | |
| 2888 // is true, that means the coordinates lie outside the line's | |
| 2889 // vertical range. | |
| 2890 function PosWithInfo(line, ch, outside, xRel) { | |
| 2891 var pos = Pos(line, ch); | |
| 2892 pos.xRel = xRel; | |
| 2893 if (outside) pos.outside = true; | |
| 2894 return pos; | |
| 2895 } | |
| 2896 | |
| 2897 // Compute the character position closest to the given coordinates. | |
| 2898 // Input must be lineSpace-local ("div" coordinate system). | |
| 2899 function coordsChar(cm, x, y) { | |
| 2900 var doc = cm.doc; | |
| 2901 y += cm.display.viewOffset; | |
| 2902 if (y < 0) return PosWithInfo(doc.first, 0, true, -1); | |
| 2903 var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; | |
| 2904 if (lineN > last) | |
| 2905 return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.lengt
h, true, 1); | |
| 2906 if (x < 0) x = 0; | |
| 2907 | |
| 2908 var lineObj = getLine(doc, lineN); | |
| 2909 for (;;) { | |
| 2910 var found = coordsCharInner(cm, lineObj, lineN, x, y); | |
| 2911 var merged = collapsedSpanAtEnd(lineObj); | |
| 2912 var mergedPos = merged && merged.find(0, true); | |
| 2913 if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.
ch && found.xRel > 0)) | |
| 2914 lineN = lineNo(lineObj = mergedPos.to.line); | |
| 2915 else | |
| 2916 return found; | |
| 2917 } | |
| 2918 } | |
| 2919 | |
| 2920 function coordsCharInner(cm, lineObj, lineNo, x, y) { | |
| 2921 var innerOff = y - heightAtLine(lineObj); | |
| 2922 var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; | |
| 2923 var preparedMeasure = prepareMeasureForLine(cm, lineObj); | |
| 2924 | |
| 2925 function getX(ch) { | |
| 2926 var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasur
e); | |
| 2927 wrongLine = true; | |
| 2928 if (innerOff > sp.bottom) return sp.left - adjust; | |
| 2929 else if (innerOff < sp.top) return sp.left + adjust; | |
| 2930 else wrongLine = false; | |
| 2931 return sp.left; | |
| 2932 } | |
| 2933 | |
| 2934 var bidi = getOrder(lineObj), dist = lineObj.text.length; | |
| 2935 var from = lineLeft(lineObj), to = lineRight(lineObj); | |
| 2936 var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside =
wrongLine; | |
| 2937 | |
| 2938 if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); | |
| 2939 // Do a binary search between these bounds. | |
| 2940 for (;;) { | |
| 2941 if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from
<= 1) { | |
| 2942 var ch = x < fromX || x - fromX <= toX - x ? from : to; | |
| 2943 var outside = ch == from ? fromOutside : toOutside | |
| 2944 var xDiff = x - (ch == from ? fromX : toX); | |
| 2945 // This is a kludge to handle the case where the coordinates | |
| 2946 // are after a line-wrapped line. We should replace it with a | |
| 2947 // more general handling of cursor positions around line | |
| 2948 // breaks. (Issue #4078) | |
| 2949 if (toOutside && !bidi && !/\s/.test(lineObj.text.charAt(ch)) && xDiff >
0 && | |
| 2950 ch < lineObj.text.length && preparedMeasure.view.measure.heights.len
gth > 1) { | |
| 2951 var charSize = measureCharPrepared(cm, preparedMeasure, ch, "right"); | |
| 2952 if (innerOff <= charSize.bottom && innerOff >= charSize.top && Math.ab
s(x - charSize.right) < xDiff) { | |
| 2953 outside = false | |
| 2954 ch++ | |
| 2955 xDiff = x - charSize.right | |
| 2956 } | |
| 2957 } | |
| 2958 while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; | |
| 2959 var pos = PosWithInfo(lineNo, ch, outside, xDiff < -1 ? -1 : xDiff > 1 ?
1 : 0); | |
| 2960 return pos; | |
| 2961 } | |
| 2962 var step = Math.ceil(dist / 2), middle = from + step; | |
| 2963 if (bidi) { | |
| 2964 middle = from; | |
| 2965 for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1)
; | |
| 2966 } | |
| 2967 var middleX = getX(middle); | |
| 2968 if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) t
oX += 1000; dist = step;} | |
| 2969 else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= ste
p;} | |
| 2970 } | |
| 2971 } | |
| 2972 | |
| 2973 var measureText; | |
| 2974 // Compute the default text height. | |
| 2975 function textHeight(display) { | |
| 2976 if (display.cachedTextHeight != null) return display.cachedTextHeight; | |
| 2977 if (measureText == null) { | |
| 2978 measureText = elt("pre"); | |
| 2979 // Measure a bunch of lines, for browsers that compute | |
| 2980 // fractional heights. | |
| 2981 for (var i = 0; i < 49; ++i) { | |
| 2982 measureText.appendChild(document.createTextNode("x")); | |
| 2983 measureText.appendChild(elt("br")); | |
| 2984 } | |
| 2985 measureText.appendChild(document.createTextNode("x")); | |
| 2986 } | |
| 2987 removeChildrenAndAdd(display.measure, measureText); | |
| 2988 var height = measureText.offsetHeight / 50; | |
| 2989 if (height > 3) display.cachedTextHeight = height; | |
| 2990 removeChildren(display.measure); | |
| 2991 return height || 1; | |
| 2992 } | |
| 2993 | |
| 2994 // Compute the default character width. | |
| 2995 function charWidth(display) { | |
| 2996 if (display.cachedCharWidth != null) return display.cachedCharWidth; | |
| 2997 var anchor = elt("span", "xxxxxxxxxx"); | |
| 2998 var pre = elt("pre", [anchor]); | |
| 2999 removeChildrenAndAdd(display.measure, pre); | |
| 3000 var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left)
/ 10; | |
| 3001 if (width > 2) display.cachedCharWidth = width; | |
| 3002 return width || 10; | |
| 3003 } | |
| 3004 | |
| 3005 // OPERATIONS | |
| 3006 | |
| 3007 // Operations are used to wrap a series of changes to the editor | |
| 3008 // state in such a way that each change won't have to update the | |
| 3009 // cursor and display (which would be awkward, slow, and | |
| 3010 // error-prone). Instead, display updates are batched and then all | |
| 3011 // combined and executed at once. | |
| 3012 | |
| 3013 var operationGroup = null; | |
| 3014 | |
| 3015 var nextOpId = 0; | |
| 3016 // Start a new operation. | |
| 3017 function startOperation(cm) { | |
| 3018 cm.curOp = { | |
| 3019 cm: cm, | |
| 3020 viewChanged: false, // Flag that indicates that lines might need to b
e redrawn | |
| 3021 startHeight: cm.doc.height, // Used to detect need to update scrollbar | |
| 3022 forceUpdate: false, // Used to force a redraw | |
| 3023 updateInput: null, // Whether to reset the input textarea | |
| 3024 typing: false, // Whether this reset should be careful to leave
existing text (for compositing) | |
| 3025 changeObjs: null, // Accumulated changes, for firing change events | |
| 3026 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on | |
| 3027 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been
called already | |
| 3028 selectionChanged: false, // Whether the selection needs to be redrawn | |
| 3029 updateMaxLine: false, // Set when the widest line needs to be determine
d anew | |
| 3030 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pu
shed to DOM yet | |
| 3031 scrollToPos: null, // Used to scroll to a specific position | |
| 3032 focus: false, | |
| 3033 id: ++nextOpId // Unique ID | |
| 3034 }; | |
| 3035 if (operationGroup) { | |
| 3036 operationGroup.ops.push(cm.curOp); | |
| 3037 } else { | |
| 3038 cm.curOp.ownsGroup = operationGroup = { | |
| 3039 ops: [cm.curOp], | |
| 3040 delayedCallbacks: [] | |
| 3041 }; | |
| 3042 } | |
| 3043 } | |
| 3044 | |
| 3045 function fireCallbacksForOps(group) { | |
| 3046 // Calls delayed callbacks and cursorActivity handlers until no | |
| 3047 // new ones appear | |
| 3048 var callbacks = group.delayedCallbacks, i = 0; | |
| 3049 do { | |
| 3050 for (; i < callbacks.length; i++) | |
| 3051 callbacks[i].call(null); | |
| 3052 for (var j = 0; j < group.ops.length; j++) { | |
| 3053 var op = group.ops[j]; | |
| 3054 if (op.cursorActivityHandlers) | |
| 3055 while (op.cursorActivityCalled < op.cursorActivityHandlers.length) | |
| 3056 op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.c
m); | |
| 3057 } | |
| 3058 } while (i < callbacks.length); | |
| 3059 } | |
| 3060 | |
| 3061 // Finish an operation, updating the display and signalling delayed events | |
| 3062 function endOperation(cm) { | |
| 3063 var op = cm.curOp, group = op.ownsGroup; | |
| 3064 if (!group) return; | |
| 3065 | |
| 3066 try { fireCallbacksForOps(group); } | |
| 3067 finally { | |
| 3068 operationGroup = null; | |
| 3069 for (var i = 0; i < group.ops.length; i++) | |
| 3070 group.ops[i].cm.curOp = null; | |
| 3071 endOperations(group); | |
| 3072 } | |
| 3073 } | |
| 3074 | |
| 3075 // The DOM updates done when an operation finishes are batched so | |
| 3076 // that the minimum number of relayouts are required. | |
| 3077 function endOperations(group) { | |
| 3078 var ops = group.ops; | |
| 3079 for (var i = 0; i < ops.length; i++) // Read DOM | |
| 3080 endOperation_R1(ops[i]); | |
| 3081 for (var i = 0; i < ops.length; i++) // Write DOM (maybe) | |
| 3082 endOperation_W1(ops[i]); | |
| 3083 for (var i = 0; i < ops.length; i++) // Read DOM | |
| 3084 endOperation_R2(ops[i]); | |
| 3085 for (var i = 0; i < ops.length; i++) // Write DOM (maybe) | |
| 3086 endOperation_W2(ops[i]); | |
| 3087 for (var i = 0; i < ops.length; i++) // Read DOM | |
| 3088 endOperation_finish(ops[i]); | |
| 3089 } | |
| 3090 | |
| 3091 function endOperation_R1(op) { | |
| 3092 var cm = op.cm, display = cm.display; | |
| 3093 maybeClipScrollbars(cm); | |
| 3094 if (op.updateMaxLine) findMaxLine(cm); | |
| 3095 | |
| 3096 op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || | |
| 3097 op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || | |
| 3098 op.scrollToPos.to.line >= display.viewTo) || | |
| 3099 display.maxLineChanged && cm.options.lineWrapping; | |
| 3100 op.update = op.mustUpdate && | |
| 3101 new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scro
llToPos}, op.forceUpdate); | |
| 3102 } | |
| 3103 | |
| 3104 function endOperation_W1(op) { | |
| 3105 op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update)
; | |
| 3106 } | |
| 3107 | |
| 3108 function endOperation_R2(op) { | |
| 3109 var cm = op.cm, display = cm.display; | |
| 3110 if (op.updatedDisplay) updateHeightsInViewport(cm); | |
| 3111 | |
| 3112 op.barMeasure = measureForScrollbars(cm); | |
| 3113 | |
| 3114 // If the max line changed since it was last measured, measure it, | |
| 3115 // and ensure the document's width matches it. | |
| 3116 // updateDisplay_W2 will use these properties to do the actual resizing | |
| 3117 if (display.maxLineChanged && !cm.options.lineWrapping) { | |
| 3118 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.l
ength).left + 3; | |
| 3119 cm.display.sizerWidth = op.adjustWidthTo; | |
| 3120 op.barMeasure.scrollWidth = | |
| 3121 Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adj
ustWidthTo + scrollGap(cm) + cm.display.barWidth); | |
| 3122 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo
- displayWidth(cm)); | |
| 3123 } | |
| 3124 | |
| 3125 if (op.updatedDisplay || op.selectionChanged) | |
| 3126 op.preparedSelection = display.input.prepareSelection(op.focus); | |
| 3127 } | |
| 3128 | |
| 3129 function endOperation_W2(op) { | |
| 3130 var cm = op.cm; | |
| 3131 | |
| 3132 if (op.adjustWidthTo != null) { | |
| 3133 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; | |
| 3134 if (op.maxScrollLeft < cm.doc.scrollLeft) | |
| 3135 setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollL
eft), true); | |
| 3136 cm.display.maxLineChanged = false; | |
| 3137 } | |
| 3138 | |
| 3139 var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus |
| document.hasFocus()) | |
| 3140 if (op.preparedSelection) | |
| 3141 cm.display.input.showSelection(op.preparedSelection, takeFocus); | |
| 3142 if (op.updatedDisplay || op.startHeight != cm.doc.height) | |
| 3143 updateScrollbars(cm, op.barMeasure); | |
| 3144 if (op.updatedDisplay) | |
| 3145 setDocumentHeight(cm, op.barMeasure); | |
| 3146 | |
| 3147 if (op.selectionChanged) restartBlink(cm); | |
| 3148 | |
| 3149 if (cm.state.focused && op.updateInput) | |
| 3150 cm.display.input.reset(op.typing); | |
| 3151 if (takeFocus) ensureFocus(op.cm); | |
| 3152 } | |
| 3153 | |
| 3154 function endOperation_finish(op) { | |
| 3155 var cm = op.cm, display = cm.display, doc = cm.doc; | |
| 3156 | |
| 3157 if (op.updatedDisplay) postUpdateDisplay(cm, op.update); | |
| 3158 | |
| 3159 // Abort mouse wheel delta measurement, when scrolling explicitly | |
| 3160 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft !=
null || op.scrollToPos)) | |
| 3161 display.wheelStartX = display.wheelStartY = null; | |
| 3162 | |
| 3163 // Propagate the scroll position to the actual DOM scroller | |
| 3164 if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || o
p.forceScroll)) { | |
| 3165 doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - displ
ay.scroller.clientHeight, op.scrollTop)); | |
| 3166 display.scrollbars.setScrollTop(doc.scrollTop); | |
| 3167 display.scroller.scrollTop = doc.scrollTop; | |
| 3168 } | |
| 3169 if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft |
| op.forceScroll)) { | |
| 3170 doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displ
ay.scroller.clientWidth, op.scrollLeft)); | |
| 3171 display.scrollbars.setScrollLeft(doc.scrollLeft); | |
| 3172 display.scroller.scrollLeft = doc.scrollLeft; | |
| 3173 alignHorizontally(cm); | |
| 3174 } | |
| 3175 // If we need to scroll a specific position into view, do so. | |
| 3176 if (op.scrollToPos) { | |
| 3177 var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), | |
| 3178 clipPos(doc, op.scrollToPos.to), op.scrollT
oPos.margin); | |
| 3179 if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coo
rds); | |
| 3180 } | |
| 3181 | |
| 3182 // Fire events for markers that are hidden/unidden by editing or | |
| 3183 // undoing | |
| 3184 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; | |
| 3185 if (hidden) for (var i = 0; i < hidden.length; ++i) | |
| 3186 if (!hidden[i].lines.length) signal(hidden[i], "hide"); | |
| 3187 if (unhidden) for (var i = 0; i < unhidden.length; ++i) | |
| 3188 if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); | |
| 3189 | |
| 3190 if (display.wrapper.offsetHeight) | |
| 3191 doc.scrollTop = cm.display.scroller.scrollTop; | |
| 3192 | |
| 3193 // Fire change events, and delayed event handlers | |
| 3194 if (op.changeObjs) | |
| 3195 signal(cm, "changes", cm, op.changeObjs); | |
| 3196 if (op.update) | |
| 3197 op.update.finish(); | |
| 3198 } | |
| 3199 | |
| 3200 // Run the given function in an operation | |
| 3201 function runInOp(cm, f) { | |
| 3202 if (cm.curOp) return f(); | |
| 3203 startOperation(cm); | |
| 3204 try { return f(); } | |
| 3205 finally { endOperation(cm); } | |
| 3206 } | |
| 3207 // Wraps a function in an operation. Returns the wrapped function. | |
| 3208 function operation(cm, f) { | |
| 3209 return function() { | |
| 3210 if (cm.curOp) return f.apply(cm, arguments); | |
| 3211 startOperation(cm); | |
| 3212 try { return f.apply(cm, arguments); } | |
| 3213 finally { endOperation(cm); } | |
| 3214 }; | |
| 3215 } | |
| 3216 // Used to add methods to editor and doc instances, wrapping them in | |
| 3217 // operations. | |
| 3218 function methodOp(f) { | |
| 3219 return function() { | |
| 3220 if (this.curOp) return f.apply(this, arguments); | |
| 3221 startOperation(this); | |
| 3222 try { return f.apply(this, arguments); } | |
| 3223 finally { endOperation(this); } | |
| 3224 }; | |
| 3225 } | |
| 3226 function docMethodOp(f) { | |
| 3227 return function() { | |
| 3228 var cm = this.cm; | |
| 3229 if (!cm || cm.curOp) return f.apply(this, arguments); | |
| 3230 startOperation(cm); | |
| 3231 try { return f.apply(this, arguments); } | |
| 3232 finally { endOperation(cm); } | |
| 3233 }; | |
| 3234 } | |
| 3235 | |
| 3236 // VIEW TRACKING | |
| 3237 | |
| 3238 // These objects are used to represent the visible (currently drawn) | |
| 3239 // part of the document. A LineView may correspond to multiple | |
| 3240 // logical lines, if those are connected by collapsed ranges. | |
| 3241 function LineView(doc, line, lineN) { | |
| 3242 // The starting line | |
| 3243 this.line = line; | |
| 3244 // Continuing lines, if any | |
| 3245 this.rest = visualLineContinued(line); | |
| 3246 // Number of logical lines in this visual line | |
| 3247 this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; | |
| 3248 this.node = this.text = null; | |
| 3249 this.hidden = lineIsHidden(doc, line); | |
| 3250 } | |
| 3251 | |
| 3252 // Create a range of LineView objects for the given lines. | |
| 3253 function buildViewArray(cm, from, to) { | |
| 3254 var array = [], nextPos; | |
| 3255 for (var pos = from; pos < to; pos = nextPos) { | |
| 3256 var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); | |
| 3257 nextPos = pos + view.size; | |
| 3258 array.push(view); | |
| 3259 } | |
| 3260 return array; | |
| 3261 } | |
| 3262 | |
| 3263 // Updates the display.view data structure for a given change to the | |
| 3264 // document. From and to are in pre-change coordinates. Lendiff is | |
| 3265 // the amount of lines added or subtracted by the change. This is | |
| 3266 // used for changes that span multiple lines, or change the way | |
| 3267 // lines are divided into visual lines. regLineChange (below) | |
| 3268 // registers single-line changes. | |
| 3269 function regChange(cm, from, to, lendiff) { | |
| 3270 if (from == null) from = cm.doc.first; | |
| 3271 if (to == null) to = cm.doc.first + cm.doc.size; | |
| 3272 if (!lendiff) lendiff = 0; | |
| 3273 | |
| 3274 var display = cm.display; | |
| 3275 if (lendiff && to < display.viewTo && | |
| 3276 (display.updateLineNumbers == null || display.updateLineNumbers > from)) | |
| 3277 display.updateLineNumbers = from; | |
| 3278 | |
| 3279 cm.curOp.viewChanged = true; | |
| 3280 | |
| 3281 if (from >= display.viewTo) { // Change after | |
| 3282 if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) | |
| 3283 resetView(cm); | |
| 3284 } else if (to <= display.viewFrom) { // Change before | |
| 3285 if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.v
iewFrom) { | |
| 3286 resetView(cm); | |
| 3287 } else { | |
| 3288 display.viewFrom += lendiff; | |
| 3289 display.viewTo += lendiff; | |
| 3290 } | |
| 3291 } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overl
ap | |
| 3292 resetView(cm); | |
| 3293 } else if (from <= display.viewFrom) { // Top overlap | |
| 3294 var cut = viewCuttingPoint(cm, to, to + lendiff, 1); | |
| 3295 if (cut) { | |
| 3296 display.view = display.view.slice(cut.index); | |
| 3297 display.viewFrom = cut.lineN; | |
| 3298 display.viewTo += lendiff; | |
| 3299 } else { | |
| 3300 resetView(cm); | |
| 3301 } | |
| 3302 } else if (to >= display.viewTo) { // Bottom overlap | |
| 3303 var cut = viewCuttingPoint(cm, from, from, -1); | |
| 3304 if (cut) { | |
| 3305 display.view = display.view.slice(0, cut.index); | |
| 3306 display.viewTo = cut.lineN; | |
| 3307 } else { | |
| 3308 resetView(cm); | |
| 3309 } | |
| 3310 } else { // Gap in the middle | |
| 3311 var cutTop = viewCuttingPoint(cm, from, from, -1); | |
| 3312 var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); | |
| 3313 if (cutTop && cutBot) { | |
| 3314 display.view = display.view.slice(0, cutTop.index) | |
| 3315 .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) | |
| 3316 .concat(display.view.slice(cutBot.index)); | |
| 3317 display.viewTo += lendiff; | |
| 3318 } else { | |
| 3319 resetView(cm); | |
| 3320 } | |
| 3321 } | |
| 3322 | |
| 3323 var ext = display.externalMeasured; | |
| 3324 if (ext) { | |
| 3325 if (to < ext.lineN) | |
| 3326 ext.lineN += lendiff; | |
| 3327 else if (from < ext.lineN + ext.size) | |
| 3328 display.externalMeasured = null; | |
| 3329 } | |
| 3330 } | |
| 3331 | |
| 3332 // Register a change to a single line. Type must be one of "text", | |
| 3333 // "gutter", "class", "widget" | |
| 3334 function regLineChange(cm, line, type) { | |
| 3335 cm.curOp.viewChanged = true; | |
| 3336 var display = cm.display, ext = cm.display.externalMeasured; | |
| 3337 if (ext && line >= ext.lineN && line < ext.lineN + ext.size) | |
| 3338 display.externalMeasured = null; | |
| 3339 | |
| 3340 if (line < display.viewFrom || line >= display.viewTo) return; | |
| 3341 var lineView = display.view[findViewIndex(cm, line)]; | |
| 3342 if (lineView.node == null) return; | |
| 3343 var arr = lineView.changes || (lineView.changes = []); | |
| 3344 if (indexOf(arr, type) == -1) arr.push(type); | |
| 3345 } | |
| 3346 | |
| 3347 // Clear the view. | |
| 3348 function resetView(cm) { | |
| 3349 cm.display.viewFrom = cm.display.viewTo = cm.doc.first; | |
| 3350 cm.display.view = []; | |
| 3351 cm.display.viewOffset = 0; | |
| 3352 } | |
| 3353 | |
| 3354 // Find the view element corresponding to a given line. Return null | |
| 3355 // when the line isn't visible. | |
| 3356 function findViewIndex(cm, n) { | |
| 3357 if (n >= cm.display.viewTo) return null; | |
| 3358 n -= cm.display.viewFrom; | |
| 3359 if (n < 0) return null; | |
| 3360 var view = cm.display.view; | |
| 3361 for (var i = 0; i < view.length; i++) { | |
| 3362 n -= view[i].size; | |
| 3363 if (n < 0) return i; | |
| 3364 } | |
| 3365 } | |
| 3366 | |
| 3367 function viewCuttingPoint(cm, oldN, newN, dir) { | |
| 3368 var index = findViewIndex(cm, oldN), diff, view = cm.display.view; | |
| 3369 if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) | |
| 3370 return {index: index, lineN: newN}; | |
| 3371 for (var i = 0, n = cm.display.viewFrom; i < index; i++) | |
| 3372 n += view[i].size; | |
| 3373 if (n != oldN) { | |
| 3374 if (dir > 0) { | |
| 3375 if (index == view.length - 1) return null; | |
| 3376 diff = (n + view[index].size) - oldN; | |
| 3377 index++; | |
| 3378 } else { | |
| 3379 diff = n - oldN; | |
| 3380 } | |
| 3381 oldN += diff; newN += diff; | |
| 3382 } | |
| 3383 while (visualLineNo(cm.doc, newN) != newN) { | |
| 3384 if (index == (dir < 0 ? 0 : view.length - 1)) return null; | |
| 3385 newN += dir * view[index - (dir < 0 ? 1 : 0)].size; | |
| 3386 index += dir; | |
| 3387 } | |
| 3388 return {index: index, lineN: newN}; | |
| 3389 } | |
| 3390 | |
| 3391 // Force the view to cover a given range, adding empty view element | |
| 3392 // or clipping off existing ones as needed. | |
| 3393 function adjustView(cm, from, to) { | |
| 3394 var display = cm.display, view = display.view; | |
| 3395 if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { | |
| 3396 display.view = buildViewArray(cm, from, to); | |
| 3397 display.viewFrom = from; | |
| 3398 } else { | |
| 3399 if (display.viewFrom > from) | |
| 3400 display.view = buildViewArray(cm, from, display.viewFrom).concat(display
.view); | |
| 3401 else if (display.viewFrom < from) | |
| 3402 display.view = display.view.slice(findViewIndex(cm, from)); | |
| 3403 display.viewFrom = from; | |
| 3404 if (display.viewTo < to) | |
| 3405 display.view = display.view.concat(buildViewArray(cm, display.viewTo, to
)); | |
| 3406 else if (display.viewTo > to) | |
| 3407 display.view = display.view.slice(0, findViewIndex(cm, to)); | |
| 3408 } | |
| 3409 display.viewTo = to; | |
| 3410 } | |
| 3411 | |
| 3412 // Count the number of lines in the view whose DOM representation is | |
| 3413 // out of date (or nonexistent). | |
| 3414 function countDirtyView(cm) { | |
| 3415 var view = cm.display.view, dirty = 0; | |
| 3416 for (var i = 0; i < view.length; i++) { | |
| 3417 var lineView = view[i]; | |
| 3418 if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; | |
| 3419 } | |
| 3420 return dirty; | |
| 3421 } | |
| 3422 | |
| 3423 // EVENT HANDLERS | |
| 3424 | |
| 3425 // Attach the necessary event handlers when initializing the editor | |
| 3426 function registerEventHandlers(cm) { | |
| 3427 var d = cm.display; | |
| 3428 on(d.scroller, "mousedown", operation(cm, onMouseDown)); | |
| 3429 // Older IE's will not fire a second mousedown for a double click | |
| 3430 if (ie && ie_version < 11) | |
| 3431 on(d.scroller, "dblclick", operation(cm, function(e) { | |
| 3432 if (signalDOMEvent(cm, e)) return; | |
| 3433 var pos = posFromMouse(cm, e); | |
| 3434 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return
; | |
| 3435 e_preventDefault(e); | |
| 3436 var word = cm.findWordAt(pos); | |
| 3437 extendSelection(cm.doc, word.anchor, word.head); | |
| 3438 })); | |
| 3439 else | |
| 3440 on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preven
tDefault(e); }); | |
| 3441 // Some browsers fire contextmenu *after* opening the menu, at | |
| 3442 // which point we can't mess with it anymore. Context menu is | |
| 3443 // handled in onMouseDown for these browsers. | |
| 3444 if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContext
Menu(cm, e);}); | |
| 3445 | |
| 3446 // Used to suppress mouse event handling when a touch happens | |
| 3447 var touchFinished, prevTouch = {end: 0}; | |
| 3448 function finishTouch() { | |
| 3449 if (d.activeTouch) { | |
| 3450 touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000); | |
| 3451 prevTouch = d.activeTouch; | |
| 3452 prevTouch.end = +new Date; | |
| 3453 } | |
| 3454 }; | |
| 3455 function isMouseLikeTouchEvent(e) { | |
| 3456 if (e.touches.length != 1) return false; | |
| 3457 var touch = e.touches[0]; | |
| 3458 return touch.radiusX <= 1 && touch.radiusY <= 1; | |
| 3459 } | |
| 3460 function farAway(touch, other) { | |
| 3461 if (other.left == null) return true; | |
| 3462 var dx = other.left - touch.left, dy = other.top - touch.top; | |
| 3463 return dx * dx + dy * dy > 20 * 20; | |
| 3464 } | |
| 3465 on(d.scroller, "touchstart", function(e) { | |
| 3466 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { | |
| 3467 clearTimeout(touchFinished); | |
| 3468 var now = +new Date; | |
| 3469 d.activeTouch = {start: now, moved: false, | |
| 3470 prev: now - prevTouch.end <= 300 ? prevTouch : null}; | |
| 3471 if (e.touches.length == 1) { | |
| 3472 d.activeTouch.left = e.touches[0].pageX; | |
| 3473 d.activeTouch.top = e.touches[0].pageY; | |
| 3474 } | |
| 3475 } | |
| 3476 }); | |
| 3477 on(d.scroller, "touchmove", function() { | |
| 3478 if (d.activeTouch) d.activeTouch.moved = true; | |
| 3479 }); | |
| 3480 on(d.scroller, "touchend", function(e) { | |
| 3481 var touch = d.activeTouch; | |
| 3482 if (touch && !eventInWidget(d, e) && touch.left != null && | |
| 3483 !touch.moved && new Date - touch.start < 300) { | |
| 3484 var pos = cm.coordsChar(d.activeTouch, "page"), range; | |
| 3485 if (!touch.prev || farAway(touch, touch.prev)) // Single tap | |
| 3486 range = new Range(pos, pos); | |
| 3487 else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double
tap | |
| 3488 range = cm.findWordAt(pos); | |
| 3489 else // Triple tap | |
| 3490 range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1,
0))); | |
| 3491 cm.setSelection(range.anchor, range.head); | |
| 3492 cm.focus(); | |
| 3493 e_preventDefault(e); | |
| 3494 } | |
| 3495 finishTouch(); | |
| 3496 }); | |
| 3497 on(d.scroller, "touchcancel", finishTouch); | |
| 3498 | |
| 3499 // Sync scrolling between fake scrollbars and real scrollable | |
| 3500 // area, ensure viewport is updated when scrolling. | |
| 3501 on(d.scroller, "scroll", function() { | |
| 3502 if (d.scroller.clientHeight) { | |
| 3503 setScrollTop(cm, d.scroller.scrollTop); | |
| 3504 setScrollLeft(cm, d.scroller.scrollLeft, true); | |
| 3505 signal(cm, "scroll", cm); | |
| 3506 } | |
| 3507 }); | |
| 3508 | |
| 3509 // Listen to wheel events in order to try and update the viewport on time. | |
| 3510 on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); | |
| 3511 on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); | |
| 3512 | |
| 3513 // Prevent wrapper from ever scrolling | |
| 3514 on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollL
eft = 0; }); | |
| 3515 | |
| 3516 d.dragFunctions = { | |
| 3517 enter: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);}, | |
| 3518 over: function(e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop
(e); }}, | |
| 3519 start: function(e){onDragStart(cm, e);}, | |
| 3520 drop: operation(cm, onDrop), | |
| 3521 leave: function(e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }} | |
| 3522 }; | |
| 3523 | |
| 3524 var inp = d.input.getField(); | |
| 3525 on(inp, "keyup", function(e) { onKeyUp.call(cm, e); }); | |
| 3526 on(inp, "keydown", operation(cm, onKeyDown)); | |
| 3527 on(inp, "keypress", operation(cm, onKeyPress)); | |
| 3528 on(inp, "focus", bind(onFocus, cm)); | |
| 3529 on(inp, "blur", bind(onBlur, cm)); | |
| 3530 } | |
| 3531 | |
| 3532 function dragDropChanged(cm, value, old) { | |
| 3533 var wasOn = old && old != CodeMirror.Init; | |
| 3534 if (!value != !wasOn) { | |
| 3535 var funcs = cm.display.dragFunctions; | |
| 3536 var toggle = value ? on : off; | |
| 3537 toggle(cm.display.scroller, "dragstart", funcs.start); | |
| 3538 toggle(cm.display.scroller, "dragenter", funcs.enter); | |
| 3539 toggle(cm.display.scroller, "dragover", funcs.over); | |
| 3540 toggle(cm.display.scroller, "dragleave", funcs.leave); | |
| 3541 toggle(cm.display.scroller, "drop", funcs.drop); | |
| 3542 } | |
| 3543 } | |
| 3544 | |
| 3545 // Called when the window resizes | |
| 3546 function onResize(cm) { | |
| 3547 var d = cm.display; | |
| 3548 if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapp
er.clientWidth) | |
| 3549 return; | |
| 3550 // Might be a text scaling operation, clear size caches. | |
| 3551 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; | |
| 3552 d.scrollbarsClipped = false; | |
| 3553 cm.setSize(); | |
| 3554 } | |
| 3555 | |
| 3556 // MOUSE EVENTS | |
| 3557 | |
| 3558 // Return true when the given mouse event happened in a widget | |
| 3559 function eventInWidget(display, e) { | |
| 3560 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { | |
| 3561 if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true"
) || | |
| 3562 (n.parentNode == display.sizer && n != display.mover)) | |
| 3563 return true; | |
| 3564 } | |
| 3565 } | |
| 3566 | |
| 3567 // Given a mouse event, find the corresponding position. If liberal | |
| 3568 // is false, it checks whether a gutter or scrollbar was clicked, | |
| 3569 // and returns null if it was. forRect is used by rectangular | |
| 3570 // selections, and tries to estimate a character position even for | |
| 3571 // coordinates beyond the right of the text. | |
| 3572 function posFromMouse(cm, e, liberal, forRect) { | |
| 3573 var display = cm.display; | |
| 3574 if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return
null; | |
| 3575 | |
| 3576 var x, y, space = display.lineSpace.getBoundingClientRect(); | |
| 3577 // Fails unpredictably on IE[67] when mouse is dragged around quickly. | |
| 3578 try { x = e.clientX - space.left; y = e.clientY - space.top; } | |
| 3579 catch (e) { return null; } | |
| 3580 var coords = coordsChar(cm, x, y), line; | |
| 3581 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text
).length == coords.ch) { | |
| 3582 var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.le
ngth; | |
| 3583 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display)
.left) / charWidth(cm.display)) - colDiff)); | |
| 3584 } | |
| 3585 return coords; | |
| 3586 } | |
| 3587 | |
| 3588 // A mouse down can be a single click, double click, triple click, | |
| 3589 // start of selection drag, start of text drag, new cursor | |
| 3590 // (ctrl-click), rectangle drag (alt-drag), or xwin | |
| 3591 // middle-click-paste. Or it might be a click on something we should | |
| 3592 // not interfere with, such as a scrollbar or widget. | |
| 3593 function onMouseDown(e) { | |
| 3594 var cm = this, display = cm.display; | |
| 3595 if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTo
uch()) return; | |
| 3596 display.shift = e.shiftKey; | |
| 3597 | |
| 3598 if (eventInWidget(display, e)) { | |
| 3599 if (!webkit) { | |
| 3600 // Briefly turn off draggability, to allow widgets to do | |
| 3601 // normal dragging things. | |
| 3602 display.scroller.draggable = false; | |
| 3603 setTimeout(function(){display.scroller.draggable = true;}, 100); | |
| 3604 } | |
| 3605 return; | |
| 3606 } | |
| 3607 if (clickInGutter(cm, e)) return; | |
| 3608 var start = posFromMouse(cm, e); | |
| 3609 window.focus(); | |
| 3610 | |
| 3611 switch (e_button(e)) { | |
| 3612 case 1: | |
| 3613 // #3261: make sure, that we're not starting a second selection | |
| 3614 if (cm.state.selectingText) | |
| 3615 cm.state.selectingText(e); | |
| 3616 else if (start) | |
| 3617 leftButtonDown(cm, e, start); | |
| 3618 else if (e_target(e) == display.scroller) | |
| 3619 e_preventDefault(e); | |
| 3620 break; | |
| 3621 case 2: | |
| 3622 if (webkit) cm.state.lastMiddleDown = +new Date; | |
| 3623 if (start) extendSelection(cm.doc, start); | |
| 3624 setTimeout(function() {display.input.focus();}, 20); | |
| 3625 e_preventDefault(e); | |
| 3626 break; | |
| 3627 case 3: | |
| 3628 if (captureRightClick) onContextMenu(cm, e); | |
| 3629 else delayBlurEvent(cm); | |
| 3630 break; | |
| 3631 } | |
| 3632 } | |
| 3633 | |
| 3634 var lastClick, lastDoubleClick; | |
| 3635 function leftButtonDown(cm, e, start) { | |
| 3636 if (ie) setTimeout(bind(ensureFocus, cm), 0); | |
| 3637 else cm.curOp.focus = activeElt(); | |
| 3638 | |
| 3639 var now = +new Date, type; | |
| 3640 if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleCli
ck.pos, start) == 0) { | |
| 3641 type = "triple"; | |
| 3642 } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, sta
rt) == 0) { | |
| 3643 type = "double"; | |
| 3644 lastDoubleClick = {time: now, pos: start}; | |
| 3645 } else { | |
| 3646 type = "single"; | |
| 3647 lastClick = {time: now, pos: start}; | |
| 3648 } | |
| 3649 | |
| 3650 var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; | |
| 3651 if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && | |
| 3652 type == "single" && (contained = sel.contains(start)) > -1 && | |
| 3653 (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRe
l > 0) && | |
| 3654 (cmp(contained.to(), start) > 0 || start.xRel < 0)) | |
| 3655 leftButtonStartDrag(cm, e, start, modifier); | |
| 3656 else | |
| 3657 leftButtonSelect(cm, e, start, type, modifier); | |
| 3658 } | |
| 3659 | |
| 3660 // Start a text drag. When it ends, see if any dragging actually | |
| 3661 // happen, and treat as a click if it didn't. | |
| 3662 function leftButtonStartDrag(cm, e, start, modifier) { | |
| 3663 var display = cm.display, startTime = +new Date; | |
| 3664 var dragEnd = operation(cm, function(e2) { | |
| 3665 if (webkit) display.scroller.draggable = false; | |
| 3666 cm.state.draggingText = false; | |
| 3667 off(document, "mouseup", dragEnd); | |
| 3668 off(display.scroller, "drop", dragEnd); | |
| 3669 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) <
10) { | |
| 3670 e_preventDefault(e2); | |
| 3671 if (!modifier && +new Date - 200 < startTime) | |
| 3672 extendSelection(cm.doc, start); | |
| 3673 // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3
081) | |
| 3674 if (webkit || ie && ie_version == 9) | |
| 3675 setTimeout(function() {document.body.focus(); display.input.focus();},
20); | |
| 3676 else | |
| 3677 display.input.focus(); | |
| 3678 } | |
| 3679 }); | |
| 3680 // Let the drag handler handle this. | |
| 3681 if (webkit) display.scroller.draggable = true; | |
| 3682 cm.state.draggingText = dragEnd; | |
| 3683 dragEnd.copy = mac ? e.altKey : e.ctrlKey | |
| 3684 // IE's approach to draggable | |
| 3685 if (display.scroller.dragDrop) display.scroller.dragDrop(); | |
| 3686 on(document, "mouseup", dragEnd); | |
| 3687 on(display.scroller, "drop", dragEnd); | |
| 3688 } | |
| 3689 | |
| 3690 // Normal selection, as opposed to text dragging. | |
| 3691 function leftButtonSelect(cm, e, start, type, addNew) { | |
| 3692 var display = cm.display, doc = cm.doc; | |
| 3693 e_preventDefault(e); | |
| 3694 | |
| 3695 var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; | |
| 3696 if (addNew && !e.shiftKey) { | |
| 3697 ourIndex = doc.sel.contains(start); | |
| 3698 if (ourIndex > -1) | |
| 3699 ourRange = ranges[ourIndex]; | |
| 3700 else | |
| 3701 ourRange = new Range(start, start); | |
| 3702 } else { | |
| 3703 ourRange = doc.sel.primary(); | |
| 3704 ourIndex = doc.sel.primIndex; | |
| 3705 } | |
| 3706 | |
| 3707 if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) { | |
| 3708 type = "rect"; | |
| 3709 if (!addNew) ourRange = new Range(start, start); | |
| 3710 start = posFromMouse(cm, e, true, true); | |
| 3711 ourIndex = -1; | |
| 3712 } else if (type == "double") { | |
| 3713 var word = cm.findWordAt(start); | |
| 3714 if (cm.display.shift || doc.extend) | |
| 3715 ourRange = extendRange(doc, ourRange, word.anchor, word.head); | |
| 3716 else | |
| 3717 ourRange = word; | |
| 3718 } else if (type == "triple") { | |
| 3719 var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1,
0))); | |
| 3720 if (cm.display.shift || doc.extend) | |
| 3721 ourRange = extendRange(doc, ourRange, line.anchor, line.head); | |
| 3722 else | |
| 3723 ourRange = line; | |
| 3724 } else { | |
| 3725 ourRange = extendRange(doc, ourRange, start); | |
| 3726 } | |
| 3727 | |
| 3728 if (!addNew) { | |
| 3729 ourIndex = 0; | |
| 3730 setSelection(doc, new Selection([ourRange], 0), sel_mouse); | |
| 3731 startSel = doc.sel; | |
| 3732 } else if (ourIndex == -1) { | |
| 3733 ourIndex = ranges.length; | |
| 3734 setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), | |
| 3735 {scroll: false, origin: "*mouse"}); | |
| 3736 } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single"
&& !e.shiftKey) { | |
| 3737 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(rang
es.slice(ourIndex + 1)), 0), | |
| 3738 {scroll: false, origin: "*mouse"}); | |
| 3739 startSel = doc.sel; | |
| 3740 } else { | |
| 3741 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); | |
| 3742 } | |
| 3743 | |
| 3744 var lastPos = start; | |
| 3745 function extendTo(pos) { | |
| 3746 if (cmp(lastPos, pos) == 0) return; | |
| 3747 lastPos = pos; | |
| 3748 | |
| 3749 if (type == "rect") { | |
| 3750 var ranges = [], tabSize = cm.options.tabSize; | |
| 3751 var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabS
ize); | |
| 3752 var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); | |
| 3753 var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol
); | |
| 3754 for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLi
ne(), Math.max(start.line, pos.line)); | |
| 3755 line <= end; line++) { | |
| 3756 var text = getLine(doc, line).text, leftPos = findColumn(text, left, t
abSize); | |
| 3757 if (left == right) | |
| 3758 ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); | |
| 3759 else if (text.length > leftPos) | |
| 3760 ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text,
right, tabSize)))); | |
| 3761 } | |
| 3762 if (!ranges.length) ranges.push(new Range(start, start)); | |
| 3763 setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).
concat(ranges), ourIndex), | |
| 3764 {origin: "*mouse", scroll: false}); | |
| 3765 cm.scrollIntoView(pos); | |
| 3766 } else { | |
| 3767 var oldRange = ourRange; | |
| 3768 var anchor = oldRange.anchor, head = pos; | |
| 3769 if (type != "single") { | |
| 3770 if (type == "double") | |
| 3771 var range = cm.findWordAt(pos); | |
| 3772 else | |
| 3773 var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line +
1, 0))); | |
| 3774 if (cmp(range.anchor, anchor) > 0) { | |
| 3775 head = range.head; | |
| 3776 anchor = minPos(oldRange.from(), range.anchor); | |
| 3777 } else { | |
| 3778 head = range.anchor; | |
| 3779 anchor = maxPos(oldRange.to(), range.head); | |
| 3780 } | |
| 3781 } | |
| 3782 var ranges = startSel.ranges.slice(0); | |
| 3783 ranges[ourIndex] = new Range(clipPos(doc, anchor), head); | |
| 3784 setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse); | |
| 3785 } | |
| 3786 } | |
| 3787 | |
| 3788 var editorSize = display.wrapper.getBoundingClientRect(); | |
| 3789 // Used to ensure timeout re-tries don't fire when another extend | |
| 3790 // happened in the meantime (clearTimeout isn't reliable -- at | |
| 3791 // least on Chrome, the timeouts still happen even when cleared, | |
| 3792 // if the clear happens after their scheduled firing time). | |
| 3793 var counter = 0; | |
| 3794 | |
| 3795 function extend(e) { | |
| 3796 var curCount = ++counter; | |
| 3797 var cur = posFromMouse(cm, e, true, type == "rect"); | |
| 3798 if (!cur) return; | |
| 3799 if (cmp(cur, lastPos) != 0) { | |
| 3800 cm.curOp.focus = activeElt(); | |
| 3801 extendTo(cur); | |
| 3802 var visible = visibleLines(display, doc); | |
| 3803 if (cur.line >= visible.to || cur.line < visible.from) | |
| 3804 setTimeout(operation(cm, function(){if (counter == curCount) extend(e)
;}), 150); | |
| 3805 } else { | |
| 3806 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.
bottom ? 20 : 0; | |
| 3807 if (outside) setTimeout(operation(cm, function() { | |
| 3808 if (counter != curCount) return; | |
| 3809 display.scroller.scrollTop += outside; | |
| 3810 extend(e); | |
| 3811 }), 50); | |
| 3812 } | |
| 3813 } | |
| 3814 | |
| 3815 function done(e) { | |
| 3816 cm.state.selectingText = false; | |
| 3817 counter = Infinity; | |
| 3818 e_preventDefault(e); | |
| 3819 display.input.focus(); | |
| 3820 off(document, "mousemove", move); | |
| 3821 off(document, "mouseup", up); | |
| 3822 doc.history.lastSelOrigin = null; | |
| 3823 } | |
| 3824 | |
| 3825 var move = operation(cm, function(e) { | |
| 3826 if (!e_button(e)) done(e); | |
| 3827 else extend(e); | |
| 3828 }); | |
| 3829 var up = operation(cm, done); | |
| 3830 cm.state.selectingText = up; | |
| 3831 on(document, "mousemove", move); | |
| 3832 on(document, "mouseup", up); | |
| 3833 } | |
| 3834 | |
| 3835 // Determines whether an event happened in the gutter, and fires the | |
| 3836 // handlers for the corresponding event. | |
| 3837 function gutterEvent(cm, e, type, prevent) { | |
| 3838 try { var mX = e.clientX, mY = e.clientY; } | |
| 3839 catch(e) { return false; } | |
| 3840 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) retu
rn false; | |
| 3841 if (prevent) e_preventDefault(e); | |
| 3842 | |
| 3843 var display = cm.display; | |
| 3844 var lineBox = display.lineDiv.getBoundingClientRect(); | |
| 3845 | |
| 3846 if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(
e); | |
| 3847 mY -= lineBox.top - display.viewOffset; | |
| 3848 | |
| 3849 for (var i = 0; i < cm.options.gutters.length; ++i) { | |
| 3850 var g = display.gutters.childNodes[i]; | |
| 3851 if (g && g.getBoundingClientRect().right >= mX) { | |
| 3852 var line = lineAtHeight(cm.doc, mY); | |
| 3853 var gutter = cm.options.gutters[i]; | |
| 3854 signal(cm, type, cm, line, gutter, e); | |
| 3855 return e_defaultPrevented(e); | |
| 3856 } | |
| 3857 } | |
| 3858 } | |
| 3859 | |
| 3860 function clickInGutter(cm, e) { | |
| 3861 return gutterEvent(cm, e, "gutterClick", true); | |
| 3862 } | |
| 3863 | |
| 3864 // Kludge to work around strange IE behavior where it'll sometimes | |
| 3865 // re-fire a series of drag-related events right after the drop (#1551) | |
| 3866 var lastDrop = 0; | |
| 3867 | |
| 3868 function onDrop(e) { | |
| 3869 var cm = this; | |
| 3870 clearDragCursor(cm); | |
| 3871 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) | |
| 3872 return; | |
| 3873 e_preventDefault(e); | |
| 3874 if (ie) lastDrop = +new Date; | |
| 3875 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; | |
| 3876 if (!pos || cm.isReadOnly()) return; | |
| 3877 // Might be a file drop, in which case we simply extract the text | |
| 3878 // and insert it. | |
| 3879 if (files && files.length && window.FileReader && window.File) { | |
| 3880 var n = files.length, text = Array(n), read = 0; | |
| 3881 var loadFile = function(file, i) { | |
| 3882 if (cm.options.allowDropFileTypes && | |
| 3883 indexOf(cm.options.allowDropFileTypes, file.type) == -1) | |
| 3884 return; | |
| 3885 | |
| 3886 var reader = new FileReader; | |
| 3887 reader.onload = operation(cm, function() { | |
| 3888 var content = reader.result; | |
| 3889 if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = ""; | |
| 3890 text[i] = content; | |
| 3891 if (++read == n) { | |
| 3892 pos = clipPos(cm.doc, pos); | |
| 3893 var change = {from: pos, to: pos, | |
| 3894 text: cm.doc.splitLines(text.join(cm.doc.lineSeparator
())), | |
| 3895 origin: "paste"}; | |
| 3896 makeChange(cm.doc, change); | |
| 3897 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(ch
ange))); | |
| 3898 } | |
| 3899 }); | |
| 3900 reader.readAsText(file); | |
| 3901 }; | |
| 3902 for (var i = 0; i < n; ++i) loadFile(files[i], i); | |
| 3903 } else { // Normal drop | |
| 3904 // Don't do a replace if the drop happened inside of the selected text. | |
| 3905 if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { | |
| 3906 cm.state.draggingText(e); | |
| 3907 // Ensure the editor is re-focused | |
| 3908 setTimeout(function() {cm.display.input.focus();}, 20); | |
| 3909 return; | |
| 3910 } | |
| 3911 try { | |
| 3912 var text = e.dataTransfer.getData("Text"); | |
| 3913 if (text) { | |
| 3914 if (cm.state.draggingText && !cm.state.draggingText.copy) | |
| 3915 var selected = cm.listSelections(); | |
| 3916 setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); | |
| 3917 if (selected) for (var i = 0; i < selected.length; ++i) | |
| 3918 replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag
"); | |
| 3919 cm.replaceSelection(text, "around", "paste"); | |
| 3920 cm.display.input.focus(); | |
| 3921 } | |
| 3922 } | |
| 3923 catch(e){} | |
| 3924 } | |
| 3925 } | |
| 3926 | |
| 3927 function onDragStart(cm, e) { | |
| 3928 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e
); return; } | |
| 3929 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; | |
| 3930 | |
| 3931 e.dataTransfer.setData("Text", cm.getSelection()); | |
| 3932 e.dataTransfer.effectAllowed = "copyMove" | |
| 3933 | |
| 3934 // Use dummy image instead of default browsers image. | |
| 3935 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so
we don't do it there. | |
| 3936 if (e.dataTransfer.setDragImage && !safari) { | |
| 3937 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); | |
| 3938 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAA
AICTAEAOw=="; | |
| 3939 if (presto) { | |
| 3940 img.width = img.height = 1; | |
| 3941 cm.display.wrapper.appendChild(img); | |
| 3942 // Force a relayout, or Opera won't use our image for some obscure reaso
n | |
| 3943 img._top = img.offsetTop; | |
| 3944 } | |
| 3945 e.dataTransfer.setDragImage(img, 0, 0); | |
| 3946 if (presto) img.parentNode.removeChild(img); | |
| 3947 } | |
| 3948 } | |
| 3949 | |
| 3950 function onDragOver(cm, e) { | |
| 3951 var pos = posFromMouse(cm, e); | |
| 3952 if (!pos) return; | |
| 3953 var frag = document.createDocumentFragment(); | |
| 3954 drawSelectionCursor(cm, pos, frag); | |
| 3955 if (!cm.display.dragCursor) { | |
| 3956 cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dr
agcursors"); | |
| 3957 cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursor
Div); | |
| 3958 } | |
| 3959 removeChildrenAndAdd(cm.display.dragCursor, frag); | |
| 3960 } | |
| 3961 | |
| 3962 function clearDragCursor(cm) { | |
| 3963 if (cm.display.dragCursor) { | |
| 3964 cm.display.lineSpace.removeChild(cm.display.dragCursor); | |
| 3965 cm.display.dragCursor = null; | |
| 3966 } | |
| 3967 } | |
| 3968 | |
| 3969 // SCROLL EVENTS | |
| 3970 | |
| 3971 // Sync the scrollable area and scrollbars, ensure the viewport | |
| 3972 // covers the visible area. | |
| 3973 function setScrollTop(cm, val) { | |
| 3974 if (Math.abs(cm.doc.scrollTop - val) < 2) return; | |
| 3975 cm.doc.scrollTop = val; | |
| 3976 if (!gecko) updateDisplaySimple(cm, {top: val}); | |
| 3977 if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = va
l; | |
| 3978 cm.display.scrollbars.setScrollTop(val); | |
| 3979 if (gecko) updateDisplaySimple(cm); | |
| 3980 startWorker(cm, 100); | |
| 3981 } | |
| 3982 // Sync scroller and scrollbar, ensure the gutter elements are | |
| 3983 // aligned. | |
| 3984 function setScrollLeft(cm, val, isScroller) { | |
| 3985 if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val
) < 2) return; | |
| 3986 val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.cl
ientWidth); | |
| 3987 cm.doc.scrollLeft = val; | |
| 3988 alignHorizontally(cm); | |
| 3989 if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft =
val; | |
| 3990 cm.display.scrollbars.setScrollLeft(val); | |
| 3991 } | |
| 3992 | |
| 3993 // Since the delta values reported on mouse wheel events are | |
| 3994 // unstandardized between browsers and even browser versions, and | |
| 3995 // generally horribly unpredictable, this code starts by measuring | |
| 3996 // the scroll effect that the first few mouse wheel events have, | |
| 3997 // and, from that, detects the way it can convert deltas to pixel | |
| 3998 // offsets afterwards. | |
| 3999 // | |
| 4000 // The reason we want to know the amount a wheel event will scroll | |
| 4001 // is that it gives us a chance to update the display before the | |
| 4002 // actual scrolling happens, reducing flickering. | |
| 4003 | |
| 4004 var wheelSamples = 0, wheelPixelsPerUnit = null; | |
| 4005 // Fill in a browser-detected starting value on browsers where we | |
| 4006 // know one. These don't have to be accurate -- the result of them | |
| 4007 // being wrong would just be a slight flicker on the first wheel | |
| 4008 // scroll (if it is large enough). | |
| 4009 if (ie) wheelPixelsPerUnit = -.53; | |
| 4010 else if (gecko) wheelPixelsPerUnit = 15; | |
| 4011 else if (chrome) wheelPixelsPerUnit = -.7; | |
| 4012 else if (safari) wheelPixelsPerUnit = -1/3; | |
| 4013 | |
| 4014 var wheelEventDelta = function(e) { | |
| 4015 var dx = e.wheelDeltaX, dy = e.wheelDeltaY; | |
| 4016 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; | |
| 4017 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; | |
| 4018 else if (dy == null) dy = e.wheelDelta; | |
| 4019 return {x: dx, y: dy}; | |
| 4020 }; | |
| 4021 CodeMirror.wheelEventPixels = function(e) { | |
| 4022 var delta = wheelEventDelta(e); | |
| 4023 delta.x *= wheelPixelsPerUnit; | |
| 4024 delta.y *= wheelPixelsPerUnit; | |
| 4025 return delta; | |
| 4026 }; | |
| 4027 | |
| 4028 function onScrollWheel(cm, e) { | |
| 4029 var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; | |
| 4030 | |
| 4031 var display = cm.display, scroll = display.scroller; | |
| 4032 // Quit if there's nothing to scroll here | |
| 4033 var canScrollX = scroll.scrollWidth > scroll.clientWidth; | |
| 4034 var canScrollY = scroll.scrollHeight > scroll.clientHeight; | |
| 4035 if (!(dx && canScrollX || dy && canScrollY)) return; | |
| 4036 | |
| 4037 // Webkit browsers on OS X abort momentum scrolls when the target | |
| 4038 // of the scroll event is removed from the scrollable element. | |
| 4039 // This hack (see related code in patchDisplay) makes sure the | |
| 4040 // element is kept around. | |
| 4041 if (dy && mac && webkit) { | |
| 4042 outer: for (var cur = e.target, view = display.view; cur != scroll; cur =
cur.parentNode) { | |
| 4043 for (var i = 0; i < view.length; i++) { | |
| 4044 if (view[i].node == cur) { | |
| 4045 cm.display.currentWheelTarget = cur; | |
| 4046 break outer; | |
| 4047 } | |
| 4048 } | |
| 4049 } | |
| 4050 } | |
| 4051 | |
| 4052 // On some browsers, horizontal scrolling will cause redraws to | |
| 4053 // happen before the gutter has been realigned, causing it to | |
| 4054 // wriggle around in a most unseemly way. When we have an | |
| 4055 // estimated pixels/delta value, we just handle horizontal | |
| 4056 // scrolling entirely here. It'll be slightly off from native, but | |
| 4057 // better than glitching out. | |
| 4058 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { | |
| 4059 if (dy && canScrollY) | |
| 4060 setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixel
sPerUnit, scroll.scrollHeight - scroll.clientHeight))); | |
| 4061 setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixel
sPerUnit, scroll.scrollWidth - scroll.clientWidth))); | |
| 4062 // Only prevent default scrolling if vertical scrolling is | |
| 4063 // actually possible. Otherwise, it causes vertical scroll | |
| 4064 // jitter on OSX trackpads when deltaX is small and deltaY | |
| 4065 // is large (issue #3579) | |
| 4066 if (!dy || (dy && canScrollY)) | |
| 4067 e_preventDefault(e); | |
| 4068 display.wheelStartX = null; // Abort measurement, if in progress | |
| 4069 return; | |
| 4070 } | |
| 4071 | |
| 4072 // 'Project' the visible viewport to cover the area that is being | |
| 4073 // scrolled into view (if we know enough to estimate it). | |
| 4074 if (dy && wheelPixelsPerUnit != null) { | |
| 4075 var pixels = dy * wheelPixelsPerUnit; | |
| 4076 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; | |
| 4077 if (pixels < 0) top = Math.max(0, top + pixels - 50); | |
| 4078 else bot = Math.min(cm.doc.height, bot + pixels + 50); | |
| 4079 updateDisplaySimple(cm, {top: top, bottom: bot}); | |
| 4080 } | |
| 4081 | |
| 4082 if (wheelSamples < 20) { | |
| 4083 if (display.wheelStartX == null) { | |
| 4084 display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.sc
rollTop; | |
| 4085 display.wheelDX = dx; display.wheelDY = dy; | |
| 4086 setTimeout(function() { | |
| 4087 if (display.wheelStartX == null) return; | |
| 4088 var movedX = scroll.scrollLeft - display.wheelStartX; | |
| 4089 var movedY = scroll.scrollTop - display.wheelStartY; | |
| 4090 var sample = (movedY && display.wheelDY && movedY / display.wheelDY) |
| | |
| 4091 (movedX && display.wheelDX && movedX / display.wheelDX); | |
| 4092 display.wheelStartX = display.wheelStartY = null; | |
| 4093 if (!sample) return; | |
| 4094 wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (w
heelSamples + 1); | |
| 4095 ++wheelSamples; | |
| 4096 }, 200); | |
| 4097 } else { | |
| 4098 display.wheelDX += dx; display.wheelDY += dy; | |
| 4099 } | |
| 4100 } | |
| 4101 } | |
| 4102 | |
| 4103 // KEY EVENTS | |
| 4104 | |
| 4105 // Run a handler that was bound to a key. | |
| 4106 function doHandleBinding(cm, bound, dropShift) { | |
| 4107 if (typeof bound == "string") { | |
| 4108 bound = commands[bound]; | |
| 4109 if (!bound) return false; | |
| 4110 } | |
| 4111 // Ensure previous input has been read, so that the handler sees a | |
| 4112 // consistent view of the document | |
| 4113 cm.display.input.ensurePolled(); | |
| 4114 var prevShift = cm.display.shift, done = false; | |
| 4115 try { | |
| 4116 if (cm.isReadOnly()) cm.state.suppressEdits = true; | |
| 4117 if (dropShift) cm.display.shift = false; | |
| 4118 done = bound(cm) != Pass; | |
| 4119 } finally { | |
| 4120 cm.display.shift = prevShift; | |
| 4121 cm.state.suppressEdits = false; | |
| 4122 } | |
| 4123 return done; | |
| 4124 } | |
| 4125 | |
| 4126 function lookupKeyForEditor(cm, name, handle) { | |
| 4127 for (var i = 0; i < cm.state.keyMaps.length; i++) { | |
| 4128 var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); | |
| 4129 if (result) return result; | |
| 4130 } | |
| 4131 return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle
, cm)) | |
| 4132 || lookupKey(name, cm.options.keyMap, handle, cm); | |
| 4133 } | |
| 4134 | |
| 4135 var stopSeq = new Delayed; | |
| 4136 function dispatchKey(cm, name, e, handle) { | |
| 4137 var seq = cm.state.keySeq; | |
| 4138 if (seq) { | |
| 4139 if (isModifierKey(name)) return "handled"; | |
| 4140 stopSeq.set(50, function() { | |
| 4141 if (cm.state.keySeq == seq) { | |
| 4142 cm.state.keySeq = null; | |
| 4143 cm.display.input.reset(); | |
| 4144 } | |
| 4145 }); | |
| 4146 name = seq + " " + name; | |
| 4147 } | |
| 4148 var result = lookupKeyForEditor(cm, name, handle); | |
| 4149 | |
| 4150 if (result == "multi") | |
| 4151 cm.state.keySeq = name; | |
| 4152 if (result == "handled") | |
| 4153 signalLater(cm, "keyHandled", cm, name, e); | |
| 4154 | |
| 4155 if (result == "handled" || result == "multi") { | |
| 4156 e_preventDefault(e); | |
| 4157 restartBlink(cm); | |
| 4158 } | |
| 4159 | |
| 4160 if (seq && !result && /\'$/.test(name)) { | |
| 4161 e_preventDefault(e); | |
| 4162 return true; | |
| 4163 } | |
| 4164 return !!result; | |
| 4165 } | |
| 4166 | |
| 4167 // Handle a key from the keydown event. | |
| 4168 function handleKeyBinding(cm, e) { | |
| 4169 var name = keyName(e, true); | |
| 4170 if (!name) return false; | |
| 4171 | |
| 4172 if (e.shiftKey && !cm.state.keySeq) { | |
| 4173 // First try to resolve full name (including 'Shift-'). Failing | |
| 4174 // that, see if there is a cursor-motion command (starting with | |
| 4175 // 'go') bound to the keyname without 'Shift-'. | |
| 4176 return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBin
ding(cm, b, true);}) | |
| 4177 || dispatchKey(cm, name, e, function(b) { | |
| 4178 if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) | |
| 4179 return doHandleBinding(cm, b); | |
| 4180 }); | |
| 4181 } else { | |
| 4182 return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b
); }); | |
| 4183 } | |
| 4184 } | |
| 4185 | |
| 4186 // Handle a key from the keypress event | |
| 4187 function handleCharBinding(cm, e, ch) { | |
| 4188 return dispatchKey(cm, "'" + ch + "'", e, | |
| 4189 function(b) { return doHandleBinding(cm, b, true); }); | |
| 4190 } | |
| 4191 | |
| 4192 var lastStoppedKey = null; | |
| 4193 function onKeyDown(e) { | |
| 4194 var cm = this; | |
| 4195 cm.curOp.focus = activeElt(); | |
| 4196 if (signalDOMEvent(cm, e)) return; | |
| 4197 // IE does strange things with escape. | |
| 4198 if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; | |
| 4199 var code = e.keyCode; | |
| 4200 cm.display.shift = code == 16 || e.shiftKey; | |
| 4201 var handled = handleKeyBinding(cm, e); | |
| 4202 if (presto) { | |
| 4203 lastStoppedKey = handled ? code : null; | |
| 4204 // Opera has no cut event... we try to at least catch the key combo | |
| 4205 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKe
y)) | |
| 4206 cm.replaceSelection("", null, "cut"); | |
| 4207 } | |
| 4208 | |
| 4209 // Turn mouse into crosshair when Alt is held on Mac. | |
| 4210 if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.classN
ame)) | |
| 4211 showCrossHair(cm); | |
| 4212 } | |
| 4213 | |
| 4214 function showCrossHair(cm) { | |
| 4215 var lineDiv = cm.display.lineDiv; | |
| 4216 addClass(lineDiv, "CodeMirror-crosshair"); | |
| 4217 | |
| 4218 function up(e) { | |
| 4219 if (e.keyCode == 18 || !e.altKey) { | |
| 4220 rmClass(lineDiv, "CodeMirror-crosshair"); | |
| 4221 off(document, "keyup", up); | |
| 4222 off(document, "mouseover", up); | |
| 4223 } | |
| 4224 } | |
| 4225 on(document, "keyup", up); | |
| 4226 on(document, "mouseover", up); | |
| 4227 } | |
| 4228 | |
| 4229 function onKeyUp(e) { | |
| 4230 if (e.keyCode == 16) this.doc.sel.shift = false; | |
| 4231 signalDOMEvent(this, e); | |
| 4232 } | |
| 4233 | |
| 4234 function onKeyPress(e) { | |
| 4235 var cm = this; | |
| 4236 if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e
.altKey || mac && e.metaKey) return; | |
| 4237 var keyCode = e.keyCode, charCode = e.charCode; | |
| 4238 if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDe
fault(e); return;} | |
| 4239 if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) retur
n; | |
| 4240 var ch = String.fromCharCode(charCode == null ? keyCode : charCode); | |
| 4241 if (handleCharBinding(cm, e, ch)) return; | |
| 4242 cm.display.input.onKeyPress(e); | |
| 4243 } | |
| 4244 | |
| 4245 // FOCUS/BLUR EVENTS | |
| 4246 | |
| 4247 function delayBlurEvent(cm) { | |
| 4248 cm.state.delayingBlurEvent = true; | |
| 4249 setTimeout(function() { | |
| 4250 if (cm.state.delayingBlurEvent) { | |
| 4251 cm.state.delayingBlurEvent = false; | |
| 4252 onBlur(cm); | |
| 4253 } | |
| 4254 }, 100); | |
| 4255 } | |
| 4256 | |
| 4257 function onFocus(cm) { | |
| 4258 if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false; | |
| 4259 | |
| 4260 if (cm.options.readOnly == "nocursor") return; | |
| 4261 if (!cm.state.focused) { | |
| 4262 signal(cm, "focus", cm); | |
| 4263 cm.state.focused = true; | |
| 4264 addClass(cm.display.wrapper, "CodeMirror-focused"); | |
| 4265 // This test prevents this from firing when a context | |
| 4266 // menu is closed (since the input reset would kill the | |
| 4267 // select-all detection hack) | |
| 4268 if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { | |
| 4269 cm.display.input.reset(); | |
| 4270 if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20)
; // Issue #1730 | |
| 4271 } | |
| 4272 cm.display.input.receivedFocus(); | |
| 4273 } | |
| 4274 restartBlink(cm); | |
| 4275 } | |
| 4276 function onBlur(cm) { | |
| 4277 if (cm.state.delayingBlurEvent) return; | |
| 4278 | |
| 4279 if (cm.state.focused) { | |
| 4280 signal(cm, "blur", cm); | |
| 4281 cm.state.focused = false; | |
| 4282 rmClass(cm.display.wrapper, "CodeMirror-focused"); | |
| 4283 } | |
| 4284 clearInterval(cm.display.blinker); | |
| 4285 setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 15
0); | |
| 4286 } | |
| 4287 | |
| 4288 // CONTEXT MENU HANDLING | |
| 4289 | |
| 4290 // To make the context menu work, we need to briefly unhide the | |
| 4291 // textarea (making it as unobtrusive as possible) to let the | |
| 4292 // right-click take effect on it. | |
| 4293 function onContextMenu(cm, e) { | |
| 4294 if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return; | |
| 4295 if (signalDOMEvent(cm, e, "contextmenu")) return; | |
| 4296 cm.display.input.onContextMenu(e); | |
| 4297 } | |
| 4298 | |
| 4299 function contextMenuInGutter(cm, e) { | |
| 4300 if (!hasHandler(cm, "gutterContextMenu")) return false; | |
| 4301 return gutterEvent(cm, e, "gutterContextMenu", false); | |
| 4302 } | |
| 4303 | |
| 4304 // UPDATING | |
| 4305 | |
| 4306 // Compute the position of the end of a change (its 'to' property | |
| 4307 // refers to the pre-change end). | |
| 4308 var changeEnd = CodeMirror.changeEnd = function(change) { | |
| 4309 if (!change.text) return change.to; | |
| 4310 return Pos(change.from.line + change.text.length - 1, | |
| 4311 lst(change.text).length + (change.text.length == 1 ? change.from.
ch : 0)); | |
| 4312 }; | |
| 4313 | |
| 4314 // Adjust a position to refer to the post-change position of the | |
| 4315 // same text, or the end of the change if the change covers it. | |
| 4316 function adjustForChange(pos, change) { | |
| 4317 if (cmp(pos, change.from) < 0) return pos; | |
| 4318 if (cmp(pos, change.to) <= 0) return changeEnd(change); | |
| 4319 | |
| 4320 var line = pos.line + change.text.length - (change.to.line - change.from.lin
e) - 1, ch = pos.ch; | |
| 4321 if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch; | |
| 4322 return Pos(line, ch); | |
| 4323 } | |
| 4324 | |
| 4325 function computeSelAfterChange(doc, change) { | |
| 4326 var out = []; | |
| 4327 for (var i = 0; i < doc.sel.ranges.length; i++) { | |
| 4328 var range = doc.sel.ranges[i]; | |
| 4329 out.push(new Range(adjustForChange(range.anchor, change), | |
| 4330 adjustForChange(range.head, change))); | |
| 4331 } | |
| 4332 return normalizeSelection(out, doc.sel.primIndex); | |
| 4333 } | |
| 4334 | |
| 4335 function offsetPos(pos, old, nw) { | |
| 4336 if (pos.line == old.line) | |
| 4337 return Pos(nw.line, pos.ch - old.ch + nw.ch); | |
| 4338 else | |
| 4339 return Pos(nw.line + (pos.line - old.line), pos.ch); | |
| 4340 } | |
| 4341 | |
| 4342 // Used by replaceSelections to allow moving the selection to the | |
| 4343 // start or around the replaced test. Hint may be "start" or "around". | |
| 4344 function computeReplacedSel(doc, changes, hint) { | |
| 4345 var out = []; | |
| 4346 var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; | |
| 4347 for (var i = 0; i < changes.length; i++) { | |
| 4348 var change = changes[i]; | |
| 4349 var from = offsetPos(change.from, oldPrev, newPrev); | |
| 4350 var to = offsetPos(changeEnd(change), oldPrev, newPrev); | |
| 4351 oldPrev = change.to; | |
| 4352 newPrev = to; | |
| 4353 if (hint == "around") { | |
| 4354 var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; | |
| 4355 out[i] = new Range(inv ? to : from, inv ? from : to); | |
| 4356 } else { | |
| 4357 out[i] = new Range(from, from); | |
| 4358 } | |
| 4359 } | |
| 4360 return new Selection(out, doc.sel.primIndex); | |
| 4361 } | |
| 4362 | |
| 4363 // Allow "beforeChange" event handlers to influence a change | |
| 4364 function filterChange(doc, change, update) { | |
| 4365 var obj = { | |
| 4366 canceled: false, | |
| 4367 from: change.from, | |
| 4368 to: change.to, | |
| 4369 text: change.text, | |
| 4370 origin: change.origin, | |
| 4371 cancel: function() { this.canceled = true; } | |
| 4372 }; | |
| 4373 if (update) obj.update = function(from, to, text, origin) { | |
| 4374 if (from) this.from = clipPos(doc, from); | |
| 4375 if (to) this.to = clipPos(doc, to); | |
| 4376 if (text) this.text = text; | |
| 4377 if (origin !== undefined) this.origin = origin; | |
| 4378 }; | |
| 4379 signal(doc, "beforeChange", doc, obj); | |
| 4380 if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); | |
| 4381 | |
| 4382 if (obj.canceled) return null; | |
| 4383 return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; | |
| 4384 } | |
| 4385 | |
| 4386 // Apply a change to a document, and add it to the document's | |
| 4387 // history, and propagating it to all linked documents. | |
| 4388 function makeChange(doc, change, ignoreReadOnly) { | |
| 4389 if (doc.cm) { | |
| 4390 if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignor
eReadOnly); | |
| 4391 if (doc.cm.state.suppressEdits) return; | |
| 4392 } | |
| 4393 | |
| 4394 if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeC
hange")) { | |
| 4395 change = filterChange(doc, change, true); | |
| 4396 if (!change) return; | |
| 4397 } | |
| 4398 | |
| 4399 // Possibly split or suppress the update based on the presence | |
| 4400 // of read-only spans in its range. | |
| 4401 var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc,
change.from, change.to); | |
| 4402 if (split) { | |
| 4403 for (var i = split.length - 1; i >= 0; --i) | |
| 4404 makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? ["
"] : change.text}); | |
| 4405 } else { | |
| 4406 makeChangeInner(doc, change); | |
| 4407 } | |
| 4408 } | |
| 4409 | |
| 4410 function makeChangeInner(doc, change) { | |
| 4411 if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, chan
ge.to) == 0) return; | |
| 4412 var selAfter = computeSelAfterChange(doc, change); | |
| 4413 addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); | |
| 4414 | |
| 4415 makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, chang
e)); | |
| 4416 var rebased = []; | |
| 4417 | |
| 4418 linkedDocs(doc, function(doc, sharedHist) { | |
| 4419 if (!sharedHist && indexOf(rebased, doc.history) == -1) { | |
| 4420 rebaseHist(doc.history, change); | |
| 4421 rebased.push(doc.history); | |
| 4422 } | |
| 4423 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)
); | |
| 4424 }); | |
| 4425 } | |
| 4426 | |
| 4427 // Revert a change stored in a document's history. | |
| 4428 function makeChangeFromHistory(doc, type, allowSelectionOnly) { | |
| 4429 if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) return; | |
| 4430 | |
| 4431 var hist = doc.history, event, selAfter = doc.sel; | |
| 4432 var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo"
? hist.undone : hist.done; | |
| 4433 | |
| 4434 // Verify that there is a useable event (so that ctrl-z won't | |
| 4435 // needlessly clear selection events) | |
| 4436 for (var i = 0; i < source.length; i++) { | |
| 4437 event = source[i]; | |
| 4438 if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.r
anges) | |
| 4439 break; | |
| 4440 } | |
| 4441 if (i == source.length) return; | |
| 4442 hist.lastOrigin = hist.lastSelOrigin = null; | |
| 4443 | |
| 4444 for (;;) { | |
| 4445 event = source.pop(); | |
| 4446 if (event.ranges) { | |
| 4447 pushSelectionToHistory(event, dest); | |
| 4448 if (allowSelectionOnly && !event.equals(doc.sel)) { | |
| 4449 setSelection(doc, event, {clearRedo: false}); | |
| 4450 return; | |
| 4451 } | |
| 4452 selAfter = event; | |
| 4453 } | |
| 4454 else break; | |
| 4455 } | |
| 4456 | |
| 4457 // Build up a reverse change object to add to the opposite history | |
| 4458 // stack (redo when undoing, and vice versa). | |
| 4459 var antiChanges = []; | |
| 4460 pushSelectionToHistory(selAfter, dest); | |
| 4461 dest.push({changes: antiChanges, generation: hist.generation}); | |
| 4462 hist.generation = event.generation || ++hist.maxGeneration; | |
| 4463 | |
| 4464 var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm,
"beforeChange"); | |
| 4465 | |
| 4466 for (var i = event.changes.length - 1; i >= 0; --i) { | |
| 4467 var change = event.changes[i]; | |
| 4468 change.origin = type; | |
| 4469 if (filter && !filterChange(doc, change, false)) { | |
| 4470 source.length = 0; | |
| 4471 return; | |
| 4472 } | |
| 4473 | |
| 4474 antiChanges.push(historyChangeFromChange(doc, change)); | |
| 4475 | |
| 4476 var after = i ? computeSelAfterChange(doc, change) : lst(source); | |
| 4477 makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); | |
| 4478 if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(
change)}); | |
| 4479 var rebased = []; | |
| 4480 | |
| 4481 // Propagate to the linked documents | |
| 4482 linkedDocs(doc, function(doc, sharedHist) { | |
| 4483 if (!sharedHist && indexOf(rebased, doc.history) == -1) { | |
| 4484 rebaseHist(doc.history, change); | |
| 4485 rebased.push(doc.history); | |
| 4486 } | |
| 4487 makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); | |
| 4488 }); | |
| 4489 } | |
| 4490 } | |
| 4491 | |
| 4492 // Sub-views need their line numbers shifted when text is added | |
| 4493 // above or below them in the parent document. | |
| 4494 function shiftDoc(doc, distance) { | |
| 4495 if (distance == 0) return; | |
| 4496 doc.first += distance; | |
| 4497 doc.sel = new Selection(map(doc.sel.ranges, function(range) { | |
| 4498 return new Range(Pos(range.anchor.line + distance, range.anchor.ch), | |
| 4499 Pos(range.head.line + distance, range.head.ch)); | |
| 4500 }), doc.sel.primIndex); | |
| 4501 if (doc.cm) { | |
| 4502 regChange(doc.cm, doc.first, doc.first - distance, distance); | |
| 4503 for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) | |
| 4504 regLineChange(doc.cm, l, "gutter"); | |
| 4505 } | |
| 4506 } | |
| 4507 | |
| 4508 // More lower-level change function, handling only a single document | |
| 4509 // (not linked ones). | |
| 4510 function makeChangeSingleDoc(doc, change, selAfter, spans) { | |
| 4511 if (doc.cm && !doc.cm.curOp) | |
| 4512 return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans
); | |
| 4513 | |
| 4514 if (change.to.line < doc.first) { | |
| 4515 shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)
); | |
| 4516 return; | |
| 4517 } | |
| 4518 if (change.from.line > doc.lastLine()) return; | |
| 4519 | |
| 4520 // Clip the change to the size of this doc | |
| 4521 if (change.from.line < doc.first) { | |
| 4522 var shift = change.text.length - 1 - (doc.first - change.from.line); | |
| 4523 shiftDoc(doc, shift); | |
| 4524 change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.
to.ch), | |
| 4525 text: [lst(change.text)], origin: change.origin}; | |
| 4526 } | |
| 4527 var last = doc.lastLine(); | |
| 4528 if (change.to.line > last) { | |
| 4529 change = {from: change.from, to: Pos(last, getLine(doc, last).text.length)
, | |
| 4530 text: [change.text[0]], origin: change.origin}; | |
| 4531 } | |
| 4532 | |
| 4533 change.removed = getBetween(doc, change.from, change.to); | |
| 4534 | |
| 4535 if (!selAfter) selAfter = computeSelAfterChange(doc, change); | |
| 4536 if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); | |
| 4537 else updateDoc(doc, change, spans); | |
| 4538 setSelectionNoUndo(doc, selAfter, sel_dontScroll); | |
| 4539 } | |
| 4540 | |
| 4541 // Handle the interaction of a change to a document with the editor | |
| 4542 // that this document is part of. | |
| 4543 function makeChangeSingleDocInEditor(cm, change, spans) { | |
| 4544 var doc = cm.doc, display = cm.display, from = change.from, to = change.to; | |
| 4545 | |
| 4546 var recomputeMaxLength = false, checkWidthStart = from.line; | |
| 4547 if (!cm.options.lineWrapping) { | |
| 4548 checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); | |
| 4549 doc.iter(checkWidthStart, to.line + 1, function(line) { | |
| 4550 if (line == display.maxLine) { | |
| 4551 recomputeMaxLength = true; | |
| 4552 return true; | |
| 4553 } | |
| 4554 }); | |
| 4555 } | |
| 4556 | |
| 4557 if (doc.sel.contains(change.from, change.to) > -1) | |
| 4558 signalCursorActivity(cm); | |
| 4559 | |
| 4560 updateDoc(doc, change, spans, estimateHeight(cm)); | |
| 4561 | |
| 4562 if (!cm.options.lineWrapping) { | |
| 4563 doc.iter(checkWidthStart, from.line + change.text.length, function(line) { | |
| 4564 var len = lineLength(line); | |
| 4565 if (len > display.maxLineLength) { | |
| 4566 display.maxLine = line; | |
| 4567 display.maxLineLength = len; | |
| 4568 display.maxLineChanged = true; | |
| 4569 recomputeMaxLength = false; | |
| 4570 } | |
| 4571 }); | |
| 4572 if (recomputeMaxLength) cm.curOp.updateMaxLine = true; | |
| 4573 } | |
| 4574 | |
| 4575 // Adjust frontier, schedule worker | |
| 4576 doc.frontier = Math.min(doc.frontier, from.line); | |
| 4577 startWorker(cm, 400); | |
| 4578 | |
| 4579 var lendiff = change.text.length - (to.line - from.line) - 1; | |
| 4580 // Remember that these lines changed, for updating the display | |
| 4581 if (change.full) | |
| 4582 regChange(cm); | |
| 4583 else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpda
te(cm.doc, change)) | |
| 4584 regLineChange(cm, from.line, "text"); | |
| 4585 else | |
| 4586 regChange(cm, from.line, to.line + 1, lendiff); | |
| 4587 | |
| 4588 var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(c
m, "change"); | |
| 4589 if (changeHandler || changesHandler) { | |
| 4590 var obj = { | |
| 4591 from: from, to: to, | |
| 4592 text: change.text, | |
| 4593 removed: change.removed, | |
| 4594 origin: change.origin | |
| 4595 }; | |
| 4596 if (changeHandler) signalLater(cm, "change", cm, obj); | |
| 4597 if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).pu
sh(obj); | |
| 4598 } | |
| 4599 cm.display.selForContextMenu = null; | |
| 4600 } | |
| 4601 | |
| 4602 function replaceRange(doc, code, from, to, origin) { | |
| 4603 if (!to) to = from; | |
| 4604 if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } | |
| 4605 if (typeof code == "string") code = doc.splitLines(code); | |
| 4606 makeChange(doc, {from: from, to: to, text: code, origin: origin}); | |
| 4607 } | |
| 4608 | |
| 4609 // SCROLLING THINGS INTO VIEW | |
| 4610 | |
| 4611 // If an editor sits on the top or bottom of the window, partially | |
| 4612 // scrolled out of view, this ensures that the cursor is visible. | |
| 4613 function maybeScrollWindow(cm, coords) { | |
| 4614 if (signalDOMEvent(cm, "scrollCursorIntoView")) return; | |
| 4615 | |
| 4616 var display = cm.display, box = display.sizer.getBoundingClientRect(), doScr
oll = null; | |
| 4617 if (coords.top + box.top < 0) doScroll = true; | |
| 4618 else if (coords.bottom + box.top > (window.innerHeight || document.documentE
lement.clientHeight)) doScroll = false; | |
| 4619 if (doScroll != null && !phantom) { | |
| 4620 var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + | |
| 4621 (coords.top - display.viewOffset - paddingTop(cm.disp
lay)) + "px; height: " + | |
| 4622 (coords.bottom - coords.top + scrollGap(cm) + display
.barHeight) + "px; left: " + | |
| 4623 coords.left + "px; width: 2px;"); | |
| 4624 cm.display.lineSpace.appendChild(scrollNode); | |
| 4625 scrollNode.scrollIntoView(doScroll); | |
| 4626 cm.display.lineSpace.removeChild(scrollNode); | |
| 4627 } | |
| 4628 } | |
| 4629 | |
| 4630 // Scroll a given position into view (immediately), verifying that | |
| 4631 // it actually became visible (as line heights are accurately | |
| 4632 // measured, the position of something may 'drift' during drawing). | |
| 4633 function scrollPosIntoView(cm, pos, end, margin) { | |
| 4634 if (margin == null) margin = 0; | |
| 4635 for (var limit = 0; limit < 5; limit++) { | |
| 4636 var changed = false, coords = cursorCoords(cm, pos); | |
| 4637 var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); | |
| 4638 var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.lef
t), | |
| 4639 Math.min(coords.top, endCoords.top) - m
argin, | |
| 4640 Math.max(coords.left, endCoords.left), | |
| 4641 Math.max(coords.bottom, endCoords.botto
m) + margin); | |
| 4642 var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; | |
| 4643 if (scrollPos.scrollTop != null) { | |
| 4644 setScrollTop(cm, scrollPos.scrollTop); | |
| 4645 if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; | |
| 4646 } | |
| 4647 if (scrollPos.scrollLeft != null) { | |
| 4648 setScrollLeft(cm, scrollPos.scrollLeft); | |
| 4649 if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; | |
| 4650 } | |
| 4651 if (!changed) break; | |
| 4652 } | |
| 4653 return coords; | |
| 4654 } | |
| 4655 | |
| 4656 // Scroll a given set of coordinates into view (immediately). | |
| 4657 function scrollIntoView(cm, x1, y1, x2, y2) { | |
| 4658 var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); | |
| 4659 if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); | |
| 4660 if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); | |
| 4661 } | |
| 4662 | |
| 4663 // Calculate a new scroll position needed to scroll the given | |
| 4664 // rectangle into view. Returns an object with scrollTop and | |
| 4665 // scrollLeft properties. When these are undefined, the | |
| 4666 // vertical/horizontal position does not need to be adjusted. | |
| 4667 function calculateScrollPos(cm, x1, y1, x2, y2) { | |
| 4668 var display = cm.display, snapMargin = textHeight(cm.display); | |
| 4669 if (y1 < 0) y1 = 0; | |
| 4670 var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop
: display.scroller.scrollTop; | |
| 4671 var screen = displayHeight(cm), result = {}; | |
| 4672 if (y2 - y1 > screen) y2 = y1 + screen; | |
| 4673 var docBottom = cm.doc.height + paddingVert(display); | |
| 4674 var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; | |
| 4675 if (y1 < screentop) { | |
| 4676 result.scrollTop = atTop ? 0 : y1; | |
| 4677 } else if (y2 > screentop + screen) { | |
| 4678 var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); | |
| 4679 if (newTop != screentop) result.scrollTop = newTop; | |
| 4680 } | |
| 4681 | |
| 4682 var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLe
ft : display.scroller.scrollLeft; | |
| 4683 var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.o
ffsetWidth : 0); | |
| 4684 var tooWide = x2 - x1 > screenw; | |
| 4685 if (tooWide) x2 = x1 + screenw; | |
| 4686 if (x1 < 10) | |
| 4687 result.scrollLeft = 0; | |
| 4688 else if (x1 < screenleft) | |
| 4689 result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); | |
| 4690 else if (x2 > screenw + screenleft - 3) | |
| 4691 result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; | |
| 4692 return result; | |
| 4693 } | |
| 4694 | |
| 4695 // Store a relative adjustment to the scroll position in the current | |
| 4696 // operation (to be applied when the operation finishes). | |
| 4697 function addToScrollPos(cm, left, top) { | |
| 4698 if (left != null || top != null) resolveScrollToPos(cm); | |
| 4699 if (left != null) | |
| 4700 cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : c
m.curOp.scrollLeft) + left; | |
| 4701 if (top != null) | |
| 4702 cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.c
urOp.scrollTop) + top; | |
| 4703 } | |
| 4704 | |
| 4705 // Make sure that at the end of the operation the current cursor is | |
| 4706 // shown. | |
| 4707 function ensureCursorVisible(cm) { | |
| 4708 resolveScrollToPos(cm); | |
| 4709 var cur = cm.getCursor(), from = cur, to = cur; | |
| 4710 if (!cm.options.lineWrapping) { | |
| 4711 from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur; | |
| 4712 to = Pos(cur.line, cur.ch + 1); | |
| 4713 } | |
| 4714 cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollM
argin, isCursor: true}; | |
| 4715 } | |
| 4716 | |
| 4717 // When an operation has its scrollToPos property set, and another | |
| 4718 // scroll action is applied before the end of the operation, this | |
| 4719 // 'simulates' scrolling that position into view in a cheap way, so | |
| 4720 // that the effect of intermediate scroll commands is not ignored. | |
| 4721 function resolveScrollToPos(cm) { | |
| 4722 var range = cm.curOp.scrollToPos; | |
| 4723 if (range) { | |
| 4724 cm.curOp.scrollToPos = null; | |
| 4725 var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.t
o); | |
| 4726 var sPos = calculateScrollPos(cm, Math.min(from.left, to.left), | |
| 4727 Math.min(from.top, to.top) - range.margin, | |
| 4728 Math.max(from.right, to.right), | |
| 4729 Math.max(from.bottom, to.bottom) + range.mar
gin); | |
| 4730 cm.scrollTo(sPos.scrollLeft, sPos.scrollTop); | |
| 4731 } | |
| 4732 } | |
| 4733 | |
| 4734 // API UTILITIES | |
| 4735 | |
| 4736 // Indent the given line. The how parameter can be "smart", | |
| 4737 // "add"/null, "subtract", or "prev". When aggressive is false | |
| 4738 // (typically set to true for forced single-line indents), empty | |
| 4739 // lines are not indented, and places where the mode returns Pass | |
| 4740 // are left alone. | |
| 4741 function indentLine(cm, n, how, aggressive) { | |
| 4742 var doc = cm.doc, state; | |
| 4743 if (how == null) how = "add"; | |
| 4744 if (how == "smart") { | |
| 4745 // Fall back to "prev" when the mode doesn't have an indentation | |
| 4746 // method. | |
| 4747 if (!doc.mode.indent) how = "prev"; | |
| 4748 else state = getStateBefore(cm, n); | |
| 4749 } | |
| 4750 | |
| 4751 var tabSize = cm.options.tabSize; | |
| 4752 var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize)
; | |
| 4753 if (line.stateAfter) line.stateAfter = null; | |
| 4754 var curSpaceString = line.text.match(/^\s*/)[0], indentation; | |
| 4755 if (!aggressive && !/\S/.test(line.text)) { | |
| 4756 indentation = 0; | |
| 4757 how = "not"; | |
| 4758 } else if (how == "smart") { | |
| 4759 indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length
), line.text); | |
| 4760 if (indentation == Pass || indentation > 150) { | |
| 4761 if (!aggressive) return; | |
| 4762 how = "prev"; | |
| 4763 } | |
| 4764 } | |
| 4765 if (how == "prev") { | |
| 4766 if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null,
tabSize); | |
| 4767 else indentation = 0; | |
| 4768 } else if (how == "add") { | |
| 4769 indentation = curSpace + cm.options.indentUnit; | |
| 4770 } else if (how == "subtract") { | |
| 4771 indentation = curSpace - cm.options.indentUnit; | |
| 4772 } else if (typeof how == "number") { | |
| 4773 indentation = curSpace + how; | |
| 4774 } | |
| 4775 indentation = Math.max(0, indentation); | |
| 4776 | |
| 4777 var indentString = "", pos = 0; | |
| 4778 if (cm.options.indentWithTabs) | |
| 4779 for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; i
ndentString += "\t";} | |
| 4780 if (pos < indentation) indentString += spaceStr(indentation - pos); | |
| 4781 | |
| 4782 if (indentString != curSpaceString) { | |
| 4783 replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length),
"+input"); | |
| 4784 line.stateAfter = null; | |
| 4785 return true; | |
| 4786 } else { | |
| 4787 // Ensure that, if the cursor was in the whitespace at the start | |
| 4788 // of the line, it is moved to the end of that space. | |
| 4789 for (var i = 0; i < doc.sel.ranges.length; i++) { | |
| 4790 var range = doc.sel.ranges[i]; | |
| 4791 if (range.head.line == n && range.head.ch < curSpaceString.length) { | |
| 4792 var pos = Pos(n, curSpaceString.length); | |
| 4793 replaceOneSelection(doc, i, new Range(pos, pos)); | |
| 4794 break; | |
| 4795 } | |
| 4796 } | |
| 4797 } | |
| 4798 } | |
| 4799 | |
| 4800 // Utility for applying a change to a line by handle or number, | |
| 4801 // returning the number and optionally registering the line as | |
| 4802 // changed. | |
| 4803 function changeLine(doc, handle, changeType, op) { | |
| 4804 var no = handle, line = handle; | |
| 4805 if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); | |
| 4806 else no = lineNo(handle); | |
| 4807 if (no == null) return null; | |
| 4808 if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType); | |
| 4809 return line; | |
| 4810 } | |
| 4811 | |
| 4812 // Helper for deleting text near the selection(s), used to implement | |
| 4813 // backspace, delete, and similar functionality. | |
| 4814 function deleteNearSelection(cm, compute) { | |
| 4815 var ranges = cm.doc.sel.ranges, kill = []; | |
| 4816 // Build up a set of ranges to kill first, merging overlapping | |
| 4817 // ranges. | |
| 4818 for (var i = 0; i < ranges.length; i++) { | |
| 4819 var toKill = compute(ranges[i]); | |
| 4820 while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { | |
| 4821 var replaced = kill.pop(); | |
| 4822 if (cmp(replaced.from, toKill.from) < 0) { | |
| 4823 toKill.from = replaced.from; | |
| 4824 break; | |
| 4825 } | |
| 4826 } | |
| 4827 kill.push(toKill); | |
| 4828 } | |
| 4829 // Next, remove those actual ranges. | |
| 4830 runInOp(cm, function() { | |
| 4831 for (var i = kill.length - 1; i >= 0; i--) | |
| 4832 replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); | |
| 4833 ensureCursorVisible(cm); | |
| 4834 }); | |
| 4835 } | |
| 4836 | |
| 4837 // Used for horizontal relative motion. Dir is -1 or 1 (left or | |
| 4838 // right), unit can be "char", "column" (like char, but doesn't | |
| 4839 // cross line boundaries), "word" (across next word), or "group" (to | |
| 4840 // the start of next group of word or non-word-non-whitespace | |
| 4841 // chars). The visually param controls whether, in right-to-left | |
| 4842 // text, direction 1 means to move towards the next index in the | |
| 4843 // string, or towards the character to the right of the current | |
| 4844 // position. The resulting position will have a hitSide=true | |
| 4845 // property if it reached the end of the document. | |
| 4846 function findPosH(doc, pos, dir, unit, visually) { | |
| 4847 var line = pos.line, ch = pos.ch, origDir = dir; | |
| 4848 var lineObj = getLine(doc, line); | |
| 4849 function findNextLine() { | |
| 4850 var l = line + dir; | |
| 4851 if (l < doc.first || l >= doc.first + doc.size) return false | |
| 4852 line = l; | |
| 4853 return lineObj = getLine(doc, l); | |
| 4854 } | |
| 4855 function moveOnce(boundToLine) { | |
| 4856 var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, tru
e); | |
| 4857 if (next == null) { | |
| 4858 if (!boundToLine && findNextLine()) { | |
| 4859 if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); | |
| 4860 else ch = dir < 0 ? lineObj.text.length : 0; | |
| 4861 } else return false | |
| 4862 } else ch = next; | |
| 4863 return true; | |
| 4864 } | |
| 4865 | |
| 4866 if (unit == "char") { | |
| 4867 moveOnce() | |
| 4868 } else if (unit == "column") { | |
| 4869 moveOnce(true) | |
| 4870 } else if (unit == "word" || unit == "group") { | |
| 4871 var sawType = null, group = unit == "group"; | |
| 4872 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); | |
| 4873 for (var first = true;; first = false) { | |
| 4874 if (dir < 0 && !moveOnce(!first)) break; | |
| 4875 var cur = lineObj.text.charAt(ch) || "\n"; | |
| 4876 var type = isWordChar(cur, helper) ? "w" | |
| 4877 : group && cur == "\n" ? "n" | |
| 4878 : !group || /\s/.test(cur) ? null | |
| 4879 : "p"; | |
| 4880 if (group && !first && !type) type = "s"; | |
| 4881 if (sawType && sawType != type) { | |
| 4882 if (dir < 0) {dir = 1; moveOnce();} | |
| 4883 break; | |
| 4884 } | |
| 4885 | |
| 4886 if (type) sawType = type; | |
| 4887 if (dir > 0 && !moveOnce(!first)) break; | |
| 4888 } | |
| 4889 } | |
| 4890 var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true); | |
| 4891 if (!cmp(pos, result)) result.hitSide = true; | |
| 4892 return result; | |
| 4893 } | |
| 4894 | |
| 4895 // For relative vertical movement. Dir may be -1 or 1. Unit can be | |
| 4896 // "page" or "line". The resulting position will have a hitSide=true | |
| 4897 // property if it reached the end of the document. | |
| 4898 function findPosV(cm, pos, dir, unit) { | |
| 4899 var doc = cm.doc, x = pos.left, y; | |
| 4900 if (unit == "page") { | |
| 4901 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeigh
t || document.documentElement.clientHeight); | |
| 4902 y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.displ
ay)); | |
| 4903 } else if (unit == "line") { | |
| 4904 y = dir > 0 ? pos.bottom + 3 : pos.top - 3; | |
| 4905 } | |
| 4906 for (;;) { | |
| 4907 var target = coordsChar(cm, x, y); | |
| 4908 if (!target.outside) break; | |
| 4909 if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } | |
| 4910 y += dir * 5; | |
| 4911 } | |
| 4912 return target; | |
| 4913 } | |
| 4914 | |
| 4915 // EDITOR METHODS | |
| 4916 | |
| 4917 // The publicly visible API. Note that methodOp(f) means | |
| 4918 // 'wrap f in an operation, performed on its `this` parameter'. | |
| 4919 | |
| 4920 // This is not the complete set of editor methods. Most of the | |
| 4921 // methods defined on the Doc type are also injected into | |
| 4922 // CodeMirror.prototype, for backwards compatibility and | |
| 4923 // convenience. | |
| 4924 | 7770 |
| 4925 CodeMirror.prototype = { | 7771 CodeMirror.prototype = { |
| 4926 constructor: CodeMirror, | 7772 constructor: CodeMirror, |
| 4927 focus: function(){window.focus(); this.display.input.focus();}, | 7773 focus: function(){window.focus(); this.display.input.focus()}, |
| 4928 | 7774 |
| 4929 setOption: function(option, value) { | 7775 setOption: function(option, value) { |
| 4930 var options = this.options, old = options[option]; | 7776 var options = this.options, old = options[option] |
| 4931 if (options[option] == value && option != "mode") return; | 7777 if (options[option] == value && option != "mode") { return } |
| 4932 options[option] = value; | 7778 options[option] = value |
| 4933 if (optionHandlers.hasOwnProperty(option)) | 7779 if (optionHandlers.hasOwnProperty(option)) |
| 4934 operation(this, optionHandlers[option])(this, value, old); | 7780 { operation(this, optionHandlers[option])(this, value, old) } |
| 7781 signal(this, "optionChange", this, option) |
| 4935 }, | 7782 }, |
| 4936 | 7783 |
| 4937 getOption: function(option) {return this.options[option];}, | 7784 getOption: function(option) {return this.options[option]}, |
| 4938 getDoc: function() {return this.doc;}, | 7785 getDoc: function() {return this.doc}, |
| 4939 | 7786 |
| 4940 addKeyMap: function(map, bottom) { | 7787 addKeyMap: function(map, bottom) { |
| 4941 this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); | 7788 this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)) |
| 4942 }, | 7789 }, |
| 4943 removeKeyMap: function(map) { | 7790 removeKeyMap: function(map) { |
| 4944 var maps = this.state.keyMaps; | 7791 var maps = this.state.keyMaps |
| 4945 for (var i = 0; i < maps.length; ++i) | 7792 for (var i = 0; i < maps.length; ++i) |
| 4946 if (maps[i] == map || maps[i].name == map) { | 7793 { if (maps[i] == map || maps[i].name == map) { |
| 4947 maps.splice(i, 1); | 7794 maps.splice(i, 1) |
| 4948 return true; | 7795 return true |
| 4949 } | 7796 } } |
| 4950 }, | 7797 }, |
| 4951 | 7798 |
| 4952 addOverlay: methodOp(function(spec, options) { | 7799 addOverlay: methodOp(function(spec, options) { |
| 4953 var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); | 7800 var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec) |
| 4954 if (mode.startState) throw new Error("Overlays may not be stateful."); | 7801 if (mode.startState) { throw new Error("Overlays may not be stateful.") } |
| 4955 this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && o
ptions.opaque}); | 7802 insertSorted(this.state.overlays, |
| 4956 this.state.modeGen++; | 7803 {mode: mode, modeSpec: spec, opaque: options && options.opaqu
e, |
| 4957 regChange(this); | 7804 priority: (options && options.priority) || 0}, |
| 7805 function (overlay) { return overlay.priority; }) |
| 7806 this.state.modeGen++ |
| 7807 regChange(this) |
| 4958 }), | 7808 }), |
| 4959 removeOverlay: methodOp(function(spec) { | 7809 removeOverlay: methodOp(function(spec) { |
| 4960 var overlays = this.state.overlays; | 7810 var this$1 = this; |
| 7811 |
| 7812 var overlays = this.state.overlays |
| 4961 for (var i = 0; i < overlays.length; ++i) { | 7813 for (var i = 0; i < overlays.length; ++i) { |
| 4962 var cur = overlays[i].modeSpec; | 7814 var cur = overlays[i].modeSpec |
| 4963 if (cur == spec || typeof spec == "string" && cur.name == spec) { | 7815 if (cur == spec || typeof spec == "string" && cur.name == spec) { |
| 4964 overlays.splice(i, 1); | 7816 overlays.splice(i, 1) |
| 4965 this.state.modeGen++; | 7817 this$1.state.modeGen++ |
| 4966 regChange(this); | 7818 regChange(this$1) |
| 4967 return; | 7819 return |
| 4968 } | 7820 } |
| 4969 } | 7821 } |
| 4970 }), | 7822 }), |
| 4971 | 7823 |
| 4972 indentLine: methodOp(function(n, dir, aggressive) { | 7824 indentLine: methodOp(function(n, dir, aggressive) { |
| 4973 if (typeof dir != "string" && typeof dir != "number") { | 7825 if (typeof dir != "string" && typeof dir != "number") { |
| 4974 if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; | 7826 if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev" } |
| 4975 else dir = dir ? "add" : "subtract"; | 7827 else { dir = dir ? "add" : "subtract" } |
| 4976 } | 7828 } |
| 4977 if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); | 7829 if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive) } |
| 4978 }), | 7830 }), |
| 4979 indentSelection: methodOp(function(how) { | 7831 indentSelection: methodOp(function(how) { |
| 4980 var ranges = this.doc.sel.ranges, end = -1; | 7832 var this$1 = this; |
| 7833 |
| 7834 var ranges = this.doc.sel.ranges, end = -1 |
| 4981 for (var i = 0; i < ranges.length; i++) { | 7835 for (var i = 0; i < ranges.length; i++) { |
| 4982 var range = ranges[i]; | 7836 var range = ranges[i] |
| 4983 if (!range.empty()) { | 7837 if (!range.empty()) { |
| 4984 var from = range.from(), to = range.to(); | 7838 var from = range.from(), to = range.to() |
| 4985 var start = Math.max(end, from.line); | 7839 var start = Math.max(end, from.line) |
| 4986 end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; | 7840 end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1 |
| 4987 for (var j = start; j < end; ++j) | 7841 for (var j = start; j < end; ++j) |
| 4988 indentLine(this, j, how); | 7842 { indentLine(this$1, j, how) } |
| 4989 var newRanges = this.doc.sel.ranges; | 7843 var newRanges = this$1.doc.sel.ranges |
| 4990 if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].
from().ch > 0) | 7844 if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].
from().ch > 0) |
| 4991 replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()),
sel_dontScroll); | 7845 { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to
()), sel_dontScroll) } |
| 4992 } else if (range.head.line > end) { | 7846 } else if (range.head.line > end) { |
| 4993 indentLine(this, range.head.line, how, true); | 7847 indentLine(this$1, range.head.line, how, true) |
| 4994 end = range.head.line; | 7848 end = range.head.line |
| 4995 if (i == this.doc.sel.primIndex) ensureCursorVisible(this); | 7849 if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1) } |
| 4996 } | 7850 } |
| 4997 } | 7851 } |
| 4998 }), | 7852 }), |
| 4999 | 7853 |
| 5000 // Fetch the parser token for a given character. Useful for hacks | 7854 // Fetch the parser token for a given character. Useful for hacks |
| 5001 // that want to inspect the mode state (say, for completion). | 7855 // that want to inspect the mode state (say, for completion). |
| 5002 getTokenAt: function(pos, precise) { | 7856 getTokenAt: function(pos, precise) { |
| 5003 return takeToken(this, pos, precise); | 7857 return takeToken(this, pos, precise) |
| 5004 }, | 7858 }, |
| 5005 | 7859 |
| 5006 getLineTokens: function(line, precise) { | 7860 getLineTokens: function(line, precise) { |
| 5007 return takeToken(this, Pos(line), precise, true); | 7861 return takeToken(this, Pos(line), precise, true) |
| 5008 }, | 7862 }, |
| 5009 | 7863 |
| 5010 getTokenTypeAt: function(pos) { | 7864 getTokenTypeAt: function(pos) { |
| 5011 pos = clipPos(this.doc, pos); | 7865 pos = clipPos(this.doc, pos) |
| 5012 var styles = getLineStyles(this, getLine(this.doc, pos.line)); | 7866 var styles = getLineStyles(this, getLine(this.doc, pos.line)) |
| 5013 var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; | 7867 var before = 0, after = (styles.length - 1) / 2, ch = pos.ch |
| 5014 var type; | 7868 var type |
| 5015 if (ch == 0) type = styles[2]; | 7869 if (ch == 0) { type = styles[2] } |
| 5016 else for (;;) { | 7870 else { for (;;) { |
| 5017 var mid = (before + after) >> 1; | 7871 var mid = (before + after) >> 1 |
| 5018 if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; | 7872 if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid } |
| 5019 else if (styles[mid * 2 + 1] < ch) before = mid + 1; | 7873 else if (styles[mid * 2 + 1] < ch) { before = mid + 1 } |
| 5020 else { type = styles[mid * 2 + 2]; break; } | 7874 else { type = styles[mid * 2 + 2]; break } |
| 5021 } | 7875 } } |
| 5022 var cut = type ? type.indexOf("cm-overlay ") : -1; | 7876 var cut = type ? type.indexOf("overlay ") : -1 |
| 5023 return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); | 7877 return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) |
| 5024 }, | 7878 }, |
| 5025 | 7879 |
| 5026 getModeAt: function(pos) { | 7880 getModeAt: function(pos) { |
| 5027 var mode = this.doc.mode; | 7881 var mode = this.doc.mode |
| 5028 if (!mode.innerMode) return mode; | 7882 if (!mode.innerMode) { return mode } |
| 5029 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; | 7883 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode |
| 5030 }, | 7884 }, |
| 5031 | 7885 |
| 5032 getHelper: function(pos, type) { | 7886 getHelper: function(pos, type) { |
| 5033 return this.getHelpers(pos, type)[0]; | 7887 return this.getHelpers(pos, type)[0] |
| 5034 }, | 7888 }, |
| 5035 | 7889 |
| 5036 getHelpers: function(pos, type) { | 7890 getHelpers: function(pos, type) { |
| 5037 var found = []; | 7891 var this$1 = this; |
| 5038 if (!helpers.hasOwnProperty(type)) return found; | 7892 |
| 5039 var help = helpers[type], mode = this.getModeAt(pos); | 7893 var found = [] |
| 7894 if (!helpers.hasOwnProperty(type)) { return found } |
| 7895 var help = helpers[type], mode = this.getModeAt(pos) |
| 5040 if (typeof mode[type] == "string") { | 7896 if (typeof mode[type] == "string") { |
| 5041 if (help[mode[type]]) found.push(help[mode[type]]); | 7897 if (help[mode[type]]) { found.push(help[mode[type]]) } |
| 5042 } else if (mode[type]) { | 7898 } else if (mode[type]) { |
| 5043 for (var i = 0; i < mode[type].length; i++) { | 7899 for (var i = 0; i < mode[type].length; i++) { |
| 5044 var val = help[mode[type][i]]; | 7900 var val = help[mode[type][i]] |
| 5045 if (val) found.push(val); | 7901 if (val) { found.push(val) } |
| 5046 } | 7902 } |
| 5047 } else if (mode.helperType && help[mode.helperType]) { | 7903 } else if (mode.helperType && help[mode.helperType]) { |
| 5048 found.push(help[mode.helperType]); | 7904 found.push(help[mode.helperType]) |
| 5049 } else if (help[mode.name]) { | 7905 } else if (help[mode.name]) { |
| 5050 found.push(help[mode.name]); | 7906 found.push(help[mode.name]) |
| 5051 } | 7907 } |
| 5052 for (var i = 0; i < help._global.length; i++) { | 7908 for (var i$1 = 0; i$1 < help._global.length; i$1++) { |
| 5053 var cur = help._global[i]; | 7909 var cur = help._global[i$1] |
| 5054 if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) | 7910 if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1) |
| 5055 found.push(cur.val); | 7911 { found.push(cur.val) } |
| 5056 } | 7912 } |
| 5057 return found; | 7913 return found |
| 5058 }, | 7914 }, |
| 5059 | 7915 |
| 5060 getStateAfter: function(line, precise) { | 7916 getStateAfter: function(line, precise) { |
| 5061 var doc = this.doc; | 7917 var doc = this.doc |
| 5062 line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); | 7918 line = clipLine(doc, line == null ? doc.first + doc.size - 1: line) |
| 5063 return getStateBefore(this, line + 1, precise); | 7919 return getStateBefore(this, line + 1, precise) |
| 5064 }, | 7920 }, |
| 5065 | 7921 |
| 5066 cursorCoords: function(start, mode) { | 7922 cursorCoords: function(start, mode) { |
| 5067 var pos, range = this.doc.sel.primary(); | 7923 var pos, range = this.doc.sel.primary() |
| 5068 if (start == null) pos = range.head; | 7924 if (start == null) { pos = range.head } |
| 5069 else if (typeof start == "object") pos = clipPos(this.doc, start); | 7925 else if (typeof start == "object") { pos = clipPos(this.doc, start) } |
| 5070 else pos = start ? range.from() : range.to(); | 7926 else { pos = start ? range.from() : range.to() } |
| 5071 return cursorCoords(this, pos, mode || "page"); | 7927 return cursorCoords(this, pos, mode || "page") |
| 5072 }, | 7928 }, |
| 5073 | 7929 |
| 5074 charCoords: function(pos, mode) { | 7930 charCoords: function(pos, mode) { |
| 5075 return charCoords(this, clipPos(this.doc, pos), mode || "page"); | 7931 return charCoords(this, clipPos(this.doc, pos), mode || "page") |
| 5076 }, | 7932 }, |
| 5077 | 7933 |
| 5078 coordsChar: function(coords, mode) { | 7934 coordsChar: function(coords, mode) { |
| 5079 coords = fromCoordSystem(this, coords, mode || "page"); | 7935 coords = fromCoordSystem(this, coords, mode || "page") |
| 5080 return coordsChar(this, coords.left, coords.top); | 7936 return coordsChar(this, coords.left, coords.top) |
| 5081 }, | 7937 }, |
| 5082 | 7938 |
| 5083 lineAtHeight: function(height, mode) { | 7939 lineAtHeight: function(height, mode) { |
| 5084 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top
; | 7940 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top |
| 5085 return lineAtHeight(this.doc, height + this.display.viewOffset); | 7941 return lineAtHeight(this.doc, height + this.display.viewOffset) |
| 5086 }, | 7942 }, |
| 5087 heightAtLine: function(line, mode) { | 7943 heightAtLine: function(line, mode, includeWidgets) { |
| 5088 var end = false, lineObj; | 7944 var end = false, lineObj |
| 5089 if (typeof line == "number") { | 7945 if (typeof line == "number") { |
| 5090 var last = this.doc.first + this.doc.size - 1; | 7946 var last = this.doc.first + this.doc.size - 1 |
| 5091 if (line < this.doc.first) line = this.doc.first; | 7947 if (line < this.doc.first) { line = this.doc.first } |
| 5092 else if (line > last) { line = last; end = true; } | 7948 else if (line > last) { line = last; end = true } |
| 5093 lineObj = getLine(this.doc, line); | 7949 lineObj = getLine(this.doc, line) |
| 5094 } else { | 7950 } else { |
| 5095 lineObj = line; | 7951 lineObj = line |
| 5096 } | 7952 } |
| 5097 return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").t
op + | 7953 return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", i
ncludeWidgets || end).top + |
| 5098 (end ? this.doc.height - heightAtLine(lineObj) : 0); | 7954 (end ? this.doc.height - heightAtLine(lineObj) : 0) |
| 5099 }, | 7955 }, |
| 5100 | 7956 |
| 5101 defaultTextHeight: function() { return textHeight(this.display); }, | 7957 defaultTextHeight: function() { return textHeight(this.display) }, |
| 5102 defaultCharWidth: function() { return charWidth(this.display); }, | 7958 defaultCharWidth: function() { return charWidth(this.display) }, |
| 5103 | 7959 |
| 5104 setGutterMarker: methodOp(function(line, gutterID, value) { | 7960 getViewport: function() { return {from: this.display.viewFrom, to: this.disp
lay.viewTo}}, |
| 5105 return changeLine(this.doc, line, "gutter", function(line) { | |
| 5106 var markers = line.gutterMarkers || (line.gutterMarkers = {}); | |
| 5107 markers[gutterID] = value; | |
| 5108 if (!value && isEmpty(markers)) line.gutterMarkers = null; | |
| 5109 return true; | |
| 5110 }); | |
| 5111 }), | |
| 5112 | |
| 5113 clearGutter: methodOp(function(gutterID) { | |
| 5114 var cm = this, doc = cm.doc, i = doc.first; | |
| 5115 doc.iter(function(line) { | |
| 5116 if (line.gutterMarkers && line.gutterMarkers[gutterID]) { | |
| 5117 line.gutterMarkers[gutterID] = null; | |
| 5118 regLineChange(cm, i, "gutter"); | |
| 5119 if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; | |
| 5120 } | |
| 5121 ++i; | |
| 5122 }); | |
| 5123 }), | |
| 5124 | |
| 5125 lineInfo: function(line) { | |
| 5126 if (typeof line == "number") { | |
| 5127 if (!isLine(this.doc, line)) return null; | |
| 5128 var n = line; | |
| 5129 line = getLine(this.doc, line); | |
| 5130 if (!line) return null; | |
| 5131 } else { | |
| 5132 var n = lineNo(line); | |
| 5133 if (n == null) return null; | |
| 5134 } | |
| 5135 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutter
Markers, | |
| 5136 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.
wrapClass, | |
| 5137 widgets: line.widgets}; | |
| 5138 }, | |
| 5139 | |
| 5140 getViewport: function() { return {from: this.display.viewFrom, to: this.disp
lay.viewTo};}, | |
| 5141 | 7961 |
| 5142 addWidget: function(pos, node, scroll, vert, horiz) { | 7962 addWidget: function(pos, node, scroll, vert, horiz) { |
| 5143 var display = this.display; | 7963 var display = this.display |
| 5144 pos = cursorCoords(this, clipPos(this.doc, pos)); | 7964 pos = cursorCoords(this, clipPos(this.doc, pos)) |
| 5145 var top = pos.bottom, left = pos.left; | 7965 var top = pos.bottom, left = pos.left |
| 5146 node.style.position = "absolute"; | 7966 node.style.position = "absolute" |
| 5147 node.setAttribute("cm-ignore-events", "true"); | 7967 node.setAttribute("cm-ignore-events", "true") |
| 5148 this.display.input.setUneditable(node); | 7968 this.display.input.setUneditable(node) |
| 5149 display.sizer.appendChild(node); | 7969 display.sizer.appendChild(node) |
| 5150 if (vert == "over") { | 7970 if (vert == "over") { |
| 5151 top = pos.top; | 7971 top = pos.top |
| 5152 } else if (vert == "above" || vert == "near") { | 7972 } else if (vert == "above" || vert == "near") { |
| 5153 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), | 7973 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), |
| 5154 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWid
th); | 7974 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWid
th) |
| 5155 // Default to positioning above (if specified and possible); otherwise d
efault to positioning below | 7975 // Default to positioning above (if specified and possible); otherwise d
efault to positioning below |
| 5156 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.
top > node.offsetHeight) | 7976 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.
top > node.offsetHeight) |
| 5157 top = pos.top - node.offsetHeight; | 7977 { top = pos.top - node.offsetHeight } |
| 5158 else if (pos.bottom + node.offsetHeight <= vspace) | 7978 else if (pos.bottom + node.offsetHeight <= vspace) |
| 5159 top = pos.bottom; | 7979 { top = pos.bottom } |
| 5160 if (left + node.offsetWidth > hspace) | 7980 if (left + node.offsetWidth > hspace) |
| 5161 left = hspace - node.offsetWidth; | 7981 { left = hspace - node.offsetWidth } |
| 5162 } | 7982 } |
| 5163 node.style.top = top + "px"; | 7983 node.style.top = top + "px" |
| 5164 node.style.left = node.style.right = ""; | 7984 node.style.left = node.style.right = "" |
| 5165 if (horiz == "right") { | 7985 if (horiz == "right") { |
| 5166 left = display.sizer.clientWidth - node.offsetWidth; | 7986 left = display.sizer.clientWidth - node.offsetWidth |
| 5167 node.style.right = "0px"; | 7987 node.style.right = "0px" |
| 5168 } else { | 7988 } else { |
| 5169 if (horiz == "left") left = 0; | 7989 if (horiz == "left") { left = 0 } |
| 5170 else if (horiz == "middle") left = (display.sizer.clientWidth - node.off
setWidth) / 2; | 7990 else if (horiz == "middle") { left = (display.sizer.clientWidth - node.o
ffsetWidth) / 2 } |
| 5171 node.style.left = left + "px"; | 7991 node.style.left = left + "px" |
| 5172 } | 7992 } |
| 5173 if (scroll) | 7993 if (scroll) |
| 5174 scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offs
etHeight); | 7994 { scrollIntoView(this, {left: left, top: top, right: left + node.offsetW
idth, bottom: top + node.offsetHeight}) } |
| 5175 }, | 7995 }, |
| 5176 | 7996 |
| 5177 triggerOnKeyDown: methodOp(onKeyDown), | 7997 triggerOnKeyDown: methodOp(onKeyDown), |
| 5178 triggerOnKeyPress: methodOp(onKeyPress), | 7998 triggerOnKeyPress: methodOp(onKeyPress), |
| 5179 triggerOnKeyUp: onKeyUp, | 7999 triggerOnKeyUp: onKeyUp, |
| 5180 | 8000 |
| 5181 execCommand: function(cmd) { | 8001 execCommand: function(cmd) { |
| 5182 if (commands.hasOwnProperty(cmd)) | 8002 if (commands.hasOwnProperty(cmd)) |
| 5183 return commands[cmd].call(null, this); | 8003 { return commands[cmd].call(null, this) } |
| 5184 }, | 8004 }, |
| 5185 | 8005 |
| 5186 triggerElectric: methodOp(function(text) { triggerElectric(this, text); }), | 8006 triggerElectric: methodOp(function(text) { triggerElectric(this, text) }), |
| 5187 | 8007 |
| 5188 findPosH: function(from, amount, unit, visually) { | 8008 findPosH: function(from, amount, unit, visually) { |
| 5189 var dir = 1; | 8009 var this$1 = this; |
| 5190 if (amount < 0) { dir = -1; amount = -amount; } | 8010 |
| 5191 for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { | 8011 var dir = 1 |
| 5192 cur = findPosH(this.doc, cur, dir, unit, visually); | 8012 if (amount < 0) { dir = -1; amount = -amount } |
| 5193 if (cur.hitSide) break; | 8013 var cur = clipPos(this.doc, from) |
| 8014 for (var i = 0; i < amount; ++i) { |
| 8015 cur = findPosH(this$1.doc, cur, dir, unit, visually) |
| 8016 if (cur.hitSide) { break } |
| 5194 } | 8017 } |
| 5195 return cur; | 8018 return cur |
| 5196 }, | 8019 }, |
| 5197 | 8020 |
| 5198 moveH: methodOp(function(dir, unit) { | 8021 moveH: methodOp(function(dir, unit) { |
| 5199 var cm = this; | 8022 var this$1 = this; |
| 5200 cm.extendSelectionsBy(function(range) { | 8023 |
| 5201 if (cm.display.shift || cm.doc.extend || range.empty()) | 8024 this.extendSelectionsBy(function (range) { |
| 5202 return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisua
lly); | 8025 if (this$1.display.shift || this$1.doc.extend || range.empty()) |
| 8026 { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rt
lMoveVisually) } |
| 5203 else | 8027 else |
| 5204 return dir < 0 ? range.from() : range.to(); | 8028 { return dir < 0 ? range.from() : range.to() } |
| 5205 }, sel_move); | 8029 }, sel_move) |
| 5206 }), | 8030 }), |
| 5207 | 8031 |
| 5208 deleteH: methodOp(function(dir, unit) { | 8032 deleteH: methodOp(function(dir, unit) { |
| 5209 var sel = this.doc.sel, doc = this.doc; | 8033 var sel = this.doc.sel, doc = this.doc |
| 5210 if (sel.somethingSelected()) | 8034 if (sel.somethingSelected()) |
| 5211 doc.replaceSelection("", null, "+delete"); | 8035 { doc.replaceSelection("", null, "+delete") } |
| 5212 else | 8036 else |
| 5213 deleteNearSelection(this, function(range) { | 8037 { deleteNearSelection(this, function (range) { |
| 5214 var other = findPosH(doc, range.head, dir, unit, false); | 8038 var other = findPosH(doc, range.head, dir, unit, false) |
| 5215 return dir < 0 ? {from: other, to: range.head} : {from: range.head, to
: other}; | 8039 return dir < 0 ? {from: other, to: range.head} : {from: range.head, to
: other} |
| 5216 }); | 8040 }) } |
| 5217 }), | 8041 }), |
| 5218 | 8042 |
| 5219 findPosV: function(from, amount, unit, goalColumn) { | 8043 findPosV: function(from, amount, unit, goalColumn) { |
| 5220 var dir = 1, x = goalColumn; | 8044 var this$1 = this; |
| 5221 if (amount < 0) { dir = -1; amount = -amount; } | 8045 |
| 5222 for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { | 8046 var dir = 1, x = goalColumn |
| 5223 var coords = cursorCoords(this, cur, "div"); | 8047 if (amount < 0) { dir = -1; amount = -amount } |
| 5224 if (x == null) x = coords.left; | 8048 var cur = clipPos(this.doc, from) |
| 5225 else coords.left = x; | 8049 for (var i = 0; i < amount; ++i) { |
| 5226 cur = findPosV(this, coords, dir, unit); | 8050 var coords = cursorCoords(this$1, cur, "div") |
| 5227 if (cur.hitSide) break; | 8051 if (x == null) { x = coords.left } |
| 8052 else { coords.left = x } |
| 8053 cur = findPosV(this$1, coords, dir, unit) |
| 8054 if (cur.hitSide) { break } |
| 5228 } | 8055 } |
| 5229 return cur; | 8056 return cur |
| 5230 }, | 8057 }, |
| 5231 | 8058 |
| 5232 moveV: methodOp(function(dir, unit) { | 8059 moveV: methodOp(function(dir, unit) { |
| 5233 var cm = this, doc = this.doc, goals = []; | 8060 var this$1 = this; |
| 5234 var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelect
ed(); | 8061 |
| 5235 doc.extendSelectionsBy(function(range) { | 8062 var doc = this.doc, goals = [] |
| 8063 var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSele
cted() |
| 8064 doc.extendSelectionsBy(function (range) { |
| 5236 if (collapse) | 8065 if (collapse) |
| 5237 return dir < 0 ? range.from() : range.to(); | 8066 { return dir < 0 ? range.from() : range.to() } |
| 5238 var headPos = cursorCoords(cm, range.head, "div"); | 8067 var headPos = cursorCoords(this$1, range.head, "div") |
| 5239 if (range.goalColumn != null) headPos.left = range.goalColumn; | 8068 if (range.goalColumn != null) { headPos.left = range.goalColumn } |
| 5240 goals.push(headPos.left); | 8069 goals.push(headPos.left) |
| 5241 var pos = findPosV(cm, headPos, dir, unit); | 8070 var pos = findPosV(this$1, headPos, dir, unit) |
| 5242 if (unit == "page" && range == doc.sel.primary()) | 8071 if (unit == "page" && range == doc.sel.primary()) |
| 5243 addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top)
; | 8072 { addToScrollPos(this$1, null, charCoords(this$1, pos, "div").top - he
adPos.top) } |
| 5244 return pos; | 8073 return pos |
| 5245 }, sel_move); | 8074 }, sel_move) |
| 5246 if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++) | 8075 if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) |
| 5247 doc.sel.ranges[i].goalColumn = goals[i]; | 8076 { doc.sel.ranges[i].goalColumn = goals[i] } } |
| 5248 }), | 8077 }), |
| 5249 | 8078 |
| 5250 // Find the word at the given position (as returned by coordsChar). | 8079 // Find the word at the given position (as returned by coordsChar). |
| 5251 findWordAt: function(pos) { | 8080 findWordAt: function(pos) { |
| 5252 var doc = this.doc, line = getLine(doc, pos.line).text; | 8081 var doc = this.doc, line = getLine(doc, pos.line).text |
| 5253 var start = pos.ch, end = pos.ch; | 8082 var start = pos.ch, end = pos.ch |
| 5254 if (line) { | 8083 if (line) { |
| 5255 var helper = this.getHelper(pos, "wordChars"); | 8084 var helper = this.getHelper(pos, "wordChars") |
| 5256 if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; | 8085 if ((pos.sticky == "before" || end == line.length) && start) { --start;
} else { ++end } |
| 5257 var startChar = line.charAt(start); | 8086 var startChar = line.charAt(start) |
| 5258 var check = isWordChar(startChar, helper) | 8087 var check = isWordChar(startChar, helper) |
| 5259 ? function(ch) { return isWordChar(ch, helper); } | 8088 ? function (ch) { return isWordChar(ch, helper); } |
| 5260 : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} | 8089 : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } |
| 5261 : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; | 8090 : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); } |
| 5262 while (start > 0 && check(line.charAt(start - 1))) --start; | 8091 while (start > 0 && check(line.charAt(start - 1))) { --start } |
| 5263 while (end < line.length && check(line.charAt(end))) ++end; | 8092 while (end < line.length && check(line.charAt(end))) { ++end } |
| 5264 } | 8093 } |
| 5265 return new Range(Pos(pos.line, start), Pos(pos.line, end)); | 8094 return new Range(Pos(pos.line, start), Pos(pos.line, end)) |
| 5266 }, | 8095 }, |
| 5267 | 8096 |
| 5268 toggleOverwrite: function(value) { | 8097 toggleOverwrite: function(value) { |
| 5269 if (value != null && value == this.state.overwrite) return; | 8098 if (value != null && value == this.state.overwrite) { return } |
| 5270 if (this.state.overwrite = !this.state.overwrite) | 8099 if (this.state.overwrite = !this.state.overwrite) |
| 5271 addClass(this.display.cursorDiv, "CodeMirror-overwrite"); | 8100 { addClass(this.display.cursorDiv, "CodeMirror-overwrite") } |
| 5272 else | 8101 else |
| 5273 rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); | 8102 { rmClass(this.display.cursorDiv, "CodeMirror-overwrite") } |
| 5274 | 8103 |
| 5275 signal(this, "overwriteToggle", this, this.state.overwrite); | 8104 signal(this, "overwriteToggle", this, this.state.overwrite) |
| 5276 }, | 8105 }, |
| 5277 hasFocus: function() { return this.display.input.getField() == activeElt();
}, | 8106 hasFocus: function() { return this.display.input.getField() == activeElt() }
, |
| 5278 isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdi
t); }, | 8107 isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdi
t) }, |
| 5279 | 8108 |
| 5280 scrollTo: methodOp(function(x, y) { | 8109 scrollTo: methodOp(function(x, y) { |
| 5281 if (x != null || y != null) resolveScrollToPos(this); | 8110 if (x != null || y != null) { resolveScrollToPos(this) } |
| 5282 if (x != null) this.curOp.scrollLeft = x; | 8111 if (x != null) { this.curOp.scrollLeft = x } |
| 5283 if (y != null) this.curOp.scrollTop = y; | 8112 if (y != null) { this.curOp.scrollTop = y } |
| 5284 }), | 8113 }), |
| 5285 getScrollInfo: function() { | 8114 getScrollInfo: function() { |
| 5286 var scroller = this.display.scroller; | 8115 var scroller = this.display.scroller |
| 5287 return {left: scroller.scrollLeft, top: scroller.scrollTop, | 8116 return {left: scroller.scrollLeft, top: scroller.scrollTop, |
| 5288 height: scroller.scrollHeight - scrollGap(this) - this.display.bar
Height, | 8117 height: scroller.scrollHeight - scrollGap(this) - this.display.bar
Height, |
| 5289 width: scroller.scrollWidth - scrollGap(this) - this.display.barWi
dth, | 8118 width: scroller.scrollWidth - scrollGap(this) - this.display.barWi
dth, |
| 5290 clientHeight: displayHeight(this), clientWidth: displayWidth(this)
}; | 8119 clientHeight: displayHeight(this), clientWidth: displayWidth(this)
} |
| 5291 }, | 8120 }, |
| 5292 | 8121 |
| 5293 scrollIntoView: methodOp(function(range, margin) { | 8122 scrollIntoView: methodOp(function(range, margin) { |
| 5294 if (range == null) { | 8123 if (range == null) { |
| 5295 range = {from: this.doc.sel.primary().head, to: null}; | 8124 range = {from: this.doc.sel.primary().head, to: null} |
| 5296 if (margin == null) margin = this.options.cursorScrollMargin; | 8125 if (margin == null) { margin = this.options.cursorScrollMargin } |
| 5297 } else if (typeof range == "number") { | 8126 } else if (typeof range == "number") { |
| 5298 range = {from: Pos(range, 0), to: null}; | 8127 range = {from: Pos(range, 0), to: null} |
| 5299 } else if (range.from == null) { | 8128 } else if (range.from == null) { |
| 5300 range = {from: range, to: null}; | 8129 range = {from: range, to: null} |
| 5301 } | 8130 } |
| 5302 if (!range.to) range.to = range.from; | 8131 if (!range.to) { range.to = range.from } |
| 5303 range.margin = margin || 0; | 8132 range.margin = margin || 0 |
| 5304 | 8133 |
| 5305 if (range.from.line != null) { | 8134 if (range.from.line != null) { |
| 5306 resolveScrollToPos(this); | 8135 resolveScrollToPos(this) |
| 5307 this.curOp.scrollToPos = range; | 8136 this.curOp.scrollToPos = range |
| 5308 } else { | 8137 } else { |
| 5309 var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.l
eft), | 8138 var sPos = calculateScrollPos(this, { |
| 5310 Math.min(range.from.top, range.to.top) - r
ange.margin, | 8139 left: Math.min(range.from.left, range.to.left), |
| 5311 Math.max(range.from.right, range.to.right)
, | 8140 top: Math.min(range.from.top, range.to.top) - range.margin, |
| 5312 Math.max(range.from.bottom, range.to.botto
m) + range.margin); | 8141 right: Math.max(range.from.right, range.to.right), |
| 5313 this.scrollTo(sPos.scrollLeft, sPos.scrollTop); | 8142 bottom: Math.max(range.from.bottom, range.to.bottom) + range.margin |
| 8143 }) |
| 8144 this.scrollTo(sPos.scrollLeft, sPos.scrollTop) |
| 5314 } | 8145 } |
| 5315 }), | 8146 }), |
| 5316 | 8147 |
| 5317 setSize: methodOp(function(width, height) { | 8148 setSize: methodOp(function(width, height) { |
| 5318 var cm = this; | 8149 var this$1 = this; |
| 5319 function interpret(val) { | 8150 |
| 5320 return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px"
: val; | 8151 var interpret = function (val) { return typeof val == "number" || /^\d+$/.
test(String(val)) ? val + "px" : val; } |
| 8152 if (width != null) { this.display.wrapper.style.width = interpret(width) } |
| 8153 if (height != null) { this.display.wrapper.style.height = interpret(height
) } |
| 8154 if (this.options.lineWrapping) { clearLineMeasurementCache(this) } |
| 8155 var lineNo = this.display.viewFrom |
| 8156 this.doc.iter(lineNo, this.display.viewTo, function (line) { |
| 8157 if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) |
| 8158 { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widg
et"); break } } } |
| 8159 ++lineNo |
| 8160 }) |
| 8161 this.curOp.forceUpdate = true |
| 8162 signal(this, "refresh", this) |
| 8163 }), |
| 8164 |
| 8165 operation: function(f){return runInOp(this, f)}, |
| 8166 |
| 8167 refresh: methodOp(function() { |
| 8168 var oldHeight = this.display.cachedTextHeight |
| 8169 regChange(this) |
| 8170 this.curOp.forceUpdate = true |
| 8171 clearCaches(this) |
| 8172 this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop) |
| 8173 updateGutterSpace(this) |
| 8174 if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) >
.5) |
| 8175 { estimateLineHeights(this) } |
| 8176 signal(this, "refresh", this) |
| 8177 }), |
| 8178 |
| 8179 swapDoc: methodOp(function(doc) { |
| 8180 var old = this.doc |
| 8181 old.cm = null |
| 8182 attachDoc(this, doc) |
| 8183 clearCaches(this) |
| 8184 this.display.input.reset() |
| 8185 this.scrollTo(doc.scrollLeft, doc.scrollTop) |
| 8186 this.curOp.forceScroll = true |
| 8187 signalLater(this, "swapDoc", this, old) |
| 8188 return old |
| 8189 }), |
| 8190 |
| 8191 getInputField: function(){return this.display.input.getField()}, |
| 8192 getWrapperElement: function(){return this.display.wrapper}, |
| 8193 getScrollerElement: function(){return this.display.scroller}, |
| 8194 getGutterElement: function(){return this.display.gutters} |
| 8195 } |
| 8196 eventMixin(CodeMirror) |
| 8197 |
| 8198 CodeMirror.registerHelper = function(type, name, value) { |
| 8199 if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_gl
obal: []} } |
| 8200 helpers[type][name] = value |
| 8201 } |
| 8202 CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { |
| 8203 CodeMirror.registerHelper(type, name, value) |
| 8204 helpers[type]._global.push({pred: predicate, val: value}) |
| 8205 } |
| 8206 } |
| 8207 |
| 8208 // Used for horizontal relative motion. Dir is -1 or 1 (left or |
| 8209 // right), unit can be "char", "column" (like char, but doesn't |
| 8210 // cross line boundaries), "word" (across next word), or "group" (to |
| 8211 // the start of next group of word or non-word-non-whitespace |
| 8212 // chars). The visually param controls whether, in right-to-left |
| 8213 // text, direction 1 means to move towards the next index in the |
| 8214 // string, or towards the character to the right of the current |
| 8215 // position. The resulting position will have a hitSide=true |
| 8216 // property if it reached the end of the document. |
| 8217 function findPosH(doc, pos, dir, unit, visually) { |
| 8218 var oldPos = pos |
| 8219 var origDir = dir |
| 8220 var lineObj = getLine(doc, pos.line) |
| 8221 function findNextLine() { |
| 8222 var l = pos.line + dir |
| 8223 if (l < doc.first || l >= doc.first + doc.size) { return false } |
| 8224 pos = new Pos(l, pos.ch, pos.sticky) |
| 8225 return lineObj = getLine(doc, l) |
| 8226 } |
| 8227 function moveOnce(boundToLine) { |
| 8228 var next |
| 8229 if (visually) { |
| 8230 next = moveVisually(doc.cm, lineObj, pos, dir) |
| 8231 } else { |
| 8232 next = moveLogically(lineObj, pos, dir) |
| 8233 } |
| 8234 if (next == null) { |
| 8235 if (!boundToLine && findNextLine()) |
| 8236 { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir) } |
| 8237 else |
| 8238 { return false } |
| 8239 } else { |
| 8240 pos = next |
| 8241 } |
| 8242 return true |
| 8243 } |
| 8244 |
| 8245 if (unit == "char") { |
| 8246 moveOnce() |
| 8247 } else if (unit == "column") { |
| 8248 moveOnce(true) |
| 8249 } else if (unit == "word" || unit == "group") { |
| 8250 var sawType = null, group = unit == "group" |
| 8251 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars") |
| 8252 for (var first = true;; first = false) { |
| 8253 if (dir < 0 && !moveOnce(!first)) { break } |
| 8254 var cur = lineObj.text.charAt(pos.ch) || "\n" |
| 8255 var type = isWordChar(cur, helper) ? "w" |
| 8256 : group && cur == "\n" ? "n" |
| 8257 : !group || /\s/.test(cur) ? null |
| 8258 : "p" |
| 8259 if (group && !first && !type) { type = "s" } |
| 8260 if (sawType && sawType != type) { |
| 8261 if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after"} |
| 8262 break |
| 5321 } | 8263 } |
| 5322 if (width != null) cm.display.wrapper.style.width = interpret(width); | 8264 |
| 5323 if (height != null) cm.display.wrapper.style.height = interpret(height); | 8265 if (type) { sawType = type } |
| 5324 if (cm.options.lineWrapping) clearLineMeasurementCache(this); | 8266 if (dir > 0 && !moveOnce(!first)) { break } |
| 5325 var lineNo = cm.display.viewFrom; | 8267 } |
| 5326 cm.doc.iter(lineNo, cm.display.viewTo, function(line) { | 8268 } |
| 5327 if (line.widgets) for (var i = 0; i < line.widgets.length; i++) | 8269 var result = skipAtomic(doc, pos, oldPos, origDir, true) |
| 5328 if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget");
break; } | 8270 if (equalCursorPos(oldPos, result)) { result.hitSide = true } |
| 5329 ++lineNo; | 8271 return result |
| 5330 }); | 8272 } |
| 5331 cm.curOp.forceUpdate = true; | 8273 |
| 5332 signal(cm, "refresh", this); | 8274 // For relative vertical movement. Dir may be -1 or 1. Unit can be |
| 5333 }), | 8275 // "page" or "line". The resulting position will have a hitSide=true |
| 5334 | 8276 // property if it reached the end of the document. |
| 5335 operation: function(f){return runInOp(this, f);}, | 8277 function findPosV(cm, pos, dir, unit) { |
| 5336 | 8278 var doc = cm.doc, x = pos.left, y |
| 5337 refresh: methodOp(function() { | 8279 if (unit == "page") { |
| 5338 var oldHeight = this.display.cachedTextHeight; | 8280 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight
|| document.documentElement.clientHeight) |
| 5339 regChange(this); | 8281 var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3) |
| 5340 this.curOp.forceUpdate = true; | 8282 y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount |
| 5341 clearCaches(this); | 8283 |
| 5342 this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop); | 8284 } else if (unit == "line") { |
| 5343 updateGutterSpace(this); | 8285 y = dir > 0 ? pos.bottom + 3 : pos.top - 3 |
| 5344 if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) >
.5) | 8286 } |
| 5345 estimateLineHeights(this); | 8287 var target |
| 5346 signal(this, "refresh", this); | 8288 for (;;) { |
| 5347 }), | 8289 target = coordsChar(cm, x, y) |
| 5348 | 8290 if (!target.outside) { break } |
| 5349 swapDoc: methodOp(function(doc) { | 8291 if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } |
| 5350 var old = this.doc; | 8292 y += dir * 5 |
| 5351 old.cm = null; | 8293 } |
| 5352 attachDoc(this, doc); | 8294 return target |
| 5353 clearCaches(this); | 8295 } |
| 5354 this.display.input.reset(); | 8296 |
| 5355 this.scrollTo(doc.scrollLeft, doc.scrollTop); | 8297 // CONTENTEDITABLE INPUT STYLE |
| 5356 this.curOp.forceScroll = true; | 8298 |
| 5357 signalLater(this, "swapDoc", this, old); | 8299 var ContentEditableInput = function ContentEditableInput(cm) { |
| 5358 return old; | 8300 this.cm = cm |
| 5359 }), | 8301 this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFo
cusOffset = null |
| 5360 | 8302 this.polling = new Delayed() |
| 5361 getInputField: function(){return this.display.input.getField();}, | 8303 this.composing = null |
| 5362 getWrapperElement: function(){return this.display.wrapper;}, | 8304 this.gracePeriod = false |
| 5363 getScrollerElement: function(){return this.display.scroller;}, | 8305 this.readDOMTimeout = null |
| 5364 getGutterElement: function(){return this.display.gutters;} | 8306 }; |
| 5365 }; | 8307 |
| 5366 eventMixin(CodeMirror); | 8308 ContentEditableInput.prototype.init = function init (display) { |
| 5367 | 8309 var this$1 = this; |
| 5368 // OPTION DEFAULTS | 8310 |
| 5369 | 8311 var input = this, cm = input.cm |
| 5370 // The default configuration options. | 8312 var div = input.div = display.lineDiv |
| 5371 var defaults = CodeMirror.defaults = {}; | 8313 disableBrowserMagic(div, cm.options.spellcheck) |
| 5372 // Functions to run when options are changed. | 8314 |
| 5373 var optionHandlers = CodeMirror.optionHandlers = {}; | 8315 on(div, "paste", function (e) { |
| 5374 | 8316 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } |
| 5375 function option(name, deflt, handle, notOnInit) { | 8317 // IE doesn't fire input events, so we schedule a read for the pasted conten
t in this way |
| 5376 CodeMirror.defaults[name] = deflt; | 8318 if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1
.updateFromDOM(); }), 20) } |
| 5377 if (handle) optionHandlers[name] = | 8319 }) |
| 5378 notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);
} : handle; | 8320 |
| 5379 } | 8321 on(div, "compositionstart", function (e) { |
| 5380 | 8322 this$1.composing = {data: e.data, done: false} |
| 5381 // Passed to option handlers when there is no old value. | 8323 }) |
| 5382 var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}
; | 8324 on(div, "compositionupdate", function (e) { |
| 5383 | 8325 if (!this$1.composing) { this$1.composing = {data: e.data, done: false} } |
| 5384 // These two are, on init, called from the constructor because they | 8326 }) |
| 5385 // have to be initialized before the editor can start at all. | 8327 on(div, "compositionend", function (e) { |
| 5386 option("value", "", function(cm, val) { | 8328 if (this$1.composing) { |
| 5387 cm.setValue(val); | 8329 if (e.data != this$1.composing.data) { this$1.readFromDOMSoon() } |
| 5388 }, true); | 8330 this$1.composing.done = true |
| 5389 option("mode", null, function(cm, val) { | 8331 } |
| 5390 cm.doc.modeOption = val; | 8332 }) |
| 5391 loadMode(cm); | 8333 |
| 5392 }, true); | 8334 on(div, "touchstart", function () { return input.forceCompositionEnd(); }) |
| 5393 | 8335 |
| 5394 option("indentUnit", 2, loadMode, true); | 8336 on(div, "input", function () { |
| 5395 option("indentWithTabs", false); | 8337 if (!this$1.composing) { this$1.readFromDOMSoon() } |
| 5396 option("smartIndent", true); | 8338 }) |
| 5397 option("tabSize", 4, function(cm) { | 8339 |
| 5398 resetModeState(cm); | 8340 function onCopyCut(e) { |
| 5399 clearCaches(cm); | 8341 if (signalDOMEvent(cm, e)) { return } |
| 5400 regChange(cm); | 8342 if (cm.somethingSelected()) { |
| 5401 }, true); | 8343 setLastCopied({lineWise: false, text: cm.getSelections()}) |
| 5402 option("lineSeparator", null, function(cm, val) { | 8344 if (e.type == "cut") { cm.replaceSelection("", null, "cut") } |
| 5403 cm.doc.lineSep = val; | 8345 } else if (!cm.options.lineWiseCopyCut) { |
| 5404 if (!val) return; | 8346 return |
| 5405 var newBreaks = [], lineNo = cm.doc.first; | 8347 } else { |
| 5406 cm.doc.iter(function(line) { | 8348 var ranges = copyableRanges(cm) |
| 5407 for (var pos = 0;;) { | 8349 setLastCopied({lineWise: true, text: ranges.text}) |
| 5408 var found = line.text.indexOf(val, pos); | 8350 if (e.type == "cut") { |
| 5409 if (found == -1) break; | 8351 cm.operation(function () { |
| 5410 pos = found + val.length; | 8352 cm.setSelections(ranges.ranges, 0, sel_dontScroll) |
| 5411 newBreaks.push(Pos(lineNo, found)); | 8353 cm.replaceSelection("", null, "cut") |
| 8354 }) |
| 5412 } | 8355 } |
| 5413 lineNo++; | 8356 } |
| 5414 }); | 8357 if (e.clipboardData) { |
| 5415 for (var i = newBreaks.length - 1; i >= 0; i--) | 8358 e.clipboardData.clearData() |
| 5416 replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i
].ch + val.length)) | 8359 var content = lastCopied.text.join("\n") |
| 5417 }); | 8360 // iOS exposes the clipboard API, but seems to discard content inserted in
to it |
| 5418 option("specialChars", /[\u0000-\u001f\u007f\u00ad\u200b-\u200f\u2028\u2029\uf
eff]/g, function(cm, val, old) { | 8361 e.clipboardData.setData("Text", content) |
| 5419 cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"
), "g"); | 8362 if (e.clipboardData.getData("Text") == content) { |
| 5420 if (old != CodeMirror.Init) cm.refresh(); | 8363 e.preventDefault() |
| 5421 }); | 8364 return |
| 5422 option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {
cm.refresh();}, true); | 8365 } |
| 5423 option("electricChars", true); | 8366 } |
| 5424 option("inputStyle", mobile ? "contenteditable" : "textarea", function() { | 8367 // Old-fashioned briefly-focus-a-textarea hack |
| 5425 throw new Error("inputStyle can not (yet) be changed in a running editor");
// FIXME | 8368 var kludge = hiddenTextarea(), te = kludge.firstChild |
| 5426 }, true); | 8369 cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild) |
| 5427 option("rtlMoveVisually", !windows); | 8370 te.value = lastCopied.text.join("\n") |
| 5428 option("wholeLineUpdateBefore", true); | 8371 var hadFocus = document.activeElement |
| 5429 | 8372 selectInput(te) |
| 5430 option("theme", "default", function(cm) { | 8373 setTimeout(function () { |
| 5431 themeChanged(cm); | 8374 cm.display.lineSpace.removeChild(kludge) |
| 5432 guttersChanged(cm); | 8375 hadFocus.focus() |
| 5433 }, true); | 8376 if (hadFocus == div) { input.showPrimarySelection() } |
| 5434 option("keyMap", "default", function(cm, val, old) { | 8377 }, 50) |
| 5435 var next = getKeyMap(val); | 8378 } |
| 5436 var prev = old != CodeMirror.Init && getKeyMap(old); | 8379 on(div, "copy", onCopyCut) |
| 5437 if (prev && prev.detach) prev.detach(cm, next); | 8380 on(div, "cut", onCopyCut) |
| 5438 if (next.attach) next.attach(cm, prev || null); | 8381 }; |
| 5439 }); | 8382 |
| 5440 option("extraKeys", null); | 8383 ContentEditableInput.prototype.prepareSelection = function prepareSelection$1 ()
{ |
| 5441 | 8384 var result = prepareSelection(this.cm, false) |
| 5442 option("lineWrapping", false, wrappingChanged, true); | 8385 result.focus = this.cm.state.focused |
| 5443 option("gutters", [], function(cm) { | 8386 return result |
| 5444 setGuttersForLineNumbers(cm.options); | 8387 }; |
| 5445 guttersChanged(cm); | 8388 |
| 5446 }, true); | 8389 ContentEditableInput.prototype.showSelection = function showSelection (info, tak
eFocus) { |
| 5447 option("fixedGutter", true, function(cm, val) { | 8390 if (!info || !this.cm.display.view.length) { return } |
| 5448 cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px
" : "0"; | 8391 if (info.focus || takeFocus) { this.showPrimarySelection() } |
| 5449 cm.refresh(); | 8392 this.showMultipleSelections(info) |
| 5450 }, true); | 8393 }; |
| 5451 option("coverGutterNextToScrollbar", false, function(cm) {updateScrollbars(cm)
;}, true); | 8394 |
| 5452 option("scrollbarStyle", "native", function(cm) { | 8395 ContentEditableInput.prototype.showPrimarySelection = function showPrimarySelect
ion () { |
| 5453 initScrollbars(cm); | 8396 var sel = window.getSelection(), prim = this.cm.doc.sel.primary() |
| 5454 updateScrollbars(cm); | 8397 var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset) |
| 5455 cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); | 8398 var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset) |
| 5456 cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); | 8399 if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && |
| 5457 }, true); | 8400 cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && |
| 5458 option("lineNumbers", false, function(cm) { | 8401 cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) |
| 5459 setGuttersForLineNumbers(cm.options); | 8402 { return } |
| 5460 guttersChanged(cm); | 8403 |
| 5461 }, true); | 8404 var start = posToDOM(this.cm, prim.from()) |
| 5462 option("firstLineNumber", 1, guttersChanged, true); | 8405 var end = posToDOM(this.cm, prim.to()) |
| 5463 option("lineNumberFormatter", function(integer) {return integer;}, guttersChan
ged, true); | 8406 if (!start && !end) { |
| 5464 option("showCursorWhenSelecting", false, updateSelection, true); | 8407 sel.removeAllRanges() |
| 5465 | 8408 return |
| 5466 option("resetSelectionOnContextMenu", true); | 8409 } |
| 5467 option("lineWiseCopyCut", true); | 8410 |
| 5468 | 8411 var view = this.cm.display.view |
| 5469 option("readOnly", false, function(cm, val) { | 8412 var old = sel.rangeCount && sel.getRangeAt(0) |
| 5470 if (val == "nocursor") { | 8413 if (!start) { |
| 5471 onBlur(cm); | 8414 start = {node: view[0].measure.map[2], offset: 0} |
| 5472 cm.display.input.blur(); | 8415 } else if (!end) { // FIXME dangerously hacky |
| 5473 cm.display.disabled = true; | 8416 var measure = view[view.length - 1].measure |
| 8417 var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map |
| 8418 end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.leng
th - 3]} |
| 8419 } |
| 8420 |
| 8421 var rng |
| 8422 try { rng = range(start.node, start.offset, end.offset, end.node) } |
| 8423 catch(e) {} // Our model of the DOM might be outdated, in which case the range
we try to set can be impossible |
| 8424 if (rng) { |
| 8425 if (!gecko && this.cm.state.focused) { |
| 8426 sel.collapse(start.node, start.offset) |
| 8427 if (!rng.collapsed) { |
| 8428 sel.removeAllRanges() |
| 8429 sel.addRange(rng) |
| 8430 } |
| 5474 } else { | 8431 } else { |
| 5475 cm.display.disabled = false; | 8432 sel.removeAllRanges() |
| 5476 } | 8433 sel.addRange(rng) |
| 5477 cm.display.input.readOnlyChanged(val) | 8434 } |
| 5478 }); | 8435 if (old && sel.anchorNode == null) { sel.addRange(old) } |
| 5479 option("disableInput", false, function(cm, val) {if (!val) cm.display.input.re
set();}, true); | 8436 else if (gecko) { this.startGracePeriod() } |
| 5480 option("dragDrop", true, dragDropChanged); | 8437 } |
| 5481 option("allowDropFileTypes", null); | 8438 this.rememberSelection() |
| 5482 | 8439 }; |
| 5483 option("cursorBlinkRate", 530); | 8440 |
| 5484 option("cursorScrollMargin", 0); | 8441 ContentEditableInput.prototype.startGracePeriod = function startGracePeriod () { |
| 5485 option("cursorHeight", 1, updateSelection, true); | 8442 var this$1 = this; |
| 5486 option("singleCursorHeightPerLine", true, updateSelection, true); | 8443 |
| 5487 option("workTime", 100); | 8444 clearTimeout(this.gracePeriod) |
| 5488 option("workDelay", 100); | 8445 this.gracePeriod = setTimeout(function () { |
| 5489 option("flattenSpans", true, resetModeState, true); | 8446 this$1.gracePeriod = false |
| 5490 option("addModeClass", false, resetModeState, true); | 8447 if (this$1.selectionChanged()) |
| 5491 option("pollInterval", 100); | 8448 { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChange
d = true; }) } |
| 5492 option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); | 8449 }, 20) |
| 5493 option("historyEventDelay", 1250); | 8450 }; |
| 5494 option("viewportMargin", 10, function(cm){cm.refresh();}, true); | 8451 |
| 5495 option("maxHighlightLength", 10000, resetModeState, true); | 8452 ContentEditableInput.prototype.showMultipleSelections = function showMultipleSel
ections (info) { |
| 5496 option("moveInputWithCursor", true, function(cm, val) { | 8453 removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors) |
| 5497 if (!val) cm.display.input.resetPosition(); | 8454 removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection) |
| 5498 }); | 8455 }; |
| 5499 | 8456 |
| 5500 option("tabindex", null, function(cm, val) { | 8457 ContentEditableInput.prototype.rememberSelection = function rememberSelection ()
{ |
| 5501 cm.display.input.getField().tabIndex = val || ""; | 8458 var sel = window.getSelection() |
| 5502 }); | 8459 this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset |
| 5503 option("autofocus", null); | 8460 this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset |
| 5504 | 8461 }; |
| 5505 // MODE DEFINITION AND QUERYING | 8462 |
| 5506 | 8463 ContentEditableInput.prototype.selectionInEditor = function selectionInEditor ()
{ |
| 5507 // Known modes, by name and by MIME | 8464 var sel = window.getSelection() |
| 5508 var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; | 8465 if (!sel.rangeCount) { return false } |
| 5509 | 8466 var node = sel.getRangeAt(0).commonAncestorContainer |
| 5510 // Extra arguments are stored as the mode's dependencies, which is | 8467 return contains(this.div, node) |
| 5511 // used by (legacy) mechanisms like loadmode.js to automatically | 8468 }; |
| 5512 // load a mode. (Preferred mechanism is the require/define calls.) | 8469 |
| 5513 CodeMirror.defineMode = function(name, mode) { | 8470 ContentEditableInput.prototype.focus = function focus () { |
| 5514 if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode =
name; | 8471 if (this.cm.options.readOnly != "nocursor") { |
| 5515 if (arguments.length > 2) | 8472 if (!this.selectionInEditor()) |
| 5516 mode.dependencies = Array.prototype.slice.call(arguments, 2); | 8473 { this.showSelection(this.prepareSelection(), true) } |
| 5517 modes[name] = mode; | 8474 this.div.focus() |
| 5518 }; | 8475 } |
| 5519 | 8476 }; |
| 5520 CodeMirror.defineMIME = function(mime, spec) { | 8477 ContentEditableInput.prototype.blur = function blur () { this.div.blur() }; |
| 5521 mimeModes[mime] = spec; | 8478 ContentEditableInput.prototype.getField = function getField () { return this.div
}; |
| 5522 }; | 8479 |
| 5523 | 8480 ContentEditableInput.prototype.supportsTouch = function supportsTouch () { retur
n true }; |
| 5524 // Given a MIME type, a {name, ...options} config object, or a name | 8481 |
| 5525 // string, return a mode config object. | 8482 ContentEditableInput.prototype.receivedFocus = function receivedFocus () { |
| 5526 CodeMirror.resolveMode = function(spec) { | 8483 var input = this |
| 5527 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { | 8484 if (this.selectionInEditor()) |
| 5528 spec = mimeModes[spec]; | 8485 { this.pollSelection() } |
| 5529 } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(
spec.name)) { | 8486 else |
| 5530 var found = mimeModes[spec.name]; | 8487 { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = tr
ue; }) } |
| 5531 if (typeof found == "string") found = {name: found}; | 8488 |
| 5532 spec = createObj(found, spec); | 8489 function poll() { |
| 5533 spec.name = found.name; | 8490 if (input.cm.state.focused) { |
| 5534 } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
{ | 8491 input.pollSelection() |
| 5535 return CodeMirror.resolveMode("application/xml"); | 8492 input.polling.set(input.cm.options.pollInterval, poll) |
| 5536 } | 8493 } |
| 5537 if (typeof spec == "string") return {name: spec}; | 8494 } |
| 5538 else return spec || {name: "null"}; | 8495 this.polling.set(this.cm.options.pollInterval, poll) |
| 5539 }; | 8496 }; |
| 5540 | 8497 |
| 5541 // Given a mode spec (anything that resolveMode accepts), find and | 8498 ContentEditableInput.prototype.selectionChanged = function selectionChanged () { |
| 5542 // initialize an actual mode object. | 8499 var sel = window.getSelection() |
| 5543 CodeMirror.getMode = function(options, spec) { | 8500 return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastA
nchorOffset || |
| 5544 var spec = CodeMirror.resolveMode(spec); | 8501 sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffs
et |
| 5545 var mfactory = modes[spec.name]; | 8502 }; |
| 5546 if (!mfactory) return CodeMirror.getMode(options, "text/plain"); | 8503 |
| 5547 var modeObj = mfactory(options, spec); | 8504 ContentEditableInput.prototype.pollSelection = function pollSelection () { |
| 5548 if (modeExtensions.hasOwnProperty(spec.name)) { | 8505 if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged(
)) { return } |
| 5549 var exts = modeExtensions[spec.name]; | 8506 var sel = window.getSelection(), cm = this.cm |
| 5550 for (var prop in exts) { | 8507 // On Android Chrome (version 56, at least), backspacing into an |
| 5551 if (!exts.hasOwnProperty(prop)) continue; | 8508 // uneditable block element will put the cursor in that element, |
| 5552 if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; | 8509 // and then, because it's not editable, hide the virtual keyboard. |
| 5553 modeObj[prop] = exts[prop]; | 8510 // Because Android doesn't allow us to actually detect backspace |
| 8511 // presses in a sane way, this code checks for when that happens |
| 8512 // and simulates a backspace press in this case. |
| 8513 if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anch
orNode)) { |
| 8514 this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.
abs}) |
| 8515 this.blur() |
| 8516 this.focus() |
| 8517 return |
| 8518 } |
| 8519 if (this.composing) { return } |
| 8520 this.rememberSelection() |
| 8521 var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) |
| 8522 var head = domToPos(cm, sel.focusNode, sel.focusOffset) |
| 8523 if (anchor && head) { runInOp(cm, function () { |
| 8524 setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll) |
| 8525 if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true } |
| 8526 }) } |
| 8527 }; |
| 8528 |
| 8529 ContentEditableInput.prototype.pollContent = function pollContent () { |
| 8530 if (this.readDOMTimeout != null) { |
| 8531 clearTimeout(this.readDOMTimeout) |
| 8532 this.readDOMTimeout = null |
| 8533 } |
| 8534 |
| 8535 var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary() |
| 8536 var from = sel.from(), to = sel.to() |
| 8537 if (from.ch == 0 && from.line > cm.firstLine()) |
| 8538 { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) } |
| 8539 if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) |
| 8540 { to = Pos(to.line + 1, 0) } |
| 8541 if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return fal
se } |
| 8542 |
| 8543 var fromIndex, fromLine, fromNode |
| 8544 if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)
) == 0) { |
| 8545 fromLine = lineNo(display.view[0].line) |
| 8546 fromNode = display.view[0].node |
| 8547 } else { |
| 8548 fromLine = lineNo(display.view[fromIndex].line) |
| 8549 fromNode = display.view[fromIndex - 1].node.nextSibling |
| 8550 } |
| 8551 var toIndex = findViewIndex(cm, to.line) |
| 8552 var toLine, toNode |
| 8553 if (toIndex == display.view.length - 1) { |
| 8554 toLine = display.viewTo - 1 |
| 8555 toNode = display.lineDiv.lastChild |
| 8556 } else { |
| 8557 toLine = lineNo(display.view[toIndex + 1].line) - 1 |
| 8558 toNode = display.view[toIndex + 1].node.previousSibling |
| 8559 } |
| 8560 |
| 8561 if (!fromNode) { return false } |
| 8562 var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine,
toLine)) |
| 8563 var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc,
toLine).text.length)) |
| 8564 while (newText.length > 1 && oldText.length > 1) { |
| 8565 if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- } |
| 8566 else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromL
ine++ } |
| 8567 else { break } |
| 8568 } |
| 8569 |
| 8570 var cutFront = 0, cutEnd = 0 |
| 8571 var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.le
ngth, oldTop.length) |
| 8572 while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCod
eAt(cutFront)) |
| 8573 { ++cutFront } |
| 8574 var newBot = lst(newText), oldBot = lst(oldText) |
| 8575 var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), |
| 8576 oldBot.length - (oldText.length == 1 ? cutFront : 0)) |
| 8577 while (cutEnd < maxCutEnd && |
| 8578 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldB
ot.length - cutEnd - 1)) |
| 8579 { ++cutEnd } |
| 8580 // Try to move start of change to start of selection if ambiguous |
| 8581 if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { |
| 8582 while (cutFront && cutFront > from.ch && |
| 8583 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(ol
dBot.length - cutEnd - 1)) { |
| 8584 cutFront-- |
| 8585 cutEnd++ |
| 8586 } |
| 8587 } |
| 8588 |
| 8589 newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(
/^\u200b+/, "") |
| 8590 newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "") |
| 8591 |
| 8592 var chFrom = Pos(fromLine, cutFront) |
| 8593 var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0) |
| 8594 if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { |
| 8595 replaceRange(cm.doc, newText, chFrom, chTo, "+input") |
| 8596 return true |
| 8597 } |
| 8598 }; |
| 8599 |
| 8600 ContentEditableInput.prototype.ensurePolled = function ensurePolled () { |
| 8601 this.forceCompositionEnd() |
| 8602 }; |
| 8603 ContentEditableInput.prototype.reset = function reset () { |
| 8604 this.forceCompositionEnd() |
| 8605 }; |
| 8606 ContentEditableInput.prototype.forceCompositionEnd = function forceCompositionEn
d () { |
| 8607 if (!this.composing) { return } |
| 8608 clearTimeout(this.readDOMTimeout) |
| 8609 this.composing = null |
| 8610 this.updateFromDOM() |
| 8611 this.div.blur() |
| 8612 this.div.focus() |
| 8613 }; |
| 8614 ContentEditableInput.prototype.readFromDOMSoon = function readFromDOMSoon () { |
| 8615 var this$1 = this; |
| 8616 |
| 8617 if (this.readDOMTimeout != null) { return } |
| 8618 this.readDOMTimeout = setTimeout(function () { |
| 8619 this$1.readDOMTimeout = null |
| 8620 if (this$1.composing) { |
| 8621 if (this$1.composing.done) { this$1.composing = null } |
| 8622 else { return } |
| 8623 } |
| 8624 this$1.updateFromDOM() |
| 8625 }, 80) |
| 8626 }; |
| 8627 |
| 8628 ContentEditableInput.prototype.updateFromDOM = function updateFromDOM () { |
| 8629 var this$1 = this; |
| 8630 |
| 8631 if (this.cm.isReadOnly() || !this.pollContent()) |
| 8632 { runInOp(this.cm, function () { return regChange(this$1.cm); }) } |
| 8633 }; |
| 8634 |
| 8635 ContentEditableInput.prototype.setUneditable = function setUneditable (node) { |
| 8636 node.contentEditable = "false" |
| 8637 }; |
| 8638 |
| 8639 ContentEditableInput.prototype.onKeyPress = function onKeyPress (e) { |
| 8640 if (e.charCode == 0) { return } |
| 8641 e.preventDefault() |
| 8642 if (!this.cm.isReadOnly()) |
| 8643 { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode
== null ? e.keyCode : e.charCode), 0) } |
| 8644 }; |
| 8645 |
| 8646 ContentEditableInput.prototype.readOnlyChanged = function readOnlyChanged (val)
{ |
| 8647 this.div.contentEditable = String(val != "nocursor") |
| 8648 }; |
| 8649 |
| 8650 ContentEditableInput.prototype.onContextMenu = function onContextMenu () {}; |
| 8651 ContentEditableInput.prototype.resetPosition = function resetPosition () {}; |
| 8652 |
| 8653 ContentEditableInput.prototype.needsContentAttribute = true |
| 8654 |
| 8655 function posToDOM(cm, pos) { |
| 8656 var view = findViewForLine(cm, pos.line) |
| 8657 if (!view || view.hidden) { return null } |
| 8658 var line = getLine(cm.doc, pos.line) |
| 8659 var info = mapFromLineView(view, line, pos.line) |
| 8660 |
| 8661 var order = getOrder(line, cm.doc.direction), side = "left" |
| 8662 if (order) { |
| 8663 var partPos = getBidiPartAt(order, pos.ch) |
| 8664 side = partPos % 2 ? "right" : "left" |
| 8665 } |
| 8666 var result = nodeAndOffsetInLineMap(info.map, pos.ch, side) |
| 8667 result.offset = result.collapse == "right" ? result.end : result.start |
| 8668 return result |
| 8669 } |
| 8670 |
| 8671 function isInGutter(node) { |
| 8672 for (var scan = node; scan; scan = scan.parentNode) |
| 8673 { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } } |
| 8674 return false |
| 8675 } |
| 8676 |
| 8677 function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } |
| 8678 |
| 8679 function domTextBetween(cm, from, to, fromLine, toLine) { |
| 8680 var text = "", closing = false, lineSep = cm.doc.lineSeparator() |
| 8681 function recognizeMarker(id) { return function (marker) { return marker.id ==
id; } } |
| 8682 function close() { |
| 8683 if (closing) { |
| 8684 text += lineSep |
| 8685 closing = false |
| 8686 } |
| 8687 } |
| 8688 function addText(str) { |
| 8689 if (str) { |
| 8690 close() |
| 8691 text += str |
| 8692 } |
| 8693 } |
| 8694 function walk(node) { |
| 8695 if (node.nodeType == 1) { |
| 8696 var cmText = node.getAttribute("cm-text") |
| 8697 if (cmText != null) { |
| 8698 addText(cmText || node.textContent.replace(/\u200b/g, "")) |
| 8699 return |
| 5554 } | 8700 } |
| 5555 } | 8701 var markerID = node.getAttribute("cm-marker"), range |
| 5556 modeObj.name = spec.name; | 8702 if (markerID) { |
| 5557 if (spec.helperType) modeObj.helperType = spec.helperType; | 8703 var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognize
Marker(+markerID)) |
| 5558 if (spec.modeProps) for (var prop in spec.modeProps) | 8704 if (found.length && (range = found[0].find())) |
| 5559 modeObj[prop] = spec.modeProps[prop]; | 8705 { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)) } |
| 5560 | 8706 return |
| 5561 return modeObj; | |
| 5562 }; | |
| 5563 | |
| 5564 // Minimal default mode. | |
| 5565 CodeMirror.defineMode("null", function() { | |
| 5566 return {token: function(stream) {stream.skipToEnd();}}; | |
| 5567 }); | |
| 5568 CodeMirror.defineMIME("text/plain", "null"); | |
| 5569 | |
| 5570 // This can be used to attach properties to mode objects from | |
| 5571 // outside the actual mode definition. | |
| 5572 var modeExtensions = CodeMirror.modeExtensions = {}; | |
| 5573 CodeMirror.extendMode = function(mode, properties) { | |
| 5574 var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (mod
eExtensions[mode] = {}); | |
| 5575 copyObj(properties, exts); | |
| 5576 }; | |
| 5577 | |
| 5578 // EXTENSIONS | |
| 5579 | |
| 5580 CodeMirror.defineExtension = function(name, func) { | |
| 5581 CodeMirror.prototype[name] = func; | |
| 5582 }; | |
| 5583 CodeMirror.defineDocExtension = function(name, func) { | |
| 5584 Doc.prototype[name] = func; | |
| 5585 }; | |
| 5586 CodeMirror.defineOption = option; | |
| 5587 | |
| 5588 var initHooks = []; | |
| 5589 CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; | |
| 5590 | |
| 5591 var helpers = CodeMirror.helpers = {}; | |
| 5592 CodeMirror.registerHelper = function(type, name, value) { | |
| 5593 if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_glob
al: []}; | |
| 5594 helpers[type][name] = value; | |
| 5595 }; | |
| 5596 CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { | |
| 5597 CodeMirror.registerHelper(type, name, value); | |
| 5598 helpers[type]._global.push({pred: predicate, val: value}); | |
| 5599 }; | |
| 5600 | |
| 5601 // MODE STATE HANDLING | |
| 5602 | |
| 5603 // Utility functions for working with state. Exported because nested | |
| 5604 // modes need to do this for their inner modes. | |
| 5605 | |
| 5606 var copyState = CodeMirror.copyState = function(mode, state) { | |
| 5607 if (state === true) return state; | |
| 5608 if (mode.copyState) return mode.copyState(state); | |
| 5609 var nstate = {}; | |
| 5610 for (var n in state) { | |
| 5611 var val = state[n]; | |
| 5612 if (val instanceof Array) val = val.concat([]); | |
| 5613 nstate[n] = val; | |
| 5614 } | |
| 5615 return nstate; | |
| 5616 }; | |
| 5617 | |
| 5618 var startState = CodeMirror.startState = function(mode, a1, a2) { | |
| 5619 return mode.startState ? mode.startState(a1, a2) : true; | |
| 5620 }; | |
| 5621 | |
| 5622 // Given a mode and a state (for that mode), find the inner mode and | |
| 5623 // state at the position that the state refers to. | |
| 5624 CodeMirror.innerMode = function(mode, state) { | |
| 5625 while (mode.innerMode) { | |
| 5626 var info = mode.innerMode(state); | |
| 5627 if (!info || info.mode == mode) break; | |
| 5628 state = info.state; | |
| 5629 mode = info.mode; | |
| 5630 } | |
| 5631 return info || {mode: mode, state: state}; | |
| 5632 }; | |
| 5633 | |
| 5634 // STANDARD COMMANDS | |
| 5635 | |
| 5636 // Commands are parameter-less actions that can be performed on an | |
| 5637 // editor, mostly used for keybindings. | |
| 5638 var commands = CodeMirror.commands = { | |
| 5639 selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.last
Line()), sel_dontScroll);}, | |
| 5640 singleSelection: function(cm) { | |
| 5641 cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScro
ll); | |
| 5642 }, | |
| 5643 killLine: function(cm) { | |
| 5644 deleteNearSelection(cm, function(range) { | |
| 5645 if (range.empty()) { | |
| 5646 var len = getLine(cm.doc, range.head.line).text.length; | |
| 5647 if (range.head.ch == len && range.head.line < cm.lastLine()) | |
| 5648 return {from: range.head, to: Pos(range.head.line + 1, 0)}; | |
| 5649 else | |
| 5650 return {from: range.head, to: Pos(range.head.line, len)}; | |
| 5651 } else { | |
| 5652 return {from: range.from(), to: range.to()}; | |
| 5653 } | |
| 5654 }); | |
| 5655 }, | |
| 5656 deleteLine: function(cm) { | |
| 5657 deleteNearSelection(cm, function(range) { | |
| 5658 return {from: Pos(range.from().line, 0), | |
| 5659 to: clipPos(cm.doc, Pos(range.to().line + 1, 0))}; | |
| 5660 }); | |
| 5661 }, | |
| 5662 delLineLeft: function(cm) { | |
| 5663 deleteNearSelection(cm, function(range) { | |
| 5664 return {from: Pos(range.from().line, 0), to: range.from()}; | |
| 5665 }); | |
| 5666 }, | |
| 5667 delWrappedLineLeft: function(cm) { | |
| 5668 deleteNearSelection(cm, function(range) { | |
| 5669 var top = cm.charCoords(range.head, "div").top + 5; | |
| 5670 var leftPos = cm.coordsChar({left: 0, top: top}, "div"); | |
| 5671 return {from: leftPos, to: range.from()}; | |
| 5672 }); | |
| 5673 }, | |
| 5674 delWrappedLineRight: function(cm) { | |
| 5675 deleteNearSelection(cm, function(range) { | |
| 5676 var top = cm.charCoords(range.head, "div").top + 5; | |
| 5677 var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100
, top: top}, "div"); | |
| 5678 return {from: range.from(), to: rightPos }; | |
| 5679 }); | |
| 5680 }, | |
| 5681 undo: function(cm) {cm.undo();}, | |
| 5682 redo: function(cm) {cm.redo();}, | |
| 5683 undoSelection: function(cm) {cm.undoSelection();}, | |
| 5684 redoSelection: function(cm) {cm.redoSelection();}, | |
| 5685 goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, | |
| 5686 goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, | |
| 5687 goLineStart: function(cm) { | |
| 5688 cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.li
ne); }, | |
| 5689 {origin: "+move", bias: 1}); | |
| 5690 }, | |
| 5691 goLineStartSmart: function(cm) { | |
| 5692 cm.extendSelectionsBy(function(range) { | |
| 5693 return lineStartSmart(cm, range.head); | |
| 5694 }, {origin: "+move", bias: 1}); | |
| 5695 }, | |
| 5696 goLineEnd: function(cm) { | |
| 5697 cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line
); }, | |
| 5698 {origin: "+move", bias: -1}); | |
| 5699 }, | |
| 5700 goLineRight: function(cm) { | |
| 5701 cm.extendSelectionsBy(function(range) { | |
| 5702 var top = cm.charCoords(range.head, "div").top + 5; | |
| 5703 return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: t
op}, "div"); | |
| 5704 }, sel_move); | |
| 5705 }, | |
| 5706 goLineLeft: function(cm) { | |
| 5707 cm.extendSelectionsBy(function(range) { | |
| 5708 var top = cm.charCoords(range.head, "div").top + 5; | |
| 5709 return cm.coordsChar({left: 0, top: top}, "div"); | |
| 5710 }, sel_move); | |
| 5711 }, | |
| 5712 goLineLeftSmart: function(cm) { | |
| 5713 cm.extendSelectionsBy(function(range) { | |
| 5714 var top = cm.charCoords(range.head, "div").top + 5; | |
| 5715 var pos = cm.coordsChar({left: 0, top: top}, "div"); | |
| 5716 if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm
, range.head); | |
| 5717 return pos; | |
| 5718 }, sel_move); | |
| 5719 }, | |
| 5720 goLineUp: function(cm) {cm.moveV(-1, "line");}, | |
| 5721 goLineDown: function(cm) {cm.moveV(1, "line");}, | |
| 5722 goPageUp: function(cm) {cm.moveV(-1, "page");}, | |
| 5723 goPageDown: function(cm) {cm.moveV(1, "page");}, | |
| 5724 goCharLeft: function(cm) {cm.moveH(-1, "char");}, | |
| 5725 goCharRight: function(cm) {cm.moveH(1, "char");}, | |
| 5726 goColumnLeft: function(cm) {cm.moveH(-1, "column");}, | |
| 5727 goColumnRight: function(cm) {cm.moveH(1, "column");}, | |
| 5728 goWordLeft: function(cm) {cm.moveH(-1, "word");}, | |
| 5729 goGroupRight: function(cm) {cm.moveH(1, "group");}, | |
| 5730 goGroupLeft: function(cm) {cm.moveH(-1, "group");}, | |
| 5731 goWordRight: function(cm) {cm.moveH(1, "word");}, | |
| 5732 delCharBefore: function(cm) {cm.deleteH(-1, "char");}, | |
| 5733 delCharAfter: function(cm) {cm.deleteH(1, "char");}, | |
| 5734 delWordBefore: function(cm) {cm.deleteH(-1, "word");}, | |
| 5735 delWordAfter: function(cm) {cm.deleteH(1, "word");}, | |
| 5736 delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, | |
| 5737 delGroupAfter: function(cm) {cm.deleteH(1, "group");}, | |
| 5738 indentAuto: function(cm) {cm.indentSelection("smart");}, | |
| 5739 indentMore: function(cm) {cm.indentSelection("add");}, | |
| 5740 indentLess: function(cm) {cm.indentSelection("subtract");}, | |
| 5741 insertTab: function(cm) {cm.replaceSelection("\t");}, | |
| 5742 insertSoftTab: function(cm) { | |
| 5743 var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSiz
e; | |
| 5744 for (var i = 0; i < ranges.length; i++) { | |
| 5745 var pos = ranges[i].from(); | |
| 5746 var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); | |
| 5747 spaces.push(spaceStr(tabSize - col % tabSize)); | |
| 5748 } | 8707 } |
| 5749 cm.replaceSelections(spaces); | 8708 if (node.getAttribute("contenteditable") == "false") { return } |
| 5750 }, | 8709 var isBlock = /^(pre|div|p)$/i.test(node.nodeName) |
| 5751 defaultTab: function(cm) { | 8710 if (isBlock) { close() } |
| 5752 if (cm.somethingSelected()) cm.indentSelection("add"); | 8711 for (var i = 0; i < node.childNodes.length; i++) |
| 5753 else cm.execCommand("insertTab"); | 8712 { walk(node.childNodes[i]) } |
| 5754 }, | 8713 if (isBlock) { closing = true } |
| 5755 transposeChars: function(cm) { | 8714 } else if (node.nodeType == 3) { |
| 5756 runInOp(cm, function() { | 8715 addText(node.nodeValue) |
| 5757 var ranges = cm.listSelections(), newSel = []; | 8716 } |
| 5758 for (var i = 0; i < ranges.length; i++) { | 8717 } |
| 5759 var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; | 8718 for (;;) { |
| 5760 if (line) { | 8719 walk(from) |
| 5761 if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); | 8720 if (from == to) { break } |
| 5762 if (cur.ch > 0) { | 8721 from = from.nextSibling |
| 5763 cur = new Pos(cur.line, cur.ch + 1); | 8722 } |
| 5764 cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), | 8723 return text |
| 5765 Pos(cur.line, cur.ch - 2), cur, "+transpose"); | 8724 } |
| 5766 } else if (cur.line > cm.doc.first) { | 8725 |
| 5767 var prev = getLine(cm.doc, cur.line - 1).text; | 8726 function domToPos(cm, node, offset) { |
| 5768 if (prev) | 8727 var lineNode |
| 5769 cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + | 8728 if (node == cm.display.lineDiv) { |
| 5770 prev.charAt(prev.length - 1), | 8729 lineNode = cm.display.lineDiv.childNodes[offset] |
| 5771 Pos(cur.line - 1, prev.length - 1), Pos(cur.line
, 1), "+transpose"); | 8730 if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true)
} |
| 5772 } | 8731 node = null; offset = 0 |
| 5773 } | 8732 } else { |
| 5774 newSel.push(new Range(cur, cur)); | 8733 for (lineNode = node;; lineNode = lineNode.parentNode) { |
| 5775 } | 8734 if (!lineNode || lineNode == cm.display.lineDiv) { return null } |
| 5776 cm.setSelections(newSel); | 8735 if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { br
eak } |
| 5777 }); | 8736 } |
| 5778 }, | 8737 } |
| 5779 newlineAndIndent: function(cm) { | 8738 for (var i = 0; i < cm.display.view.length; i++) { |
| 5780 runInOp(cm, function() { | 8739 var lineView = cm.display.view[i] |
| 5781 var len = cm.listSelections().length; | 8740 if (lineView.node == lineNode) |
| 5782 for (var i = 0; i < len; i++) { | 8741 { return locateNodeInLineView(lineView, node, offset) } |
| 5783 var range = cm.listSelections()[i]; | 8742 } |
| 5784 cm.replaceRange(cm.doc.lineSeparator(), range.anchor, range.head, "+in
put"); | 8743 } |
| 5785 cm.indentLine(range.from().line + 1, null, true); | 8744 |
| 5786 } | 8745 function locateNodeInLineView(lineView, node, offset) { |
| 5787 ensureCursorVisible(cm); | 8746 var wrapper = lineView.text.firstChild, bad = false |
| 5788 }); | 8747 if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.lin
e), 0), true) } |
| 5789 }, | 8748 if (node == wrapper) { |
| 5790 openLine: function(cm) {cm.replaceSelection("\n", "start")}, | 8749 bad = true |
| 5791 toggleOverwrite: function(cm) {cm.toggleOverwrite();} | 8750 node = wrapper.childNodes[offset] |
| 5792 }; | 8751 offset = 0 |
| 5793 | 8752 if (!node) { |
| 5794 | 8753 var line = lineView.rest ? lst(lineView.rest) : lineView.line |
| 5795 // STANDARD KEYMAPS | 8754 return badPos(Pos(lineNo(line), line.text.length), bad) |
| 5796 | 8755 } |
| 5797 var keyMap = CodeMirror.keyMap = {}; | 8756 } |
| 5798 | 8757 |
| 5799 keyMap.basic = { | 8758 var textNode = node.nodeType == 3 ? node : null, topNode = node |
| 5800 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goL
ineDown", | 8759 if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3)
{ |
| 5801 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageD
own": "goPageDown", | 8760 textNode = node.firstChild |
| 5802 "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "
delCharBefore", | 8761 if (offset) { offset = textNode.nodeValue.length } |
| 5803 "Tab": "defaultTab", "Shift-Tab": "indentAuto", | 8762 } |
| 5804 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", | 8763 while (topNode.parentNode != wrapper) { topNode = topNode.parentNode } |
| 5805 "Esc": "singleSelection" | 8764 var measure = lineView.measure, maps = measure.maps |
| 5806 }; | 8765 |
| 5807 // Note that the save and find-related commands aren't defined by | 8766 function find(textNode, topNode, offset) { |
| 5808 // default. User code or addons can define them. Unknown commands | 8767 for (var i = -1; i < (maps ? maps.length : 0); i++) { |
| 5809 // are simply ignored. | 8768 var map = i < 0 ? measure.map : maps[i] |
| 5810 keyMap.pcDefault = { | 8769 for (var j = 0; j < map.length; j += 3) { |
| 5811 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl
-Z": "redo", "Ctrl-Y": "redo", | 8770 var curNode = map[j + 2] |
| 5812 "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "C
trl-Down": "goLineDown", | 8771 if (curNode == textNode || curNode == topNode) { |
| 5813 "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLin
eStart", "Alt-Right": "goLineEnd", | 8772 var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]) |
| 5814 "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S"
: "save", "Ctrl-F": "find", | 8773 var ch = map[j] + offset |
| 5815 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace",
"Shift-Ctrl-R": "replaceAll", | 8774 if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0)
] } |
| 5816 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", | 8775 return Pos(line, ch) |
| 5817 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSe
lection", | |
| 5818 fallthrough: "basic" | |
| 5819 }; | |
| 5820 // Very basic readline/emacs-style bindings, which are standard on Mac. | |
| 5821 keyMap.emacsy = { | |
| 5822 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl
-N": "goLineDown", | |
| 5823 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctr
l-E": "goLineEnd", | |
| 5824 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter"
, "Ctrl-H": "delCharBefore", | |
| 5825 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLi
ne", "Ctrl-T": "transposeChars", | |
| 5826 "Ctrl-O": "openLine" | |
| 5827 }; | |
| 5828 keyMap.macDefault = { | |
| 5829 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z":
"redo", "Cmd-Y": "redo", | |
| 5830 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cm
d-Down": "goDocEnd", "Alt-Left": "goGroupLeft", | |
| 5831 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineR
ight", "Alt-Backspace": "delGroupBefore", | |
| 5832 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S
": "save", "Cmd-F": "find", | |
| 5833 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shi
ft-Cmd-Alt-F": "replaceAll", | |
| 5834 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLi
neLeft", "Cmd-Delete": "delWrappedLineRight", | |
| 5835 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocS
tart", "Ctrl-Down": "goDocEnd", | |
| 5836 fallthrough: ["basic", "emacsy"] | |
| 5837 }; | |
| 5838 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; | |
| 5839 | |
| 5840 // KEYMAP DISPATCH | |
| 5841 | |
| 5842 function normalizeKeyName(name) { | |
| 5843 var parts = name.split(/-(?!$)/), name = parts[parts.length - 1]; | |
| 5844 var alt, ctrl, shift, cmd; | |
| 5845 for (var i = 0; i < parts.length - 1; i++) { | |
| 5846 var mod = parts[i]; | |
| 5847 if (/^(cmd|meta|m)$/i.test(mod)) cmd = true; | |
| 5848 else if (/^a(lt)?$/i.test(mod)) alt = true; | |
| 5849 else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true; | |
| 5850 else if (/^s(hift)$/i.test(mod)) shift = true; | |
| 5851 else throw new Error("Unrecognized modifier name: " + mod); | |
| 5852 } | |
| 5853 if (alt) name = "Alt-" + name; | |
| 5854 if (ctrl) name = "Ctrl-" + name; | |
| 5855 if (cmd) name = "Cmd-" + name; | |
| 5856 if (shift) name = "Shift-" + name; | |
| 5857 return name; | |
| 5858 } | |
| 5859 | |
| 5860 // This is a kludge to keep keymaps mostly working as raw objects | |
| 5861 // (backwards compatibility) while at the same time support features | |
| 5862 // like normalization and multi-stroke key bindings. It compiles a | |
| 5863 // new normalized keymap, and then updates the old object to reflect | |
| 5864 // this. | |
| 5865 CodeMirror.normalizeKeyMap = function(keymap) { | |
| 5866 var copy = {}; | |
| 5867 for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) { | |
| 5868 var value = keymap[keyname]; | |
| 5869 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue; | |
| 5870 if (value == "...") { delete keymap[keyname]; continue; } | |
| 5871 | |
| 5872 var keys = map(keyname.split(" "), normalizeKeyName); | |
| 5873 for (var i = 0; i < keys.length; i++) { | |
| 5874 var val, name; | |
| 5875 if (i == keys.length - 1) { | |
| 5876 name = keys.join(" "); | |
| 5877 val = value; | |
| 5878 } else { | |
| 5879 name = keys.slice(0, i + 1).join(" "); | |
| 5880 val = "..."; | |
| 5881 } | |
| 5882 var prev = copy[name]; | |
| 5883 if (!prev) copy[name] = val; | |
| 5884 else if (prev != val) throw new Error("Inconsistent bindings for " + nam
e); | |
| 5885 } | |
| 5886 delete keymap[keyname]; | |
| 5887 } | |
| 5888 for (var prop in copy) keymap[prop] = copy[prop]; | |
| 5889 return keymap; | |
| 5890 }; | |
| 5891 | |
| 5892 var lookupKey = CodeMirror.lookupKey = function(key, map, handle, context) { | |
| 5893 map = getKeyMap(map); | |
| 5894 var found = map.call ? map.call(key, context) : map[key]; | |
| 5895 if (found === false) return "nothing"; | |
| 5896 if (found === "...") return "multi"; | |
| 5897 if (found != null && handle(found)) return "handled"; | |
| 5898 | |
| 5899 if (map.fallthrough) { | |
| 5900 if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") | |
| 5901 return lookupKey(key, map.fallthrough, handle, context); | |
| 5902 for (var i = 0; i < map.fallthrough.length; i++) { | |
| 5903 var result = lookupKey(key, map.fallthrough[i], handle, context); | |
| 5904 if (result) return result; | |
| 5905 } | |
| 5906 } | |
| 5907 }; | |
| 5908 | |
| 5909 // Modifier key presses don't count as 'real' key presses for the | |
| 5910 // purpose of keymap fallthrough. | |
| 5911 var isModifierKey = CodeMirror.isModifierKey = function(value) { | |
| 5912 var name = typeof value == "string" ? value : keyNames[value.keyCode]; | |
| 5913 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; | |
| 5914 }; | |
| 5915 | |
| 5916 // Look up the name of a key as indicated by an event object. | |
| 5917 var keyName = CodeMirror.keyName = function(event, noShift) { | |
| 5918 if (presto && event.keyCode == 34 && event["char"]) return false; | |
| 5919 var base = keyNames[event.keyCode], name = base; | |
| 5920 if (name == null || event.altGraphKey) return false; | |
| 5921 if (event.altKey && base != "Alt") name = "Alt-" + name; | |
| 5922 if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name =
"Ctrl-" + name; | |
| 5923 if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "
Cmd-" + name; | |
| 5924 if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name; | |
| 5925 return name; | |
| 5926 }; | |
| 5927 | |
| 5928 function getKeyMap(val) { | |
| 5929 return typeof val == "string" ? keyMap[val] : val; | |
| 5930 } | |
| 5931 | |
| 5932 // FROMTEXTAREA | |
| 5933 | |
| 5934 CodeMirror.fromTextArea = function(textarea, options) { | |
| 5935 options = options ? copyObj(options) : {}; | |
| 5936 options.value = textarea.value; | |
| 5937 if (!options.tabindex && textarea.tabIndex) | |
| 5938 options.tabindex = textarea.tabIndex; | |
| 5939 if (!options.placeholder && textarea.placeholder) | |
| 5940 options.placeholder = textarea.placeholder; | |
| 5941 // Set autofocus to true if this textarea is focused, or if it has | |
| 5942 // autofocus and no other element is focused. | |
| 5943 if (options.autofocus == null) { | |
| 5944 var hasFocus = activeElt(); | |
| 5945 options.autofocus = hasFocus == textarea || | |
| 5946 textarea.getAttribute("autofocus") != null && hasFocus == document.body; | |
| 5947 } | |
| 5948 | |
| 5949 function save() {textarea.value = cm.getValue();} | |
| 5950 if (textarea.form) { | |
| 5951 on(textarea.form, "submit", save); | |
| 5952 // Deplorable hack to make the submit method do the right thing. | |
| 5953 if (!options.leaveSubmitMethodAlone) { | |
| 5954 var form = textarea.form, realSubmit = form.submit; | |
| 5955 try { | |
| 5956 var wrappedSubmit = form.submit = function() { | |
| 5957 save(); | |
| 5958 form.submit = realSubmit; | |
| 5959 form.submit(); | |
| 5960 form.submit = wrappedSubmit; | |
| 5961 }; | |
| 5962 } catch(e) {} | |
| 5963 } | |
| 5964 } | |
| 5965 | |
| 5966 options.finishInit = function(cm) { | |
| 5967 cm.save = save; | |
| 5968 cm.getTextArea = function() { return textarea; }; | |
| 5969 cm.toTextArea = function() { | |
| 5970 cm.toTextArea = isNaN; // Prevent this from being ran twice | |
| 5971 save(); | |
| 5972 textarea.parentNode.removeChild(cm.getWrapperElement()); | |
| 5973 textarea.style.display = ""; | |
| 5974 if (textarea.form) { | |
| 5975 off(textarea.form, "submit", save); | |
| 5976 if (typeof textarea.form.submit == "function") | |
| 5977 textarea.form.submit = realSubmit; | |
| 5978 } | |
| 5979 }; | |
| 5980 }; | |
| 5981 | |
| 5982 textarea.style.display = "none"; | |
| 5983 var cm = CodeMirror(function(node) { | |
| 5984 textarea.parentNode.insertBefore(node, textarea.nextSibling); | |
| 5985 }, options); | |
| 5986 return cm; | |
| 5987 }; | |
| 5988 | |
| 5989 // STRING STREAM | |
| 5990 | |
| 5991 // Fed to the mode parsers, provides helper functions to make | |
| 5992 // parsers more succinct. | |
| 5993 | |
| 5994 var StringStream = CodeMirror.StringStream = function(string, tabSize) { | |
| 5995 this.pos = this.start = 0; | |
| 5996 this.string = string; | |
| 5997 this.tabSize = tabSize || 8; | |
| 5998 this.lastColumnPos = this.lastColumnValue = 0; | |
| 5999 this.lineStart = 0; | |
| 6000 }; | |
| 6001 | |
| 6002 StringStream.prototype = { | |
| 6003 eol: function() {return this.pos >= this.string.length;}, | |
| 6004 sol: function() {return this.pos == this.lineStart;}, | |
| 6005 peek: function() {return this.string.charAt(this.pos) || undefined;}, | |
| 6006 next: function() { | |
| 6007 if (this.pos < this.string.length) | |
| 6008 return this.string.charAt(this.pos++); | |
| 6009 }, | |
| 6010 eat: function(match) { | |
| 6011 var ch = this.string.charAt(this.pos); | |
| 6012 if (typeof match == "string") var ok = ch == match; | |
| 6013 else var ok = ch && (match.test ? match.test(ch) : match(ch)); | |
| 6014 if (ok) {++this.pos; return ch;} | |
| 6015 }, | |
| 6016 eatWhile: function(match) { | |
| 6017 var start = this.pos; | |
| 6018 while (this.eat(match)){} | |
| 6019 return this.pos > start; | |
| 6020 }, | |
| 6021 eatSpace: function() { | |
| 6022 var start = this.pos; | |
| 6023 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; | |
| 6024 return this.pos > start; | |
| 6025 }, | |
| 6026 skipToEnd: function() {this.pos = this.string.length;}, | |
| 6027 skipTo: function(ch) { | |
| 6028 var found = this.string.indexOf(ch, this.pos); | |
| 6029 if (found > -1) {this.pos = found; return true;} | |
| 6030 }, | |
| 6031 backUp: function(n) {this.pos -= n;}, | |
| 6032 column: function() { | |
| 6033 if (this.lastColumnPos < this.start) { | |
| 6034 this.lastColumnValue = countColumn(this.string, this.start, this.tabSize
, this.lastColumnPos, this.lastColumnValue); | |
| 6035 this.lastColumnPos = this.start; | |
| 6036 } | |
| 6037 return this.lastColumnValue - (this.lineStart ? countColumn(this.string, t
his.lineStart, this.tabSize) : 0); | |
| 6038 }, | |
| 6039 indentation: function() { | |
| 6040 return countColumn(this.string, null, this.tabSize) - | |
| 6041 (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize)
: 0); | |
| 6042 }, | |
| 6043 match: function(pattern, consume, caseInsensitive) { | |
| 6044 if (typeof pattern == "string") { | |
| 6045 var cased = function(str) {return caseInsensitive ? str.toLowerCase() :
str;}; | |
| 6046 var substr = this.string.substr(this.pos, pattern.length); | |
| 6047 if (cased(substr) == cased(pattern)) { | |
| 6048 if (consume !== false) this.pos += pattern.length; | |
| 6049 return true; | |
| 6050 } | |
| 6051 } else { | |
| 6052 var match = this.string.slice(this.pos).match(pattern); | |
| 6053 if (match && match.index > 0) return null; | |
| 6054 if (match && consume !== false) this.pos += match[0].length; | |
| 6055 return match; | |
| 6056 } | |
| 6057 }, | |
| 6058 current: function(){return this.string.slice(this.start, this.pos);}, | |
| 6059 hideFirstChars: function(n, inner) { | |
| 6060 this.lineStart += n; | |
| 6061 try { return inner(); } | |
| 6062 finally { this.lineStart -= n; } | |
| 6063 } | |
| 6064 }; | |
| 6065 | |
| 6066 // TEXTMARKERS | |
| 6067 | |
| 6068 // Created with markText and setBookmark methods. A TextMarker is a | |
| 6069 // handle that can be used to clear or find a marked position in the | |
| 6070 // document. Line objects hold arrays (markedSpans) containing | |
| 6071 // {from, to, marker} object pointing to such marker objects, and | |
| 6072 // indicating that such a marker is present on that line. Multiple | |
| 6073 // lines may point to the same marker when it spans across lines. | |
| 6074 // The spans will have null for their from/to properties when the | |
| 6075 // marker continues beyond the start/end of the line. Markers have | |
| 6076 // links back to the lines they currently touch. | |
| 6077 | |
| 6078 var nextMarkerId = 0; | |
| 6079 | |
| 6080 var TextMarker = CodeMirror.TextMarker = function(doc, type) { | |
| 6081 this.lines = []; | |
| 6082 this.type = type; | |
| 6083 this.doc = doc; | |
| 6084 this.id = ++nextMarkerId; | |
| 6085 }; | |
| 6086 eventMixin(TextMarker); | |
| 6087 | |
| 6088 // Clear the marker. | |
| 6089 TextMarker.prototype.clear = function() { | |
| 6090 if (this.explicitlyCleared) return; | |
| 6091 var cm = this.doc.cm, withOp = cm && !cm.curOp; | |
| 6092 if (withOp) startOperation(cm); | |
| 6093 if (hasHandler(this, "clear")) { | |
| 6094 var found = this.find(); | |
| 6095 if (found) signalLater(this, "clear", found.from, found.to); | |
| 6096 } | |
| 6097 var min = null, max = null; | |
| 6098 for (var i = 0; i < this.lines.length; ++i) { | |
| 6099 var line = this.lines[i]; | |
| 6100 var span = getMarkedSpanFor(line.markedSpans, this); | |
| 6101 if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text"); | |
| 6102 else if (cm) { | |
| 6103 if (span.to != null) max = lineNo(line); | |
| 6104 if (span.from != null) min = lineNo(line); | |
| 6105 } | |
| 6106 line.markedSpans = removeMarkedSpan(line.markedSpans, span); | |
| 6107 if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) &
& cm) | |
| 6108 updateLineHeight(line, textHeight(cm.display)); | |
| 6109 } | |
| 6110 if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < th
is.lines.length; ++i) { | |
| 6111 var visual = visualLine(this.lines[i]), len = lineLength(visual); | |
| 6112 if (len > cm.display.maxLineLength) { | |
| 6113 cm.display.maxLine = visual; | |
| 6114 cm.display.maxLineLength = len; | |
| 6115 cm.display.maxLineChanged = true; | |
| 6116 } | |
| 6117 } | |
| 6118 | |
| 6119 if (min != null && cm && this.collapsed) regChange(cm, min, max + 1); | |
| 6120 this.lines.length = 0; | |
| 6121 this.explicitlyCleared = true; | |
| 6122 if (this.atomic && this.doc.cantEdit) { | |
| 6123 this.doc.cantEdit = false; | |
| 6124 if (cm) reCheckSelection(cm.doc); | |
| 6125 } | |
| 6126 if (cm) signalLater(cm, "markerCleared", cm, this); | |
| 6127 if (withOp) endOperation(cm); | |
| 6128 if (this.parent) this.parent.clear(); | |
| 6129 }; | |
| 6130 | |
| 6131 // Find the position of the marker in the document. Returns a {from, | |
| 6132 // to} object by default. Side can be passed to get a specific side | |
| 6133 // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the | |
| 6134 // Pos objects returned contain a line object, rather than a line | |
| 6135 // number (used to prevent looking up the same line twice). | |
| 6136 TextMarker.prototype.find = function(side, lineObj) { | |
| 6137 if (side == null && this.type == "bookmark") side = 1; | |
| 6138 var from, to; | |
| 6139 for (var i = 0; i < this.lines.length; ++i) { | |
| 6140 var line = this.lines[i]; | |
| 6141 var span = getMarkedSpanFor(line.markedSpans, this); | |
| 6142 if (span.from != null) { | |
| 6143 from = Pos(lineObj ? line : lineNo(line), span.from); | |
| 6144 if (side == -1) return from; | |
| 6145 } | |
| 6146 if (span.to != null) { | |
| 6147 to = Pos(lineObj ? line : lineNo(line), span.to); | |
| 6148 if (side == 1) return to; | |
| 6149 } | |
| 6150 } | |
| 6151 return from && {from: from, to: to}; | |
| 6152 }; | |
| 6153 | |
| 6154 // Signals that the marker's widget changed, and surrounding layout | |
| 6155 // should be recomputed. | |
| 6156 TextMarker.prototype.changed = function() { | |
| 6157 var pos = this.find(-1, true), widget = this, cm = this.doc.cm; | |
| 6158 if (!pos || !cm) return; | |
| 6159 runInOp(cm, function() { | |
| 6160 var line = pos.line, lineN = lineNo(pos.line); | |
| 6161 var view = findViewForLine(cm, lineN); | |
| 6162 if (view) { | |
| 6163 clearLineMeasurementCacheFor(view); | |
| 6164 cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; | |
| 6165 } | |
| 6166 cm.curOp.updateMaxLine = true; | |
| 6167 if (!lineIsHidden(widget.doc, line) && widget.height != null) { | |
| 6168 var oldHeight = widget.height; | |
| 6169 widget.height = null; | |
| 6170 var dHeight = widgetHeight(widget) - oldHeight; | |
| 6171 if (dHeight) | |
| 6172 updateLineHeight(line, line.height + dHeight); | |
| 6173 } | |
| 6174 }); | |
| 6175 }; | |
| 6176 | |
| 6177 TextMarker.prototype.attachLine = function(line) { | |
| 6178 if (!this.lines.length && this.doc.cm) { | |
| 6179 var op = this.doc.cm.curOp; | |
| 6180 if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) | |
| 6181 (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); | |
| 6182 } | |
| 6183 this.lines.push(line); | |
| 6184 }; | |
| 6185 TextMarker.prototype.detachLine = function(line) { | |
| 6186 this.lines.splice(indexOf(this.lines, line), 1); | |
| 6187 if (!this.lines.length && this.doc.cm) { | |
| 6188 var op = this.doc.cm.curOp; | |
| 6189 (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); | |
| 6190 } | |
| 6191 }; | |
| 6192 | |
| 6193 // Collapsed markers have unique ids, in order to be able to order | |
| 6194 // them, which is needed for uniquely determining an outer marker | |
| 6195 // when they overlap (they may nest, but not partially overlap). | |
| 6196 var nextMarkerId = 0; | |
| 6197 | |
| 6198 // Create a marker, wire it up to the right lines, and | |
| 6199 function markText(doc, from, to, options, type) { | |
| 6200 // Shared markers (across linked documents) are handled separately | |
| 6201 // (markTextShared will call out to this again, once per | |
| 6202 // document). | |
| 6203 if (options && options.shared) return markTextShared(doc, from, to, options,
type); | |
| 6204 // Ensure we are in an operation. | |
| 6205 if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, t
o, options, type); | |
| 6206 | |
| 6207 var marker = new TextMarker(doc, type), diff = cmp(from, to); | |
| 6208 if (options) copyObj(options, marker, false); | |
| 6209 // Don't connect empty markers unless clearWhenEmpty is false | |
| 6210 if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) | |
| 6211 return marker; | |
| 6212 if (marker.replacedWith) { | |
| 6213 // Showing up as a widget implies collapsed (widget replaces text) | |
| 6214 marker.collapsed = true; | |
| 6215 marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget"
); | |
| 6216 if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-
events", "true"); | |
| 6217 if (options.insertLeft) marker.widgetNode.insertLeft = true; | |
| 6218 } | |
| 6219 if (marker.collapsed) { | |
| 6220 if (conflictingCollapsedRange(doc, from.line, from, to, marker) || | |
| 6221 from.line != to.line && conflictingCollapsedRange(doc, to.line, from,
to, marker)) | |
| 6222 throw new Error("Inserting collapsed marker partially overlapping an exi
sting one"); | |
| 6223 sawCollapsedSpans = true; | |
| 6224 } | |
| 6225 | |
| 6226 if (marker.addToHistory) | |
| 6227 addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel,
NaN); | |
| 6228 | |
| 6229 var curLine = from.line, cm = doc.cm, updateMaxLine; | |
| 6230 doc.iter(curLine, to.line + 1, function(line) { | |
| 6231 if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line)
== cm.display.maxLine) | |
| 6232 updateMaxLine = true; | |
| 6233 if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0); | |
| 6234 addMarkedSpan(line, new MarkedSpan(marker, | |
| 6235 curLine == from.line ? from.ch : null, | |
| 6236 curLine == to.line ? to.ch : null)); | |
| 6237 ++curLine; | |
| 6238 }); | |
| 6239 // lineIsHidden depends on the presence of the spans, so needs a second pass | |
| 6240 if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { | |
| 6241 if (lineIsHidden(doc, line)) updateLineHeight(line, 0); | |
| 6242 }); | |
| 6243 | |
| 6244 if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker
.clear(); }); | |
| 6245 | |
| 6246 if (marker.readOnly) { | |
| 6247 sawReadOnlySpans = true; | |
| 6248 if (doc.history.done.length || doc.history.undone.length) | |
| 6249 doc.clearHistory(); | |
| 6250 } | |
| 6251 if (marker.collapsed) { | |
| 6252 marker.id = ++nextMarkerId; | |
| 6253 marker.atomic = true; | |
| 6254 } | |
| 6255 if (cm) { | |
| 6256 // Sync editor state | |
| 6257 if (updateMaxLine) cm.curOp.updateMaxLine = true; | |
| 6258 if (marker.collapsed) | |
| 6259 regChange(cm, from.line, to.line + 1); | |
| 6260 else if (marker.className || marker.title || marker.startStyle || marker.e
ndStyle || marker.css) | |
| 6261 for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text"); | |
| 6262 if (marker.atomic) reCheckSelection(cm.doc); | |
| 6263 signalLater(cm, "markerAdded", cm, marker); | |
| 6264 } | |
| 6265 return marker; | |
| 6266 } | |
| 6267 | |
| 6268 // SHARED TEXTMARKERS | |
| 6269 | |
| 6270 // A shared marker spans multiple linked documents. It is | |
| 6271 // implemented as a meta-marker-object controlling multiple normal | |
| 6272 // markers. | |
| 6273 var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary
) { | |
| 6274 this.markers = markers; | |
| 6275 this.primary = primary; | |
| 6276 for (var i = 0; i < markers.length; ++i) | |
| 6277 markers[i].parent = this; | |
| 6278 }; | |
| 6279 eventMixin(SharedTextMarker); | |
| 6280 | |
| 6281 SharedTextMarker.prototype.clear = function() { | |
| 6282 if (this.explicitlyCleared) return; | |
| 6283 this.explicitlyCleared = true; | |
| 6284 for (var i = 0; i < this.markers.length; ++i) | |
| 6285 this.markers[i].clear(); | |
| 6286 signalLater(this, "clear"); | |
| 6287 }; | |
| 6288 SharedTextMarker.prototype.find = function(side, lineObj) { | |
| 6289 return this.primary.find(side, lineObj); | |
| 6290 }; | |
| 6291 | |
| 6292 function markTextShared(doc, from, to, options, type) { | |
| 6293 options = copyObj(options); | |
| 6294 options.shared = false; | |
| 6295 var markers = [markText(doc, from, to, options, type)], primary = markers[0]
; | |
| 6296 var widget = options.widgetNode; | |
| 6297 linkedDocs(doc, function(doc) { | |
| 6298 if (widget) options.widgetNode = widget.cloneNode(true); | |
| 6299 markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options,
type)); | |
| 6300 for (var i = 0; i < doc.linked.length; ++i) | |
| 6301 if (doc.linked[i].isParent) return; | |
| 6302 primary = lst(markers); | |
| 6303 }); | |
| 6304 return new SharedTextMarker(markers, primary); | |
| 6305 } | |
| 6306 | |
| 6307 function findSharedMarkers(doc) { | |
| 6308 return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), | |
| 6309 function(m) { return m.parent; }); | |
| 6310 } | |
| 6311 | |
| 6312 function copySharedMarkers(doc, markers) { | |
| 6313 for (var i = 0; i < markers.length; i++) { | |
| 6314 var marker = markers[i], pos = marker.find(); | |
| 6315 var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); | |
| 6316 if (cmp(mFrom, mTo)) { | |
| 6317 var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.t
ype); | |
| 6318 marker.markers.push(subMark); | |
| 6319 subMark.parent = marker; | |
| 6320 } | |
| 6321 } | |
| 6322 } | |
| 6323 | |
| 6324 function detachSharedMarkers(markers) { | |
| 6325 for (var i = 0; i < markers.length; i++) { | |
| 6326 var marker = markers[i], linked = [marker.primary.doc];; | |
| 6327 linkedDocs(marker.primary.doc, function(d) { linked.push(d); }); | |
| 6328 for (var j = 0; j < marker.markers.length; j++) { | |
| 6329 var subMarker = marker.markers[j]; | |
| 6330 if (indexOf(linked, subMarker.doc) == -1) { | |
| 6331 subMarker.parent = null; | |
| 6332 marker.markers.splice(j--, 1); | |
| 6333 } | 8776 } |
| 6334 } | 8777 } |
| 6335 } | 8778 } |
| 6336 } | 8779 } |
| 6337 | 8780 var found = find(textNode, topNode, offset) |
| 6338 // TEXTMARKER SPANS | 8781 if (found) { return badPos(found, bad) } |
| 6339 | 8782 |
| 6340 function MarkedSpan(marker, from, to) { | 8783 // FIXME this is all really shaky. might handle the few cases it needs to hand
le, but likely to cause problems |
| 6341 this.marker = marker; | 8784 for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.len
gth - offset : 0; after; after = after.nextSibling) { |
| 6342 this.from = from; this.to = to; | 8785 found = find(after, after.firstChild, 0) |
| 6343 } | 8786 if (found) |
| 6344 | 8787 { return badPos(Pos(found.line, found.ch - dist), bad) } |
| 6345 // Search an array of spans for a span matching the given marker. | 8788 else |
| 6346 function getMarkedSpanFor(spans, marker) { | 8789 { dist += after.textContent.length } |
| 6347 if (spans) for (var i = 0; i < spans.length; ++i) { | 8790 } |
| 6348 var span = spans[i]; | 8791 for (var before = topNode.previousSibling, dist$1 = offset; before; before = b
efore.previousSibling) { |
| 6349 if (span.marker == marker) return span; | 8792 found = find(before, before.firstChild, -1) |
| 6350 } | 8793 if (found) |
| 6351 } | 8794 { return badPos(Pos(found.line, found.ch + dist$1), bad) } |
| 6352 // Remove a span from an array, returning undefined if no spans are | 8795 else |
| 6353 // left (we don't store arrays for lines without spans). | 8796 { dist$1 += before.textContent.length } |
| 6354 function removeMarkedSpan(spans, span) { | 8797 } |
| 6355 for (var r, i = 0; i < spans.length; ++i) | 8798 } |
| 6356 if (spans[i] != span) (r || (r = [])).push(spans[i]); | 8799 |
| 6357 return r; | 8800 // TEXTAREA INPUT STYLE |
| 6358 } | 8801 |
| 6359 // Add a span to a line. | 8802 var TextareaInput = function TextareaInput(cm) { |
| 6360 function addMarkedSpan(line, span) { | 8803 this.cm = cm |
| 6361 line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [spa
n]; | 8804 // See input.poll and input.reset |
| 6362 span.marker.attachLine(line); | 8805 this.prevInput = "" |
| 6363 } | 8806 |
| 6364 | 8807 // Flag that indicates whether we expect input to appear real soon |
| 6365 // Used for the algorithm that adjusts markers for a change in the | 8808 // now (after some event like 'keypress' or 'input') and are |
| 6366 // document. These functions cut an array of spans at a given | 8809 // polling intensively. |
| 6367 // character position, returning an array of remaining chunks (or | 8810 this.pollingFast = false |
| 6368 // undefined if nothing remains). | 8811 // Self-resetting timeout for the poller |
| 6369 function markedSpansBefore(old, startCh, isInsert) { | 8812 this.polling = new Delayed() |
| 6370 if (old) for (var i = 0, nw; i < old.length; ++i) { | 8813 // Tracks when input.reset has punted to just putting a short |
| 6371 var span = old[i], marker = span.marker; | 8814 // string into the textarea instead of the full selection. |
| 6372 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from
<= startCh : span.from < startCh); | 8815 this.inaccurateSelection = false |
| 6373 if (startsBefore || span.from == startCh && marker.type == "bookmark" && (
!isInsert || !span.marker.insertLeft)) { | 8816 // Used to work around IE issue with selection being forgotten when focus move
s away from textarea |
| 6374 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= s
tartCh : span.to > startCh); | 8817 this.hasSelection = false |
| 6375 (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? nul
l : span.to)); | 8818 this.composing = null |
| 8819 }; |
| 8820 |
| 8821 TextareaInput.prototype.init = function init (display) { |
| 8822 var this$1 = this; |
| 8823 |
| 8824 var input = this, cm = this.cm |
| 8825 |
| 8826 // Wraps and hides input textarea |
| 8827 var div = this.wrapper = hiddenTextarea() |
| 8828 // The semihidden textarea that is focused when the editor is |
| 8829 // focused, and receives input. |
| 8830 var te = this.textarea = div.firstChild |
| 8831 display.wrapper.insertBefore(div, display.wrapper.firstChild) |
| 8832 |
| 8833 // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to w
ork in iOS 8 anymore) |
| 8834 if (ios) { te.style.width = "0px" } |
| 8835 |
| 8836 on(te, "input", function () { |
| 8837 if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = nu
ll } |
| 8838 input.poll() |
| 8839 }) |
| 8840 |
| 8841 on(te, "paste", function (e) { |
| 8842 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } |
| 8843 |
| 8844 cm.state.pasteIncoming = true |
| 8845 input.fastPoll() |
| 8846 }) |
| 8847 |
| 8848 function prepareCopyCut(e) { |
| 8849 if (signalDOMEvent(cm, e)) { return } |
| 8850 if (cm.somethingSelected()) { |
| 8851 setLastCopied({lineWise: false, text: cm.getSelections()}) |
| 8852 if (input.inaccurateSelection) { |
| 8853 input.prevInput = "" |
| 8854 input.inaccurateSelection = false |
| 8855 te.value = lastCopied.text.join("\n") |
| 8856 selectInput(te) |
| 6376 } | 8857 } |
| 6377 } | 8858 } else if (!cm.options.lineWiseCopyCut) { |
| 6378 return nw; | 8859 return |
| 6379 } | 8860 } else { |
| 6380 function markedSpansAfter(old, endCh, isInsert) { | 8861 var ranges = copyableRanges(cm) |
| 6381 if (old) for (var i = 0, nw; i < old.length; ++i) { | 8862 setLastCopied({lineWise: true, text: ranges.text}) |
| 6382 var span = old[i], marker = span.marker; | 8863 if (e.type == "cut") { |
| 6383 var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= end
Ch : span.to > endCh); | 8864 cm.setSelections(ranges.ranges, null, sel_dontScroll) |
| 6384 if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isIn
sert || span.marker.insertLeft)) { | 8865 } else { |
| 6385 var startsBefore = span.from == null || (marker.inclusiveLeft ? span.fro
m <= endCh : span.from < endCh); | 8866 input.prevInput = "" |
| 6386 (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span
.from - endCh, | 8867 te.value = ranges.text.join("\n") |
| 6387 span.to == null ? null : span.to -
endCh)); | 8868 selectInput(te) |
| 6388 } | 8869 } |
| 6389 } | 8870 } |
| 6390 return nw; | 8871 if (e.type == "cut") { cm.state.cutIncoming = true } |
| 6391 } | 8872 } |
| 6392 | 8873 on(te, "cut", prepareCopyCut) |
| 6393 // Given a change object, compute the new set of marker spans that | 8874 on(te, "copy", prepareCopyCut) |
| 6394 // cover the line in which the change took place. Removes spans | 8875 |
| 6395 // entirely within the change, reconnects spans belonging to the | 8876 on(display.scroller, "paste", function (e) { |
| 6396 // same marker that appear on both sides of the change, and cuts off | 8877 if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } |
| 6397 // spans partially within the change. Returns an array of span | 8878 cm.state.pasteIncoming = true |
| 6398 // arrays with one element for each line in (after) the change. | 8879 input.focus() |
| 6399 function stretchSpansOverChange(doc, change) { | 8880 }) |
| 6400 if (change.full) return null; | 8881 |
| 6401 var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.lin
e).markedSpans; | 8882 // Prevent normal selection in the editor (we handle our own) |
| 6402 var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).ma
rkedSpans; | 8883 on(display.lineSpace, "selectstart", function (e) { |
| 6403 if (!oldFirst && !oldLast) return null; | 8884 if (!eventInWidget(display, e)) { e_preventDefault(e) } |
| 6404 | 8885 }) |
| 6405 var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.fr
om, change.to) == 0; | 8886 |
| 6406 // Get the spans that 'stick out' on both sides | 8887 on(te, "compositionstart", function () { |
| 6407 var first = markedSpansBefore(oldFirst, startCh, isInsert); | 8888 var start = cm.getCursor("from") |
| 6408 var last = markedSpansAfter(oldLast, endCh, isInsert); | 8889 if (input.composing) { input.composing.range.clear() } |
| 6409 | 8890 input.composing = { |
| 6410 // Next, merge those two ends | 8891 start: start, |
| 6411 var sameLine = change.text.length == 1, offset = lst(change.text).length + (
sameLine ? startCh : 0); | 8892 range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-comp
osing"}) |
| 6412 if (first) { | 8893 } |
| 6413 // Fix up .to properties of first | 8894 }) |
| 6414 for (var i = 0; i < first.length; ++i) { | 8895 on(te, "compositionend", function () { |
| 6415 var span = first[i]; | 8896 if (input.composing) { |
| 6416 if (span.to == null) { | 8897 input.poll() |
| 6417 var found = getMarkedSpanFor(last, span.marker); | 8898 input.composing.range.clear() |
| 6418 if (!found) span.to = startCh; | 8899 input.composing = null |
| 6419 else if (sameLine) span.to = found.to == null ? null : found.to + offs
et; | 8900 } |
| 8901 }) |
| 8902 }; |
| 8903 |
| 8904 TextareaInput.prototype.prepareSelection = function prepareSelection$1 () { |
| 8905 // Redraw the selection and/or cursor |
| 8906 var cm = this.cm, display = cm.display, doc = cm.doc |
| 8907 var result = prepareSelection(cm) |
| 8908 |
| 8909 // Move the hidden textarea near the cursor to prevent scrolling artifacts |
| 8910 if (cm.options.moveInputWithCursor) { |
| 8911 var headPos = cursorCoords(cm, doc.sel.primary().head, "div") |
| 8912 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lin
eDiv.getBoundingClientRect() |
| 8913 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, |
| 8914 headPos.top + lineOff.top - wrapOff.top)
) |
| 8915 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, |
| 8916 headPos.left + lineOff.left - wrapOff.l
eft)) |
| 8917 } |
| 8918 |
| 8919 return result |
| 8920 }; |
| 8921 |
| 8922 TextareaInput.prototype.showSelection = function showSelection (drawn) { |
| 8923 var cm = this.cm, display = cm.display |
| 8924 removeChildrenAndAdd(display.cursorDiv, drawn.cursors) |
| 8925 removeChildrenAndAdd(display.selectionDiv, drawn.selection) |
| 8926 if (drawn.teTop != null) { |
| 8927 this.wrapper.style.top = drawn.teTop + "px" |
| 8928 this.wrapper.style.left = drawn.teLeft + "px" |
| 8929 } |
| 8930 }; |
| 8931 |
| 8932 // Reset the input to correspond to the selection (or to be empty, |
| 8933 // when not typing and nothing is selected) |
| 8934 TextareaInput.prototype.reset = function reset (typing) { |
| 8935 if (this.contextMenuPending) { return } |
| 8936 var minimal, selected, cm = this.cm, doc = cm.doc |
| 8937 if (cm.somethingSelected()) { |
| 8938 this.prevInput = "" |
| 8939 var range = doc.sel.primary() |
| 8940 minimal = hasCopyEvent && |
| 8941 (range.to().line - range.from().line > 100 || (selected = cm.getSelection(
)).length > 1000) |
| 8942 var content = minimal ? "-" : selected || cm.getSelection() |
| 8943 this.textarea.value = content |
| 8944 if (cm.state.focused) { selectInput(this.textarea) } |
| 8945 if (ie && ie_version >= 9) { this.hasSelection = content } |
| 8946 } else if (!typing) { |
| 8947 this.prevInput = this.textarea.value = "" |
| 8948 if (ie && ie_version >= 9) { this.hasSelection = null } |
| 8949 } |
| 8950 this.inaccurateSelection = minimal |
| 8951 }; |
| 8952 |
| 8953 TextareaInput.prototype.getField = function getField () { return this.textarea }
; |
| 8954 |
| 8955 TextareaInput.prototype.supportsTouch = function supportsTouch () { return false
}; |
| 8956 |
| 8957 TextareaInput.prototype.focus = function focus () { |
| 8958 if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.
textarea)) { |
| 8959 try { this.textarea.focus() } |
| 8960 catch (e) {} // IE8 will throw if the textarea is display: none or not in DO
M |
| 8961 } |
| 8962 }; |
| 8963 |
| 8964 TextareaInput.prototype.blur = function blur () { this.textarea.blur() }; |
| 8965 |
| 8966 TextareaInput.prototype.resetPosition = function resetPosition () { |
| 8967 this.wrapper.style.top = this.wrapper.style.left = 0 |
| 8968 }; |
| 8969 |
| 8970 TextareaInput.prototype.receivedFocus = function receivedFocus () { this.slowPol
l() }; |
| 8971 |
| 8972 // Poll for input changes, using the normal rate of polling. This |
| 8973 // runs as long as the editor is focused. |
| 8974 TextareaInput.prototype.slowPoll = function slowPoll () { |
| 8975 var this$1 = this; |
| 8976 |
| 8977 if (this.pollingFast) { return } |
| 8978 this.polling.set(this.cm.options.pollInterval, function () { |
| 8979 this$1.poll() |
| 8980 if (this$1.cm.state.focused) { this$1.slowPoll() } |
| 8981 }) |
| 8982 }; |
| 8983 |
| 8984 // When an event has just come in that is likely to add or change |
| 8985 // something in the input textarea, we poll faster, to ensure that |
| 8986 // the change appears on the screen quickly. |
| 8987 TextareaInput.prototype.fastPoll = function fastPoll () { |
| 8988 var missed = false, input = this |
| 8989 input.pollingFast = true |
| 8990 function p() { |
| 8991 var changed = input.poll() |
| 8992 if (!changed && !missed) {missed = true; input.polling.set(60, p)} |
| 8993 else {input.pollingFast = false; input.slowPoll()} |
| 8994 } |
| 8995 input.polling.set(20, p) |
| 8996 }; |
| 8997 |
| 8998 // Read input from the textarea, and update the document to match. |
| 8999 // When something is selected, it is present in the textarea, and |
| 9000 // selected (unless it is huge, in which case a placeholder is |
| 9001 // used). When nothing is selected, the cursor sits after previously |
| 9002 // seen text (can be empty), which is stored in prevInput (we must |
| 9003 // not reset the textarea when typing, because that breaks IME). |
| 9004 TextareaInput.prototype.poll = function poll () { |
| 9005 var this$1 = this; |
| 9006 |
| 9007 var cm = this.cm, input = this.textarea, prevInput = this.prevInput |
| 9008 // Since this is called a *lot*, try to bail out as cheaply as |
| 9009 // possible when it is clear that nothing happened. hasSelection |
| 9010 // will be the case when there is a lot of text in the textarea, |
| 9011 // in which case reading its value would be expensive. |
| 9012 if (this.contextMenuPending || !cm.state.focused || |
| 9013 (hasSelection(input) && !prevInput && !this.composing) || |
| 9014 cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) |
| 9015 { return false } |
| 9016 |
| 9017 var text = input.value |
| 9018 // If nothing changed, bail. |
| 9019 if (text == prevInput && !cm.somethingSelected()) { return false } |
| 9020 // Work around nonsensical selection resetting in IE9/10, and |
| 9021 // inexplicable appearance of private area unicode characters on |
| 9022 // some key combos in Mac (#2689). |
| 9023 if (ie && ie_version >= 9 && this.hasSelection === text || |
| 9024 mac && /[\uf700-\uf7ff]/.test(text)) { |
| 9025 cm.display.input.reset() |
| 9026 return false |
| 9027 } |
| 9028 |
| 9029 if (cm.doc.sel == cm.display.selForContextMenu) { |
| 9030 var first = text.charCodeAt(0) |
| 9031 if (first == 0x200b && !prevInput) { prevInput = "\u200b" } |
| 9032 if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } |
| 9033 } |
| 9034 // Find the part of the input that is actually new |
| 9035 var same = 0, l = Math.min(prevInput.length, text.length) |
| 9036 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++sa
me } |
| 9037 |
| 9038 runInOp(cm, function () { |
| 9039 applyTextInput(cm, text.slice(same), prevInput.length - same, |
| 9040 null, this$1.composing ? "*compose" : null) |
| 9041 |
| 9042 // Don't leave long text in the textarea, since it makes further polling slo
w |
| 9043 if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.pr
evInput = "" } |
| 9044 else { this$1.prevInput = text } |
| 9045 |
| 9046 if (this$1.composing) { |
| 9047 this$1.composing.range.clear() |
| 9048 this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor(
"to"), |
| 9049 {className: "CodeMirror-composing"}) |
| 9050 } |
| 9051 }) |
| 9052 return true |
| 9053 }; |
| 9054 |
| 9055 TextareaInput.prototype.ensurePolled = function ensurePolled () { |
| 9056 if (this.pollingFast && this.poll()) { this.pollingFast = false } |
| 9057 }; |
| 9058 |
| 9059 TextareaInput.prototype.onKeyPress = function onKeyPress () { |
| 9060 if (ie && ie_version >= 9) { this.hasSelection = null } |
| 9061 this.fastPoll() |
| 9062 }; |
| 9063 |
| 9064 TextareaInput.prototype.onContextMenu = function onContextMenu (e) { |
| 9065 var input = this, cm = input.cm, display = cm.display, te = input.textarea |
| 9066 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop |
| 9067 if (!pos || presto) { return } // Opera is difficult. |
| 9068 |
| 9069 // Reset the current text selection only if the click is done outside of the s
election |
| 9070 // and 'resetSelectionOnContextMenu' option is true. |
| 9071 var reset = cm.options.resetSelectionOnContextMenu |
| 9072 if (reset && cm.doc.sel.contains(pos) == -1) |
| 9073 { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll)
} |
| 9074 |
| 9075 var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText |
| 9076 input.wrapper.style.cssText = "position: absolute" |
| 9077 var wrapperBox = input.wrapper.getBoundingClientRect() |
| 9078 te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top:
" + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.l
eft - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255,
.05)" : "transparent") + ";\n outline: none; border-width: 0; outline: non
e; overflow: hidden; opacity: .05; filter: alpha(opacity=5);" |
| 9079 var oldScrollY |
| 9080 if (webkit) { oldScrollY = window.scrollY } // Work around Chrome issue (#2712
) |
| 9081 display.input.focus() |
| 9082 if (webkit) { window.scrollTo(null, oldScrollY) } |
| 9083 display.input.reset() |
| 9084 // Adds "Select all" to context menu in FF |
| 9085 if (!cm.somethingSelected()) { te.value = input.prevInput = " " } |
| 9086 input.contextMenuPending = true |
| 9087 display.selForContextMenu = cm.doc.sel |
| 9088 clearTimeout(display.detectingSelectAll) |
| 9089 |
| 9090 // Select-all will be greyed out if there's nothing to select, so |
| 9091 // this adds a zero-width space so that we can later check whether |
| 9092 // it got selected. |
| 9093 function prepareSelectAllHack() { |
| 9094 if (te.selectionStart != null) { |
| 9095 var selected = cm.somethingSelected() |
| 9096 var extval = "\u200b" + (selected ? te.value : "") |
| 9097 te.value = "\u21da" // Used to catch context-menu undo |
| 9098 te.value = extval |
| 9099 input.prevInput = selected ? "" : "\u200b" |
| 9100 te.selectionStart = 1; te.selectionEnd = extval.length |
| 9101 // Re-set this, in case some other handler touched the |
| 9102 // selection in the meantime. |
| 9103 display.selForContextMenu = cm.doc.sel |
| 9104 } |
| 9105 } |
| 9106 function rehide() { |
| 9107 input.contextMenuPending = false |
| 9108 input.wrapper.style.cssText = oldWrapperCSS |
| 9109 te.style.cssText = oldCSS |
| 9110 if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller
.scrollTop = scrollPos) } |
| 9111 |
| 9112 // Try to detect the user choosing select-all |
| 9113 if (te.selectionStart != null) { |
| 9114 if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack() } |
| 9115 var i = 0, poll = function () { |
| 9116 if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && |
| 9117 te.selectionEnd > 0 && input.prevInput == "\u200b") { |
| 9118 operation(cm, selectAll)(cm) |
| 9119 } else if (i++ < 10) { |
| 9120 display.detectingSelectAll = setTimeout(poll, 500) |
| 9121 } else { |
| 9122 display.selForContextMenu = null |
| 9123 display.input.reset() |
| 6420 } | 9124 } |
| 6421 } | 9125 } |
| 6422 } | 9126 display.detectingSelectAll = setTimeout(poll, 200) |
| 6423 if (last) { | 9127 } |
| 6424 // Fix up .from in last (or move them into first in case of sameLine) | 9128 } |
| 6425 for (var i = 0; i < last.length; ++i) { | 9129 |
| 6426 var span = last[i]; | 9130 if (ie && ie_version >= 9) { prepareSelectAllHack() } |
| 6427 if (span.to != null) span.to += offset; | 9131 if (captureRightClick) { |
| 6428 if (span.from == null) { | 9132 e_stop(e) |
| 6429 var found = getMarkedSpanFor(first, span.marker); | 9133 var mouseup = function () { |
| 6430 if (!found) { | 9134 off(window, "mouseup", mouseup) |
| 6431 span.from = offset; | 9135 setTimeout(rehide, 20) |
| 6432 if (sameLine) (first || (first = [])).push(span); | 9136 } |
| 6433 } | 9137 on(window, "mouseup", mouseup) |
| 6434 } else { | 9138 } else { |
| 6435 span.from += offset; | 9139 setTimeout(rehide, 50) |
| 6436 if (sameLine) (first || (first = [])).push(span); | 9140 } |
| 9141 }; |
| 9142 |
| 9143 TextareaInput.prototype.readOnlyChanged = function readOnlyChanged (val) { |
| 9144 if (!val) { this.reset() } |
| 9145 }; |
| 9146 |
| 9147 TextareaInput.prototype.setUneditable = function setUneditable () {}; |
| 9148 |
| 9149 TextareaInput.prototype.needsContentAttribute = false |
| 9150 |
| 9151 function fromTextArea(textarea, options) { |
| 9152 options = options ? copyObj(options) : {} |
| 9153 options.value = textarea.value |
| 9154 if (!options.tabindex && textarea.tabIndex) |
| 9155 { options.tabindex = textarea.tabIndex } |
| 9156 if (!options.placeholder && textarea.placeholder) |
| 9157 { options.placeholder = textarea.placeholder } |
| 9158 // Set autofocus to true if this textarea is focused, or if it has |
| 9159 // autofocus and no other element is focused. |
| 9160 if (options.autofocus == null) { |
| 9161 var hasFocus = activeElt() |
| 9162 options.autofocus = hasFocus == textarea || |
| 9163 textarea.getAttribute("autofocus") != null && hasFocus == document.body |
| 9164 } |
| 9165 |
| 9166 function save() {textarea.value = cm.getValue()} |
| 9167 |
| 9168 var realSubmit |
| 9169 if (textarea.form) { |
| 9170 on(textarea.form, "submit", save) |
| 9171 // Deplorable hack to make the submit method do the right thing. |
| 9172 if (!options.leaveSubmitMethodAlone) { |
| 9173 var form = textarea.form |
| 9174 realSubmit = form.submit |
| 9175 try { |
| 9176 var wrappedSubmit = form.submit = function () { |
| 9177 save() |
| 9178 form.submit = realSubmit |
| 9179 form.submit() |
| 9180 form.submit = wrappedSubmit |
| 6437 } | 9181 } |
| 9182 } catch(e) {} |
| 9183 } |
| 9184 } |
| 9185 |
| 9186 options.finishInit = function (cm) { |
| 9187 cm.save = save |
| 9188 cm.getTextArea = function () { return textarea; } |
| 9189 cm.toTextArea = function () { |
| 9190 cm.toTextArea = isNaN // Prevent this from being ran twice |
| 9191 save() |
| 9192 textarea.parentNode.removeChild(cm.getWrapperElement()) |
| 9193 textarea.style.display = "" |
| 9194 if (textarea.form) { |
| 9195 off(textarea.form, "submit", save) |
| 9196 if (typeof textarea.form.submit == "function") |
| 9197 { textarea.form.submit = realSubmit } |
| 6438 } | 9198 } |
| 6439 } | 9199 } |
| 6440 // Make sure we didn't create any zero-length spans | 9200 } |
| 6441 if (first) first = clearEmptySpans(first); | 9201 |
| 6442 if (last && last != first) last = clearEmptySpans(last); | 9202 textarea.style.display = "none" |
| 6443 | 9203 var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(
node, textarea.nextSibling); }, |
| 6444 var newMarkers = [first]; | 9204 options) |
| 6445 if (!sameLine) { | 9205 return cm |
| 6446 // Fill gap with whole-line-spans | 9206 } |
| 6447 var gap = change.text.length - 2, gapMarkers; | 9207 |
| 6448 if (gap > 0 && first) | 9208 function addLegacyProps(CodeMirror) { |
| 6449 for (var i = 0; i < first.length; ++i) | 9209 CodeMirror.off = off |
| 6450 if (first[i].to == null) | 9210 CodeMirror.on = on |
| 6451 (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marke
r, null, null)); | 9211 CodeMirror.wheelEventPixels = wheelEventPixels |
| 6452 for (var i = 0; i < gap; ++i) | 9212 CodeMirror.Doc = Doc |
| 6453 newMarkers.push(gapMarkers); | 9213 CodeMirror.splitLines = splitLinesAuto |
| 6454 newMarkers.push(last); | 9214 CodeMirror.countColumn = countColumn |
| 6455 } | 9215 CodeMirror.findColumn = findColumn |
| 6456 return newMarkers; | 9216 CodeMirror.isWordChar = isWordCharBasic |
| 6457 } | 9217 CodeMirror.Pass = Pass |
| 6458 | 9218 CodeMirror.signal = signal |
| 6459 // Remove spans that are empty and don't have a clearWhenEmpty | 9219 CodeMirror.Line = Line |
| 6460 // option of false. | 9220 CodeMirror.changeEnd = changeEnd |
| 6461 function clearEmptySpans(spans) { | 9221 CodeMirror.scrollbarModel = scrollbarModel |
| 6462 for (var i = 0; i < spans.length; ++i) { | 9222 CodeMirror.Pos = Pos |
| 6463 var span = spans[i]; | 9223 CodeMirror.cmpPos = cmp |
| 6464 if (span.from != null && span.from == span.to && span.marker.clearWhenEmpt
y !== false) | 9224 CodeMirror.modes = modes |
| 6465 spans.splice(i--, 1); | 9225 CodeMirror.mimeModes = mimeModes |
| 6466 } | 9226 CodeMirror.resolveMode = resolveMode |
| 6467 if (!spans.length) return null; | 9227 CodeMirror.getMode = getMode |
| 6468 return spans; | 9228 CodeMirror.modeExtensions = modeExtensions |
| 6469 } | 9229 CodeMirror.extendMode = extendMode |
| 6470 | 9230 CodeMirror.copyState = copyState |
| 6471 // Used for un/re-doing changes from the history. Combines the | 9231 CodeMirror.startState = startState |
| 6472 // result of computing the existing spans with the set of spans that | 9232 CodeMirror.innerMode = innerMode |
| 6473 // existed in the history (so that deleting around a span and then | 9233 CodeMirror.commands = commands |
| 6474 // undoing brings back the span). | 9234 CodeMirror.keyMap = keyMap |
| 6475 function mergeOldSpans(doc, change) { | 9235 CodeMirror.keyName = keyName |
| 6476 var old = getOldSpans(doc, change); | 9236 CodeMirror.isModifierKey = isModifierKey |
| 6477 var stretched = stretchSpansOverChange(doc, change); | 9237 CodeMirror.lookupKey = lookupKey |
| 6478 if (!old) return stretched; | 9238 CodeMirror.normalizeKeyMap = normalizeKeyMap |
| 6479 if (!stretched) return old; | 9239 CodeMirror.StringStream = StringStream |
| 6480 | 9240 CodeMirror.SharedTextMarker = SharedTextMarker |
| 6481 for (var i = 0; i < old.length; ++i) { | 9241 CodeMirror.TextMarker = TextMarker |
| 6482 var oldCur = old[i], stretchCur = stretched[i]; | 9242 CodeMirror.LineWidget = LineWidget |
| 6483 if (oldCur && stretchCur) { | 9243 CodeMirror.e_preventDefault = e_preventDefault |
| 6484 spans: for (var j = 0; j < stretchCur.length; ++j) { | 9244 CodeMirror.e_stopPropagation = e_stopPropagation |
| 6485 var span = stretchCur[j]; | 9245 CodeMirror.e_stop = e_stop |
| 6486 for (var k = 0; k < oldCur.length; ++k) | 9246 CodeMirror.addClass = addClass |
| 6487 if (oldCur[k].marker == span.marker) continue spans; | 9247 CodeMirror.contains = contains |
| 6488 oldCur.push(span); | 9248 CodeMirror.rmClass = rmClass |
| 6489 } | 9249 CodeMirror.keyNames = keyNames |
| 6490 } else if (stretchCur) { | 9250 } |
| 6491 old[i] = stretchCur; | 9251 |
| 6492 } | 9252 // EDITOR CONSTRUCTOR |
| 6493 } | 9253 |
| 6494 return old; | 9254 defineOptions(CodeMirror) |
| 6495 } | 9255 |
| 6496 | 9256 addEditorMethods(CodeMirror) |
| 6497 // Used to 'clip' out readOnly ranges when making a change. | 9257 |
| 6498 function removeReadOnlyRanges(doc, from, to) { | 9258 // Set up methods on CodeMirror's prototype to redirect to the editor's document
. |
| 6499 var markers = null; | 9259 var dontDelegate = "iter insert remove copy getEditor constructor".split(" ") |
| 6500 doc.iter(from.line, to.line + 1, function(line) { | 9260 for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && inde
xOf(dontDelegate, prop) < 0) |
| 6501 if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { | 9261 { CodeMirror.prototype[prop] = (function(method) { |
| 6502 var mark = line.markedSpans[i].marker; | 9262 return function() {return method.apply(this.doc, arguments)} |
| 6503 if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) | 9263 })(Doc.prototype[prop]) } } |
| 6504 (markers || (markers = [])).push(mark); | 9264 |
| 6505 } | 9265 eventMixin(Doc) |
| 6506 }); | 9266 |
| 6507 if (!markers) return null; | 9267 // INPUT HANDLING |
| 6508 var parts = [{from: from, to: to}]; | 9268 |
| 6509 for (var i = 0; i < markers.length; ++i) { | 9269 CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentE
ditableInput} |
| 6510 var mk = markers[i], m = mk.find(0); | 9270 |
| 6511 for (var j = 0; j < parts.length; ++j) { | 9271 // MODE DEFINITION AND QUERYING |
| 6512 var p = parts[j]; | 9272 |
| 6513 if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue; | 9273 // Extra arguments are stored as the mode's dependencies, which is |
| 6514 var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to
); | 9274 // used by (legacy) mechanisms like loadmode.js to automatically |
| 6515 if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) | 9275 // load a mode. (Preferred mechanism is the require/define calls.) |
| 6516 newParts.push({from: p.from, to: m.from}); | 9276 CodeMirror.defineMode = function(name/*, mode, …*/) { |
| 6517 if (dto > 0 || !mk.inclusiveRight && !dto) | 9277 if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode =
name } |
| 6518 newParts.push({from: m.to, to: p.to}); | 9278 defineMode.apply(this, arguments) |
| 6519 parts.splice.apply(parts, newParts); | 9279 } |
| 6520 j += newParts.length - 1; | 9280 |
| 6521 } | 9281 CodeMirror.defineMIME = defineMIME |
| 6522 } | 9282 |
| 6523 return parts; | 9283 // Minimal default mode. |
| 6524 } | 9284 CodeMirror.defineMode("null", function () { return ({token: function (stream) {
return stream.skipToEnd(); }}); }) |
| 6525 | 9285 CodeMirror.defineMIME("text/plain", "null") |
| 6526 // Connect or disconnect spans from a line. | 9286 |
| 6527 function detachMarkedSpans(line) { | 9287 // EXTENSIONS |
| 6528 var spans = line.markedSpans; | 9288 |
| 6529 if (!spans) return; | 9289 CodeMirror.defineExtension = function (name, func) { |
| 6530 for (var i = 0; i < spans.length; ++i) | 9290 CodeMirror.prototype[name] = func |
| 6531 spans[i].marker.detachLine(line); | 9291 } |
| 6532 line.markedSpans = null; | 9292 CodeMirror.defineDocExtension = function (name, func) { |
| 6533 } | 9293 Doc.prototype[name] = func |
| 6534 function attachMarkedSpans(line, spans) { | 9294 } |
| 6535 if (!spans) return; | 9295 |
| 6536 for (var i = 0; i < spans.length; ++i) | 9296 CodeMirror.fromTextArea = fromTextArea |
| 6537 spans[i].marker.attachLine(line); | 9297 |
| 6538 line.markedSpans = spans; | 9298 addLegacyProps(CodeMirror) |
| 6539 } | 9299 |
| 6540 | 9300 CodeMirror.version = "5.25.1" |
| 6541 // Helpers used when computing which overlapping collapsed span | 9301 |
| 6542 // counts as the larger one. | 9302 return CodeMirror; |
| 6543 function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; } | 9303 |
| 6544 function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; } | 9304 }))); |
| 6545 | |
| 6546 // Returns a number indicating which of two overlapping collapsed | |
| 6547 // spans is larger (and thus includes the other). Falls back to | |
| 6548 // comparing ids when the spans cover exactly the same range. | |
| 6549 function compareCollapsedMarkers(a, b) { | |
| 6550 var lenDiff = a.lines.length - b.lines.length; | |
| 6551 if (lenDiff != 0) return lenDiff; | |
| 6552 var aPos = a.find(), bPos = b.find(); | |
| 6553 var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); | |
| 6554 if (fromCmp) return -fromCmp; | |
| 6555 var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); | |
| 6556 if (toCmp) return toCmp; | |
| 6557 return b.id - a.id; | |
| 6558 } | |
| 6559 | |
| 6560 // Find out whether a line ends or starts in a collapsed span. If | |
| 6561 // so, return the marker for that span. | |
| 6562 function collapsedSpanAtSide(line, start) { | |
| 6563 var sps = sawCollapsedSpans && line.markedSpans, found; | |
| 6564 if (sps) for (var sp, i = 0; i < sps.length; ++i) { | |
| 6565 sp = sps[i]; | |
| 6566 if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && | |
| 6567 (!found || compareCollapsedMarkers(found, sp.marker) < 0)) | |
| 6568 found = sp.marker; | |
| 6569 } | |
| 6570 return found; | |
| 6571 } | |
| 6572 function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true);
} | |
| 6573 function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); } | |
| 6574 | |
| 6575 // Test whether there exists a collapsed span that partially | |
| 6576 // overlaps (covers the start or end, but not both) of a new span. | |
| 6577 // Such overlap is not allowed. | |
| 6578 function conflictingCollapsedRange(doc, lineNo, from, to, marker) { | |
| 6579 var line = getLine(doc, lineNo); | |
| 6580 var sps = sawCollapsedSpans && line.markedSpans; | |
| 6581 if (sps) for (var i = 0; i < sps.length; ++i) { | |
| 6582 var sp = sps[i]; | |
| 6583 if (!sp.marker.collapsed) continue; | |
| 6584 var found = sp.marker.find(0); | |
| 6585 var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(ma
rker); | |
| 6586 var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker
); | |
| 6587 if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; | |
| 6588 if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cm
p(found.to, from) >= 0 : cmp(found.to, from) > 0) || | |
| 6589 fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cm
p(found.from, to) <= 0 : cmp(found.from, to) < 0)) | |
| 6590 return true; | |
| 6591 } | |
| 6592 } | |
| 6593 | |
| 6594 // A visual line is a line as drawn on the screen. Folding, for | |
| 6595 // example, can cause multiple logical lines to appear on the same | |
| 6596 // visual line. This finds the start of the visual line that the | |
| 6597 // given line is part of (usually that is the line itself). | |
| 6598 function visualLine(line) { | |
| 6599 var merged; | |
| 6600 while (merged = collapsedSpanAtStart(line)) | |
| 6601 line = merged.find(-1, true).line; | |
| 6602 return line; | |
| 6603 } | |
| 6604 | |
| 6605 // Returns an array of logical lines that continue the visual line | |
| 6606 // started by the argument, or undefined if there are no such lines. | |
| 6607 function visualLineContinued(line) { | |
| 6608 var merged, lines; | |
| 6609 while (merged = collapsedSpanAtEnd(line)) { | |
| 6610 line = merged.find(1, true).line; | |
| 6611 (lines || (lines = [])).push(line); | |
| 6612 } | |
| 6613 return lines; | |
| 6614 } | |
| 6615 | |
| 6616 // Get the line number of the start of the visual line that the | |
| 6617 // given line number is part of. | |
| 6618 function visualLineNo(doc, lineN) { | |
| 6619 var line = getLine(doc, lineN), vis = visualLine(line); | |
| 6620 if (line == vis) return lineN; | |
| 6621 return lineNo(vis); | |
| 6622 } | |
| 6623 // Get the line number of the start of the next visual line after | |
| 6624 // the given line. | |
| 6625 function visualLineEndNo(doc, lineN) { | |
| 6626 if (lineN > doc.lastLine()) return lineN; | |
| 6627 var line = getLine(doc, lineN), merged; | |
| 6628 if (!lineIsHidden(doc, line)) return lineN; | |
| 6629 while (merged = collapsedSpanAtEnd(line)) | |
| 6630 line = merged.find(1, true).line; | |
| 6631 return lineNo(line) + 1; | |
| 6632 } | |
| 6633 | |
| 6634 // Compute whether a line is hidden. Lines count as hidden when they | |
| 6635 // are part of a visual line that starts with another line, or when | |
| 6636 // they are entirely covered by collapsed, non-widget span. | |
| 6637 function lineIsHidden(doc, line) { | |
| 6638 var sps = sawCollapsedSpans && line.markedSpans; | |
| 6639 if (sps) for (var sp, i = 0; i < sps.length; ++i) { | |
| 6640 sp = sps[i]; | |
| 6641 if (!sp.marker.collapsed) continue; | |
| 6642 if (sp.from == null) return true; | |
| 6643 if (sp.marker.widgetNode) continue; | |
| 6644 if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line
, sp)) | |
| 6645 return true; | |
| 6646 } | |
| 6647 } | |
| 6648 function lineIsHiddenInner(doc, line, span) { | |
| 6649 if (span.to == null) { | |
| 6650 var end = span.marker.find(1, true); | |
| 6651 return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSp
ans, span.marker)); | |
| 6652 } | |
| 6653 if (span.marker.inclusiveRight && span.to == line.text.length) | |
| 6654 return true; | |
| 6655 for (var sp, i = 0; i < line.markedSpans.length; ++i) { | |
| 6656 sp = line.markedSpans[i]; | |
| 6657 if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && | |
| 6658 (sp.to == null || sp.to != span.from) && | |
| 6659 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && | |
| 6660 lineIsHiddenInner(doc, line, sp)) return true; | |
| 6661 } | |
| 6662 } | |
| 6663 | |
| 6664 // LINE WIDGETS | |
| 6665 | |
| 6666 // Line widgets are block elements displayed above or below a line. | |
| 6667 | |
| 6668 var LineWidget = CodeMirror.LineWidget = function(doc, node, options) { | |
| 6669 if (options) for (var opt in options) if (options.hasOwnProperty(opt)) | |
| 6670 this[opt] = options[opt]; | |
| 6671 this.doc = doc; | |
| 6672 this.node = node; | |
| 6673 }; | |
| 6674 eventMixin(LineWidget); | |
| 6675 | |
| 6676 function adjustScrollWhenAboveVisible(cm, line, diff) { | |
| 6677 if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollT
op)) | |
| 6678 addToScrollPos(cm, null, diff); | |
| 6679 } | |
| 6680 | |
| 6681 LineWidget.prototype.clear = function() { | |
| 6682 var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(
line); | |
| 6683 if (no == null || !ws) return; | |
| 6684 for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); | |
| 6685 if (!ws.length) line.widgets = null; | |
| 6686 var height = widgetHeight(this); | |
| 6687 updateLineHeight(line, Math.max(0, line.height - height)); | |
| 6688 if (cm) runInOp(cm, function() { | |
| 6689 adjustScrollWhenAboveVisible(cm, line, -height); | |
| 6690 regLineChange(cm, no, "widget"); | |
| 6691 }); | |
| 6692 }; | |
| 6693 LineWidget.prototype.changed = function() { | |
| 6694 var oldH = this.height, cm = this.doc.cm, line = this.line; | |
| 6695 this.height = null; | |
| 6696 var diff = widgetHeight(this) - oldH; | |
| 6697 if (!diff) return; | |
| 6698 updateLineHeight(line, line.height + diff); | |
| 6699 if (cm) runInOp(cm, function() { | |
| 6700 cm.curOp.forceUpdate = true; | |
| 6701 adjustScrollWhenAboveVisible(cm, line, diff); | |
| 6702 }); | |
| 6703 }; | |
| 6704 | |
| 6705 function widgetHeight(widget) { | |
| 6706 if (widget.height != null) return widget.height; | |
| 6707 var cm = widget.doc.cm; | |
| 6708 if (!cm) return 0; | |
| 6709 if (!contains(document.body, widget.node)) { | |
| 6710 var parentStyle = "position: relative;"; | |
| 6711 if (widget.coverGutter) | |
| 6712 parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"
; | |
| 6713 if (widget.noHScroll) | |
| 6714 parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; | |
| 6715 removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, p
arentStyle)); | |
| 6716 } | |
| 6717 return widget.height = widget.node.parentNode.offsetHeight; | |
| 6718 } | |
| 6719 | |
| 6720 function addLineWidget(doc, handle, node, options) { | |
| 6721 var widget = new LineWidget(doc, node, options); | |
| 6722 var cm = doc.cm; | |
| 6723 if (cm && widget.noHScroll) cm.display.alignWidgets = true; | |
| 6724 changeLine(doc, handle, "widget", function(line) { | |
| 6725 var widgets = line.widgets || (line.widgets = []); | |
| 6726 if (widget.insertAt == null) widgets.push(widget); | |
| 6727 else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insert
At)), 0, widget); | |
| 6728 widget.line = line; | |
| 6729 if (cm && !lineIsHidden(doc, line)) { | |
| 6730 var aboveVisible = heightAtLine(line) < doc.scrollTop; | |
| 6731 updateLineHeight(line, line.height + widgetHeight(widget)); | |
| 6732 if (aboveVisible) addToScrollPos(cm, null, widget.height); | |
| 6733 cm.curOp.forceUpdate = true; | |
| 6734 } | |
| 6735 return true; | |
| 6736 }); | |
| 6737 return widget; | |
| 6738 } | |
| 6739 | |
| 6740 // LINE DATA STRUCTURE | |
| 6741 | |
| 6742 // Line objects. These hold state related to a line, including | |
| 6743 // highlighting info (the styles array). | |
| 6744 var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { | |
| 6745 this.text = text; | |
| 6746 attachMarkedSpans(this, markedSpans); | |
| 6747 this.height = estimateHeight ? estimateHeight(this) : 1; | |
| 6748 }; | |
| 6749 eventMixin(Line); | |
| 6750 Line.prototype.lineNo = function() { return lineNo(this); }; | |
| 6751 | |
| 6752 // Change the content (text, markers) of a line. Automatically | |
| 6753 // invalidates cached information and tries to re-estimate the | |
| 6754 // line's height. | |
| 6755 function updateLine(line, text, markedSpans, estimateHeight) { | |
| 6756 line.text = text; | |
| 6757 if (line.stateAfter) line.stateAfter = null; | |
| 6758 if (line.styles) line.styles = null; | |
| 6759 if (line.order != null) line.order = null; | |
| 6760 detachMarkedSpans(line); | |
| 6761 attachMarkedSpans(line, markedSpans); | |
| 6762 var estHeight = estimateHeight ? estimateHeight(line) : 1; | |
| 6763 if (estHeight != line.height) updateLineHeight(line, estHeight); | |
| 6764 } | |
| 6765 | |
| 6766 // Detach a line from the document tree and its markers. | |
| 6767 function cleanUpLine(line) { | |
| 6768 line.parent = null; | |
| 6769 detachMarkedSpans(line); | |
| 6770 } | |
| 6771 | |
| 6772 function extractLineClasses(type, output) { | |
| 6773 if (type) for (;;) { | |
| 6774 var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); | |
| 6775 if (!lineClass) break; | |
| 6776 type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineC
lass[0].length); | |
| 6777 var prop = lineClass[1] ? "bgClass" : "textClass"; | |
| 6778 if (output[prop] == null) | |
| 6779 output[prop] = lineClass[2]; | |
| 6780 else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output
[prop])) | |
| 6781 output[prop] += " " + lineClass[2]; | |
| 6782 } | |
| 6783 return type; | |
| 6784 } | |
| 6785 | |
| 6786 function callBlankLine(mode, state) { | |
| 6787 if (mode.blankLine) return mode.blankLine(state); | |
| 6788 if (!mode.innerMode) return; | |
| 6789 var inner = CodeMirror.innerMode(mode, state); | |
| 6790 if (inner.mode.blankLine) return inner.mode.blankLine(inner.state); | |
| 6791 } | |
| 6792 | |
| 6793 function readToken(mode, stream, state, inner) { | |
| 6794 for (var i = 0; i < 10; i++) { | |
| 6795 if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode; | |
| 6796 var style = mode.token(stream, state); | |
| 6797 if (stream.pos > stream.start) return style; | |
| 6798 } | |
| 6799 throw new Error("Mode " + mode.name + " failed to advance stream."); | |
| 6800 } | |
| 6801 | |
| 6802 // Utility for getTokenAt and getLineTokens | |
| 6803 function takeToken(cm, pos, precise, asArray) { | |
| 6804 function getObj(copy) { | |
| 6805 return {start: stream.start, end: stream.pos, | |
| 6806 string: stream.current(), | |
| 6807 type: style || null, | |
| 6808 state: copy ? copyState(doc.mode, state) : state}; | |
| 6809 } | |
| 6810 | |
| 6811 var doc = cm.doc, mode = doc.mode, style; | |
| 6812 pos = clipPos(doc, pos); | |
| 6813 var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, prec
ise); | |
| 6814 var stream = new StringStream(line.text, cm.options.tabSize), tokens; | |
| 6815 if (asArray) tokens = []; | |
| 6816 while ((asArray || stream.pos < pos.ch) && !stream.eol()) { | |
| 6817 stream.start = stream.pos; | |
| 6818 style = readToken(mode, stream, state); | |
| 6819 if (asArray) tokens.push(getObj(true)); | |
| 6820 } | |
| 6821 return asArray ? tokens : getObj(); | |
| 6822 } | |
| 6823 | |
| 6824 // Run the given mode's parser over a line, calling f for each token. | |
| 6825 function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { | |
| 6826 var flattenSpans = mode.flattenSpans; | |
| 6827 if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; | |
| 6828 var curStart = 0, curStyle = null; | |
| 6829 var stream = new StringStream(text, cm.options.tabSize), style; | |
| 6830 var inner = cm.options.addModeClass && [null]; | |
| 6831 if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses); | |
| 6832 while (!stream.eol()) { | |
| 6833 if (stream.pos > cm.options.maxHighlightLength) { | |
| 6834 flattenSpans = false; | |
| 6835 if (forceToEnd) processLine(cm, text, state, stream.pos); | |
| 6836 stream.pos = text.length; | |
| 6837 style = null; | |
| 6838 } else { | |
| 6839 style = extractLineClasses(readToken(mode, stream, state, inner), lineCl
asses); | |
| 6840 } | |
| 6841 if (inner) { | |
| 6842 var mName = inner[0].name; | |
| 6843 if (mName) style = "m-" + (style ? mName + " " + style : mName); | |
| 6844 } | |
| 6845 if (!flattenSpans || curStyle != style) { | |
| 6846 while (curStart < stream.start) { | |
| 6847 curStart = Math.min(stream.start, curStart + 50000); | |
| 6848 f(curStart, curStyle); | |
| 6849 } | |
| 6850 curStyle = style; | |
| 6851 } | |
| 6852 stream.start = stream.pos; | |
| 6853 } | |
| 6854 while (curStart < stream.pos) { | |
| 6855 // Webkit seems to refuse to render text nodes longer than 57444 character
s | |
| 6856 var pos = Math.min(stream.pos, curStart + 50000); | |
| 6857 f(pos, curStyle); | |
| 6858 curStart = pos; | |
| 6859 } | |
| 6860 } | |
| 6861 | |
| 6862 // Compute a style array (an array starting with a mode generation | |
| 6863 // -- for invalidation -- followed by pairs of end positions and | |
| 6864 // style strings), which is used to highlight the tokens on the | |
| 6865 // line. | |
| 6866 function highlightLine(cm, line, state, forceToEnd) { | |
| 6867 // A styles array always starts with a number identifying the | |
| 6868 // mode/overlays that it is based on (for easy invalidation). | |
| 6869 var st = [cm.state.modeGen], lineClasses = {}; | |
| 6870 // Compute the base array of styles | |
| 6871 runMode(cm, line.text, cm.doc.mode, state, function(end, style) { | |
| 6872 st.push(end, style); | |
| 6873 }, lineClasses, forceToEnd); | |
| 6874 | |
| 6875 // Run overlays, adjust style array. | |
| 6876 for (var o = 0; o < cm.state.overlays.length; ++o) { | |
| 6877 var overlay = cm.state.overlays[o], i = 1, at = 0; | |
| 6878 runMode(cm, line.text, overlay.mode, true, function(end, style) { | |
| 6879 var start = i; | |
| 6880 // Ensure there's a token end at the current position, and that i points
at it | |
| 6881 while (at < end) { | |
| 6882 var i_end = st[i]; | |
| 6883 if (i_end > end) | |
| 6884 st.splice(i, 1, end, st[i+1], i_end); | |
| 6885 i += 2; | |
| 6886 at = Math.min(end, i_end); | |
| 6887 } | |
| 6888 if (!style) return; | |
| 6889 if (overlay.opaque) { | |
| 6890 st.splice(start, i - start, end, "cm-overlay " + style); | |
| 6891 i = start + 2; | |
| 6892 } else { | |
| 6893 for (; start < i; start += 2) { | |
| 6894 var cur = st[start+1]; | |
| 6895 st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style; | |
| 6896 } | |
| 6897 } | |
| 6898 }, lineClasses); | |
| 6899 } | |
| 6900 | |
| 6901 return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ?
lineClasses : null}; | |
| 6902 } | |
| 6903 | |
| 6904 function getLineStyles(cm, line, updateFrontier) { | |
| 6905 if (!line.styles || line.styles[0] != cm.state.modeGen) { | |
| 6906 var state = getStateBefore(cm, lineNo(line)); | |
| 6907 var result = highlightLine(cm, line, line.text.length > cm.options.maxHigh
lightLength ? copyState(cm.doc.mode, state) : state); | |
| 6908 line.stateAfter = state; | |
| 6909 line.styles = result.styles; | |
| 6910 if (result.classes) line.styleClasses = result.classes; | |
| 6911 else if (line.styleClasses) line.styleClasses = null; | |
| 6912 if (updateFrontier === cm.doc.frontier) cm.doc.frontier++; | |
| 6913 } | |
| 6914 return line.styles; | |
| 6915 } | |
| 6916 | |
| 6917 // Lightweight form of highlight -- proceed over this line and | |
| 6918 // update state, but don't save a style array. Used for lines that | |
| 6919 // aren't currently visible. | |
| 6920 function processLine(cm, text, state, startAt) { | |
| 6921 var mode = cm.doc.mode; | |
| 6922 var stream = new StringStream(text, cm.options.tabSize); | |
| 6923 stream.start = stream.pos = startAt || 0; | |
| 6924 if (text == "") callBlankLine(mode, state); | |
| 6925 while (!stream.eol()) { | |
| 6926 readToken(mode, stream, state); | |
| 6927 stream.start = stream.pos; | |
| 6928 } | |
| 6929 } | |
| 6930 | |
| 6931 // Convert a style as returned by a mode (either null, or a string | |
| 6932 // containing one or more styles) to a CSS style. This is cached, | |
| 6933 // and also looks for line-wide styles. | |
| 6934 var styleToClassCache = {}, styleToClassCacheWithMode = {}; | |
| 6935 function interpretTokenStyle(style, options) { | |
| 6936 if (!style || /^\s*$/.test(style)) return null; | |
| 6937 var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassC
ache; | |
| 6938 return cache[style] || | |
| 6939 (cache[style] = style.replace(/\S+/g, "cm-$&")); | |
| 6940 } | |
| 6941 | |
| 6942 // Render the DOM representation of the text of a line. Also builds | |
| 6943 // up a 'line map', which points at the DOM nodes that represent | |
| 6944 // specific stretches of text, and is used by the measuring code. | |
| 6945 // The returned object contains the DOM node, this map, and | |
| 6946 // information about line-wide styles that were set by the mode. | |
| 6947 function buildLineContent(cm, lineView) { | |
| 6948 // The padding-right forces the element to have a 'border', which | |
| 6949 // is needed on Webkit to be able to get line-level bounding | |
| 6950 // rectangles for it (in measureChar). | |
| 6951 var content = elt("span", null, null, webkit ? "padding-right: .1px" : null)
; | |
| 6952 var builder = {pre: elt("pre", [content], "CodeMirror-line"), content: conte
nt, | |
| 6953 col: 0, pos: 0, cm: cm, | |
| 6954 trailingSpace: false, | |
| 6955 splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}; | |
| 6956 lineView.measure = {}; | |
| 6957 | |
| 6958 // Iterate over the logical lines that make up this visual line. | |
| 6959 for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { | |
| 6960 var line = i ? lineView.rest[i - 1] : lineView.line, order; | |
| 6961 builder.pos = 0; | |
| 6962 builder.addToken = buildToken; | |
| 6963 // Optionally wire in some hacks into the token-rendering | |
| 6964 // algorithm, to deal with browser quirks. | |
| 6965 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) | |
| 6966 builder.addToken = buildTokenBadBidi(builder.addToken, order); | |
| 6967 builder.map = []; | |
| 6968 var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineN
o(line); | |
| 6969 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpda
te)); | |
| 6970 if (line.styleClasses) { | |
| 6971 if (line.styleClasses.bgClass) | |
| 6972 builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgCla
ss || ""); | |
| 6973 if (line.styleClasses.textClass) | |
| 6974 builder.textClass = joinClasses(line.styleClasses.textClass, builder.t
extClass || ""); | |
| 6975 } | |
| 6976 | |
| 6977 // Ensure at least a single node is present, for measuring. | |
| 6978 if (builder.map.length == 0) | |
| 6979 builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.d
isplay.measure))); | |
| 6980 | |
| 6981 // Store the map and a cache object for the current logical line | |
| 6982 if (i == 0) { | |
| 6983 lineView.measure.map = builder.map; | |
| 6984 lineView.measure.cache = {}; | |
| 6985 } else { | |
| 6986 (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map
); | |
| 6987 (lineView.measure.caches || (lineView.measure.caches = [])).push({}); | |
| 6988 } | |
| 6989 } | |
| 6990 | |
| 6991 // See issue #2901 | |
| 6992 if (webkit) { | |
| 6993 var last = builder.content.lastChild | |
| 6994 if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.query
Selector(".cm-tab"))) | |
| 6995 builder.content.className = "cm-tab-wrap-hack"; | |
| 6996 } | |
| 6997 | |
| 6998 signal(cm, "renderLine", cm, lineView.line, builder.pre); | |
| 6999 if (builder.pre.className) | |
| 7000 builder.textClass = joinClasses(builder.pre.className, builder.textClass |
| ""); | |
| 7001 | |
| 7002 return builder; | |
| 7003 } | |
| 7004 | |
| 7005 function defaultSpecialCharPlaceholder(ch) { | |
| 7006 var token = elt("span", "\u2022", "cm-invalidchar"); | |
| 7007 token.title = "\\u" + ch.charCodeAt(0).toString(16); | |
| 7008 token.setAttribute("aria-label", token.title); | |
| 7009 return token; | |
| 7010 } | |
| 7011 | |
| 7012 // Build up the DOM representation for a single token, and add it to | |
| 7013 // the line map. Takes care to render special characters separately. | |
| 7014 function buildToken(builder, text, style, startStyle, endStyle, title, css) { | |
| 7015 if (!text) return; | |
| 7016 var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSp
ace) : text | |
| 7017 var special = builder.cm.state.specialChars, mustWrap = false; | |
| 7018 if (!special.test(text)) { | |
| 7019 builder.col += text.length; | |
| 7020 var content = document.createTextNode(displayText); | |
| 7021 builder.map.push(builder.pos, builder.pos + text.length, content); | |
| 7022 if (ie && ie_version < 9) mustWrap = true; | |
| 7023 builder.pos += text.length; | |
| 7024 } else { | |
| 7025 var content = document.createDocumentFragment(), pos = 0; | |
| 7026 while (true) { | |
| 7027 special.lastIndex = pos; | |
| 7028 var m = special.exec(text); | |
| 7029 var skipped = m ? m.index - pos : text.length - pos; | |
| 7030 if (skipped) { | |
| 7031 var txt = document.createTextNode(displayText.slice(pos, pos + skipped
)); | |
| 7032 if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); | |
| 7033 else content.appendChild(txt); | |
| 7034 builder.map.push(builder.pos, builder.pos + skipped, txt); | |
| 7035 builder.col += skipped; | |
| 7036 builder.pos += skipped; | |
| 7037 } | |
| 7038 if (!m) break; | |
| 7039 pos += skipped + 1; | |
| 7040 if (m[0] == "\t") { | |
| 7041 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder
.col % tabSize; | |
| 7042 var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"
)); | |
| 7043 txt.setAttribute("role", "presentation"); | |
| 7044 txt.setAttribute("cm-text", "\t"); | |
| 7045 builder.col += tabWidth; | |
| 7046 } else if (m[0] == "\r" || m[0] == "\n") { | |
| 7047 var txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\
u2424", "cm-invalidchar")); | |
| 7048 txt.setAttribute("cm-text", m[0]); | |
| 7049 builder.col += 1; | |
| 7050 } else { | |
| 7051 var txt = builder.cm.options.specialCharPlaceholder(m[0]); | |
| 7052 txt.setAttribute("cm-text", m[0]); | |
| 7053 if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); | |
| 7054 else content.appendChild(txt); | |
| 7055 builder.col += 1; | |
| 7056 } | |
| 7057 builder.map.push(builder.pos, builder.pos + 1, txt); | |
| 7058 builder.pos++; | |
| 7059 } | |
| 7060 } | |
| 7061 builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 | |
| 7062 if (style || startStyle || endStyle || mustWrap || css) { | |
| 7063 var fullStyle = style || ""; | |
| 7064 if (startStyle) fullStyle += startStyle; | |
| 7065 if (endStyle) fullStyle += endStyle; | |
| 7066 var token = elt("span", [content], fullStyle, css); | |
| 7067 if (title) token.title = title; | |
| 7068 return builder.content.appendChild(token); | |
| 7069 } | |
| 7070 builder.content.appendChild(content); | |
| 7071 } | |
| 7072 | |
| 7073 function splitSpaces(text, trailingBefore) { | |
| 7074 if (text.length > 1 && !/ /.test(text)) return text | |
| 7075 var spaceBefore = trailingBefore, result = "" | |
| 7076 for (var i = 0; i < text.length; i++) { | |
| 7077 var ch = text.charAt(i) | |
| 7078 if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i
+ 1) == 32)) | |
| 7079 ch = "\u00a0" | |
| 7080 result += ch | |
| 7081 spaceBefore = ch == " " | |
| 7082 } | |
| 7083 return result | |
| 7084 } | |
| 7085 | |
| 7086 // Work around nonsense dimensions being reported for stretches of | |
| 7087 // right-to-left text. | |
| 7088 function buildTokenBadBidi(inner, order) { | |
| 7089 return function(builder, text, style, startStyle, endStyle, title, css) { | |
| 7090 style = style ? style + " cm-force-border" : "cm-force-border"; | |
| 7091 var start = builder.pos, end = start + text.length; | |
| 7092 for (;;) { | |
| 7093 // Find the part that overlaps with the start of this text | |
| 7094 for (var i = 0; i < order.length; i++) { | |
| 7095 var part = order[i]; | |
| 7096 if (part.to > start && part.from <= start) break; | |
| 7097 } | |
| 7098 if (part.to >= end) return inner(builder, text, style, startStyle, endSt
yle, title, css); | |
| 7099 inner(builder, text.slice(0, part.to - start), style, startStyle, null,
title, css); | |
| 7100 startStyle = null; | |
| 7101 text = text.slice(part.to - start); | |
| 7102 start = part.to; | |
| 7103 } | |
| 7104 }; | |
| 7105 } | |
| 7106 | |
| 7107 function buildCollapsedSpan(builder, size, marker, ignoreWidget) { | |
| 7108 var widget = !ignoreWidget && marker.widgetNode; | |
| 7109 if (widget) builder.map.push(builder.pos, builder.pos + size, widget); | |
| 7110 if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { | |
| 7111 if (!widget) | |
| 7112 widget = builder.content.appendChild(document.createElement("span")); | |
| 7113 widget.setAttribute("cm-marker", marker.id); | |
| 7114 } | |
| 7115 if (widget) { | |
| 7116 builder.cm.display.input.setUneditable(widget); | |
| 7117 builder.content.appendChild(widget); | |
| 7118 } | |
| 7119 builder.pos += size; | |
| 7120 builder.trailingSpace = false | |
| 7121 } | |
| 7122 | |
| 7123 // Outputs a number of spans to make up a line, taking highlighting | |
| 7124 // and marked text into account. | |
| 7125 function insertLineContent(line, builder, styles) { | |
| 7126 var spans = line.markedSpans, allText = line.text, at = 0; | |
| 7127 if (!spans) { | |
| 7128 for (var i = 1; i < styles.length; i+=2) | |
| 7129 builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTo
kenStyle(styles[i+1], builder.cm.options)); | |
| 7130 return; | |
| 7131 } | |
| 7132 | |
| 7133 var len = allText.length, pos = 0, i = 1, text = "", style, css; | |
| 7134 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapse
d; | |
| 7135 for (;;) { | |
| 7136 if (nextChange == pos) { // Update current marker set | |
| 7137 spanStyle = spanEndStyle = spanStartStyle = title = css = ""; | |
| 7138 collapsed = null; nextChange = Infinity; | |
| 7139 var foundBookmarks = [], endStyles | |
| 7140 for (var j = 0; j < spans.length; ++j) { | |
| 7141 var sp = spans[j], m = sp.marker; | |
| 7142 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { | |
| 7143 foundBookmarks.push(m); | |
| 7144 } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collap
sed && sp.to == pos && sp.from == pos)) { | |
| 7145 if (sp.to != null && sp.to != pos && nextChange > sp.to) { | |
| 7146 nextChange = sp.to; | |
| 7147 spanEndStyle = ""; | |
| 7148 } | |
| 7149 if (m.className) spanStyle += " " + m.className; | |
| 7150 if (m.css) css = (css ? css + ";" : "") + m.css; | |
| 7151 if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startS
tyle; | |
| 7152 if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = []
)).push(m.endStyle, sp.to) | |
| 7153 if (m.title && !title) title = m.title; | |
| 7154 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.
marker, m) < 0)) | |
| 7155 collapsed = sp; | |
| 7156 } else if (sp.from > pos && nextChange > sp.from) { | |
| 7157 nextChange = sp.from; | |
| 7158 } | |
| 7159 } | |
| 7160 if (endStyles) for (var j = 0; j < endStyles.length; j += 2) | |
| 7161 if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j] | |
| 7162 | |
| 7163 if (!collapsed || collapsed.from == pos) for (var j = 0; j < foundBookma
rks.length; ++j) | |
| 7164 buildCollapsedSpan(builder, 0, foundBookmarks[j]); | |
| 7165 if (collapsed && (collapsed.from || 0) == pos) { | |
| 7166 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapse
d.to) - pos, | |
| 7167 collapsed.marker, collapsed.from == null); | |
| 7168 if (collapsed.to == null) return; | |
| 7169 if (collapsed.to == pos) collapsed = false; | |
| 7170 } | |
| 7171 } | |
| 7172 if (pos >= len) break; | |
| 7173 | |
| 7174 var upto = Math.min(len, nextChange); | |
| 7175 while (true) { | |
| 7176 if (text) { | |
| 7177 var end = pos + text.length; | |
| 7178 if (!collapsed) { | |
| 7179 var tokenText = end > upto ? text.slice(0, upto - pos) : text; | |
| 7180 builder.addToken(builder, tokenText, style ? style + spanStyle : spa
nStyle, | |
| 7181 spanStartStyle, pos + tokenText.length == nextChang
e ? spanEndStyle : "", title, css); | |
| 7182 } | |
| 7183 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} | |
| 7184 pos = end; | |
| 7185 spanStartStyle = ""; | |
| 7186 } | |
| 7187 text = allText.slice(at, at = styles[i++]); | |
| 7188 style = interpretTokenStyle(styles[i++], builder.cm.options); | |
| 7189 } | |
| 7190 } | |
| 7191 } | |
| 7192 | |
| 7193 // DOCUMENT DATA STRUCTURE | |
| 7194 | |
| 7195 // By default, updates that start and end at the beginning of a line | |
| 7196 // are treated specially, in order to make the association of line | |
| 7197 // widgets and marker elements with the text behave more intuitive. | |
| 7198 function isWholeLineUpdate(doc, change) { | |
| 7199 return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && | |
| 7200 (!doc.cm || doc.cm.options.wholeLineUpdateBefore); | |
| 7201 } | |
| 7202 | |
| 7203 // Perform a change on the document data structure. | |
| 7204 function updateDoc(doc, change, markedSpans, estimateHeight) { | |
| 7205 function spansFor(n) {return markedSpans ? markedSpans[n] : null;} | |
| 7206 function update(line, text, spans) { | |
| 7207 updateLine(line, text, spans, estimateHeight); | |
| 7208 signalLater(line, "change", line, change); | |
| 7209 } | |
| 7210 function linesFor(start, end) { | |
| 7211 for (var i = start, result = []; i < end; ++i) | |
| 7212 result.push(new Line(text[i], spansFor(i), estimateHeight)); | |
| 7213 return result; | |
| 7214 } | |
| 7215 | |
| 7216 var from = change.from, to = change.to, text = change.text; | |
| 7217 var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); | |
| 7218 var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to
.line - from.line; | |
| 7219 | |
| 7220 // Adjust the line structure | |
| 7221 if (change.full) { | |
| 7222 doc.insert(0, linesFor(0, text.length)); | |
| 7223 doc.remove(text.length, doc.size - text.length); | |
| 7224 } else if (isWholeLineUpdate(doc, change)) { | |
| 7225 // This is a whole-line replace. Treated specially to make | |
| 7226 // sure line objects move the way they are supposed to. | |
| 7227 var added = linesFor(0, text.length - 1); | |
| 7228 update(lastLine, lastLine.text, lastSpans); | |
| 7229 if (nlines) doc.remove(from.line, nlines); | |
| 7230 if (added.length) doc.insert(from.line, added); | |
| 7231 } else if (firstLine == lastLine) { | |
| 7232 if (text.length == 1) { | |
| 7233 update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLin
e.text.slice(to.ch), lastSpans); | |
| 7234 } else { | |
| 7235 var added = linesFor(1, text.length - 1); | |
| 7236 added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, e
stimateHeight)); | |
| 7237 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0
)); | |
| 7238 doc.insert(from.line + 1, added); | |
| 7239 } | |
| 7240 } else if (text.length == 1) { | |
| 7241 update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.te
xt.slice(to.ch), spansFor(0)); | |
| 7242 doc.remove(from.line + 1, nlines); | |
| 7243 } else { | |
| 7244 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0))
; | |
| 7245 update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); | |
| 7246 var added = linesFor(1, text.length - 1); | |
| 7247 if (nlines > 1) doc.remove(from.line + 1, nlines - 1); | |
| 7248 doc.insert(from.line + 1, added); | |
| 7249 } | |
| 7250 | |
| 7251 signalLater(doc, "change", doc, change); | |
| 7252 } | |
| 7253 | |
| 7254 // The document is represented as a BTree consisting of leaves, with | |
| 7255 // chunk of lines in them, and branches, with up to ten leaves or | |
| 7256 // other branch nodes below them. The top node is always a branch | |
| 7257 // node, and is the document object itself (meaning it has | |
| 7258 // additional methods and properties). | |
| 7259 // | |
| 7260 // All nodes have parent links. The tree is used both to go from | |
| 7261 // line numbers to line objects, and to go from objects to numbers. | |
| 7262 // It also indexes by height, and is used to convert between height | |
| 7263 // and line object, and to find the total height of the document. | |
| 7264 // | |
| 7265 // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html | |
| 7266 | |
| 7267 function LeafChunk(lines) { | |
| 7268 this.lines = lines; | |
| 7269 this.parent = null; | |
| 7270 for (var i = 0, height = 0; i < lines.length; ++i) { | |
| 7271 lines[i].parent = this; | |
| 7272 height += lines[i].height; | |
| 7273 } | |
| 7274 this.height = height; | |
| 7275 } | |
| 7276 | |
| 7277 LeafChunk.prototype = { | |
| 7278 chunkSize: function() { return this.lines.length; }, | |
| 7279 // Remove the n lines at offset 'at'. | |
| 7280 removeInner: function(at, n) { | |
| 7281 for (var i = at, e = at + n; i < e; ++i) { | |
| 7282 var line = this.lines[i]; | |
| 7283 this.height -= line.height; | |
| 7284 cleanUpLine(line); | |
| 7285 signalLater(line, "delete"); | |
| 7286 } | |
| 7287 this.lines.splice(at, n); | |
| 7288 }, | |
| 7289 // Helper used to collapse a small branch into a single leaf. | |
| 7290 collapse: function(lines) { | |
| 7291 lines.push.apply(lines, this.lines); | |
| 7292 }, | |
| 7293 // Insert the given array of lines at offset 'at', count them as | |
| 7294 // having the given height. | |
| 7295 insertInner: function(at, lines, height) { | |
| 7296 this.height += height; | |
| 7297 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice
(at)); | |
| 7298 for (var i = 0; i < lines.length; ++i) lines[i].parent = this; | |
| 7299 }, | |
| 7300 // Used to iterate over a part of the tree. | |
| 7301 iterN: function(at, n, op) { | |
| 7302 for (var e = at + n; at < e; ++at) | |
| 7303 if (op(this.lines[at])) return true; | |
| 7304 } | |
| 7305 }; | |
| 7306 | |
| 7307 function BranchChunk(children) { | |
| 7308 this.children = children; | |
| 7309 var size = 0, height = 0; | |
| 7310 for (var i = 0; i < children.length; ++i) { | |
| 7311 var ch = children[i]; | |
| 7312 size += ch.chunkSize(); height += ch.height; | |
| 7313 ch.parent = this; | |
| 7314 } | |
| 7315 this.size = size; | |
| 7316 this.height = height; | |
| 7317 this.parent = null; | |
| 7318 } | |
| 7319 | |
| 7320 BranchChunk.prototype = { | |
| 7321 chunkSize: function() { return this.size; }, | |
| 7322 removeInner: function(at, n) { | |
| 7323 this.size -= n; | |
| 7324 for (var i = 0; i < this.children.length; ++i) { | |
| 7325 var child = this.children[i], sz = child.chunkSize(); | |
| 7326 if (at < sz) { | |
| 7327 var rm = Math.min(n, sz - at), oldHeight = child.height; | |
| 7328 child.removeInner(at, rm); | |
| 7329 this.height -= oldHeight - child.height; | |
| 7330 if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } | |
| 7331 if ((n -= rm) == 0) break; | |
| 7332 at = 0; | |
| 7333 } else at -= sz; | |
| 7334 } | |
| 7335 // If the result is smaller than 25 lines, ensure that it is a | |
| 7336 // single leaf node. | |
| 7337 if (this.size - n < 25 && | |
| 7338 (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))
) { | |
| 7339 var lines = []; | |
| 7340 this.collapse(lines); | |
| 7341 this.children = [new LeafChunk(lines)]; | |
| 7342 this.children[0].parent = this; | |
| 7343 } | |
| 7344 }, | |
| 7345 collapse: function(lines) { | |
| 7346 for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(l
ines); | |
| 7347 }, | |
| 7348 insertInner: function(at, lines, height) { | |
| 7349 this.size += lines.length; | |
| 7350 this.height += height; | |
| 7351 for (var i = 0; i < this.children.length; ++i) { | |
| 7352 var child = this.children[i], sz = child.chunkSize(); | |
| 7353 if (at <= sz) { | |
| 7354 child.insertInner(at, lines, height); | |
| 7355 if (child.lines && child.lines.length > 50) { | |
| 7356 // To avoid memory thrashing when child.lines is huge (e.g. first vi
ew of a large file), it's never spliced. | |
| 7357 // Instead, small slices are taken. They're taken in order because s
equential memory accesses are fastest. | |
| 7358 var remaining = child.lines.length % 25 + 25 | |
| 7359 for (var pos = remaining; pos < child.lines.length;) { | |
| 7360 var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)); | |
| 7361 child.height -= leaf.height; | |
| 7362 this.children.splice(++i, 0, leaf); | |
| 7363 leaf.parent = this; | |
| 7364 } | |
| 7365 child.lines = child.lines.slice(0, remaining); | |
| 7366 this.maybeSpill(); | |
| 7367 } | |
| 7368 break; | |
| 7369 } | |
| 7370 at -= sz; | |
| 7371 } | |
| 7372 }, | |
| 7373 // When a node has grown, check whether it should be split. | |
| 7374 maybeSpill: function() { | |
| 7375 if (this.children.length <= 10) return; | |
| 7376 var me = this; | |
| 7377 do { | |
| 7378 var spilled = me.children.splice(me.children.length - 5, 5); | |
| 7379 var sibling = new BranchChunk(spilled); | |
| 7380 if (!me.parent) { // Become the parent node | |
| 7381 var copy = new BranchChunk(me.children); | |
| 7382 copy.parent = me; | |
| 7383 me.children = [copy, sibling]; | |
| 7384 me = copy; | |
| 7385 } else { | |
| 7386 me.size -= sibling.size; | |
| 7387 me.height -= sibling.height; | |
| 7388 var myIndex = indexOf(me.parent.children, me); | |
| 7389 me.parent.children.splice(myIndex + 1, 0, sibling); | |
| 7390 } | |
| 7391 sibling.parent = me.parent; | |
| 7392 } while (me.children.length > 10); | |
| 7393 me.parent.maybeSpill(); | |
| 7394 }, | |
| 7395 iterN: function(at, n, op) { | |
| 7396 for (var i = 0; i < this.children.length; ++i) { | |
| 7397 var child = this.children[i], sz = child.chunkSize(); | |
| 7398 if (at < sz) { | |
| 7399 var used = Math.min(n, sz - at); | |
| 7400 if (child.iterN(at, used, op)) return true; | |
| 7401 if ((n -= used) == 0) break; | |
| 7402 at = 0; | |
| 7403 } else at -= sz; | |
| 7404 } | |
| 7405 } | |
| 7406 }; | |
| 7407 | |
| 7408 var nextDocId = 0; | |
| 7409 var Doc = CodeMirror.Doc = function(text, mode, firstLine, lineSep) { | |
| 7410 if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep); | |
| 7411 if (firstLine == null) firstLine = 0; | |
| 7412 | |
| 7413 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); | |
| 7414 this.first = firstLine; | |
| 7415 this.scrollTop = this.scrollLeft = 0; | |
| 7416 this.cantEdit = false; | |
| 7417 this.cleanGeneration = 1; | |
| 7418 this.frontier = firstLine; | |
| 7419 var start = Pos(firstLine, 0); | |
| 7420 this.sel = simpleSelection(start); | |
| 7421 this.history = new History(null); | |
| 7422 this.id = ++nextDocId; | |
| 7423 this.modeOption = mode; | |
| 7424 this.lineSep = lineSep; | |
| 7425 this.extend = false; | |
| 7426 | |
| 7427 if (typeof text == "string") text = this.splitLines(text); | |
| 7428 updateDoc(this, {from: start, to: start, text: text}); | |
| 7429 setSelection(this, simpleSelection(start), sel_dontScroll); | |
| 7430 }; | |
| 7431 | |
| 7432 Doc.prototype = createObj(BranchChunk.prototype, { | |
| 7433 constructor: Doc, | |
| 7434 // Iterate over the document. Supports two forms -- with only one | |
| 7435 // argument, it calls that for each line in the document. With | |
| 7436 // three, it iterates over the range given by the first two (with | |
| 7437 // the second being non-inclusive). | |
| 7438 iter: function(from, to, op) { | |
| 7439 if (op) this.iterN(from - this.first, to - from, op); | |
| 7440 else this.iterN(this.first, this.first + this.size, from); | |
| 7441 }, | |
| 7442 | |
| 7443 // Non-public interface for adding and removing lines. | |
| 7444 insert: function(at, lines) { | |
| 7445 var height = 0; | |
| 7446 for (var i = 0; i < lines.length; ++i) height += lines[i].height; | |
| 7447 this.insertInner(at - this.first, lines, height); | |
| 7448 }, | |
| 7449 remove: function(at, n) { this.removeInner(at - this.first, n); }, | |
| 7450 | |
| 7451 // From here, the methods are part of the public interface. Most | |
| 7452 // are also available from CodeMirror (editor) instances. | |
| 7453 | |
| 7454 getValue: function(lineSep) { | |
| 7455 var lines = getLines(this, this.first, this.first + this.size); | |
| 7456 if (lineSep === false) return lines; | |
| 7457 return lines.join(lineSep || this.lineSeparator()); | |
| 7458 }, | |
| 7459 setValue: docMethodOp(function(code) { | |
| 7460 var top = Pos(this.first, 0), last = this.first + this.size - 1; | |
| 7461 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length
), | |
| 7462 text: this.splitLines(code), origin: "setValue", full: t
rue}, true); | |
| 7463 setSelection(this, simpleSelection(top)); | |
| 7464 }), | |
| 7465 replaceRange: function(code, from, to, origin) { | |
| 7466 from = clipPos(this, from); | |
| 7467 to = to ? clipPos(this, to) : from; | |
| 7468 replaceRange(this, code, from, to, origin); | |
| 7469 }, | |
| 7470 getRange: function(from, to, lineSep) { | |
| 7471 var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); | |
| 7472 if (lineSep === false) return lines; | |
| 7473 return lines.join(lineSep || this.lineSeparator()); | |
| 7474 }, | |
| 7475 | |
| 7476 getLine: function(line) {var l = this.getLineHandle(line); return l && l.tex
t;}, | |
| 7477 | |
| 7478 getLineHandle: function(line) {if (isLine(this, line)) return getLine(this,
line);}, | |
| 7479 getLineNumber: function(line) {return lineNo(line);}, | |
| 7480 | |
| 7481 getLineHandleVisualStart: function(line) { | |
| 7482 if (typeof line == "number") line = getLine(this, line); | |
| 7483 return visualLine(line); | |
| 7484 }, | |
| 7485 | |
| 7486 lineCount: function() {return this.size;}, | |
| 7487 firstLine: function() {return this.first;}, | |
| 7488 lastLine: function() {return this.first + this.size - 1;}, | |
| 7489 | |
| 7490 clipPos: function(pos) {return clipPos(this, pos);}, | |
| 7491 | |
| 7492 getCursor: function(start) { | |
| 7493 var range = this.sel.primary(), pos; | |
| 7494 if (start == null || start == "head") pos = range.head; | |
| 7495 else if (start == "anchor") pos = range.anchor; | |
| 7496 else if (start == "end" || start == "to" || start === false) pos = range.t
o(); | |
| 7497 else pos = range.from(); | |
| 7498 return pos; | |
| 7499 }, | |
| 7500 listSelections: function() { return this.sel.ranges; }, | |
| 7501 somethingSelected: function() {return this.sel.somethingSelected();}, | |
| 7502 | |
| 7503 setCursor: docMethodOp(function(line, ch, options) { | |
| 7504 setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line,
ch || 0) : line), null, options); | |
| 7505 }), | |
| 7506 setSelection: docMethodOp(function(anchor, head, options) { | |
| 7507 setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anch
or), options); | |
| 7508 }), | |
| 7509 extendSelection: docMethodOp(function(head, other, options) { | |
| 7510 extendSelection(this, clipPos(this, head), other && clipPos(this, other),
options); | |
| 7511 }), | |
| 7512 extendSelections: docMethodOp(function(heads, options) { | |
| 7513 extendSelections(this, clipPosArray(this, heads), options); | |
| 7514 }), | |
| 7515 extendSelectionsBy: docMethodOp(function(f, options) { | |
| 7516 var heads = map(this.sel.ranges, f); | |
| 7517 extendSelections(this, clipPosArray(this, heads), options); | |
| 7518 }), | |
| 7519 setSelections: docMethodOp(function(ranges, primary, options) { | |
| 7520 if (!ranges.length) return; | |
| 7521 for (var i = 0, out = []; i < ranges.length; i++) | |
| 7522 out[i] = new Range(clipPos(this, ranges[i].anchor), | |
| 7523 clipPos(this, ranges[i].head)); | |
| 7524 if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIn
dex); | |
| 7525 setSelection(this, normalizeSelection(out, primary), options); | |
| 7526 }), | |
| 7527 addSelection: docMethodOp(function(anchor, head, options) { | |
| 7528 var ranges = this.sel.ranges.slice(0); | |
| 7529 ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)
)); | |
| 7530 setSelection(this, normalizeSelection(ranges, ranges.length - 1), options)
; | |
| 7531 }), | |
| 7532 | |
| 7533 getSelection: function(lineSep) { | |
| 7534 var ranges = this.sel.ranges, lines; | |
| 7535 for (var i = 0; i < ranges.length; i++) { | |
| 7536 var sel = getBetween(this, ranges[i].from(), ranges[i].to()); | |
| 7537 lines = lines ? lines.concat(sel) : sel; | |
| 7538 } | |
| 7539 if (lineSep === false) return lines; | |
| 7540 else return lines.join(lineSep || this.lineSeparator()); | |
| 7541 }, | |
| 7542 getSelections: function(lineSep) { | |
| 7543 var parts = [], ranges = this.sel.ranges; | |
| 7544 for (var i = 0; i < ranges.length; i++) { | |
| 7545 var sel = getBetween(this, ranges[i].from(), ranges[i].to()); | |
| 7546 if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator()); | |
| 7547 parts[i] = sel; | |
| 7548 } | |
| 7549 return parts; | |
| 7550 }, | |
| 7551 replaceSelection: function(code, collapse, origin) { | |
| 7552 var dup = []; | |
| 7553 for (var i = 0; i < this.sel.ranges.length; i++) | |
| 7554 dup[i] = code; | |
| 7555 this.replaceSelections(dup, collapse, origin || "+input"); | |
| 7556 }, | |
| 7557 replaceSelections: docMethodOp(function(code, collapse, origin) { | |
| 7558 var changes = [], sel = this.sel; | |
| 7559 for (var i = 0; i < sel.ranges.length; i++) { | |
| 7560 var range = sel.ranges[i]; | |
| 7561 changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(
code[i]), origin: origin}; | |
| 7562 } | |
| 7563 var newSel = collapse && collapse != "end" && computeReplacedSel(this, cha
nges, collapse); | |
| 7564 for (var i = changes.length - 1; i >= 0; i--) | |
| 7565 makeChange(this, changes[i]); | |
| 7566 if (newSel) setSelectionReplaceHistory(this, newSel); | |
| 7567 else if (this.cm) ensureCursorVisible(this.cm); | |
| 7568 }), | |
| 7569 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), | |
| 7570 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), | |
| 7571 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", t
rue);}), | |
| 7572 redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", t
rue);}), | |
| 7573 | |
| 7574 setExtending: function(val) {this.extend = val;}, | |
| 7575 getExtending: function() {return this.extend;}, | |
| 7576 | |
| 7577 historySize: function() { | |
| 7578 var hist = this.history, done = 0, undone = 0; | |
| 7579 for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++don
e; | |
| 7580 for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) +
+undone; | |
| 7581 return {undo: done, redo: undone}; | |
| 7582 }, | |
| 7583 clearHistory: function() {this.history = new History(this.history.maxGenerat
ion);}, | |
| 7584 | |
| 7585 markClean: function() { | |
| 7586 this.cleanGeneration = this.changeGeneration(true); | |
| 7587 }, | |
| 7588 changeGeneration: function(forceSplit) { | |
| 7589 if (forceSplit) | |
| 7590 this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin =
null; | |
| 7591 return this.history.generation; | |
| 7592 }, | |
| 7593 isClean: function (gen) { | |
| 7594 return this.history.generation == (gen || this.cleanGeneration); | |
| 7595 }, | |
| 7596 | |
| 7597 getHistory: function() { | |
| 7598 return {done: copyHistoryArray(this.history.done), | |
| 7599 undone: copyHistoryArray(this.history.undone)}; | |
| 7600 }, | |
| 7601 setHistory: function(histData) { | |
| 7602 var hist = this.history = new History(this.history.maxGeneration); | |
| 7603 hist.done = copyHistoryArray(histData.done.slice(0), null, true); | |
| 7604 hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); | |
| 7605 }, | |
| 7606 | |
| 7607 addLineClass: docMethodOp(function(handle, where, cls) { | |
| 7608 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", fu
nction(line) { | |
| 7609 var prop = where == "text" ? "textClass" | |
| 7610 : where == "background" ? "bgClass" | |
| 7611 : where == "gutter" ? "gutterClass" : "wrapClass"; | |
| 7612 if (!line[prop]) line[prop] = cls; | |
| 7613 else if (classTest(cls).test(line[prop])) return false; | |
| 7614 else line[prop] += " " + cls; | |
| 7615 return true; | |
| 7616 }); | |
| 7617 }), | |
| 7618 removeLineClass: docMethodOp(function(handle, where, cls) { | |
| 7619 return changeLine(this, handle, where == "gutter" ? "gutter" : "class", fu
nction(line) { | |
| 7620 var prop = where == "text" ? "textClass" | |
| 7621 : where == "background" ? "bgClass" | |
| 7622 : where == "gutter" ? "gutterClass" : "wrapClass"; | |
| 7623 var cur = line[prop]; | |
| 7624 if (!cur) return false; | |
| 7625 else if (cls == null) line[prop] = null; | |
| 7626 else { | |
| 7627 var found = cur.match(classTest(cls)); | |
| 7628 if (!found) return false; | |
| 7629 var end = found.index + found[0].length; | |
| 7630 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.l
ength ? "" : " ") + cur.slice(end) || null; | |
| 7631 } | |
| 7632 return true; | |
| 7633 }); | |
| 7634 }), | |
| 7635 | |
| 7636 addLineWidget: docMethodOp(function(handle, node, options) { | |
| 7637 return addLineWidget(this, handle, node, options); | |
| 7638 }), | |
| 7639 removeLineWidget: function(widget) { widget.clear(); }, | |
| 7640 | |
| 7641 markText: function(from, to, options) { | |
| 7642 return markText(this, clipPos(this, from), clipPos(this, to), options, opt
ions && options.type || "range"); | |
| 7643 }, | |
| 7644 setBookmark: function(pos, options) { | |
| 7645 var realOpts = {replacedWith: options && (options.nodeType == null ? optio
ns.widget : options), | |
| 7646 insertLeft: options && options.insertLeft, | |
| 7647 clearWhenEmpty: false, shared: options && options.shared, | |
| 7648 handleMouseEvents: options && options.handleMouseEvents}; | |
| 7649 pos = clipPos(this, pos); | |
| 7650 return markText(this, pos, pos, realOpts, "bookmark"); | |
| 7651 }, | |
| 7652 findMarksAt: function(pos) { | |
| 7653 pos = clipPos(this, pos); | |
| 7654 var markers = [], spans = getLine(this, pos.line).markedSpans; | |
| 7655 if (spans) for (var i = 0; i < spans.length; ++i) { | |
| 7656 var span = spans[i]; | |
| 7657 if ((span.from == null || span.from <= pos.ch) && | |
| 7658 (span.to == null || span.to >= pos.ch)) | |
| 7659 markers.push(span.marker.parent || span.marker); | |
| 7660 } | |
| 7661 return markers; | |
| 7662 }, | |
| 7663 findMarks: function(from, to, filter) { | |
| 7664 from = clipPos(this, from); to = clipPos(this, to); | |
| 7665 var found = [], lineNo = from.line; | |
| 7666 this.iter(from.line, to.line + 1, function(line) { | |
| 7667 var spans = line.markedSpans; | |
| 7668 if (spans) for (var i = 0; i < spans.length; i++) { | |
| 7669 var span = spans[i]; | |
| 7670 if (!(span.to != null && lineNo == from.line && from.ch >= span.to || | |
| 7671 span.from == null && lineNo != from.line || | |
| 7672 span.from != null && lineNo == to.line && span.from >= to.ch) && | |
| 7673 (!filter || filter(span.marker))) | |
| 7674 found.push(span.marker.parent || span.marker); | |
| 7675 } | |
| 7676 ++lineNo; | |
| 7677 }); | |
| 7678 return found; | |
| 7679 }, | |
| 7680 getAllMarks: function() { | |
| 7681 var markers = []; | |
| 7682 this.iter(function(line) { | |
| 7683 var sps = line.markedSpans; | |
| 7684 if (sps) for (var i = 0; i < sps.length; ++i) | |
| 7685 if (sps[i].from != null) markers.push(sps[i].marker); | |
| 7686 }); | |
| 7687 return markers; | |
| 7688 }, | |
| 7689 | |
| 7690 posFromIndex: function(off) { | |
| 7691 var ch, lineNo = this.first, sepSize = this.lineSeparator().length; | |
| 7692 this.iter(function(line) { | |
| 7693 var sz = line.text.length + sepSize; | |
| 7694 if (sz > off) { ch = off; return true; } | |
| 7695 off -= sz; | |
| 7696 ++lineNo; | |
| 7697 }); | |
| 7698 return clipPos(this, Pos(lineNo, ch)); | |
| 7699 }, | |
| 7700 indexFromPos: function (coords) { | |
| 7701 coords = clipPos(this, coords); | |
| 7702 var index = coords.ch; | |
| 7703 if (coords.line < this.first || coords.ch < 0) return 0; | |
| 7704 var sepSize = this.lineSeparator().length; | |
| 7705 this.iter(this.first, coords.line, function (line) { | |
| 7706 index += line.text.length + sepSize; | |
| 7707 }); | |
| 7708 return index; | |
| 7709 }, | |
| 7710 | |
| 7711 copy: function(copyHistory) { | |
| 7712 var doc = new Doc(getLines(this, this.first, this.first + this.size), | |
| 7713 this.modeOption, this.first, this.lineSep); | |
| 7714 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; | |
| 7715 doc.sel = this.sel; | |
| 7716 doc.extend = false; | |
| 7717 if (copyHistory) { | |
| 7718 doc.history.undoDepth = this.history.undoDepth; | |
| 7719 doc.setHistory(this.getHistory()); | |
| 7720 } | |
| 7721 return doc; | |
| 7722 }, | |
| 7723 | |
| 7724 linkedDoc: function(options) { | |
| 7725 if (!options) options = {}; | |
| 7726 var from = this.first, to = this.first + this.size; | |
| 7727 if (options.from != null && options.from > from) from = options.from; | |
| 7728 if (options.to != null && options.to < to) to = options.to; | |
| 7729 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOpti
on, from, this.lineSep); | |
| 7730 if (options.sharedHist) copy.history = this.history; | |
| 7731 (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.s
haredHist}); | |
| 7732 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}
]; | |
| 7733 copySharedMarkers(copy, findSharedMarkers(this)); | |
| 7734 return copy; | |
| 7735 }, | |
| 7736 unlinkDoc: function(other) { | |
| 7737 if (other instanceof CodeMirror) other = other.doc; | |
| 7738 if (this.linked) for (var i = 0; i < this.linked.length; ++i) { | |
| 7739 var link = this.linked[i]; | |
| 7740 if (link.doc != other) continue; | |
| 7741 this.linked.splice(i, 1); | |
| 7742 other.unlinkDoc(this); | |
| 7743 detachSharedMarkers(findSharedMarkers(this)); | |
| 7744 break; | |
| 7745 } | |
| 7746 // If the histories were shared, split them again | |
| 7747 if (other.history == this.history) { | |
| 7748 var splitIds = [other.id]; | |
| 7749 linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); | |
| 7750 other.history = new History(null); | |
| 7751 other.history.done = copyHistoryArray(this.history.done, splitIds); | |
| 7752 other.history.undone = copyHistoryArray(this.history.undone, splitIds); | |
| 7753 } | |
| 7754 }, | |
| 7755 iterLinkedDocs: function(f) {linkedDocs(this, f);}, | |
| 7756 | |
| 7757 getMode: function() {return this.mode;}, | |
| 7758 getEditor: function() {return this.cm;}, | |
| 7759 | |
| 7760 splitLines: function(str) { | |
| 7761 if (this.lineSep) return str.split(this.lineSep); | |
| 7762 return splitLinesAuto(str); | |
| 7763 }, | |
| 7764 lineSeparator: function() { return this.lineSep || "\n"; } | |
| 7765 }); | |
| 7766 | |
| 7767 // Public alias. | |
| 7768 Doc.prototype.eachLine = Doc.prototype.iter; | |
| 7769 | |
| 7770 // Set up methods on CodeMirror's prototype to redirect to the editor's docume
nt. | |
| 7771 var dontDelegate = "iter insert remove copy getEditor constructor".split(" "); | |
| 7772 for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && inde
xOf(dontDelegate, prop) < 0) | |
| 7773 CodeMirror.prototype[prop] = (function(method) { | |
| 7774 return function() {return method.apply(this.doc, arguments);}; | |
| 7775 })(Doc.prototype[prop]); | |
| 7776 | |
| 7777 eventMixin(Doc); | |
| 7778 | |
| 7779 // Call f for all linked documents. | |
| 7780 function linkedDocs(doc, f, sharedHistOnly) { | |
| 7781 function propagate(doc, skip, sharedHist) { | |
| 7782 if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { | |
| 7783 var rel = doc.linked[i]; | |
| 7784 if (rel.doc == skip) continue; | |
| 7785 var shared = sharedHist && rel.sharedHist; | |
| 7786 if (sharedHistOnly && !shared) continue; | |
| 7787 f(rel.doc, shared); | |
| 7788 propagate(rel.doc, doc, shared); | |
| 7789 } | |
| 7790 } | |
| 7791 propagate(doc, null, true); | |
| 7792 } | |
| 7793 | |
| 7794 // Attach a document to an editor. | |
| 7795 function attachDoc(cm, doc) { | |
| 7796 if (doc.cm) throw new Error("This document is already in use."); | |
| 7797 cm.doc = doc; | |
| 7798 doc.cm = cm; | |
| 7799 estimateLineHeights(cm); | |
| 7800 loadMode(cm); | |
| 7801 if (!cm.options.lineWrapping) findMaxLine(cm); | |
| 7802 cm.options.mode = doc.modeOption; | |
| 7803 regChange(cm); | |
| 7804 } | |
| 7805 | |
| 7806 // LINE UTILITIES | |
| 7807 | |
| 7808 // Find the line object corresponding to the given line number. | |
| 7809 function getLine(doc, n) { | |
| 7810 n -= doc.first; | |
| 7811 if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.f
irst) + " in the document."); | |
| 7812 for (var chunk = doc; !chunk.lines;) { | |
| 7813 for (var i = 0;; ++i) { | |
| 7814 var child = chunk.children[i], sz = child.chunkSize(); | |
| 7815 if (n < sz) { chunk = child; break; } | |
| 7816 n -= sz; | |
| 7817 } | |
| 7818 } | |
| 7819 return chunk.lines[n]; | |
| 7820 } | |
| 7821 | |
| 7822 // Get the part of a document between two positions, as an array of | |
| 7823 // strings. | |
| 7824 function getBetween(doc, start, end) { | |
| 7825 var out = [], n = start.line; | |
| 7826 doc.iter(start.line, end.line + 1, function(line) { | |
| 7827 var text = line.text; | |
| 7828 if (n == end.line) text = text.slice(0, end.ch); | |
| 7829 if (n == start.line) text = text.slice(start.ch); | |
| 7830 out.push(text); | |
| 7831 ++n; | |
| 7832 }); | |
| 7833 return out; | |
| 7834 } | |
| 7835 // Get the lines between from and to, as array of strings. | |
| 7836 function getLines(doc, from, to) { | |
| 7837 var out = []; | |
| 7838 doc.iter(from, to, function(line) { out.push(line.text); }); | |
| 7839 return out; | |
| 7840 } | |
| 7841 | |
| 7842 // Update the height of a line, propagating the height change | |
| 7843 // upwards to parent nodes. | |
| 7844 function updateLineHeight(line, height) { | |
| 7845 var diff = height - line.height; | |
| 7846 if (diff) for (var n = line; n; n = n.parent) n.height += diff; | |
| 7847 } | |
| 7848 | |
| 7849 // Given a line object, find its line number by walking up through | |
| 7850 // its parent links. | |
| 7851 function lineNo(line) { | |
| 7852 if (line.parent == null) return null; | |
| 7853 var cur = line.parent, no = indexOf(cur.lines, line); | |
| 7854 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { | |
| 7855 for (var i = 0;; ++i) { | |
| 7856 if (chunk.children[i] == cur) break; | |
| 7857 no += chunk.children[i].chunkSize(); | |
| 7858 } | |
| 7859 } | |
| 7860 return no + cur.first; | |
| 7861 } | |
| 7862 | |
| 7863 // Find the line at the given vertical position, using the height | |
| 7864 // information in the document tree. | |
| 7865 function lineAtHeight(chunk, h) { | |
| 7866 var n = chunk.first; | |
| 7867 outer: do { | |
| 7868 for (var i = 0; i < chunk.children.length; ++i) { | |
| 7869 var child = chunk.children[i], ch = child.height; | |
| 7870 if (h < ch) { chunk = child; continue outer; } | |
| 7871 h -= ch; | |
| 7872 n += child.chunkSize(); | |
| 7873 } | |
| 7874 return n; | |
| 7875 } while (!chunk.lines); | |
| 7876 for (var i = 0; i < chunk.lines.length; ++i) { | |
| 7877 var line = chunk.lines[i], lh = line.height; | |
| 7878 if (h < lh) break; | |
| 7879 h -= lh; | |
| 7880 } | |
| 7881 return n + i; | |
| 7882 } | |
| 7883 | |
| 7884 | |
| 7885 // Find the height above the given line. | |
| 7886 function heightAtLine(lineObj) { | |
| 7887 lineObj = visualLine(lineObj); | |
| 7888 | |
| 7889 var h = 0, chunk = lineObj.parent; | |
| 7890 for (var i = 0; i < chunk.lines.length; ++i) { | |
| 7891 var line = chunk.lines[i]; | |
| 7892 if (line == lineObj) break; | |
| 7893 else h += line.height; | |
| 7894 } | |
| 7895 for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { | |
| 7896 for (var i = 0; i < p.children.length; ++i) { | |
| 7897 var cur = p.children[i]; | |
| 7898 if (cur == chunk) break; | |
| 7899 else h += cur.height; | |
| 7900 } | |
| 7901 } | |
| 7902 return h; | |
| 7903 } | |
| 7904 | |
| 7905 // Get the bidi ordering for the given line (and cache it). Returns | |
| 7906 // false for lines that are fully left-to-right, and an array of | |
| 7907 // BidiSpan objects otherwise. | |
| 7908 function getOrder(line) { | |
| 7909 var order = line.order; | |
| 7910 if (order == null) order = line.order = bidiOrdering(line.text); | |
| 7911 return order; | |
| 7912 } | |
| 7913 | |
| 7914 // HISTORY | |
| 7915 | |
| 7916 function History(startGen) { | |
| 7917 // Arrays of change events and selections. Doing something adds an | |
| 7918 // event to done and clears undo. Undoing moves events from done | |
| 7919 // to undone, redoing moves them in the other direction. | |
| 7920 this.done = []; this.undone = []; | |
| 7921 this.undoDepth = Infinity; | |
| 7922 // Used to track when changes can be merged into a single undo | |
| 7923 // event | |
| 7924 this.lastModTime = this.lastSelTime = 0; | |
| 7925 this.lastOp = this.lastSelOp = null; | |
| 7926 this.lastOrigin = this.lastSelOrigin = null; | |
| 7927 // Used by the isClean() method | |
| 7928 this.generation = this.maxGeneration = startGen || 1; | |
| 7929 } | |
| 7930 | |
| 7931 // Create a history change event from an updateDoc-style change | |
| 7932 // object. | |
| 7933 function historyChangeFromChange(doc, change) { | |
| 7934 var histChange = {from: copyPos(change.from), to: changeEnd(change), text: g
etBetween(doc, change.from, change.to)}; | |
| 7935 attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); | |
| 7936 linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from
.line, change.to.line + 1);}, true); | |
| 7937 return histChange; | |
| 7938 } | |
| 7939 | |
| 7940 // Pop all selection events off the end of a history array. Stop at | |
| 7941 // a change event. | |
| 7942 function clearSelectionEvents(array) { | |
| 7943 while (array.length) { | |
| 7944 var last = lst(array); | |
| 7945 if (last.ranges) array.pop(); | |
| 7946 else break; | |
| 7947 } | |
| 7948 } | |
| 7949 | |
| 7950 // Find the top change event in the history. Pop off selection | |
| 7951 // events that are in the way. | |
| 7952 function lastChangeEvent(hist, force) { | |
| 7953 if (force) { | |
| 7954 clearSelectionEvents(hist.done); | |
| 7955 return lst(hist.done); | |
| 7956 } else if (hist.done.length && !lst(hist.done).ranges) { | |
| 7957 return lst(hist.done); | |
| 7958 } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges)
{ | |
| 7959 hist.done.pop(); | |
| 7960 return lst(hist.done); | |
| 7961 } | |
| 7962 } | |
| 7963 | |
| 7964 // Register a change in the history. Merges changes that are within | |
| 7965 // a single operation, ore are close together with an origin that | |
| 7966 // allows merging (starting with "+") into a single event. | |
| 7967 function addChangeToHistory(doc, change, selAfter, opId) { | |
| 7968 var hist = doc.history; | |
| 7969 hist.undone.length = 0; | |
| 7970 var time = +new Date, cur; | |
| 7971 | |
| 7972 if ((hist.lastOp == opId || | |
| 7973 hist.lastOrigin == change.origin && change.origin && | |
| 7974 ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time -
doc.cm.options.historyEventDelay) || | |
| 7975 change.origin.charAt(0) == "*")) && | |
| 7976 (cur = lastChangeEvent(hist, hist.lastOp == opId))) { | |
| 7977 // Merge this change into the last event | |
| 7978 var last = lst(cur.changes); | |
| 7979 if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { | |
| 7980 // Optimized case for simple insertion -- don't want to add | |
| 7981 // new changesets for every character typed | |
| 7982 last.to = changeEnd(change); | |
| 7983 } else { | |
| 7984 // Add new sub-event | |
| 7985 cur.changes.push(historyChangeFromChange(doc, change)); | |
| 7986 } | |
| 7987 } else { | |
| 7988 // Can not be merged, start a new event. | |
| 7989 var before = lst(hist.done); | |
| 7990 if (!before || !before.ranges) | |
| 7991 pushSelectionToHistory(doc.sel, hist.done); | |
| 7992 cur = {changes: [historyChangeFromChange(doc, change)], | |
| 7993 generation: hist.generation}; | |
| 7994 hist.done.push(cur); | |
| 7995 while (hist.done.length > hist.undoDepth) { | |
| 7996 hist.done.shift(); | |
| 7997 if (!hist.done[0].ranges) hist.done.shift(); | |
| 7998 } | |
| 7999 } | |
| 8000 hist.done.push(selAfter); | |
| 8001 hist.generation = ++hist.maxGeneration; | |
| 8002 hist.lastModTime = hist.lastSelTime = time; | |
| 8003 hist.lastOp = hist.lastSelOp = opId; | |
| 8004 hist.lastOrigin = hist.lastSelOrigin = change.origin; | |
| 8005 | |
| 8006 if (!last) signal(doc, "historyAdded"); | |
| 8007 } | |
| 8008 | |
| 8009 function selectionEventCanBeMerged(doc, origin, prev, sel) { | |
| 8010 var ch = origin.charAt(0); | |
| 8011 return ch == "*" || | |
| 8012 ch == "+" && | |
| 8013 prev.ranges.length == sel.ranges.length && | |
| 8014 prev.somethingSelected() == sel.somethingSelected() && | |
| 8015 new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEven
tDelay : 500); | |
| 8016 } | |
| 8017 | |
| 8018 // Called whenever the selection changes, sets the new selection as | |
| 8019 // the pending selection in the history, and pushes the old pending | |
| 8020 // selection into the 'done' array when it was significantly | |
| 8021 // different (in number of selected ranges, emptiness, or time). | |
| 8022 function addSelectionToHistory(doc, sel, opId, options) { | |
| 8023 var hist = doc.history, origin = options && options.origin; | |
| 8024 | |
| 8025 // A new event is started when the previous origin does not match | |
| 8026 // the current, or the origins don't allow matching. Origins | |
| 8027 // starting with * are always merged, those starting with + are | |
| 8028 // merged when similar and close together in time. | |
| 8029 if (opId == hist.lastSelOp || | |
| 8030 (origin && hist.lastSelOrigin == origin && | |
| 8031 (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || | |
| 8032 selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) | |
| 8033 hist.done[hist.done.length - 1] = sel; | |
| 8034 else | |
| 8035 pushSelectionToHistory(sel, hist.done); | |
| 8036 | |
| 8037 hist.lastSelTime = +new Date; | |
| 8038 hist.lastSelOrigin = origin; | |
| 8039 hist.lastSelOp = opId; | |
| 8040 if (options && options.clearRedo !== false) | |
| 8041 clearSelectionEvents(hist.undone); | |
| 8042 } | |
| 8043 | |
| 8044 function pushSelectionToHistory(sel, dest) { | |
| 8045 var top = lst(dest); | |
| 8046 if (!(top && top.ranges && top.equals(sel))) | |
| 8047 dest.push(sel); | |
| 8048 } | |
| 8049 | |
| 8050 // Used to store marked span information in the history. | |
| 8051 function attachLocalSpans(doc, change, from, to) { | |
| 8052 var existing = change["spans_" + doc.id], n = 0; | |
| 8053 doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), func
tion(line) { | |
| 8054 if (line.markedSpans) | |
| 8055 (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.mark
edSpans; | |
| 8056 ++n; | |
| 8057 }); | |
| 8058 } | |
| 8059 | |
| 8060 // When un/re-doing restores text containing marked spans, those | |
| 8061 // that have been explicitly cleared should not be restored. | |
| 8062 function removeClearedSpans(spans) { | |
| 8063 if (!spans) return null; | |
| 8064 for (var i = 0, out; i < spans.length; ++i) { | |
| 8065 if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i)
; } | |
| 8066 else if (out) out.push(spans[i]); | |
| 8067 } | |
| 8068 return !out ? spans : out.length ? out : null; | |
| 8069 } | |
| 8070 | |
| 8071 // Retrieve and filter the old marked spans stored in a change event. | |
| 8072 function getOldSpans(doc, change) { | |
| 8073 var found = change["spans_" + doc.id]; | |
| 8074 if (!found) return null; | |
| 8075 for (var i = 0, nw = []; i < change.text.length; ++i) | |
| 8076 nw.push(removeClearedSpans(found[i])); | |
| 8077 return nw; | |
| 8078 } | |
| 8079 | |
| 8080 // Used both to provide a JSON-safe object in .getHistory, and, when | |
| 8081 // detaching a document, to split the history in two | |
| 8082 function copyHistoryArray(events, newGroup, instantiateSel) { | |
| 8083 for (var i = 0, copy = []; i < events.length; ++i) { | |
| 8084 var event = events[i]; | |
| 8085 if (event.ranges) { | |
| 8086 copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : ev
ent); | |
| 8087 continue; | |
| 8088 } | |
| 8089 var changes = event.changes, newChanges = []; | |
| 8090 copy.push({changes: newChanges}); | |
| 8091 for (var j = 0; j < changes.length; ++j) { | |
| 8092 var change = changes[j], m; | |
| 8093 newChanges.push({from: change.from, to: change.to, text: change.text}); | |
| 8094 if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$
/)) { | |
| 8095 if (indexOf(newGroup, Number(m[1])) > -1) { | |
| 8096 lst(newChanges)[prop] = change[prop]; | |
| 8097 delete change[prop]; | |
| 8098 } | |
| 8099 } | |
| 8100 } | |
| 8101 } | |
| 8102 return copy; | |
| 8103 } | |
| 8104 | |
| 8105 // Rebasing/resetting history to deal with externally-sourced changes | |
| 8106 | |
| 8107 function rebaseHistSelSingle(pos, from, to, diff) { | |
| 8108 if (to < pos.line) { | |
| 8109 pos.line += diff; | |
| 8110 } else if (from < pos.line) { | |
| 8111 pos.line = from; | |
| 8112 pos.ch = 0; | |
| 8113 } | |
| 8114 } | |
| 8115 | |
| 8116 // Tries to rebase an array of history events given a change in the | |
| 8117 // document. If the change touches the same lines as the event, the | |
| 8118 // event, and everything 'behind' it, is discarded. If the change is | |
| 8119 // before the event, the event's positions are updated. Uses a | |
| 8120 // copy-on-write scheme for the positions, to avoid having to | |
| 8121 // reallocate them all on every rebase, but also avoid problems with | |
| 8122 // shared position objects being unsafely updated. | |
| 8123 function rebaseHistArray(array, from, to, diff) { | |
| 8124 for (var i = 0; i < array.length; ++i) { | |
| 8125 var sub = array[i], ok = true; | |
| 8126 if (sub.ranges) { | |
| 8127 if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } | |
| 8128 for (var j = 0; j < sub.ranges.length; j++) { | |
| 8129 rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); | |
| 8130 rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); | |
| 8131 } | |
| 8132 continue; | |
| 8133 } | |
| 8134 for (var j = 0; j < sub.changes.length; ++j) { | |
| 8135 var cur = sub.changes[j]; | |
| 8136 if (to < cur.from.line) { | |
| 8137 cur.from = Pos(cur.from.line + diff, cur.from.ch); | |
| 8138 cur.to = Pos(cur.to.line + diff, cur.to.ch); | |
| 8139 } else if (from <= cur.to.line) { | |
| 8140 ok = false; | |
| 8141 break; | |
| 8142 } | |
| 8143 } | |
| 8144 if (!ok) { | |
| 8145 array.splice(0, i + 1); | |
| 8146 i = 0; | |
| 8147 } | |
| 8148 } | |
| 8149 } | |
| 8150 | |
| 8151 function rebaseHist(hist, change) { | |
| 8152 var from = change.from.line, to = change.to.line, diff = change.text.length
- (to - from) - 1; | |
| 8153 rebaseHistArray(hist.done, from, to, diff); | |
| 8154 rebaseHistArray(hist.undone, from, to, diff); | |
| 8155 } | |
| 8156 | |
| 8157 // EVENT UTILITIES | |
| 8158 | |
| 8159 // Due to the fact that we still support jurassic IE versions, some | |
| 8160 // compatibility wrappers are needed. | |
| 8161 | |
| 8162 var e_preventDefault = CodeMirror.e_preventDefault = function(e) { | |
| 8163 if (e.preventDefault) e.preventDefault(); | |
| 8164 else e.returnValue = false; | |
| 8165 }; | |
| 8166 var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) { | |
| 8167 if (e.stopPropagation) e.stopPropagation(); | |
| 8168 else e.cancelBubble = true; | |
| 8169 }; | |
| 8170 function e_defaultPrevented(e) { | |
| 8171 return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == fa
lse; | |
| 8172 } | |
| 8173 var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropa
gation(e);}; | |
| 8174 | |
| 8175 function e_target(e) {return e.target || e.srcElement;} | |
| 8176 function e_button(e) { | |
| 8177 var b = e.which; | |
| 8178 if (b == null) { | |
| 8179 if (e.button & 1) b = 1; | |
| 8180 else if (e.button & 2) b = 3; | |
| 8181 else if (e.button & 4) b = 2; | |
| 8182 } | |
| 8183 if (mac && e.ctrlKey && b == 1) b = 3; | |
| 8184 return b; | |
| 8185 } | |
| 8186 | |
| 8187 // EVENT HANDLING | |
| 8188 | |
| 8189 // Lightweight event framework. on/off also work on DOM nodes, | |
| 8190 // registering native DOM handlers. | |
| 8191 | |
| 8192 var on = CodeMirror.on = function(emitter, type, f) { | |
| 8193 if (emitter.addEventListener) | |
| 8194 emitter.addEventListener(type, f, false); | |
| 8195 else if (emitter.attachEvent) | |
| 8196 emitter.attachEvent("on" + type, f); | |
| 8197 else { | |
| 8198 var map = emitter._handlers || (emitter._handlers = {}); | |
| 8199 var arr = map[type] || (map[type] = []); | |
| 8200 arr.push(f); | |
| 8201 } | |
| 8202 }; | |
| 8203 | |
| 8204 var noHandlers = [] | |
| 8205 function getHandlers(emitter, type, copy) { | |
| 8206 var arr = emitter._handlers && emitter._handlers[type] | |
| 8207 if (copy) return arr && arr.length > 0 ? arr.slice() : noHandlers | |
| 8208 else return arr || noHandlers | |
| 8209 } | |
| 8210 | |
| 8211 var off = CodeMirror.off = function(emitter, type, f) { | |
| 8212 if (emitter.removeEventListener) | |
| 8213 emitter.removeEventListener(type, f, false); | |
| 8214 else if (emitter.detachEvent) | |
| 8215 emitter.detachEvent("on" + type, f); | |
| 8216 else { | |
| 8217 var handlers = getHandlers(emitter, type, false) | |
| 8218 for (var i = 0; i < handlers.length; ++i) | |
| 8219 if (handlers[i] == f) { handlers.splice(i, 1); break; } | |
| 8220 } | |
| 8221 }; | |
| 8222 | |
| 8223 var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { | |
| 8224 var handlers = getHandlers(emitter, type, true) | |
| 8225 if (!handlers.length) return; | |
| 8226 var args = Array.prototype.slice.call(arguments, 2); | |
| 8227 for (var i = 0; i < handlers.length; ++i) handlers[i].apply(null, args); | |
| 8228 }; | |
| 8229 | |
| 8230 var orphanDelayedCallbacks = null; | |
| 8231 | |
| 8232 // Often, we want to signal events at a point where we are in the | |
| 8233 // middle of some work, but don't want the handler to start calling | |
| 8234 // other methods on the editor, which might be in an inconsistent | |
| 8235 // state or simply not expect any other events to happen. | |
| 8236 // signalLater looks whether there are any handlers, and schedules | |
| 8237 // them to be executed when the last operation ends, or, if no | |
| 8238 // operation is active, when a timeout fires. | |
| 8239 function signalLater(emitter, type /*, values...*/) { | |
| 8240 var arr = getHandlers(emitter, type, false) | |
| 8241 if (!arr.length) return; | |
| 8242 var args = Array.prototype.slice.call(arguments, 2), list; | |
| 8243 if (operationGroup) { | |
| 8244 list = operationGroup.delayedCallbacks; | |
| 8245 } else if (orphanDelayedCallbacks) { | |
| 8246 list = orphanDelayedCallbacks; | |
| 8247 } else { | |
| 8248 list = orphanDelayedCallbacks = []; | |
| 8249 setTimeout(fireOrphanDelayed, 0); | |
| 8250 } | |
| 8251 function bnd(f) {return function(){f.apply(null, args);};}; | |
| 8252 for (var i = 0; i < arr.length; ++i) | |
| 8253 list.push(bnd(arr[i])); | |
| 8254 } | |
| 8255 | |
| 8256 function fireOrphanDelayed() { | |
| 8257 var delayed = orphanDelayedCallbacks; | |
| 8258 orphanDelayedCallbacks = null; | |
| 8259 for (var i = 0; i < delayed.length; ++i) delayed[i](); | |
| 8260 } | |
| 8261 | |
| 8262 // The DOM events that CodeMirror handles can be overridden by | |
| 8263 // registering a (non-DOM) handler on the editor for the event name, | |
| 8264 // and preventDefault-ing the event in that handler. | |
| 8265 function signalDOMEvent(cm, e, override) { | |
| 8266 if (typeof e == "string") | |
| 8267 e = {type: e, preventDefault: function() { this.defaultPrevented = true; }
}; | |
| 8268 signal(cm, override || e.type, cm, e); | |
| 8269 return e_defaultPrevented(e) || e.codemirrorIgnore; | |
| 8270 } | |
| 8271 | |
| 8272 function signalCursorActivity(cm) { | |
| 8273 var arr = cm._handlers && cm._handlers.cursorActivity; | |
| 8274 if (!arr) return; | |
| 8275 var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandler
s = []); | |
| 8276 for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) | |
| 8277 set.push(arr[i]); | |
| 8278 } | |
| 8279 | |
| 8280 function hasHandler(emitter, type) { | |
| 8281 return getHandlers(emitter, type).length > 0 | |
| 8282 } | |
| 8283 | |
| 8284 // Add on and off methods to a constructor's prototype, to make | |
| 8285 // registering events on such objects more convenient. | |
| 8286 function eventMixin(ctor) { | |
| 8287 ctor.prototype.on = function(type, f) {on(this, type, f);}; | |
| 8288 ctor.prototype.off = function(type, f) {off(this, type, f);}; | |
| 8289 } | |
| 8290 | |
| 8291 // MISC UTILITIES | |
| 8292 | |
| 8293 // Number of pixels added to scroller and sizer to hide scrollbar | |
| 8294 var scrollerGap = 30; | |
| 8295 | |
| 8296 // Returned or thrown by various protocols to signal 'I'm not | |
| 8297 // handling this'. | |
| 8298 var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}
; | |
| 8299 | |
| 8300 // Reused option objects for setSelection & friends | |
| 8301 var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move
= {origin: "+move"}; | |
| 8302 | |
| 8303 function Delayed() {this.id = null;} | |
| 8304 Delayed.prototype.set = function(ms, f) { | |
| 8305 clearTimeout(this.id); | |
| 8306 this.id = setTimeout(f, ms); | |
| 8307 }; | |
| 8308 | |
| 8309 // Counts the column offset in a string, taking tabs into account. | |
| 8310 // Used mostly to find indentation. | |
| 8311 var countColumn = CodeMirror.countColumn = function(string, end, tabSize, star
tIndex, startValue) { | |
| 8312 if (end == null) { | |
| 8313 end = string.search(/[^\s\u00a0]/); | |
| 8314 if (end == -1) end = string.length; | |
| 8315 } | |
| 8316 for (var i = startIndex || 0, n = startValue || 0;;) { | |
| 8317 var nextTab = string.indexOf("\t", i); | |
| 8318 if (nextTab < 0 || nextTab >= end) | |
| 8319 return n + (end - i); | |
| 8320 n += nextTab - i; | |
| 8321 n += tabSize - (n % tabSize); | |
| 8322 i = nextTab + 1; | |
| 8323 } | |
| 8324 }; | |
| 8325 | |
| 8326 // The inverse of countColumn -- find the offset that corresponds to | |
| 8327 // a particular column. | |
| 8328 var findColumn = CodeMirror.findColumn = function(string, goal, tabSize) { | |
| 8329 for (var pos = 0, col = 0;;) { | |
| 8330 var nextTab = string.indexOf("\t", pos); | |
| 8331 if (nextTab == -1) nextTab = string.length; | |
| 8332 var skipped = nextTab - pos; | |
| 8333 if (nextTab == string.length || col + skipped >= goal) | |
| 8334 return pos + Math.min(skipped, goal - col); | |
| 8335 col += nextTab - pos; | |
| 8336 col += tabSize - (col % tabSize); | |
| 8337 pos = nextTab + 1; | |
| 8338 if (col >= goal) return pos; | |
| 8339 } | |
| 8340 } | |
| 8341 | |
| 8342 var spaceStrs = [""]; | |
| 8343 function spaceStr(n) { | |
| 8344 while (spaceStrs.length <= n) | |
| 8345 spaceStrs.push(lst(spaceStrs) + " "); | |
| 8346 return spaceStrs[n]; | |
| 8347 } | |
| 8348 | |
| 8349 function lst(arr) { return arr[arr.length-1]; } | |
| 8350 | |
| 8351 var selectInput = function(node) { node.select(); }; | |
| 8352 if (ios) // Mobile Safari apparently has a bug where select() is broken. | |
| 8353 selectInput = function(node) { node.selectionStart = 0; node.selectionEnd =
node.value.length; }; | |
| 8354 else if (ie) // Suppress mysterious IE10 errors | |
| 8355 selectInput = function(node) { try { node.select(); } catch(_e) {} }; | |
| 8356 | |
| 8357 function indexOf(array, elt) { | |
| 8358 for (var i = 0; i < array.length; ++i) | |
| 8359 if (array[i] == elt) return i; | |
| 8360 return -1; | |
| 8361 } | |
| 8362 function map(array, f) { | |
| 8363 var out = []; | |
| 8364 for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); | |
| 8365 return out; | |
| 8366 } | |
| 8367 | |
| 8368 function nothing() {} | |
| 8369 | |
| 8370 function createObj(base, props) { | |
| 8371 var inst; | |
| 8372 if (Object.create) { | |
| 8373 inst = Object.create(base); | |
| 8374 } else { | |
| 8375 nothing.prototype = base; | |
| 8376 inst = new nothing(); | |
| 8377 } | |
| 8378 if (props) copyObj(props, inst); | |
| 8379 return inst; | |
| 8380 }; | |
| 8381 | |
| 8382 function copyObj(obj, target, overwrite) { | |
| 8383 if (!target) target = {}; | |
| 8384 for (var prop in obj) | |
| 8385 if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProp
erty(prop))) | |
| 8386 target[prop] = obj[prop]; | |
| 8387 return target; | |
| 8388 } | |
| 8389 | |
| 8390 function bind(f) { | |
| 8391 var args = Array.prototype.slice.call(arguments, 1); | |
| 8392 return function(){return f.apply(null, args);}; | |
| 8393 } | |
| 8394 | |
| 8395 var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u304
0-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; | |
| 8396 var isWordCharBasic = CodeMirror.isWordChar = function(ch) { | |
| 8397 return /\w/.test(ch) || ch > "\x80" && | |
| 8398 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(c
h)); | |
| 8399 }; | |
| 8400 function isWordChar(ch, helper) { | |
| 8401 if (!helper) return isWordCharBasic(ch); | |
| 8402 if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; | |
| 8403 return helper.test(ch); | |
| 8404 } | |
| 8405 | |
| 8406 function isEmpty(obj) { | |
| 8407 for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; | |
| 8408 return true; | |
| 8409 } | |
| 8410 | |
| 8411 // Extending unicode characters. A series of a non-extending char + | |
| 8412 // any number of extending chars is treated as a single unit as far | |
| 8413 // as editing and measuring is concerned. This is not fully correct, | |
| 8414 // since some scripts/fonts/browsers also treat other configurations | |
| 8415 // of code points as a group. | |
| 8416 var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05
c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u
06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u081
9\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u
0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u
0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0
a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b
3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3
e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0c
c6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d6
3\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0e
b4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0
f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037
\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086
\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17b
d\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u19
39-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u
1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-
\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1c
e8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\
u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\
ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b
3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab
2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe0
0-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; | |
| 8417 function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChar
s.test(ch); } | |
| 8418 | |
| 8419 // DOM UTILITIES | |
| 8420 | |
| 8421 function elt(tag, content, className, style) { | |
| 8422 var e = document.createElement(tag); | |
| 8423 if (className) e.className = className; | |
| 8424 if (style) e.style.cssText = style; | |
| 8425 if (typeof content == "string") e.appendChild(document.createTextNode(conten
t)); | |
| 8426 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(con
tent[i]); | |
| 8427 return e; | |
| 8428 } | |
| 8429 | |
| 8430 var range; | |
| 8431 if (document.createRange) range = function(node, start, end, endNode) { | |
| 8432 var r = document.createRange(); | |
| 8433 r.setEnd(endNode || node, end); | |
| 8434 r.setStart(node, start); | |
| 8435 return r; | |
| 8436 }; | |
| 8437 else range = function(node, start, end) { | |
| 8438 var r = document.body.createTextRange(); | |
| 8439 try { r.moveToElementText(node.parentNode); } | |
| 8440 catch(e) { return r; } | |
| 8441 r.collapse(true); | |
| 8442 r.moveEnd("character", end); | |
| 8443 r.moveStart("character", start); | |
| 8444 return r; | |
| 8445 }; | |
| 8446 | |
| 8447 function removeChildren(e) { | |
| 8448 for (var count = e.childNodes.length; count > 0; --count) | |
| 8449 e.removeChild(e.firstChild); | |
| 8450 return e; | |
| 8451 } | |
| 8452 | |
| 8453 function removeChildrenAndAdd(parent, e) { | |
| 8454 return removeChildren(parent).appendChild(e); | |
| 8455 } | |
| 8456 | |
| 8457 var contains = CodeMirror.contains = function(parent, child) { | |
| 8458 if (child.nodeType == 3) // Android browser always returns false when child
is a textnode | |
| 8459 child = child.parentNode; | |
| 8460 if (parent.contains) | |
| 8461 return parent.contains(child); | |
| 8462 do { | |
| 8463 if (child.nodeType == 11) child = child.host; | |
| 8464 if (child == parent) return true; | |
| 8465 } while (child = child.parentNode); | |
| 8466 }; | |
| 8467 | |
| 8468 function activeElt() { | |
| 8469 var activeElement = document.activeElement; | |
| 8470 while (activeElement && activeElement.root && activeElement.root.activeEleme
nt) | |
| 8471 activeElement = activeElement.root.activeElement; | |
| 8472 return activeElement; | |
| 8473 } | |
| 8474 // Older versions of IE throws unspecified error when touching | |
| 8475 // document.activeElement in some cases (during loading, in iframe) | |
| 8476 if (ie && ie_version < 11) activeElt = function() { | |
| 8477 try { return document.activeElement; } | |
| 8478 catch(e) { return document.body; } | |
| 8479 }; | |
| 8480 | |
| 8481 function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*")
; } | |
| 8482 var rmClass = CodeMirror.rmClass = function(node, cls) { | |
| 8483 var current = node.className; | |
| 8484 var match = classTest(cls).exec(current); | |
| 8485 if (match) { | |
| 8486 var after = current.slice(match.index + match[0].length); | |
| 8487 node.className = current.slice(0, match.index) + (after ? match[1] + after
: ""); | |
| 8488 } | |
| 8489 }; | |
| 8490 var addClass = CodeMirror.addClass = function(node, cls) { | |
| 8491 var current = node.className; | |
| 8492 if (!classTest(cls).test(current)) node.className += (current ? " " : "") +
cls; | |
| 8493 }; | |
| 8494 function joinClasses(a, b) { | |
| 8495 var as = a.split(" "); | |
| 8496 for (var i = 0; i < as.length; i++) | |
| 8497 if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i]; | |
| 8498 return b; | |
| 8499 } | |
| 8500 | |
| 8501 // WINDOW-WIDE EVENTS | |
| 8502 | |
| 8503 // These must be handled carefully, because naively registering a | |
| 8504 // handler for each editor will cause the editors to never be | |
| 8505 // garbage collected. | |
| 8506 | |
| 8507 function forEachCodeMirror(f) { | |
| 8508 if (!document.body.getElementsByClassName) return; | |
| 8509 var byClass = document.body.getElementsByClassName("CodeMirror"); | |
| 8510 for (var i = 0; i < byClass.length; i++) { | |
| 8511 var cm = byClass[i].CodeMirror; | |
| 8512 if (cm) f(cm); | |
| 8513 } | |
| 8514 } | |
| 8515 | |
| 8516 var globalsRegistered = false; | |
| 8517 function ensureGlobalHandlers() { | |
| 8518 if (globalsRegistered) return; | |
| 8519 registerGlobalHandlers(); | |
| 8520 globalsRegistered = true; | |
| 8521 } | |
| 8522 function registerGlobalHandlers() { | |
| 8523 // When the window resizes, we need to refresh active editors. | |
| 8524 var resizeTimer; | |
| 8525 on(window, "resize", function() { | |
| 8526 if (resizeTimer == null) resizeTimer = setTimeout(function() { | |
| 8527 resizeTimer = null; | |
| 8528 forEachCodeMirror(onResize); | |
| 8529 }, 100); | |
| 8530 }); | |
| 8531 // When the window loses focus, we want to show the editor as blurred | |
| 8532 on(window, "blur", function() { | |
| 8533 forEachCodeMirror(onBlur); | |
| 8534 }); | |
| 8535 } | |
| 8536 | |
| 8537 // FEATURE DETECTION | |
| 8538 | |
| 8539 // Detect drag-and-drop | |
| 8540 var dragAndDrop = function() { | |
| 8541 // There is *some* kind of drag-and-drop support in IE6-8, but I | |
| 8542 // couldn't get it to work yet. | |
| 8543 if (ie && ie_version < 9) return false; | |
| 8544 var div = elt('div'); | |
| 8545 return "draggable" in div || "dragDrop" in div; | |
| 8546 }(); | |
| 8547 | |
| 8548 var zwspSupported; | |
| 8549 function zeroWidthElement(measure) { | |
| 8550 if (zwspSupported == null) { | |
| 8551 var test = elt("span", "\u200b"); | |
| 8552 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("
x")])); | |
| 8553 if (measure.firstChild.offsetHeight != 0) | |
| 8554 zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie &
& ie_version < 8); | |
| 8555 } | |
| 8556 var node = zwspSupported ? elt("span", "\u200b") : | |
| 8557 elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-rig
ht: -1px"); | |
| 8558 node.setAttribute("cm-text", ""); | |
| 8559 return node; | |
| 8560 } | |
| 8561 | |
| 8562 // Feature-detect IE's crummy client rect reporting for bidi text | |
| 8563 var badBidiRects; | |
| 8564 function hasBadBidiRects(measure) { | |
| 8565 if (badBidiRects != null) return badBidiRects; | |
| 8566 var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"))
; | |
| 8567 var r0 = range(txt, 0, 1).getBoundingClientRect(); | |
| 8568 var r1 = range(txt, 1, 2).getBoundingClientRect(); | |
| 8569 removeChildren(measure); | |
| 8570 if (!r0 || r0.left == r0.right) return false; // Safari returns null in some
cases (#2780) | |
| 8571 return badBidiRects = (r1.right - r0.right < 3); | |
| 8572 } | |
| 8573 | |
| 8574 // See if "".split is the broken IE version, if so, provide an | |
| 8575 // alternative way to split lines. | |
| 8576 var splitLinesAuto = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ?
function(string) { | |
| 8577 var pos = 0, result = [], l = string.length; | |
| 8578 while (pos <= l) { | |
| 8579 var nl = string.indexOf("\n", pos); | |
| 8580 if (nl == -1) nl = string.length; | |
| 8581 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); | |
| 8582 var rt = line.indexOf("\r"); | |
| 8583 if (rt != -1) { | |
| 8584 result.push(line.slice(0, rt)); | |
| 8585 pos += rt + 1; | |
| 8586 } else { | |
| 8587 result.push(line); | |
| 8588 pos = nl + 1; | |
| 8589 } | |
| 8590 } | |
| 8591 return result; | |
| 8592 } : function(string){return string.split(/\r\n?|\n/);}; | |
| 8593 | |
| 8594 var hasSelection = window.getSelection ? function(te) { | |
| 8595 try { return te.selectionStart != te.selectionEnd; } | |
| 8596 catch(e) { return false; } | |
| 8597 } : function(te) { | |
| 8598 try {var range = te.ownerDocument.selection.createRange();} | |
| 8599 catch(e) {} | |
| 8600 if (!range || range.parentElement() != te) return false; | |
| 8601 return range.compareEndPoints("StartToEnd", range) != 0; | |
| 8602 }; | |
| 8603 | |
| 8604 var hasCopyEvent = (function() { | |
| 8605 var e = elt("div"); | |
| 8606 if ("oncopy" in e) return true; | |
| 8607 e.setAttribute("oncopy", "return;"); | |
| 8608 return typeof e.oncopy == "function"; | |
| 8609 })(); | |
| 8610 | |
| 8611 var badZoomedRects = null; | |
| 8612 function hasBadZoomedRects(measure) { | |
| 8613 if (badZoomedRects != null) return badZoomedRects; | |
| 8614 var node = removeChildrenAndAdd(measure, elt("span", "x")); | |
| 8615 var normal = node.getBoundingClientRect(); | |
| 8616 var fromRange = range(node, 0, 1).getBoundingClientRect(); | |
| 8617 return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; | |
| 8618 } | |
| 8619 | |
| 8620 // KEY NAMES | |
| 8621 | |
| 8622 var keyNames = CodeMirror.keyNames = { | |
| 8623 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl",
18: "Alt", | |
| 8624 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "Page
Down", 35: "End", | |
| 8625 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn",
45: "Insert", | |
| 8626 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", | |
| 8627 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", | |
| 8628 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "
`", 219: "[", 220: "\\", | |
| 8629 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right
", 63272: "Delete", | |
| 8630 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Ins
ert" | |
| 8631 }; | |
| 8632 (function() { | |
| 8633 // Number keys | |
| 8634 for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i)
; | |
| 8635 // Alphabetic keys | |
| 8636 for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); | |
| 8637 // Function keys | |
| 8638 for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F"
+ i; | |
| 8639 })(); | |
| 8640 | |
| 8641 // BIDI HELPERS | |
| 8642 | |
| 8643 function iterateBidiSections(order, from, to, f) { | |
| 8644 if (!order) return f(from, to, "ltr"); | |
| 8645 var found = false; | |
| 8646 for (var i = 0; i < order.length; ++i) { | |
| 8647 var part = order[i]; | |
| 8648 if (part.from < to && part.to > from || from == to && part.to == from) { | |
| 8649 f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "r
tl" : "ltr"); | |
| 8650 found = true; | |
| 8651 } | |
| 8652 } | |
| 8653 if (!found) f(from, to, "ltr"); | |
| 8654 } | |
| 8655 | |
| 8656 function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } | |
| 8657 function bidiRight(part) { return part.level % 2 ? part.from : part.to; } | |
| 8658 | |
| 8659 function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(
order[0]) : 0; } | |
| 8660 function lineRight(line) { | |
| 8661 var order = getOrder(line); | |
| 8662 if (!order) return line.text.length; | |
| 8663 return bidiRight(lst(order)); | |
| 8664 } | |
| 8665 | |
| 8666 function lineStart(cm, lineN) { | |
| 8667 var line = getLine(cm.doc, lineN); | |
| 8668 var visual = visualLine(line); | |
| 8669 if (visual != line) lineN = lineNo(visual); | |
| 8670 var order = getOrder(visual); | |
| 8671 var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visu
al); | |
| 8672 return Pos(lineN, ch); | |
| 8673 } | |
| 8674 function lineEnd(cm, lineN) { | |
| 8675 var merged, line = getLine(cm.doc, lineN); | |
| 8676 while (merged = collapsedSpanAtEnd(line)) { | |
| 8677 line = merged.find(1, true).line; | |
| 8678 lineN = null; | |
| 8679 } | |
| 8680 var order = getOrder(line); | |
| 8681 var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : l
ineRight(line); | |
| 8682 return Pos(lineN == null ? lineNo(line) : lineN, ch); | |
| 8683 } | |
| 8684 function lineStartSmart(cm, pos) { | |
| 8685 var start = lineStart(cm, pos.line); | |
| 8686 var line = getLine(cm.doc, start.line); | |
| 8687 var order = getOrder(line); | |
| 8688 if (!order || order[0].level == 0) { | |
| 8689 var firstNonWS = Math.max(0, line.text.search(/\S/)); | |
| 8690 var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; | |
| 8691 return Pos(start.line, inWS ? 0 : firstNonWS); | |
| 8692 } | |
| 8693 return start; | |
| 8694 } | |
| 8695 | |
| 8696 function compareBidiLevel(order, a, b) { | |
| 8697 var linedir = order[0].level; | |
| 8698 if (a == linedir) return true; | |
| 8699 if (b == linedir) return false; | |
| 8700 return a < b; | |
| 8701 } | |
| 8702 var bidiOther; | |
| 8703 function getBidiPartAt(order, pos) { | |
| 8704 bidiOther = null; | |
| 8705 for (var i = 0, found; i < order.length; ++i) { | |
| 8706 var cur = order[i]; | |
| 8707 if (cur.from < pos && cur.to > pos) return i; | |
| 8708 if ((cur.from == pos || cur.to == pos)) { | |
| 8709 if (found == null) { | |
| 8710 found = i; | |
| 8711 } else if (compareBidiLevel(order, cur.level, order[found].level)) { | |
| 8712 if (cur.from != cur.to) bidiOther = found; | |
| 8713 return i; | |
| 8714 } else { | |
| 8715 if (cur.from != cur.to) bidiOther = i; | |
| 8716 return found; | |
| 8717 } | |
| 8718 } | |
| 8719 } | |
| 8720 return found; | |
| 8721 } | |
| 8722 | |
| 8723 function moveInLine(line, pos, dir, byUnit) { | |
| 8724 if (!byUnit) return pos + dir; | |
| 8725 do pos += dir; | |
| 8726 while (pos > 0 && isExtendingChar(line.text.charAt(pos))); | |
| 8727 return pos; | |
| 8728 } | |
| 8729 | |
| 8730 // This is needed in order to move 'visually' through bi-directional | |
| 8731 // text -- i.e., pressing left should make the cursor go left, even | |
| 8732 // when in RTL text. The tricky part is the 'jumps', where RTL and | |
| 8733 // LTR text touch each other. This often requires the cursor offset | |
| 8734 // to move more than one unit, in order to visually move one unit. | |
| 8735 function moveVisually(line, start, dir, byUnit) { | |
| 8736 var bidi = getOrder(line); | |
| 8737 if (!bidi) return moveLogically(line, start, dir, byUnit); | |
| 8738 var pos = getBidiPartAt(bidi, start), part = bidi[pos]; | |
| 8739 var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); | |
| 8740 | |
| 8741 for (;;) { | |
| 8742 if (target > part.from && target < part.to) return target; | |
| 8743 if (target == part.from || target == part.to) { | |
| 8744 if (getBidiPartAt(bidi, target) == pos) return target; | |
| 8745 part = bidi[pos += dir]; | |
| 8746 return (dir > 0) == part.level % 2 ? part.to : part.from; | |
| 8747 } else { | |
| 8748 part = bidi[pos += dir]; | |
| 8749 if (!part) return null; | |
| 8750 if ((dir > 0) == part.level % 2) | |
| 8751 target = moveInLine(line, part.to, -1, byUnit); | |
| 8752 else | |
| 8753 target = moveInLine(line, part.from, 1, byUnit); | |
| 8754 } | |
| 8755 } | |
| 8756 } | |
| 8757 | |
| 8758 function moveLogically(line, start, dir, byUnit) { | |
| 8759 var target = start + dir; | |
| 8760 if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target)))
target += dir; | |
| 8761 return target < 0 || target > line.text.length ? null : target; | |
| 8762 } | |
| 8763 | |
| 8764 // Bidirectional ordering algorithm | |
| 8765 // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm | |
| 8766 // that this (partially) implements. | |
| 8767 | |
| 8768 // One-char codes used for character types: | |
| 8769 // L (L): Left-to-Right | |
| 8770 // R (R): Right-to-Left | |
| 8771 // r (AL): Right-to-Left Arabic | |
| 8772 // 1 (EN): European Number | |
| 8773 // + (ES): European Number Separator | |
| 8774 // % (ET): European Number Terminator | |
| 8775 // n (AN): Arabic Number | |
| 8776 // , (CS): Common Number Separator | |
| 8777 // m (NSM): Non-Spacing Mark | |
| 8778 // b (BN): Boundary Neutral | |
| 8779 // s (B): Paragraph Separator | |
| 8780 // t (S): Segment Separator | |
| 8781 // w (WS): Whitespace | |
| 8782 // N (ON): Other Neutrals | |
| 8783 | |
| 8784 // Returns null if characters are ordered as they appear | |
| 8785 // (left-to-right), or an array of sections ({from, to, level} | |
| 8786 // objects) in the order in which they occur visually. | |
| 8787 var bidiOrdering = (function() { | |
| 8788 // Character types for codepoints 0 to 0xff | |
| 8789 var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NN
NNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbb
bbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLL
LLLLLLLLLLLLLLLLLLLLLLLLLLLN"; | |
| 8790 // Character types for codepoints 0x600 to 0x6ff | |
| 8791 var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmm
mmmmmmmmmmmmmmmmNmmmm"; | |
| 8792 function charType(code) { | |
| 8793 if (code <= 0xf7) return lowTypes.charAt(code); | |
| 8794 else if (0x590 <= code && code <= 0x5f4) return "R"; | |
| 8795 else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code -
0x600); | |
| 8796 else if (0x6ee <= code && code <= 0x8ac) return "r"; | |
| 8797 else if (0x2000 <= code && code <= 0x200b) return "w"; | |
| 8798 else if (code == 0x200c) return "b"; | |
| 8799 else return "L"; | |
| 8800 } | |
| 8801 | |
| 8802 var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; | |
| 8803 var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, count
sAsNum = /[1n]/; | |
| 8804 // Browsers seem to always treat the boundaries of block elements as being L
. | |
| 8805 var outerType = "L"; | |
| 8806 | |
| 8807 function BidiSpan(level, from, to) { | |
| 8808 this.level = level; | |
| 8809 this.from = from; this.to = to; | |
| 8810 } | |
| 8811 | |
| 8812 return function(str) { | |
| 8813 if (!bidiRE.test(str)) return false; | |
| 8814 var len = str.length, types = []; | |
| 8815 for (var i = 0, type; i < len; ++i) | |
| 8816 types.push(type = charType(str.charCodeAt(i))); | |
| 8817 | |
| 8818 // W1. Examine each non-spacing mark (NSM) in the level run, and | |
| 8819 // change the type of the NSM to the type of the previous | |
| 8820 // character. If the NSM is at the start of the level run, it will | |
| 8821 // get the type of sor. | |
| 8822 for (var i = 0, prev = outerType; i < len; ++i) { | |
| 8823 var type = types[i]; | |
| 8824 if (type == "m") types[i] = prev; | |
| 8825 else prev = type; | |
| 8826 } | |
| 8827 | |
| 8828 // W2. Search backwards from each instance of a European number | |
| 8829 // until the first strong type (R, L, AL, or sor) is found. If an | |
| 8830 // AL is found, change the type of the European number to Arabic | |
| 8831 // number. | |
| 8832 // W3. Change all ALs to R. | |
| 8833 for (var i = 0, cur = outerType; i < len; ++i) { | |
| 8834 var type = types[i]; | |
| 8835 if (type == "1" && cur == "r") types[i] = "n"; | |
| 8836 else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] =
"R"; } | |
| 8837 } | |
| 8838 | |
| 8839 // W4. A single European separator between two European numbers | |
| 8840 // changes to a European number. A single common separator between | |
| 8841 // two numbers of the same type changes to that type. | |
| 8842 for (var i = 1, prev = types[0]; i < len - 1; ++i) { | |
| 8843 var type = types[i]; | |
| 8844 if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; | |
| 8845 else if (type == "," && prev == types[i+1] && | |
| 8846 (prev == "1" || prev == "n")) types[i] = prev; | |
| 8847 prev = type; | |
| 8848 } | |
| 8849 | |
| 8850 // W5. A sequence of European terminators adjacent to European | |
| 8851 // numbers changes to all European numbers. | |
| 8852 // W6. Otherwise, separators and terminators change to Other | |
| 8853 // Neutral. | |
| 8854 for (var i = 0; i < len; ++i) { | |
| 8855 var type = types[i]; | |
| 8856 if (type == ",") types[i] = "N"; | |
| 8857 else if (type == "%") { | |
| 8858 for (var end = i + 1; end < len && types[end] == "%"; ++end) {} | |
| 8859 var replace = (i && types[i-1] == "!") || (end < len && types[end] ==
"1") ? "1" : "N"; | |
| 8860 for (var j = i; j < end; ++j) types[j] = replace; | |
| 8861 i = end - 1; | |
| 8862 } | |
| 8863 } | |
| 8864 | |
| 8865 // W7. Search backwards from each instance of a European number | |
| 8866 // until the first strong type (R, L, or sor) is found. If an L is | |
| 8867 // found, then change the type of the European number to L. | |
| 8868 for (var i = 0, cur = outerType; i < len; ++i) { | |
| 8869 var type = types[i]; | |
| 8870 if (cur == "L" && type == "1") types[i] = "L"; | |
| 8871 else if (isStrong.test(type)) cur = type; | |
| 8872 } | |
| 8873 | |
| 8874 // N1. A sequence of neutrals takes the direction of the | |
| 8875 // surrounding strong text if the text on both sides has the same | |
| 8876 // direction. European and Arabic numbers act as if they were R in | |
| 8877 // terms of their influence on neutrals. Start-of-level-run (sor) | |
| 8878 // and end-of-level-run (eor) are used at level run boundaries. | |
| 8879 // N2. Any remaining neutrals take the embedding direction. | |
| 8880 for (var i = 0; i < len; ++i) { | |
| 8881 if (isNeutral.test(types[i])) { | |
| 8882 for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end)
{} | |
| 8883 var before = (i ? types[i-1] : outerType) == "L"; | |
| 8884 var after = (end < len ? types[end] : outerType) == "L"; | |
| 8885 var replace = before || after ? "L" : "R"; | |
| 8886 for (var j = i; j < end; ++j) types[j] = replace; | |
| 8887 i = end - 1; | |
| 8888 } | |
| 8889 } | |
| 8890 | |
| 8891 // Here we depart from the documented algorithm, in order to avoid | |
| 8892 // building up an actual levels array. Since there are only three | |
| 8893 // levels (0, 1, 2) in an implementation that doesn't take | |
| 8894 // explicit embedding into account, we can build up the order on | |
| 8895 // the fly, without following the level-based algorithm. | |
| 8896 var order = [], m; | |
| 8897 for (var i = 0; i < len;) { | |
| 8898 if (countsAsLeft.test(types[i])) { | |
| 8899 var start = i; | |
| 8900 for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} | |
| 8901 order.push(new BidiSpan(0, start, i)); | |
| 8902 } else { | |
| 8903 var pos = i, at = order.length; | |
| 8904 for (++i; i < len && types[i] != "L"; ++i) {} | |
| 8905 for (var j = pos; j < i;) { | |
| 8906 if (countsAsNum.test(types[j])) { | |
| 8907 if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)); | |
| 8908 var nstart = j; | |
| 8909 for (++j; j < i && countsAsNum.test(types[j]); ++j) {} | |
| 8910 order.splice(at, 0, new BidiSpan(2, nstart, j)); | |
| 8911 pos = j; | |
| 8912 } else ++j; | |
| 8913 } | |
| 8914 if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)); | |
| 8915 } | |
| 8916 } | |
| 8917 if (order[0].level == 1 && (m = str.match(/^\s+/))) { | |
| 8918 order[0].from = m[0].length; | |
| 8919 order.unshift(new BidiSpan(0, 0, m[0].length)); | |
| 8920 } | |
| 8921 if (lst(order).level == 1 && (m = str.match(/\s+$/))) { | |
| 8922 lst(order).to -= m[0].length; | |
| 8923 order.push(new BidiSpan(0, len - m[0].length, len)); | |
| 8924 } | |
| 8925 if (order[0].level == 2) | |
| 8926 order.unshift(new BidiSpan(1, order[0].to, order[0].to)); | |
| 8927 if (order[0].level != lst(order).level) | |
| 8928 order.push(new BidiSpan(order[0].level, len, len)); | |
| 8929 | |
| 8930 return order; | |
| 8931 }; | |
| 8932 })(); | |
| 8933 | |
| 8934 // THE END | |
| 8935 | |
| 8936 CodeMirror.version = "5.17.1"; | |
| 8937 | |
| 8938 return CodeMirror; | |
| 8939 }); | |
| OLD | NEW |