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(mod) { |
11 if (typeof exports == "object" && typeof module == "object") // CommonJS | 11 if (typeof exports == "object" && typeof module == "object") // CommonJS |
12 module.exports = mod(); | 12 module.exports = mod(); |
13 else if (typeof define == "function" && define.amd) // AMD | 13 else if (typeof define == "function" && define.amd) // AMD |
14 return define([], mod); | 14 return define([], mod); |
15 else // Plain browser env | 15 else // Plain browser env |
16 this.CodeMirror = mod(); | 16 (this || window).CodeMirror = mod(); |
17 })(function() { | 17 })(function() { |
18 "use strict"; | 18 "use strict"; |
19 | 19 |
20 // BROWSER SNIFFING | 20 // BROWSER SNIFFING |
21 | 21 |
22 // Kludges for bugs and behavior differences that can't be feature | 22 // Kludges for bugs and behavior differences that can't be feature |
23 // detected are enabled based on userAgent etc sniffing. | 23 // detected are enabled based on userAgent etc sniffing. |
| 24 var userAgent = navigator.userAgent; |
| 25 var platform = navigator.platform; |
24 | 26 |
25 var gecko = /gecko\/\d/i.test(navigator.userAgent); | 27 var gecko = /gecko\/\d/i.test(userAgent); |
26 // ie_uptoN means Internet Explorer version N or lower | 28 var ie_upto10 = /MSIE \d/.test(userAgent); |
27 var ie_upto10 = /MSIE \d/.test(navigator.userAgent); | 29 var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); |
28 var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent
); | |
29 var ie = ie_upto10 || ie_11up; | 30 var ie = ie_upto10 || ie_11up; |
30 var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); | 31 var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); |
31 var webkit = /WebKit\//.test(navigator.userAgent); | 32 var webkit = /WebKit\//.test(userAgent); |
32 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); | 33 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); |
33 var chrome = /Chrome\//.test(navigator.userAgent); | 34 var chrome = /Chrome\//.test(userAgent); |
34 var presto = /Opera\//.test(navigator.userAgent); | 35 var presto = /Opera\//.test(userAgent); |
35 var safari = /Apple Computer/.test(navigator.vendor); | 36 var safari = /Apple Computer/.test(navigator.vendor); |
36 var khtml = /KHTML\//.test(navigator.userAgent); | 37 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); |
37 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAge
nt); | 38 var phantom = /PhantomJS/.test(userAgent); |
38 var phantom = /PhantomJS/.test(navigator.userAgent); | |
39 | 39 |
40 var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(naviga
tor.userAgent); | 40 var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent); |
41 // This is woefully incomplete. Suggestions for alternative methods welcome. | 41 // This is woefully incomplete. Suggestions for alternative methods welcome. |
42 var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i
.test(navigator.userAgent); | 42 var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i
.test(userAgent); |
43 var mac = ios || /Mac/.test(navigator.platform); | 43 var mac = ios || /Mac/.test(platform); |
44 var windows = /win/i.test(navigator.platform); | 44 var chromeOS = /\bCrOS\b/.test(userAgent); |
| 45 var windows = /win/i.test(platform); |
45 | 46 |
46 var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/
); | 47 var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); |
47 if (presto_version) presto_version = Number(presto_version[1]); | 48 if (presto_version) presto_version = Number(presto_version[1]); |
48 if (presto_version && presto_version >= 15) { presto = false; webkit = true; } | 49 if (presto_version && presto_version >= 15) { presto = false; webkit = true; } |
49 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X | 50 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X |
50 var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || pre
sto_version < 12.11)); | 51 var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || pre
sto_version < 12.11)); |
51 var captureRightClick = gecko || (ie && ie_version >= 9); | 52 var captureRightClick = gecko || (ie && ie_version >= 9); |
52 | 53 |
53 // Optimize some code when these features are not used. | 54 // Optimize some code when these features are not used. |
54 var sawReadOnlySpans = false, sawCollapsedSpans = false; | 55 var sawReadOnlySpans = false, sawCollapsedSpans = false; |
55 | 56 |
56 // EDITOR CONSTRUCTOR | 57 // EDITOR CONSTRUCTOR |
57 | 58 |
58 // A CodeMirror instance represents an editor. This is the object | 59 // A CodeMirror instance represents an editor. This is the object |
59 // that user code is usually dealing with. | 60 // that user code is usually dealing with. |
60 | 61 |
61 function CodeMirror(place, options) { | 62 function CodeMirror(place, options) { |
62 if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); | 63 if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); |
63 | 64 |
64 this.options = options = options ? copyObj(options) : {}; | 65 this.options = options = options ? copyObj(options) : {}; |
65 // Determine effective options based on given values and defaults. | 66 // Determine effective options based on given values and defaults. |
66 copyObj(defaults, options, false); | 67 copyObj(defaults, options, false); |
67 setGuttersForLineNumbers(options); | 68 setGuttersForLineNumbers(options); |
68 | 69 |
69 var doc = options.value; | 70 var doc = options.value; |
70 if (typeof doc == "string") doc = new Doc(doc, options.mode); | 71 if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.l
ineSeparator); |
71 this.doc = doc; | 72 this.doc = doc; |
72 | 73 |
73 var display = this.display = new Display(place, doc); | 74 var input = new CodeMirror.inputStyles[options.inputStyle](this); |
| 75 var display = this.display = new Display(place, doc, input); |
74 display.wrapper.CodeMirror = this; | 76 display.wrapper.CodeMirror = this; |
75 updateGutters(this); | 77 updateGutters(this); |
76 themeChanged(this); | 78 themeChanged(this); |
77 if (options.lineWrapping) | 79 if (options.lineWrapping) |
78 this.display.wrapper.className += " CodeMirror-wrap"; | 80 this.display.wrapper.className += " CodeMirror-wrap"; |
79 if (options.autofocus && !mobile) focusInput(this); | 81 if (options.autofocus && !mobile) display.input.focus(); |
80 initScrollbars(this); | 82 initScrollbars(this); |
81 | 83 |
82 this.state = { | 84 this.state = { |
83 keyMaps: [], // stores maps added by addKeyMap | 85 keyMaps: [], // stores maps added by addKeyMap |
84 overlays: [], // highlighting overlays, as added by addOverlay | 86 overlays: [], // highlighting overlays, as added by addOverlay |
85 modeGen: 0, // bumped when mode/overlay changes, used to invalidate high
lighting info | 87 modeGen: 0, // bumped when mode/overlay changes, used to invalidate high
lighting info |
86 overwrite: false, focused: false, | 88 overwrite: false, |
| 89 delayingBlurEvent: false, |
| 90 focused: false, |
87 suppressEdits: false, // used to disable editing during key handlers when
in readOnly mode | 91 suppressEdits: false, // used to disable editing during key handlers when
in readOnly mode |
88 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edit
s in readInput | 92 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edit
s in input.poll |
| 93 selectingText: false, |
89 draggingText: false, | 94 draggingText: false, |
90 highlight: new Delayed(), // stores highlight worker timeout | 95 highlight: new Delayed(), // stores highlight worker timeout |
91 keySeq: null // Unfinished key sequence | 96 keySeq: null, // Unfinished key sequence |
| 97 specialChars: null |
92 }; | 98 }; |
93 | 99 |
| 100 var cm = this; |
| 101 |
94 // Override magic textarea content restore that IE sometimes does | 102 // Override magic textarea content restore that IE sometimes does |
95 // on our hidden textarea on reload | 103 // on our hidden textarea on reload |
96 if (ie && ie_version < 11) setTimeout(bind(resetInput, this, true), 20); | 104 if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(tr
ue); }, 20); |
97 | 105 |
98 registerEventHandlers(this); | 106 registerEventHandlers(this); |
99 ensureGlobalHandlers(); | 107 ensureGlobalHandlers(); |
100 | 108 |
101 startOperation(this); | 109 startOperation(this); |
102 this.curOp.forceUpdate = true; | 110 this.curOp.forceUpdate = true; |
103 attachDoc(this, doc); | 111 attachDoc(this, doc); |
104 | 112 |
105 if ((options.autofocus && !mobile) || activeElt() == display.input) | 113 if ((options.autofocus && !mobile) || cm.hasFocus()) |
106 setTimeout(bind(onFocus, this), 20); | 114 setTimeout(bind(onFocus, this), 20); |
107 else | 115 else |
108 onBlur(this); | 116 onBlur(this); |
109 | 117 |
110 for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) | 118 for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) |
111 optionHandlers[opt](this, options[opt], Init); | 119 optionHandlers[opt](this, options[opt], Init); |
112 maybeUpdateLineNumberWidth(this); | 120 maybeUpdateLineNumberWidth(this); |
| 121 if (options.finishInit) options.finishInit(this); |
113 for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); | 122 for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); |
114 endOperation(this); | 123 endOperation(this); |
115 // Suppress optimizelegibility in Webkit, since it breaks text | 124 // Suppress optimizelegibility in Webkit, since it breaks text |
116 // measuring on line wrapping boundaries. | 125 // measuring on line wrapping boundaries. |
117 if (webkit && options.lineWrapping && | 126 if (webkit && options.lineWrapping && |
118 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") | 127 getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") |
119 display.lineDiv.style.textRendering = "auto"; | 128 display.lineDiv.style.textRendering = "auto"; |
120 } | 129 } |
121 | 130 |
122 // DISPLAY CONSTRUCTOR | 131 // DISPLAY CONSTRUCTOR |
123 | 132 |
124 // The display handles the DOM integration, both for input reading | 133 // The display handles the DOM integration, both for input reading |
125 // and content drawing. It holds references to DOM nodes and | 134 // and content drawing. It holds references to DOM nodes and |
126 // display-related state. | 135 // display-related state. |
127 | 136 |
128 function Display(place, doc) { | 137 function Display(place, doc, input) { |
129 var d = this; | 138 var d = this; |
| 139 this.input = input; |
130 | 140 |
131 // The semihidden textarea that is focused when the editor is | |
132 // focused, and receives input. | |
133 var input = d.input = elt("textarea", null, null, "position: absolute; paddi
ng: 0; width: 1px; height: 1em; outline: none"); | |
134 // The textarea is kept positioned near the cursor to prevent the | |
135 // fact that it'll be scrolled into view on input from scrolling | |
136 // our fake cursor out of view. On webkit, when wrap=off, paste is | |
137 // very slow. So make the area wide instead. | |
138 if (webkit) input.style.width = "1000px"; | |
139 else input.setAttribute("wrap", "off"); | |
140 // If border: 0; -- iOS fails to open keyboard (issue #1287) | |
141 if (ios) input.style.border = "1px solid black"; | |
142 input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize
", "off"); input.setAttribute("spellcheck", "false"); | |
143 | |
144 // Wraps and hides input textarea | |
145 d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative
; width: 3px; height: 0px;"); | |
146 // Covers bottom-right square when both scrollbars are present. | 141 // Covers bottom-right square when both scrollbars are present. |
147 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); | 142 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); |
148 d.scrollbarFiller.setAttribute("not-content", "true"); | 143 d.scrollbarFiller.setAttribute("cm-not-content", "true"); |
149 // Covers bottom of gutter when coverGutterNextToScrollbar is on | 144 // Covers bottom of gutter when coverGutterNextToScrollbar is on |
150 // and h scrollbar is present. | 145 // and h scrollbar is present. |
151 d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); | 146 d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); |
152 d.gutterFiller.setAttribute("not-content", "true"); | 147 d.gutterFiller.setAttribute("cm-not-content", "true"); |
153 // Will contain the actual code, positioned to cover the viewport. | 148 // Will contain the actual code, positioned to cover the viewport. |
154 d.lineDiv = elt("div", null, "CodeMirror-code"); | 149 d.lineDiv = elt("div", null, "CodeMirror-code"); |
155 // Elements are added to these to represent selection and cursors. | 150 // Elements are added to these to represent selection and cursors. |
156 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); | 151 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); |
157 d.cursorDiv = elt("div", null, "CodeMirror-cursors"); | 152 d.cursorDiv = elt("div", null, "CodeMirror-cursors"); |
158 // A visibility: hidden element used to find the size of things. | 153 // A visibility: hidden element used to find the size of things. |
159 d.measure = elt("div", null, "CodeMirror-measure"); | 154 d.measure = elt("div", null, "CodeMirror-measure"); |
160 // When lines outside of the viewport are measured, they are drawn in this. | 155 // When lines outside of the viewport are measured, they are drawn in this. |
161 d.lineMeasure = elt("div", null, "CodeMirror-measure"); | 156 d.lineMeasure = elt("div", null, "CodeMirror-measure"); |
162 // Wraps everything that needs to exist inside the vertically-padded coordin
ate system | 157 // Wraps everything that needs to exist inside the vertically-padded coordin
ate system |
163 d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursor
Div, d.lineDiv], | 158 d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursor
Div, d.lineDiv], |
164 null, "position: relative; outline: none"); | 159 null, "position: relative; outline: none"); |
165 // Moved around its parent to cover visible view. | 160 // Moved around its parent to cover visible view. |
166 d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null,
"position: relative"); | 161 d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null,
"position: relative"); |
167 // Set to the height of the document, allowing scrolling. | 162 // Set to the height of the document, allowing scrolling. |
168 d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); | 163 d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); |
169 d.sizerWidth = null; | 164 d.sizerWidth = null; |
170 // Behavior of elts with overflow: auto and padding is | 165 // Behavior of elts with overflow: auto and padding is |
171 // inconsistent across browsers. This is used to ensure the | 166 // inconsistent across browsers. This is used to ensure the |
172 // scrollable area is big enough. | 167 // scrollable area is big enough. |
173 d.heightForcer = elt("div", null, null, "position: absolute; height: " + scr
ollerGap + "px; width: 1px;"); | 168 d.heightForcer = elt("div", null, null, "position: absolute; height: " + scr
ollerGap + "px; width: 1px;"); |
174 // Will contain the gutters, if any. | 169 // Will contain the gutters, if any. |
175 d.gutters = elt("div", null, "CodeMirror-gutters"); | 170 d.gutters = elt("div", null, "CodeMirror-gutters"); |
176 d.lineGutter = null; | 171 d.lineGutter = null; |
177 // Actual scrollable element. | 172 // Actual scrollable element. |
178 d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-sc
roll"); | 173 d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-sc
roll"); |
179 d.scroller.setAttribute("tabIndex", "-1"); | 174 d.scroller.setAttribute("tabIndex", "-1"); |
180 // The element in which the editor lives. | 175 // The element in which the editor lives. |
181 d.wrapper = elt("div", [d.inputDiv, d.scrollbarFiller, d.gutterFiller, d.scr
oller], "CodeMirror"); | 176 d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "Cod
eMirror"); |
182 | 177 |
183 // Work around IE7 z-index bug (not perfect, hence IE7 not really being supp
orted) | 178 // Work around IE7 z-index bug (not perfect, hence IE7 not really being supp
orted) |
184 if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.pa
ddingRight = 0; } | 179 if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.pa
ddingRight = 0; } |
185 // Needed to hide big blue blinking cursor on Mobile Safari | 180 if (!webkit && !(gecko && mobile)) d.scroller.draggable = true; |
186 if (ios) input.style.width = "0px"; | |
187 if (!webkit) d.scroller.draggable = true; | |
188 // Needed to handle Tab key in KHTML | |
189 if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "a
bsolute"; } | |
190 | 181 |
191 if (place) { | 182 if (place) { |
192 if (place.appendChild) place.appendChild(d.wrapper); | 183 if (place.appendChild) place.appendChild(d.wrapper); |
193 else place(d.wrapper); | 184 else place(d.wrapper); |
194 } | 185 } |
195 | 186 |
196 // Current rendered range (may be bigger than the view window). | 187 // Current rendered range (may be bigger than the view window). |
197 d.viewFrom = d.viewTo = doc.first; | 188 d.viewFrom = d.viewTo = doc.first; |
198 d.reportedViewFrom = d.reportedViewTo = doc.first; | 189 d.reportedViewFrom = d.reportedViewTo = doc.first; |
199 // Information about the rendered lines. | 190 // Information about the rendered lines. |
200 d.view = []; | 191 d.view = []; |
201 d.renderedView = null; | 192 d.renderedView = null; |
202 // Holds info about a single rendered line when it was rendered | 193 // Holds info about a single rendered line when it was rendered |
203 // for measurement, while not in view. | 194 // for measurement, while not in view. |
204 d.externalMeasured = null; | 195 d.externalMeasured = null; |
205 // Empty space (in pixels) above the view | 196 // Empty space (in pixels) above the view |
206 d.viewOffset = 0; | 197 d.viewOffset = 0; |
207 d.lastWrapHeight = d.lastWrapWidth = 0; | 198 d.lastWrapHeight = d.lastWrapWidth = 0; |
208 d.updateLineNumbers = null; | 199 d.updateLineNumbers = null; |
209 | 200 |
210 d.nativeBarWidth = d.barHeight = d.barWidth = 0; | 201 d.nativeBarWidth = d.barHeight = d.barWidth = 0; |
211 d.scrollbarsClipped = false; | 202 d.scrollbarsClipped = false; |
212 | 203 |
213 // Used to only resize the line number gutter when necessary (when | 204 // Used to only resize the line number gutter when necessary (when |
214 // the amount of lines crosses a boundary that makes its width change) | 205 // the amount of lines crosses a boundary that makes its width change) |
215 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; | 206 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; |
216 // See readInput and resetInput | |
217 d.prevInput = ""; | |
218 // Set to true when a non-horizontal-scrolling line widget is | 207 // Set to true when a non-horizontal-scrolling line widget is |
219 // added. As an optimization, line widget aligning is skipped when | 208 // added. As an optimization, line widget aligning is skipped when |
220 // this is false. | 209 // this is false. |
221 d.alignWidgets = false; | 210 d.alignWidgets = false; |
222 // Flag that indicates whether we expect input to appear real soon | |
223 // now (after some event like 'keypress' or 'input') and are | |
224 // polling intensively. | |
225 d.pollingFast = false; | |
226 // Self-resetting timeout for the poller | |
227 d.poll = new Delayed(); | |
228 | 211 |
229 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; | 212 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; |
230 | 213 |
231 // Tracks when resetInput has punted to just putting a short | |
232 // string into the textarea instead of the full selection. | |
233 d.inaccurateSelection = false; | |
234 | |
235 // Tracks the maximum line length so that the horizontal scrollbar | 214 // Tracks the maximum line length so that the horizontal scrollbar |
236 // can be kept static when scrolling. | 215 // can be kept static when scrolling. |
237 d.maxLine = null; | 216 d.maxLine = null; |
238 d.maxLineLength = 0; | 217 d.maxLineLength = 0; |
239 d.maxLineChanged = false; | 218 d.maxLineChanged = false; |
240 | 219 |
241 // Used for measuring wheel scrolling granularity | 220 // Used for measuring wheel scrolling granularity |
242 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; | 221 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; |
243 | 222 |
244 // True when shift is held down. | 223 // True when shift is held down. |
245 d.shift = false; | 224 d.shift = false; |
246 | 225 |
247 // Used to track whether anything happened since the context menu | 226 // Used to track whether anything happened since the context menu |
248 // was opened. | 227 // was opened. |
249 d.selForContextMenu = null; | 228 d.selForContextMenu = null; |
| 229 |
| 230 d.activeTouch = null; |
| 231 |
| 232 input.init(d); |
250 } | 233 } |
251 | 234 |
252 // STATE UPDATES | 235 // STATE UPDATES |
253 | 236 |
254 // Used to get the editor into a consistent state again when options change. | 237 // Used to get the editor into a consistent state again when options change. |
255 | 238 |
256 function loadMode(cm) { | 239 function loadMode(cm) { |
257 cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); | 240 cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); |
258 resetModeState(cm); | 241 resetModeState(cm); |
259 } | 242 } |
(...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
421 var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; m
in-height: 1px")], "CodeMirror-hscrollbar"); | 404 var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; m
in-height: 1px")], "CodeMirror-hscrollbar"); |
422 place(vert); place(horiz); | 405 place(vert); place(horiz); |
423 | 406 |
424 on(vert, "scroll", function() { | 407 on(vert, "scroll", function() { |
425 if (vert.clientHeight) scroll(vert.scrollTop, "vertical"); | 408 if (vert.clientHeight) scroll(vert.scrollTop, "vertical"); |
426 }); | 409 }); |
427 on(horiz, "scroll", function() { | 410 on(horiz, "scroll", function() { |
428 if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); | 411 if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); |
429 }); | 412 }); |
430 | 413 |
431 this.checkedOverlay = false; | 414 this.checkedZeroWidth = false; |
432 // Need to set a minimum width to see the scrollbar on IE7 (but must not set
it on IE8). | 415 // Need to set a minimum width to see the scrollbar on IE7 (but must not set
it on IE8). |
433 if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWi
dth = "18px"; | 416 if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWi
dth = "18px"; |
434 } | 417 } |
435 | 418 |
436 NativeScrollbars.prototype = copyObj({ | 419 NativeScrollbars.prototype = copyObj({ |
437 update: function(measure) { | 420 update: function(measure) { |
438 var needsH = measure.scrollWidth > measure.clientWidth + 1; | 421 var needsH = measure.scrollWidth > measure.clientWidth + 1; |
439 var needsV = measure.scrollHeight > measure.clientHeight + 1; | 422 var needsV = measure.scrollHeight > measure.clientHeight + 1; |
440 var sWidth = measure.nativeBarWidth; | 423 var sWidth = measure.nativeBarWidth; |
441 | 424 |
(...skipping 14 matching lines...) Expand all Loading... |
456 this.horiz.style.right = needsV ? sWidth + "px" : "0"; | 439 this.horiz.style.right = needsV ? sWidth + "px" : "0"; |
457 this.horiz.style.left = measure.barLeft + "px"; | 440 this.horiz.style.left = measure.barLeft + "px"; |
458 var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth
: 0); | 441 var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth
: 0); |
459 this.horiz.firstChild.style.width = | 442 this.horiz.firstChild.style.width = |
460 (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; | 443 (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; |
461 } else { | 444 } else { |
462 this.horiz.style.display = ""; | 445 this.horiz.style.display = ""; |
463 this.horiz.firstChild.style.width = "0"; | 446 this.horiz.firstChild.style.width = "0"; |
464 } | 447 } |
465 | 448 |
466 if (!this.checkedOverlay && measure.clientHeight > 0) { | 449 if (!this.checkedZeroWidth && measure.clientHeight > 0) { |
467 if (sWidth == 0) this.overlayHack(); | 450 if (sWidth == 0) this.zeroWidthHack(); |
468 this.checkedOverlay = true; | 451 this.checkedZeroWidth = true; |
469 } | 452 } |
470 | 453 |
471 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; | 454 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; |
472 }, | 455 }, |
473 setScrollLeft: function(pos) { | 456 setScrollLeft: function(pos) { |
474 if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; | 457 if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; |
| 458 if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHor
iz); |
475 }, | 459 }, |
476 setScrollTop: function(pos) { | 460 setScrollTop: function(pos) { |
477 if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; | 461 if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; |
| 462 if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert)
; |
478 }, | 463 }, |
479 overlayHack: function() { | 464 zeroWidthHack: function() { |
480 var w = mac && !mac_geMountainLion ? "12px" : "18px"; | 465 var w = mac && !mac_geMountainLion ? "12px" : "18px"; |
481 this.horiz.style.minHeight = this.vert.style.minWidth = w; | 466 this.horiz.style.height = this.vert.style.width = w; |
482 var self = this; | 467 this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"; |
483 var barMouseDown = function(e) { | 468 this.disableHoriz = new Delayed; |
484 if (e_target(e) != self.vert && e_target(e) != self.horiz) | 469 this.disableVert = new Delayed; |
485 operation(self.cm, onMouseDown)(e); | 470 }, |
486 }; | 471 enableZeroWidthBar: function(bar, delay) { |
487 on(this.vert, "mousedown", barMouseDown); | 472 bar.style.pointerEvents = "auto"; |
488 on(this.horiz, "mousedown", barMouseDown); | 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); |
489 }, | 486 }, |
490 clear: function() { | 487 clear: function() { |
491 var parent = this.horiz.parentNode; | 488 var parent = this.horiz.parentNode; |
492 parent.removeChild(this.horiz); | 489 parent.removeChild(this.horiz); |
493 parent.removeChild(this.vert); | 490 parent.removeChild(this.vert); |
494 } | 491 } |
495 }, NativeScrollbars.prototype); | 492 }, NativeScrollbars.prototype); |
496 | 493 |
497 function NullScrollbars() {} | 494 function NullScrollbars() {} |
498 | 495 |
499 NullScrollbars.prototype = copyObj({ | 496 NullScrollbars.prototype = copyObj({ |
500 update: function() { return {bottom: 0, right: 0}; }, | 497 update: function() { return {bottom: 0, right: 0}; }, |
501 setScrollLeft: function() {}, | 498 setScrollLeft: function() {}, |
502 setScrollTop: function() {}, | 499 setScrollTop: function() {}, |
503 clear: function() {} | 500 clear: function() {} |
504 }, NullScrollbars.prototype); | 501 }, NullScrollbars.prototype); |
505 | 502 |
506 CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbar
s}; | 503 CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbar
s}; |
507 | 504 |
508 function initScrollbars(cm) { | 505 function initScrollbars(cm) { |
509 if (cm.display.scrollbars) { | 506 if (cm.display.scrollbars) { |
510 cm.display.scrollbars.clear(); | 507 cm.display.scrollbars.clear(); |
511 if (cm.display.scrollbars.addClass) | 508 if (cm.display.scrollbars.addClass) |
512 rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); | 509 rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); |
513 } | 510 } |
514 | 511 |
515 cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarSt
yle](function(node) { | 512 cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarSt
yle](function(node) { |
516 cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); | 513 cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); |
| 514 // Prevent clicks in the scrollbars from killing focus |
517 on(node, "mousedown", function() { | 515 on(node, "mousedown", function() { |
518 if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); | 516 if (cm.state.focused) setTimeout(function() { cm.display.input.focus();
}, 0); |
519 }); | 517 }); |
520 node.setAttribute("not-content", "true"); | 518 node.setAttribute("cm-not-content", "true"); |
521 }, function(pos, axis) { | 519 }, function(pos, axis) { |
522 if (axis == "horizontal") setScrollLeft(cm, pos); | 520 if (axis == "horizontal") setScrollLeft(cm, pos); |
523 else setScrollTop(cm, pos); | 521 else setScrollTop(cm, pos); |
524 }, cm); | 522 }, cm); |
525 if (cm.display.scrollbars.addClass) | 523 if (cm.display.scrollbars.addClass) |
526 addClass(cm.display.wrapper, cm.display.scrollbars.addClass); | 524 addClass(cm.display.wrapper, cm.display.scrollbars.addClass); |
527 } | 525 } |
528 | 526 |
529 function updateScrollbars(cm, measure) { | 527 function updateScrollbars(cm, measure) { |
530 if (!measure) measure = measureForScrollbars(cm); | 528 if (!measure) measure = measureForScrollbars(cm); |
531 var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; | 529 var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; |
532 updateScrollbarsInner(cm, measure); | 530 updateScrollbarsInner(cm, measure); |
533 for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight !=
cm.display.barHeight; i++) { | 531 for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight !=
cm.display.barHeight; i++) { |
534 if (startWidth != cm.display.barWidth && cm.options.lineWrapping) | 532 if (startWidth != cm.display.barWidth && cm.options.lineWrapping) |
535 updateHeightsInViewport(cm); | 533 updateHeightsInViewport(cm); |
536 updateScrollbarsInner(cm, measureForScrollbars(cm)); | 534 updateScrollbarsInner(cm, measureForScrollbars(cm)); |
537 startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; | 535 startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; |
538 } | 536 } |
539 } | 537 } |
540 | 538 |
541 // Re-synchronize the fake scrollbars with the actual size of the | 539 // Re-synchronize the fake scrollbars with the actual size of the |
542 // content. | 540 // content. |
543 function updateScrollbarsInner(cm, measure) { | 541 function updateScrollbarsInner(cm, measure) { |
544 var d = cm.display; | 542 var d = cm.display; |
545 var sizes = d.scrollbars.update(measure); | 543 var sizes = d.scrollbars.update(measure); |
546 | 544 |
547 d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; | 545 d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; |
548 d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; | 546 d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; |
| 547 d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" |
549 | 548 |
550 if (sizes.right && sizes.bottom) { | 549 if (sizes.right && sizes.bottom) { |
551 d.scrollbarFiller.style.display = "block"; | 550 d.scrollbarFiller.style.display = "block"; |
552 d.scrollbarFiller.style.height = sizes.bottom + "px"; | 551 d.scrollbarFiller.style.height = sizes.bottom + "px"; |
553 d.scrollbarFiller.style.width = sizes.right + "px"; | 552 d.scrollbarFiller.style.width = sizes.right + "px"; |
554 } else d.scrollbarFiller.style.display = ""; | 553 } else d.scrollbarFiller.style.display = ""; |
555 if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixe
dGutter) { | 554 if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixe
dGutter) { |
556 d.gutterFiller.style.display = "block"; | 555 d.gutterFiller.style.display = "block"; |
557 d.gutterFiller.style.height = sizes.bottom + "px"; | 556 d.gutterFiller.style.height = sizes.bottom + "px"; |
558 d.gutterFiller.style.width = measure.gutterWidth + "px"; | 557 d.gutterFiller.style.width = measure.gutterWidth + "px"; |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
607 // size for the current document size. Returns true when an update | 606 // size for the current document size. Returns true when an update |
608 // is needed. | 607 // is needed. |
609 function maybeUpdateLineNumberWidth(cm) { | 608 function maybeUpdateLineNumberWidth(cm) { |
610 if (!cm.options.lineNumbers) return false; | 609 if (!cm.options.lineNumbers) return false; |
611 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1)
, display = cm.display; | 610 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1)
, display = cm.display; |
612 if (last.length != display.lineNumChars) { | 611 if (last.length != display.lineNumChars) { |
613 var test = display.measure.appendChild(elt("div", [elt("div", last)], | 612 var test = display.measure.appendChild(elt("div", [elt("div", last)], |
614 "CodeMirror-linenumber CodeMirr
or-gutter-elt")); | 613 "CodeMirror-linenumber CodeMirr
or-gutter-elt")); |
615 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - inn
erW; | 614 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - inn
erW; |
616 display.lineGutter.style.width = ""; | 615 display.lineGutter.style.width = ""; |
617 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidt
h - padding); | 616 display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidt
h - padding) + 1; |
618 display.lineNumWidth = display.lineNumInnerWidth + padding; | 617 display.lineNumWidth = display.lineNumInnerWidth + padding; |
619 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; | 618 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; |
620 display.lineGutter.style.width = display.lineNumWidth + "px"; | 619 display.lineGutter.style.width = display.lineNumWidth + "px"; |
621 updateGutterSpace(cm); | 620 updateGutterSpace(cm); |
622 return true; | 621 return true; |
623 } | 622 } |
624 return false; | 623 return false; |
625 } | 624 } |
626 | 625 |
627 function lineNumberFor(options, i) { | 626 function lineNumberFor(options, i) { |
(...skipping 14 matching lines...) Expand all Loading... |
642 | 641 |
643 this.viewport = viewport; | 642 this.viewport = viewport; |
644 // Store some values that we'll need later (but don't want to force a relayo
ut for) | 643 // Store some values that we'll need later (but don't want to force a relayo
ut for) |
645 this.visible = visibleLines(display, cm.doc, viewport); | 644 this.visible = visibleLines(display, cm.doc, viewport); |
646 this.editorIsHidden = !display.wrapper.offsetWidth; | 645 this.editorIsHidden = !display.wrapper.offsetWidth; |
647 this.wrapperHeight = display.wrapper.clientHeight; | 646 this.wrapperHeight = display.wrapper.clientHeight; |
648 this.wrapperWidth = display.wrapper.clientWidth; | 647 this.wrapperWidth = display.wrapper.clientWidth; |
649 this.oldDisplayWidth = displayWidth(cm); | 648 this.oldDisplayWidth = displayWidth(cm); |
650 this.force = force; | 649 this.force = force; |
651 this.dims = getDimensions(cm); | 650 this.dims = getDimensions(cm); |
| 651 this.events = []; |
652 } | 652 } |
653 | 653 |
| 654 DisplayUpdate.prototype.signal = function(emitter, type) { |
| 655 if (hasHandler(emitter, type)) |
| 656 this.events.push(arguments); |
| 657 }; |
| 658 DisplayUpdate.prototype.finish = function() { |
| 659 for (var i = 0; i < this.events.length; i++) |
| 660 signal.apply(null, this.events[i]); |
| 661 }; |
| 662 |
654 function maybeClipScrollbars(cm) { | 663 function maybeClipScrollbars(cm) { |
655 var display = cm.display; | 664 var display = cm.display; |
656 if (!display.scrollbarsClipped && display.scroller.offsetWidth) { | 665 if (!display.scrollbarsClipped && display.scroller.offsetWidth) { |
657 display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.c
lientWidth; | 666 display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.c
lientWidth; |
658 display.heightForcer.style.height = scrollGap(cm) + "px"; | 667 display.heightForcer.style.height = scrollGap(cm) + "px"; |
659 display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; | 668 display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; |
660 display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; | 669 display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; |
661 display.scrollbarsClipped = true; | 670 display.scrollbarsClipped = true; |
662 } | 671 } |
663 } | 672 } |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
717 if (toUpdate > 4) display.lineDiv.style.display = ""; | 726 if (toUpdate > 4) display.lineDiv.style.display = ""; |
718 display.renderedView = display.view; | 727 display.renderedView = display.view; |
719 // There might have been a widget with a focused element that got | 728 // There might have been a widget with a focused element that got |
720 // hidden or updated, if so re-focus it. | 729 // hidden or updated, if so re-focus it. |
721 if (focused && activeElt() != focused && focused.offsetHeight) focused.focus
(); | 730 if (focused && activeElt() != focused && focused.offsetHeight) focused.focus
(); |
722 | 731 |
723 // Prevent selection and cursors from interfering with the scroll | 732 // Prevent selection and cursors from interfering with the scroll |
724 // width and height. | 733 // width and height. |
725 removeChildren(display.cursorDiv); | 734 removeChildren(display.cursorDiv); |
726 removeChildren(display.selectionDiv); | 735 removeChildren(display.selectionDiv); |
727 display.gutters.style.height = 0; | 736 display.gutters.style.height = display.sizer.style.minHeight = 0; |
728 | 737 |
729 if (different) { | 738 if (different) { |
730 display.lastWrapHeight = update.wrapperHeight; | 739 display.lastWrapHeight = update.wrapperHeight; |
731 display.lastWrapWidth = update.wrapperWidth; | 740 display.lastWrapWidth = update.wrapperWidth; |
732 startWorker(cm, 400); | 741 startWorker(cm, 400); |
733 } | 742 } |
734 | 743 |
735 display.updateLineNumbers = null; | 744 display.updateLineNumbers = null; |
736 | 745 |
737 return true; | 746 return true; |
738 } | 747 } |
739 | 748 |
740 function postUpdateDisplay(cm, update) { | 749 function postUpdateDisplay(cm, update) { |
741 var force = update.force, viewport = update.viewport; | 750 var viewport = update.viewport; |
| 751 |
742 for (var first = true;; first = false) { | 752 for (var first = true;; first = false) { |
743 if (first && cm.options.lineWrapping && update.oldDisplayWidth != displayW
idth(cm)) { | 753 if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displa
yWidth(cm)) { |
744 force = true; | |
745 } else { | |
746 force = false; | |
747 // Clip forced viewport to actual scrollable area. | 754 // Clip forced viewport to actual scrollable area. |
748 if (viewport && viewport.top != null) | 755 if (viewport && viewport.top != null) |
749 viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - di
splayHeight(cm), viewport.top)}; | 756 viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - di
splayHeight(cm), viewport.top)}; |
750 // Updated line heights might result in the drawn area not | 757 // Updated line heights might result in the drawn area not |
751 // actually covering the viewport. Keep looping until it does. | 758 // actually covering the viewport. Keep looping until it does. |
752 update.visible = visibleLines(cm.display, cm.doc, viewport); | 759 update.visible = visibleLines(cm.display, cm.doc, viewport); |
753 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= c
m.display.viewTo) | 760 if (update.visible.from >= cm.display.viewFrom && update.visible.to <= c
m.display.viewTo) |
754 break; | 761 break; |
755 } | 762 } |
756 if (!updateDisplayIfNeeded(cm, update)) break; | 763 if (!updateDisplayIfNeeded(cm, update)) break; |
757 updateHeightsInViewport(cm); | 764 updateHeightsInViewport(cm); |
758 var barMeasure = measureForScrollbars(cm); | 765 var barMeasure = measureForScrollbars(cm); |
759 updateSelection(cm); | 766 updateSelection(cm); |
| 767 updateScrollbars(cm, barMeasure); |
760 setDocumentHeight(cm, barMeasure); | 768 setDocumentHeight(cm, barMeasure); |
761 updateScrollbars(cm, barMeasure); | |
762 } | 769 } |
763 | 770 |
764 signalLater(cm, "update", cm); | 771 update.signal(cm, "update", cm); |
765 if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo
!= cm.display.reportedViewTo) { | 772 if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo
!= cm.display.reportedViewTo) { |
766 signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.view
To); | 773 update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.vi
ewTo); |
767 cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedView
To = cm.display.viewTo; | 774 cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedView
To = cm.display.viewTo; |
768 } | 775 } |
769 } | 776 } |
770 | 777 |
771 function updateDisplaySimple(cm, viewport) { | 778 function updateDisplaySimple(cm, viewport) { |
772 var update = new DisplayUpdate(cm, viewport); | 779 var update = new DisplayUpdate(cm, viewport); |
773 if (updateDisplayIfNeeded(cm, update)) { | 780 if (updateDisplayIfNeeded(cm, update)) { |
774 updateHeightsInViewport(cm); | 781 updateHeightsInViewport(cm); |
775 postUpdateDisplay(cm, update); | 782 postUpdateDisplay(cm, update); |
776 var barMeasure = measureForScrollbars(cm); | 783 var barMeasure = measureForScrollbars(cm); |
777 updateSelection(cm); | 784 updateSelection(cm); |
| 785 updateScrollbars(cm, barMeasure); |
778 setDocumentHeight(cm, barMeasure); | 786 setDocumentHeight(cm, barMeasure); |
779 updateScrollbars(cm, barMeasure); | 787 update.finish(); |
780 } | 788 } |
781 } | 789 } |
782 | 790 |
783 function setDocumentHeight(cm, measure) { | 791 function setDocumentHeight(cm, measure) { |
784 cm.display.sizer.style.minHeight = measure.docHeight + "px"; | 792 cm.display.sizer.style.minHeight = measure.docHeight + "px"; |
785 var total = measure.docHeight + cm.display.barHeight; | 793 cm.display.heightForcer.style.top = measure.docHeight + "px"; |
786 cm.display.heightForcer.style.top = total + "px"; | 794 cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight
+ scrollGap(cm)) + "px"; |
787 cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.cl
ientHeight) + "px"; | |
788 } | 795 } |
789 | 796 |
790 // Read the actual heights of the rendered lines, and update their | 797 // Read the actual heights of the rendered lines, and update their |
791 // stored heights to match. | 798 // stored heights to match. |
792 function updateHeightsInViewport(cm) { | 799 function updateHeightsInViewport(cm) { |
793 var display = cm.display; | 800 var display = cm.display; |
794 var prevBottom = display.lineDiv.offsetTop; | 801 var prevBottom = display.lineDiv.offsetTop; |
795 for (var i = 0; i < display.view.length; i++) { | 802 for (var i = 0; i < display.view.length; i++) { |
796 var cur = display.view[i], height; | 803 var cur = display.view[i], height; |
797 if (cur.hidden) continue; | 804 if (cur.hidden) continue; |
(...skipping 13 matching lines...) Expand all Loading... |
811 if (cur.rest) for (var j = 0; j < cur.rest.length; j++) | 818 if (cur.rest) for (var j = 0; j < cur.rest.length; j++) |
812 updateWidgetHeight(cur.rest[j]); | 819 updateWidgetHeight(cur.rest[j]); |
813 } | 820 } |
814 } | 821 } |
815 } | 822 } |
816 | 823 |
817 // Read and store the height of line widgets associated with the | 824 // Read and store the height of line widgets associated with the |
818 // given line. | 825 // given line. |
819 function updateWidgetHeight(line) { | 826 function updateWidgetHeight(line) { |
820 if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) | 827 if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) |
821 line.widgets[i].height = line.widgets[i].node.offsetHeight; | 828 line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight; |
822 } | 829 } |
823 | 830 |
824 // Do a bulk-read of the DOM positions and sizes needed to draw the | 831 // Do a bulk-read of the DOM positions and sizes needed to draw the |
825 // view, so that we don't interleave reading and writing to the DOM. | 832 // view, so that we don't interleave reading and writing to the DOM. |
826 function getDimensions(cm) { | 833 function getDimensions(cm) { |
827 var d = cm.display, left = {}, width = {}; | 834 var d = cm.display, left = {}, width = {}; |
828 var gutterLeft = d.gutters.clientLeft; | 835 var gutterLeft = d.gutters.clientLeft; |
829 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { | 836 for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { |
830 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; | 837 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; |
831 width[cm.options.gutters[i]] = n.clientWidth; | 838 width[cm.options.gutters[i]] = n.clientWidth; |
(...skipping 22 matching lines...) Expand all Loading... |
854 node.parentNode.removeChild(node); | 861 node.parentNode.removeChild(node); |
855 return next; | 862 return next; |
856 } | 863 } |
857 | 864 |
858 var view = display.view, lineN = display.viewFrom; | 865 var view = display.view, lineN = display.viewFrom; |
859 // Loop over the elements in the view, syncing cur (the DOM nodes | 866 // Loop over the elements in the view, syncing cur (the DOM nodes |
860 // in display.lineDiv) with the view as we go. | 867 // in display.lineDiv) with the view as we go. |
861 for (var i = 0; i < view.length; i++) { | 868 for (var i = 0; i < view.length; i++) { |
862 var lineView = view[i]; | 869 var lineView = view[i]; |
863 if (lineView.hidden) { | 870 if (lineView.hidden) { |
864 } else if (!lineView.node) { // Not drawn yet | 871 } else if (!lineView.node || lineView.node.parentNode != container) { // N
ot drawn yet |
865 var node = buildLineElement(cm, lineView, lineN, dims); | 872 var node = buildLineElement(cm, lineView, lineN, dims); |
866 container.insertBefore(node, cur); | 873 container.insertBefore(node, cur); |
867 } else { // Already drawn | 874 } else { // Already drawn |
868 while (cur != lineView.node) cur = rm(cur); | 875 while (cur != lineView.node) cur = rm(cur); |
869 var updateNumber = lineNumbers && updateNumbersFrom != null && | 876 var updateNumber = lineNumbers && updateNumbersFrom != null && |
870 updateNumbersFrom <= lineN && lineView.lineNumber; | 877 updateNumbersFrom <= lineN && lineView.lineNumber; |
871 if (lineView.changes) { | 878 if (lineView.changes) { |
872 if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; | 879 if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; |
873 updateLineForChanges(cm, lineView, lineN, dims); | 880 updateLineForChanges(cm, lineView, lineN, dims); |
874 } | 881 } |
(...skipping 10 matching lines...) Expand all Loading... |
885 | 892 |
886 // When an aspect of a line changes, a string is added to | 893 // When an aspect of a line changes, a string is added to |
887 // lineView.changes. This updates the relevant part of the line's | 894 // lineView.changes. This updates the relevant part of the line's |
888 // DOM structure. | 895 // DOM structure. |
889 function updateLineForChanges(cm, lineView, lineN, dims) { | 896 function updateLineForChanges(cm, lineView, lineN, dims) { |
890 for (var j = 0; j < lineView.changes.length; j++) { | 897 for (var j = 0; j < lineView.changes.length; j++) { |
891 var type = lineView.changes[j]; | 898 var type = lineView.changes[j]; |
892 if (type == "text") updateLineText(cm, lineView); | 899 if (type == "text") updateLineText(cm, lineView); |
893 else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); | 900 else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); |
894 else if (type == "class") updateLineClasses(lineView); | 901 else if (type == "class") updateLineClasses(lineView); |
895 else if (type == "widget") updateLineWidgets(lineView, dims); | 902 else if (type == "widget") updateLineWidgets(cm, lineView, dims); |
896 } | 903 } |
897 lineView.changes = null; | 904 lineView.changes = null; |
898 } | 905 } |
899 | 906 |
900 // Lines with gutter elements, widgets or a background class need to | 907 // Lines with gutter elements, widgets or a background class need to |
901 // be wrapped, and have the extra elements added to the wrapper div | 908 // be wrapped, and have the extra elements added to the wrapper div |
902 function ensureLineWrapped(lineView) { | 909 function ensureLineWrapped(lineView) { |
903 if (lineView.node == lineView.text) { | 910 if (lineView.node == lineView.text) { |
904 lineView.node = elt("div", null, null, "position: relative"); | 911 lineView.node = elt("div", null, null, "position: relative"); |
905 if (lineView.text.parentNode) | 912 if (lineView.text.parentNode) |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
960 lineView.node.className = ""; | 967 lineView.node.className = ""; |
961 var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.li
ne.textClass || "") : lineView.line.textClass; | 968 var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.li
ne.textClass || "") : lineView.line.textClass; |
962 lineView.text.className = textClass || ""; | 969 lineView.text.className = textClass || ""; |
963 } | 970 } |
964 | 971 |
965 function updateLineGutter(cm, lineView, lineN, dims) { | 972 function updateLineGutter(cm, lineView, lineN, dims) { |
966 if (lineView.gutter) { | 973 if (lineView.gutter) { |
967 lineView.node.removeChild(lineView.gutter); | 974 lineView.node.removeChild(lineView.gutter); |
968 lineView.gutter = null; | 975 lineView.gutter = null; |
969 } | 976 } |
| 977 if (lineView.gutterBackground) { |
| 978 lineView.node.removeChild(lineView.gutterBackground); |
| 979 lineView.gutterBackground = null; |
| 980 } |
| 981 if (lineView.line.gutterClass) { |
| 982 var wrap = ensureLineWrapped(lineView); |
| 983 lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background
" + lineView.line.gutterClass, |
| 984 "left: " + (cm.options.fixedGutter ? dims.
fixedPos : -dims.gutterTotalWidth) + |
| 985 "px; width: " + dims.gutterTotalWidth + "p
x"); |
| 986 wrap.insertBefore(lineView.gutterBackground, lineView.text); |
| 987 } |
970 var markers = lineView.line.gutterMarkers; | 988 var markers = lineView.line.gutterMarkers; |
971 if (cm.options.lineNumbers || markers) { | 989 if (cm.options.lineNumbers || markers) { |
972 var wrap = ensureLineWrapped(lineView); | 990 var wrap = ensureLineWrapped(lineView); |
973 var gutterWrap = lineView.gutter = | 991 var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wra
pper", "left: " + |
974 wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "left: "
+ | 992 (cm.options.fixedGutter ? dims.fixe
dPos : -dims.gutterTotalWidth) + "px"); |
975 (cm.options.fixedGutter ? dims.fixedPos : -dims.gu
tterTotalWidth) + | 993 cm.display.input.setUneditable(gutterWrap); |
976 "px; width: " + dims.gutterTotalWidth + "px"), | 994 wrap.insertBefore(gutterWrap, lineView.text); |
977 lineView.text); | |
978 if (lineView.line.gutterClass) | 995 if (lineView.line.gutterClass) |
979 gutterWrap.className += " " + lineView.line.gutterClass; | 996 gutterWrap.className += " " + lineView.line.gutterClass; |
980 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumber
s"])) | 997 if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumber
s"])) |
981 lineView.lineNumber = gutterWrap.appendChild( | 998 lineView.lineNumber = gutterWrap.appendChild( |
982 elt("div", lineNumberFor(cm.options, lineN), | 999 elt("div", lineNumberFor(cm.options, lineN), |
983 "CodeMirror-linenumber CodeMirror-gutter-elt", | 1000 "CodeMirror-linenumber CodeMirror-gutter-elt", |
984 "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width:
" | 1001 "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width:
" |
985 + cm.display.lineNumInnerWidth + "px")); | 1002 + cm.display.lineNumInnerWidth + "px")); |
986 if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { | 1003 if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { |
987 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && ma
rkers[id]; | 1004 var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && ma
rkers[id]; |
988 if (found) | 1005 if (found) |
989 gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "l
eft: " + | 1006 gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "l
eft: " + |
990 dims.gutterLeft[id] + "px; width: " + dims.
gutterWidth[id] + "px")); | 1007 dims.gutterLeft[id] + "px; width: " + dims.
gutterWidth[id] + "px")); |
991 } | 1008 } |
992 } | 1009 } |
993 } | 1010 } |
994 | 1011 |
995 function updateLineWidgets(lineView, dims) { | 1012 function updateLineWidgets(cm, lineView, dims) { |
996 if (lineView.alignable) lineView.alignable = null; | 1013 if (lineView.alignable) lineView.alignable = null; |
997 for (var node = lineView.node.firstChild, next; node; node = next) { | 1014 for (var node = lineView.node.firstChild, next; node; node = next) { |
998 var next = node.nextSibling; | 1015 var next = node.nextSibling; |
999 if (node.className == "CodeMirror-linewidget") | 1016 if (node.className == "CodeMirror-linewidget") |
1000 lineView.node.removeChild(node); | 1017 lineView.node.removeChild(node); |
1001 } | 1018 } |
1002 insertLineWidgets(lineView, dims); | 1019 insertLineWidgets(cm, lineView, dims); |
1003 } | 1020 } |
1004 | 1021 |
1005 // Build a line's DOM representation from scratch | 1022 // Build a line's DOM representation from scratch |
1006 function buildLineElement(cm, lineView, lineN, dims) { | 1023 function buildLineElement(cm, lineView, lineN, dims) { |
1007 var built = getLineContent(cm, lineView); | 1024 var built = getLineContent(cm, lineView); |
1008 lineView.text = lineView.node = built.pre; | 1025 lineView.text = lineView.node = built.pre; |
1009 if (built.bgClass) lineView.bgClass = built.bgClass; | 1026 if (built.bgClass) lineView.bgClass = built.bgClass; |
1010 if (built.textClass) lineView.textClass = built.textClass; | 1027 if (built.textClass) lineView.textClass = built.textClass; |
1011 | 1028 |
1012 updateLineClasses(lineView); | 1029 updateLineClasses(lineView); |
1013 updateLineGutter(cm, lineView, lineN, dims); | 1030 updateLineGutter(cm, lineView, lineN, dims); |
1014 insertLineWidgets(lineView, dims); | 1031 insertLineWidgets(cm, lineView, dims); |
1015 return lineView.node; | 1032 return lineView.node; |
1016 } | 1033 } |
1017 | 1034 |
1018 // A lineView may contain multiple logical lines (when merged by | 1035 // A lineView may contain multiple logical lines (when merged by |
1019 // collapsed spans). The widgets for all of them need to be drawn. | 1036 // collapsed spans). The widgets for all of them need to be drawn. |
1020 function insertLineWidgets(lineView, dims) { | 1037 function insertLineWidgets(cm, lineView, dims) { |
1021 insertLineWidgetsFor(lineView.line, lineView, dims, true); | 1038 insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); |
1022 if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) | 1039 if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) |
1023 insertLineWidgetsFor(lineView.rest[i], lineView, dims, false); | 1040 insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); |
1024 } | 1041 } |
1025 | 1042 |
1026 function insertLineWidgetsFor(line, lineView, dims, allowAbove) { | 1043 function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { |
1027 if (!line.widgets) return; | 1044 if (!line.widgets) return; |
1028 var wrap = ensureLineWrapped(lineView); | 1045 var wrap = ensureLineWrapped(lineView); |
1029 for (var i = 0, ws = line.widgets; i < ws.length; ++i) { | 1046 for (var i = 0, ws = line.widgets; i < ws.length; ++i) { |
1030 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidge
t"); | 1047 var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidge
t"); |
1031 if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true
"); | 1048 if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true
"); |
1032 positionLineWidget(widget, node, lineView, dims); | 1049 positionLineWidget(widget, node, lineView, dims); |
| 1050 cm.display.input.setUneditable(node); |
1033 if (allowAbove && widget.above) | 1051 if (allowAbove && widget.above) |
1034 wrap.insertBefore(node, lineView.gutter || lineView.text); | 1052 wrap.insertBefore(node, lineView.gutter || lineView.text); |
1035 else | 1053 else |
1036 wrap.appendChild(node); | 1054 wrap.appendChild(node); |
1037 signalLater(widget, "redraw"); | 1055 signalLater(widget, "redraw"); |
1038 } | 1056 } |
1039 } | 1057 } |
1040 | 1058 |
1041 function positionLineWidget(widget, node, lineView, dims) { | 1059 function positionLineWidget(widget, node, lineView, dims) { |
1042 if (widget.noHScroll) { | 1060 if (widget.noHScroll) { |
(...skipping 22 matching lines...) Expand all Loading... |
1065 }; | 1083 }; |
1066 | 1084 |
1067 // Compare two positions, return 0 if they are the same, a negative | 1085 // Compare two positions, return 0 if they are the same, a negative |
1068 // number when a is less, and a positive number otherwise. | 1086 // number when a is less, and a positive number otherwise. |
1069 var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch
- b.ch; }; | 1087 var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch
- b.ch; }; |
1070 | 1088 |
1071 function copyPos(x) {return Pos(x.line, x.ch);} | 1089 function copyPos(x) {return Pos(x.line, x.ch);} |
1072 function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } | 1090 function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } |
1073 function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } | 1091 function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } |
1074 | 1092 |
| 1093 // INPUT HANDLING |
| 1094 |
| 1095 function ensureFocus(cm) { |
| 1096 if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } |
| 1097 } |
| 1098 |
| 1099 // This will be set to a {lineWise: bool, text: [string]} object, so |
| 1100 // that, when pasting, we know what kind of selections the copied |
| 1101 // text was made out of. |
| 1102 var lastCopied = null; |
| 1103 |
| 1104 function applyTextInput(cm, inserted, deleted, sel, origin) { |
| 1105 var doc = cm.doc; |
| 1106 cm.display.shift = false; |
| 1107 if (!sel) sel = doc.sel; |
| 1108 |
| 1109 var paste = cm.state.pasteIncoming || origin == "paste"; |
| 1110 var textLines = doc.splitLines(inserted), multiPaste = null |
| 1111 // When pasing N lines into N selections, insert one line per selection |
| 1112 if (paste && sel.ranges.length > 1) { |
| 1113 if (lastCopied && lastCopied.text.join("\n") == inserted) { |
| 1114 if (sel.ranges.length % lastCopied.text.length == 0) { |
| 1115 multiPaste = []; |
| 1116 for (var i = 0; i < lastCopied.text.length; i++) |
| 1117 multiPaste.push(doc.splitLines(lastCopied.text[i])); |
| 1118 } |
| 1119 } else if (textLines.length == sel.ranges.length) { |
| 1120 multiPaste = map(textLines, function(l) { return [l]; }); |
| 1121 } |
| 1122 } |
| 1123 |
| 1124 // Normal behavior is to insert the new text into every selection |
| 1125 for (var i = sel.ranges.length - 1; i >= 0; i--) { |
| 1126 var range = sel.ranges[i]; |
| 1127 var from = range.from(), to = range.to(); |
| 1128 if (range.empty()) { |
| 1129 if (deleted && deleted > 0) // Handle deletion |
| 1130 from = Pos(from.line, from.ch - deleted); |
| 1131 else if (cm.state.overwrite && !paste) // Handle overwrite |
| 1132 to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch +
lst(textLines).length)); |
| 1133 else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n")
== inserted) |
| 1134 from = to = Pos(from.line, 0) |
| 1135 } |
| 1136 var updateInput = cm.curOp.updateInput; |
| 1137 var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % m
ultiPaste.length] : textLines, |
| 1138 origin: origin || (paste ? "paste" : cm.state.cutIncomi
ng ? "cut" : "+input")}; |
| 1139 makeChange(cm.doc, changeEvent); |
| 1140 signalLater(cm, "inputRead", cm, changeEvent); |
| 1141 } |
| 1142 if (inserted && !paste) |
| 1143 triggerElectric(cm, inserted); |
| 1144 |
| 1145 ensureCursorVisible(cm); |
| 1146 cm.curOp.updateInput = updateInput; |
| 1147 cm.curOp.typing = true; |
| 1148 cm.state.pasteIncoming = cm.state.cutIncoming = false; |
| 1149 } |
| 1150 |
| 1151 function handlePaste(e, cm) { |
| 1152 var pasted = e.clipboardData && e.clipboardData.getData("text/plain"); |
| 1153 if (pasted) { |
| 1154 e.preventDefault(); |
| 1155 if (!cm.isReadOnly() && !cm.options.disableInput) |
| 1156 runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); }
); |
| 1157 return true; |
| 1158 } |
| 1159 } |
| 1160 |
| 1161 function triggerElectric(cm, inserted) { |
| 1162 // When an 'electric' character is inserted, immediately trigger a reindent |
| 1163 if (!cm.options.electricChars || !cm.options.smartIndent) return; |
| 1164 var sel = cm.doc.sel; |
| 1165 |
| 1166 for (var i = sel.ranges.length - 1; i >= 0; i--) { |
| 1167 var range = sel.ranges[i]; |
| 1168 if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head
.line)) continue; |
| 1169 var mode = cm.getModeAt(range.head); |
| 1170 var indented = false; |
| 1171 if (mode.electricChars) { |
| 1172 for (var j = 0; j < mode.electricChars.length; j++) |
| 1173 if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { |
| 1174 indented = indentLine(cm, range.head.line, "smart"); |
| 1175 break; |
| 1176 } |
| 1177 } else if (mode.electricInput) { |
| 1178 if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(
0, range.head.ch))) |
| 1179 indented = indentLine(cm, range.head.line, "smart"); |
| 1180 } |
| 1181 if (indented) signalLater(cm, "electricInput", cm, range.head.line); |
| 1182 } |
| 1183 } |
| 1184 |
| 1185 function copyableRanges(cm) { |
| 1186 var text = [], ranges = []; |
| 1187 for (var i = 0; i < cm.doc.sel.ranges.length; i++) { |
| 1188 var line = cm.doc.sel.ranges[i].head.line; |
| 1189 var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; |
| 1190 ranges.push(lineRange); |
| 1191 text.push(cm.getRange(lineRange.anchor, lineRange.head)); |
| 1192 } |
| 1193 return {text: text, ranges: ranges}; |
| 1194 } |
| 1195 |
| 1196 function disableBrowserMagic(field) { |
| 1197 field.setAttribute("autocorrect", "off"); |
| 1198 field.setAttribute("autocapitalize", "off"); |
| 1199 field.setAttribute("spellcheck", "false"); |
| 1200 } |
| 1201 |
| 1202 // TEXTAREA INPUT STYLE |
| 1203 |
| 1204 function TextareaInput(cm) { |
| 1205 this.cm = cm; |
| 1206 // See input.poll and input.reset |
| 1207 this.prevInput = ""; |
| 1208 |
| 1209 // Flag that indicates whether we expect input to appear real soon |
| 1210 // now (after some event like 'keypress' or 'input') and are |
| 1211 // polling intensively. |
| 1212 this.pollingFast = false; |
| 1213 // Self-resetting timeout for the poller |
| 1214 this.polling = new Delayed(); |
| 1215 // Tracks when input.reset has punted to just putting a short |
| 1216 // string into the textarea instead of the full selection. |
| 1217 this.inaccurateSelection = false; |
| 1218 // Used to work around IE issue with selection being forgotten when focus mo
ves away from textarea |
| 1219 this.hasSelection = false; |
| 1220 this.composing = null; |
| 1221 }; |
| 1222 |
| 1223 function hiddenTextarea() { |
| 1224 var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padd
ing: 0; width: 1px; height: 1em; outline: none"); |
| 1225 var div = elt("div", [te], null, "overflow: hidden; position: relative; widt
h: 3px; height: 0px;"); |
| 1226 // The textarea is kept positioned near the cursor to prevent the |
| 1227 // fact that it'll be scrolled into view on input from scrolling |
| 1228 // our fake cursor out of view. On webkit, when wrap=off, paste is |
| 1229 // very slow. So make the area wide instead. |
| 1230 if (webkit) te.style.width = "1000px"; |
| 1231 else te.setAttribute("wrap", "off"); |
| 1232 // If border: 0; -- iOS fails to open keyboard (issue #1287) |
| 1233 if (ios) te.style.border = "1px solid black"; |
| 1234 disableBrowserMagic(te); |
| 1235 return div; |
| 1236 } |
| 1237 |
| 1238 TextareaInput.prototype = copyObj({ |
| 1239 init: function(display) { |
| 1240 var input = this, cm = this.cm; |
| 1241 |
| 1242 // Wraps and hides input textarea |
| 1243 var div = this.wrapper = hiddenTextarea(); |
| 1244 // The semihidden textarea that is focused when the editor is |
| 1245 // focused, and receives input. |
| 1246 var te = this.textarea = div.firstChild; |
| 1247 display.wrapper.insertBefore(div, display.wrapper.firstChild); |
| 1248 |
| 1249 // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem
to work in iOS 8 anymore) |
| 1250 if (ios) te.style.width = "0px"; |
| 1251 |
| 1252 on(te, "input", function() { |
| 1253 if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = nu
ll; |
| 1254 input.poll(); |
| 1255 }); |
| 1256 |
| 1257 on(te, "paste", function(e) { |
| 1258 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return |
| 1259 |
| 1260 cm.state.pasteIncoming = true; |
| 1261 input.fastPoll(); |
| 1262 }); |
| 1263 |
| 1264 function prepareCopyCut(e) { |
| 1265 if (signalDOMEvent(cm, e)) return |
| 1266 if (cm.somethingSelected()) { |
| 1267 lastCopied = {lineWise: false, text: cm.getSelections()}; |
| 1268 if (input.inaccurateSelection) { |
| 1269 input.prevInput = ""; |
| 1270 input.inaccurateSelection = false; |
| 1271 te.value = lastCopied.text.join("\n"); |
| 1272 selectInput(te); |
| 1273 } |
| 1274 } else if (!cm.options.lineWiseCopyCut) { |
| 1275 return; |
| 1276 } else { |
| 1277 var ranges = copyableRanges(cm); |
| 1278 lastCopied = {lineWise: true, text: ranges.text}; |
| 1279 if (e.type == "cut") { |
| 1280 cm.setSelections(ranges.ranges, null, sel_dontScroll); |
| 1281 } else { |
| 1282 input.prevInput = ""; |
| 1283 te.value = ranges.text.join("\n"); |
| 1284 selectInput(te); |
| 1285 } |
| 1286 } |
| 1287 if (e.type == "cut") cm.state.cutIncoming = true; |
| 1288 } |
| 1289 on(te, "cut", prepareCopyCut); |
| 1290 on(te, "copy", prepareCopyCut); |
| 1291 |
| 1292 on(display.scroller, "paste", function(e) { |
| 1293 if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return; |
| 1294 cm.state.pasteIncoming = true; |
| 1295 input.focus(); |
| 1296 }); |
| 1297 |
| 1298 // Prevent normal selection in the editor (we handle our own) |
| 1299 on(display.lineSpace, "selectstart", function(e) { |
| 1300 if (!eventInWidget(display, e)) e_preventDefault(e); |
| 1301 }); |
| 1302 |
| 1303 on(te, "compositionstart", function() { |
| 1304 var start = cm.getCursor("from"); |
| 1305 if (input.composing) input.composing.range.clear() |
| 1306 input.composing = { |
| 1307 start: start, |
| 1308 range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-
composing"}) |
| 1309 }; |
| 1310 }); |
| 1311 on(te, "compositionend", function() { |
| 1312 if (input.composing) { |
| 1313 input.poll(); |
| 1314 input.composing.range.clear(); |
| 1315 input.composing = null; |
| 1316 } |
| 1317 }); |
| 1318 }, |
| 1319 |
| 1320 prepareSelection: function() { |
| 1321 // Redraw the selection and/or cursor |
| 1322 var cm = this.cm, display = cm.display, doc = cm.doc; |
| 1323 var result = prepareSelection(cm); |
| 1324 |
| 1325 // Move the hidden textarea near the cursor to prevent scrolling artifacts |
| 1326 if (cm.options.moveInputWithCursor) { |
| 1327 var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); |
| 1328 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display
.lineDiv.getBoundingClientRect(); |
| 1329 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, |
| 1330 headPos.top + lineOff.top - wrapOff.
top)); |
| 1331 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, |
| 1332 headPos.left + lineOff.left - wrapO
ff.left)); |
| 1333 } |
| 1334 |
| 1335 return result; |
| 1336 }, |
| 1337 |
| 1338 showSelection: function(drawn) { |
| 1339 var cm = this.cm, display = cm.display; |
| 1340 removeChildrenAndAdd(display.cursorDiv, drawn.cursors); |
| 1341 removeChildrenAndAdd(display.selectionDiv, drawn.selection); |
| 1342 if (drawn.teTop != null) { |
| 1343 this.wrapper.style.top = drawn.teTop + "px"; |
| 1344 this.wrapper.style.left = drawn.teLeft + "px"; |
| 1345 } |
| 1346 }, |
| 1347 |
| 1348 // Reset the input to correspond to the selection (or to be empty, |
| 1349 // when not typing and nothing is selected) |
| 1350 reset: function(typing) { |
| 1351 if (this.contextMenuPending) return; |
| 1352 var minimal, selected, cm = this.cm, doc = cm.doc; |
| 1353 if (cm.somethingSelected()) { |
| 1354 this.prevInput = ""; |
| 1355 var range = doc.sel.primary(); |
| 1356 minimal = hasCopyEvent && |
| 1357 (range.to().line - range.from().line > 100 || (selected = cm.getSelect
ion()).length > 1000); |
| 1358 var content = minimal ? "-" : selected || cm.getSelection(); |
| 1359 this.textarea.value = content; |
| 1360 if (cm.state.focused) selectInput(this.textarea); |
| 1361 if (ie && ie_version >= 9) this.hasSelection = content; |
| 1362 } else if (!typing) { |
| 1363 this.prevInput = this.textarea.value = ""; |
| 1364 if (ie && ie_version >= 9) this.hasSelection = null; |
| 1365 } |
| 1366 this.inaccurateSelection = minimal; |
| 1367 }, |
| 1368 |
| 1369 getField: function() { return this.textarea; }, |
| 1370 |
| 1371 supportsTouch: function() { return false; }, |
| 1372 |
| 1373 focus: function() { |
| 1374 if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != t
his.textarea)) { |
| 1375 try { this.textarea.focus(); } |
| 1376 catch (e) {} // IE8 will throw if the textarea is display: none or not i
n DOM |
| 1377 } |
| 1378 }, |
| 1379 |
| 1380 blur: function() { this.textarea.blur(); }, |
| 1381 |
| 1382 resetPosition: function() { |
| 1383 this.wrapper.style.top = this.wrapper.style.left = 0; |
| 1384 }, |
| 1385 |
| 1386 receivedFocus: function() { this.slowPoll(); }, |
| 1387 |
| 1388 // Poll for input changes, using the normal rate of polling. This |
| 1389 // runs as long as the editor is focused. |
| 1390 slowPoll: function() { |
| 1391 var input = this; |
| 1392 if (input.pollingFast) return; |
| 1393 input.polling.set(this.cm.options.pollInterval, function() { |
| 1394 input.poll(); |
| 1395 if (input.cm.state.focused) input.slowPoll(); |
| 1396 }); |
| 1397 }, |
| 1398 |
| 1399 // When an event has just come in that is likely to add or change |
| 1400 // something in the input textarea, we poll faster, to ensure that |
| 1401 // the change appears on the screen quickly. |
| 1402 fastPoll: function() { |
| 1403 var missed = false, input = this; |
| 1404 input.pollingFast = true; |
| 1405 function p() { |
| 1406 var changed = input.poll(); |
| 1407 if (!changed && !missed) {missed = true; input.polling.set(60, p);} |
| 1408 else {input.pollingFast = false; input.slowPoll();} |
| 1409 } |
| 1410 input.polling.set(20, p); |
| 1411 }, |
| 1412 |
| 1413 // Read input from the textarea, and update the document to match. |
| 1414 // When something is selected, it is present in the textarea, and |
| 1415 // selected (unless it is huge, in which case a placeholder is |
| 1416 // used). When nothing is selected, the cursor sits after previously |
| 1417 // seen text (can be empty), which is stored in prevInput (we must |
| 1418 // not reset the textarea when typing, because that breaks IME). |
| 1419 poll: function() { |
| 1420 var cm = this.cm, input = this.textarea, prevInput = this.prevInput; |
| 1421 // Since this is called a *lot*, try to bail out as cheaply as |
| 1422 // possible when it is clear that nothing happened. hasSelection |
| 1423 // will be the case when there is a lot of text in the textarea, |
| 1424 // in which case reading its value would be expensive. |
| 1425 if (this.contextMenuPending || !cm.state.focused || |
| 1426 (hasSelection(input) && !prevInput && !this.composing) || |
| 1427 cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) |
| 1428 return false; |
| 1429 |
| 1430 var text = input.value; |
| 1431 // If nothing changed, bail. |
| 1432 if (text == prevInput && !cm.somethingSelected()) return false; |
| 1433 // Work around nonsensical selection resetting in IE9/10, and |
| 1434 // inexplicable appearance of private area unicode characters on |
| 1435 // some key combos in Mac (#2689). |
| 1436 if (ie && ie_version >= 9 && this.hasSelection === text || |
| 1437 mac && /[\uf700-\uf7ff]/.test(text)) { |
| 1438 cm.display.input.reset(); |
| 1439 return false; |
| 1440 } |
| 1441 |
| 1442 if (cm.doc.sel == cm.display.selForContextMenu) { |
| 1443 var first = text.charCodeAt(0); |
| 1444 if (first == 0x200b && !prevInput) prevInput = "\u200b"; |
| 1445 if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo");
} |
| 1446 } |
| 1447 // Find the part of the input that is actually new |
| 1448 var same = 0, l = Math.min(prevInput.length, text.length); |
| 1449 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++
same; |
| 1450 |
| 1451 var self = this; |
| 1452 runInOp(cm, function() { |
| 1453 applyTextInput(cm, text.slice(same), prevInput.length - same, |
| 1454 null, self.composing ? "*compose" : null); |
| 1455 |
| 1456 // Don't leave long text in the textarea, since it makes further polling
slow |
| 1457 if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.pr
evInput = ""; |
| 1458 else self.prevInput = text; |
| 1459 |
| 1460 if (self.composing) { |
| 1461 self.composing.range.clear(); |
| 1462 self.composing.range = cm.markText(self.composing.start, cm.getCursor(
"to"), |
| 1463 {className: "CodeMirror-composing"}
); |
| 1464 } |
| 1465 }); |
| 1466 return true; |
| 1467 }, |
| 1468 |
| 1469 ensurePolled: function() { |
| 1470 if (this.pollingFast && this.poll()) this.pollingFast = false; |
| 1471 }, |
| 1472 |
| 1473 onKeyPress: function() { |
| 1474 if (ie && ie_version >= 9) this.hasSelection = null; |
| 1475 this.fastPoll(); |
| 1476 }, |
| 1477 |
| 1478 onContextMenu: function(e) { |
| 1479 var input = this, cm = input.cm, display = cm.display, te = input.textarea
; |
| 1480 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; |
| 1481 if (!pos || presto) return; // Opera is difficult. |
| 1482 |
| 1483 // Reset the current text selection only if the click is done outside of t
he selection |
| 1484 // and 'resetSelectionOnContextMenu' option is true. |
| 1485 var reset = cm.options.resetSelectionOnContextMenu; |
| 1486 if (reset && cm.doc.sel.contains(pos) == -1) |
| 1487 operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll
); |
| 1488 |
| 1489 var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText
; |
| 1490 input.wrapper.style.cssText = "position: absolute" |
| 1491 var wrapperBox = input.wrapper.getBoundingClientRect() |
| 1492 te.style.cssText = "position: absolute; width: 30px; height: 30px; top: "
+ (e.clientY - wrapperBox.top - 5) + |
| 1493 "px; left: " + (e.clientX - wrapperBox.left - 5) + "px; z-index: 1000; b
ackground: " + |
| 1494 (ie ? "rgba(255, 255, 255, .05)" : "transparent") + |
| 1495 "; outline: none; border-width: 0; outline: none; overflow: hidden; opac
ity: .05; filter: alpha(opacity=5);"; |
| 1496 if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (
#2712) |
| 1497 display.input.focus(); |
| 1498 if (webkit) window.scrollTo(null, oldScrollY); |
| 1499 display.input.reset(); |
| 1500 // Adds "Select all" to context menu in FF |
| 1501 if (!cm.somethingSelected()) te.value = input.prevInput = " "; |
| 1502 input.contextMenuPending = true; |
| 1503 display.selForContextMenu = cm.doc.sel; |
| 1504 clearTimeout(display.detectingSelectAll); |
| 1505 |
| 1506 // Select-all will be greyed out if there's nothing to select, so |
| 1507 // this adds a zero-width space so that we can later check whether |
| 1508 // it got selected. |
| 1509 function prepareSelectAllHack() { |
| 1510 if (te.selectionStart != null) { |
| 1511 var selected = cm.somethingSelected(); |
| 1512 var extval = "\u200b" + (selected ? te.value : ""); |
| 1513 te.value = "\u21da"; // Used to catch context-menu undo |
| 1514 te.value = extval; |
| 1515 input.prevInput = selected ? "" : "\u200b"; |
| 1516 te.selectionStart = 1; te.selectionEnd = extval.length; |
| 1517 // Re-set this, in case some other handler touched the |
| 1518 // selection in the meantime. |
| 1519 display.selForContextMenu = cm.doc.sel; |
| 1520 } |
| 1521 } |
| 1522 function rehide() { |
| 1523 input.contextMenuPending = false; |
| 1524 input.wrapper.style.cssText = oldWrapperCSS |
| 1525 te.style.cssText = oldCSS; |
| 1526 if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroll
er.scrollTop = scrollPos); |
| 1527 |
| 1528 // Try to detect the user choosing select-all |
| 1529 if (te.selectionStart != null) { |
| 1530 if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); |
| 1531 var i = 0, poll = function() { |
| 1532 if (display.selForContextMenu == cm.doc.sel && te.selectionStart ==
0 && |
| 1533 te.selectionEnd > 0 && input.prevInput == "\u200b") |
| 1534 operation(cm, commands.selectAll)(cm); |
| 1535 else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500
); |
| 1536 else display.input.reset(); |
| 1537 }; |
| 1538 display.detectingSelectAll = setTimeout(poll, 200); |
| 1539 } |
| 1540 } |
| 1541 |
| 1542 if (ie && ie_version >= 9) prepareSelectAllHack(); |
| 1543 if (captureRightClick) { |
| 1544 e_stop(e); |
| 1545 var mouseup = function() { |
| 1546 off(window, "mouseup", mouseup); |
| 1547 setTimeout(rehide, 20); |
| 1548 }; |
| 1549 on(window, "mouseup", mouseup); |
| 1550 } else { |
| 1551 setTimeout(rehide, 50); |
| 1552 } |
| 1553 }, |
| 1554 |
| 1555 readOnlyChanged: function(val) { |
| 1556 if (!val) this.reset(); |
| 1557 }, |
| 1558 |
| 1559 setUneditable: nothing, |
| 1560 |
| 1561 needsContentAttribute: false |
| 1562 }, TextareaInput.prototype); |
| 1563 |
| 1564 // CONTENTEDITABLE INPUT STYLE |
| 1565 |
| 1566 function ContentEditableInput(cm) { |
| 1567 this.cm = cm; |
| 1568 this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.last
FocusOffset = null; |
| 1569 this.polling = new Delayed(); |
| 1570 this.gracePeriod = false; |
| 1571 } |
| 1572 |
| 1573 ContentEditableInput.prototype = copyObj({ |
| 1574 init: function(display) { |
| 1575 var input = this, cm = input.cm; |
| 1576 var div = input.div = display.lineDiv; |
| 1577 disableBrowserMagic(div); |
| 1578 |
| 1579 on(div, "paste", function(e) { |
| 1580 if (!signalDOMEvent(cm, e)) handlePaste(e, cm); |
| 1581 }) |
| 1582 |
| 1583 on(div, "compositionstart", function(e) { |
| 1584 var data = e.data; |
| 1585 input.composing = {sel: cm.doc.sel, data: data, startData: data}; |
| 1586 if (!data) return; |
| 1587 var prim = cm.doc.sel.primary(); |
| 1588 var line = cm.getLine(prim.head.line); |
| 1589 var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length)); |
| 1590 if (found > -1 && found <= prim.head.ch) |
| 1591 input.composing.sel = simpleSelection(Pos(prim.head.line, found), |
| 1592 Pos(prim.head.line, found + data
.length)); |
| 1593 }); |
| 1594 on(div, "compositionupdate", function(e) { |
| 1595 input.composing.data = e.data; |
| 1596 }); |
| 1597 on(div, "compositionend", function(e) { |
| 1598 var ours = input.composing; |
| 1599 if (!ours) return; |
| 1600 if (e.data != ours.startData && !/\u200b/.test(e.data)) |
| 1601 ours.data = e.data; |
| 1602 // Need a small delay to prevent other code (input event, |
| 1603 // selection polling) from doing damage when fired right after |
| 1604 // compositionend. |
| 1605 setTimeout(function() { |
| 1606 if (!ours.handled) |
| 1607 input.applyComposition(ours); |
| 1608 if (input.composing == ours) |
| 1609 input.composing = null; |
| 1610 }, 50); |
| 1611 }); |
| 1612 |
| 1613 on(div, "touchstart", function() { |
| 1614 input.forceCompositionEnd(); |
| 1615 }); |
| 1616 |
| 1617 on(div, "input", function() { |
| 1618 if (input.composing) return; |
| 1619 if (cm.isReadOnly() || !input.pollContent()) |
| 1620 runInOp(input.cm, function() {regChange(cm);}); |
| 1621 }); |
| 1622 |
| 1623 function onCopyCut(e) { |
| 1624 if (signalDOMEvent(cm, e)) return |
| 1625 if (cm.somethingSelected()) { |
| 1626 lastCopied = {lineWise: false, text: cm.getSelections()}; |
| 1627 if (e.type == "cut") cm.replaceSelection("", null, "cut"); |
| 1628 } else if (!cm.options.lineWiseCopyCut) { |
| 1629 return; |
| 1630 } else { |
| 1631 var ranges = copyableRanges(cm); |
| 1632 lastCopied = {lineWise: true, text: ranges.text}; |
| 1633 if (e.type == "cut") { |
| 1634 cm.operation(function() { |
| 1635 cm.setSelections(ranges.ranges, 0, sel_dontScroll); |
| 1636 cm.replaceSelection("", null, "cut"); |
| 1637 }); |
| 1638 } |
| 1639 } |
| 1640 // iOS exposes the clipboard API, but seems to discard content inserted
into it |
| 1641 if (e.clipboardData && !ios) { |
| 1642 e.preventDefault(); |
| 1643 e.clipboardData.clearData(); |
| 1644 e.clipboardData.setData("text/plain", lastCopied.text.join("\n")); |
| 1645 } else { |
| 1646 // Old-fashioned briefly-focus-a-textarea hack |
| 1647 var kludge = hiddenTextarea(), te = kludge.firstChild; |
| 1648 cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstCh
ild); |
| 1649 te.value = lastCopied.text.join("\n"); |
| 1650 var hadFocus = document.activeElement; |
| 1651 selectInput(te); |
| 1652 setTimeout(function() { |
| 1653 cm.display.lineSpace.removeChild(kludge); |
| 1654 hadFocus.focus(); |
| 1655 }, 50); |
| 1656 } |
| 1657 } |
| 1658 on(div, "copy", onCopyCut); |
| 1659 on(div, "cut", onCopyCut); |
| 1660 }, |
| 1661 |
| 1662 prepareSelection: function() { |
| 1663 var result = prepareSelection(this.cm, false); |
| 1664 result.focus = this.cm.state.focused; |
| 1665 return result; |
| 1666 }, |
| 1667 |
| 1668 showSelection: function(info, takeFocus) { |
| 1669 if (!info || !this.cm.display.view.length) return; |
| 1670 if (info.focus || takeFocus) this.showPrimarySelection(); |
| 1671 this.showMultipleSelections(info); |
| 1672 }, |
| 1673 |
| 1674 showPrimarySelection: function() { |
| 1675 var sel = window.getSelection(), prim = this.cm.doc.sel.primary(); |
| 1676 var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset); |
| 1677 var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset); |
| 1678 if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && |
| 1679 cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && |
| 1680 cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) |
| 1681 return; |
| 1682 |
| 1683 var start = posToDOM(this.cm, prim.from()); |
| 1684 var end = posToDOM(this.cm, prim.to()); |
| 1685 if (!start && !end) return; |
| 1686 |
| 1687 var view = this.cm.display.view; |
| 1688 var old = sel.rangeCount && sel.getRangeAt(0); |
| 1689 if (!start) { |
| 1690 start = {node: view[0].measure.map[2], offset: 0}; |
| 1691 } else if (!end) { // FIXME dangerously hacky |
| 1692 var measure = view[view.length - 1].measure; |
| 1693 var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure
.map; |
| 1694 end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.
length - 3]}; |
| 1695 } |
| 1696 |
| 1697 try { var rng = range(start.node, start.offset, end.offset, end.node); } |
| 1698 catch(e) {} // Our model of the DOM might be outdated, in which case the r
ange we try to set can be impossible |
| 1699 if (rng) { |
| 1700 if (!gecko && this.cm.state.focused) { |
| 1701 sel.collapse(start.node, start.offset); |
| 1702 if (!rng.collapsed) sel.addRange(rng); |
| 1703 } else { |
| 1704 sel.removeAllRanges(); |
| 1705 sel.addRange(rng); |
| 1706 } |
| 1707 if (old && sel.anchorNode == null) sel.addRange(old); |
| 1708 else if (gecko) this.startGracePeriod(); |
| 1709 } |
| 1710 this.rememberSelection(); |
| 1711 }, |
| 1712 |
| 1713 startGracePeriod: function() { |
| 1714 var input = this; |
| 1715 clearTimeout(this.gracePeriod); |
| 1716 this.gracePeriod = setTimeout(function() { |
| 1717 input.gracePeriod = false; |
| 1718 if (input.selectionChanged()) |
| 1719 input.cm.operation(function() { input.cm.curOp.selectionChanged = true
; }); |
| 1720 }, 20); |
| 1721 }, |
| 1722 |
| 1723 showMultipleSelections: function(info) { |
| 1724 removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); |
| 1725 removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); |
| 1726 }, |
| 1727 |
| 1728 rememberSelection: function() { |
| 1729 var sel = window.getSelection(); |
| 1730 this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOf
fset; |
| 1731 this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset
; |
| 1732 }, |
| 1733 |
| 1734 selectionInEditor: function() { |
| 1735 var sel = window.getSelection(); |
| 1736 if (!sel.rangeCount) return false; |
| 1737 var node = sel.getRangeAt(0).commonAncestorContainer; |
| 1738 return contains(this.div, node); |
| 1739 }, |
| 1740 |
| 1741 focus: function() { |
| 1742 if (this.cm.options.readOnly != "nocursor") this.div.focus(); |
| 1743 }, |
| 1744 blur: function() { this.div.blur(); }, |
| 1745 getField: function() { return this.div; }, |
| 1746 |
| 1747 supportsTouch: function() { return true; }, |
| 1748 |
| 1749 receivedFocus: function() { |
| 1750 var input = this; |
| 1751 if (this.selectionInEditor()) |
| 1752 this.pollSelection(); |
| 1753 else |
| 1754 runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; })
; |
| 1755 |
| 1756 function poll() { |
| 1757 if (input.cm.state.focused) { |
| 1758 input.pollSelection(); |
| 1759 input.polling.set(input.cm.options.pollInterval, poll); |
| 1760 } |
| 1761 } |
| 1762 this.polling.set(this.cm.options.pollInterval, poll); |
| 1763 }, |
| 1764 |
| 1765 selectionChanged: function() { |
| 1766 var sel = window.getSelection(); |
| 1767 return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.l
astAnchorOffset || |
| 1768 sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocus
Offset; |
| 1769 }, |
| 1770 |
| 1771 pollSelection: function() { |
| 1772 if (!this.composing && !this.gracePeriod && this.selectionChanged()) { |
| 1773 var sel = window.getSelection(), cm = this.cm; |
| 1774 this.rememberSelection(); |
| 1775 var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); |
| 1776 var head = domToPos(cm, sel.focusNode, sel.focusOffset); |
| 1777 if (anchor && head) runInOp(cm, function() { |
| 1778 setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); |
| 1779 if (anchor.bad || head.bad) cm.curOp.selectionChanged = true; |
| 1780 }); |
| 1781 } |
| 1782 }, |
| 1783 |
| 1784 pollContent: function() { |
| 1785 var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); |
| 1786 var from = sel.from(), to = sel.to(); |
| 1787 if (from.line < display.viewFrom || to.line > display.viewTo - 1) return f
alse; |
| 1788 |
| 1789 var fromIndex; |
| 1790 if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.l
ine)) == 0) { |
| 1791 var fromLine = lineNo(display.view[0].line); |
| 1792 var fromNode = display.view[0].node; |
| 1793 } else { |
| 1794 var fromLine = lineNo(display.view[fromIndex].line); |
| 1795 var fromNode = display.view[fromIndex - 1].node.nextSibling; |
| 1796 } |
| 1797 var toIndex = findViewIndex(cm, to.line); |
| 1798 if (toIndex == display.view.length - 1) { |
| 1799 var toLine = display.viewTo - 1; |
| 1800 var toNode = display.lineDiv.lastChild; |
| 1801 } else { |
| 1802 var toLine = lineNo(display.view[toIndex + 1].line) - 1; |
| 1803 var toNode = display.view[toIndex + 1].node.previousSibling; |
| 1804 } |
| 1805 |
| 1806 var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromL
ine, toLine)); |
| 1807 var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.
doc, toLine).text.length)); |
| 1808 while (newText.length > 1 && oldText.length > 1) { |
| 1809 if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine
--; } |
| 1810 else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); f
romLine++; } |
| 1811 else break; |
| 1812 } |
| 1813 |
| 1814 var cutFront = 0, cutEnd = 0; |
| 1815 var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTo
p.length, oldTop.length); |
| 1816 while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.cha
rCodeAt(cutFront)) |
| 1817 ++cutFront; |
| 1818 var newBot = lst(newText), oldBot = lst(oldText); |
| 1819 var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront :
0), |
| 1820 oldBot.length - (oldText.length == 1 ? cutFront :
0)); |
| 1821 while (cutEnd < maxCutEnd && |
| 1822 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(
oldBot.length - cutEnd - 1)) |
| 1823 ++cutEnd; |
| 1824 |
| 1825 newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd); |
| 1826 newText[0] = newText[0].slice(cutFront); |
| 1827 |
| 1828 var chFrom = Pos(fromLine, cutFront); |
| 1829 var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); |
| 1830 if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { |
| 1831 replaceRange(cm.doc, newText, chFrom, chTo, "+input"); |
| 1832 return true; |
| 1833 } |
| 1834 }, |
| 1835 |
| 1836 ensurePolled: function() { |
| 1837 this.forceCompositionEnd(); |
| 1838 }, |
| 1839 reset: function() { |
| 1840 this.forceCompositionEnd(); |
| 1841 }, |
| 1842 forceCompositionEnd: function() { |
| 1843 if (!this.composing || this.composing.handled) return; |
| 1844 this.applyComposition(this.composing); |
| 1845 this.composing.handled = true; |
| 1846 this.div.blur(); |
| 1847 this.div.focus(); |
| 1848 }, |
| 1849 applyComposition: function(composing) { |
| 1850 if (this.cm.isReadOnly()) |
| 1851 operation(this.cm, regChange)(this.cm) |
| 1852 else if (composing.data && composing.data != composing.startData) |
| 1853 operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing
.sel); |
| 1854 }, |
| 1855 |
| 1856 setUneditable: function(node) { |
| 1857 node.contentEditable = "false" |
| 1858 }, |
| 1859 |
| 1860 onKeyPress: function(e) { |
| 1861 e.preventDefault(); |
| 1862 if (!this.cm.isReadOnly()) |
| 1863 operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCo
de == null ? e.keyCode : e.charCode), 0); |
| 1864 }, |
| 1865 |
| 1866 readOnlyChanged: function(val) { |
| 1867 this.div.contentEditable = String(val != "nocursor") |
| 1868 }, |
| 1869 |
| 1870 onContextMenu: nothing, |
| 1871 resetPosition: nothing, |
| 1872 |
| 1873 needsContentAttribute: true |
| 1874 }, ContentEditableInput.prototype); |
| 1875 |
| 1876 function posToDOM(cm, pos) { |
| 1877 var view = findViewForLine(cm, pos.line); |
| 1878 if (!view || view.hidden) return null; |
| 1879 var line = getLine(cm.doc, pos.line); |
| 1880 var info = mapFromLineView(view, line, pos.line); |
| 1881 |
| 1882 var order = getOrder(line), side = "left"; |
| 1883 if (order) { |
| 1884 var partPos = getBidiPartAt(order, pos.ch); |
| 1885 side = partPos % 2 ? "right" : "left"; |
| 1886 } |
| 1887 var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); |
| 1888 result.offset = result.collapse == "right" ? result.end : result.start; |
| 1889 return result; |
| 1890 } |
| 1891 |
| 1892 function badPos(pos, bad) { if (bad) pos.bad = true; return pos; } |
| 1893 |
| 1894 function domToPos(cm, node, offset) { |
| 1895 var lineNode; |
| 1896 if (node == cm.display.lineDiv) { |
| 1897 lineNode = cm.display.lineDiv.childNodes[offset]; |
| 1898 if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true)
; |
| 1899 node = null; offset = 0; |
| 1900 } else { |
| 1901 for (lineNode = node;; lineNode = lineNode.parentNode) { |
| 1902 if (!lineNode || lineNode == cm.display.lineDiv) return null; |
| 1903 if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) br
eak; |
| 1904 } |
| 1905 } |
| 1906 for (var i = 0; i < cm.display.view.length; i++) { |
| 1907 var lineView = cm.display.view[i]; |
| 1908 if (lineView.node == lineNode) |
| 1909 return locateNodeInLineView(lineView, node, offset); |
| 1910 } |
| 1911 } |
| 1912 |
| 1913 function locateNodeInLineView(lineView, node, offset) { |
| 1914 var wrapper = lineView.text.firstChild, bad = false; |
| 1915 if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.lin
e), 0), true); |
| 1916 if (node == wrapper) { |
| 1917 bad = true; |
| 1918 node = wrapper.childNodes[offset]; |
| 1919 offset = 0; |
| 1920 if (!node) { |
| 1921 var line = lineView.rest ? lst(lineView.rest) : lineView.line; |
| 1922 return badPos(Pos(lineNo(line), line.text.length), bad); |
| 1923 } |
| 1924 } |
| 1925 |
| 1926 var textNode = node.nodeType == 3 ? node : null, topNode = node; |
| 1927 if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType ==
3) { |
| 1928 textNode = node.firstChild; |
| 1929 if (offset) offset = textNode.nodeValue.length; |
| 1930 } |
| 1931 while (topNode.parentNode != wrapper) topNode = topNode.parentNode; |
| 1932 var measure = lineView.measure, maps = measure.maps; |
| 1933 |
| 1934 function find(textNode, topNode, offset) { |
| 1935 for (var i = -1; i < (maps ? maps.length : 0); i++) { |
| 1936 var map = i < 0 ? measure.map : maps[i]; |
| 1937 for (var j = 0; j < map.length; j += 3) { |
| 1938 var curNode = map[j + 2]; |
| 1939 if (curNode == textNode || curNode == topNode) { |
| 1940 var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); |
| 1941 var ch = map[j] + offset; |
| 1942 if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)
]; |
| 1943 return Pos(line, ch); |
| 1944 } |
| 1945 } |
| 1946 } |
| 1947 } |
| 1948 var found = find(textNode, topNode, offset); |
| 1949 if (found) return badPos(found, bad); |
| 1950 |
| 1951 // FIXME this is all really shaky. might handle the few cases it needs to ha
ndle, but likely to cause problems |
| 1952 for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.l
ength - offset : 0; after; after = after.nextSibling) { |
| 1953 found = find(after, after.firstChild, 0); |
| 1954 if (found) |
| 1955 return badPos(Pos(found.line, found.ch - dist), bad); |
| 1956 else |
| 1957 dist += after.textContent.length; |
| 1958 } |
| 1959 for (var before = topNode.previousSibling, dist = offset; before; before = b
efore.previousSibling) { |
| 1960 found = find(before, before.firstChild, -1); |
| 1961 if (found) |
| 1962 return badPos(Pos(found.line, found.ch + dist), bad); |
| 1963 else |
| 1964 dist += after.textContent.length; |
| 1965 } |
| 1966 } |
| 1967 |
| 1968 function domTextBetween(cm, from, to, fromLine, toLine) { |
| 1969 var text = "", closing = false, lineSep = cm.doc.lineSeparator(); |
| 1970 function recognizeMarker(id) { return function(marker) { return marker.id ==
id; }; } |
| 1971 function walk(node) { |
| 1972 if (node.nodeType == 1) { |
| 1973 var cmText = node.getAttribute("cm-text"); |
| 1974 if (cmText != null) { |
| 1975 if (cmText == "") cmText = node.textContent.replace(/\u200b/g, ""); |
| 1976 text += cmText; |
| 1977 return; |
| 1978 } |
| 1979 var markerID = node.getAttribute("cm-marker"), range; |
| 1980 if (markerID) { |
| 1981 var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recogni
zeMarker(+markerID)); |
| 1982 if (found.length && (range = found[0].find())) |
| 1983 text += getBetween(cm.doc, range.from, range.to).join(lineSep); |
| 1984 return; |
| 1985 } |
| 1986 if (node.getAttribute("contenteditable") == "false") return; |
| 1987 for (var i = 0; i < node.childNodes.length; i++) |
| 1988 walk(node.childNodes[i]); |
| 1989 if (/^(pre|div|p)$/i.test(node.nodeName)) |
| 1990 closing = true; |
| 1991 } else if (node.nodeType == 3) { |
| 1992 var val = node.nodeValue; |
| 1993 if (!val) return; |
| 1994 if (closing) { |
| 1995 text += lineSep; |
| 1996 closing = false; |
| 1997 } |
| 1998 text += val; |
| 1999 } |
| 2000 } |
| 2001 for (;;) { |
| 2002 walk(from); |
| 2003 if (from == to) break; |
| 2004 from = from.nextSibling; |
| 2005 } |
| 2006 return text; |
| 2007 } |
| 2008 |
| 2009 CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": Conten
tEditableInput}; |
| 2010 |
1075 // SELECTION / CURSOR | 2011 // SELECTION / CURSOR |
1076 | 2012 |
1077 // Selection objects are immutable. A new one is created every time | 2013 // Selection objects are immutable. A new one is created every time |
1078 // the selection changes. A selection is one or more non-overlapping | 2014 // the selection changes. A selection is one or more non-overlapping |
1079 // (and non-touching) ranges, sorted, and an integer that indicates | 2015 // (and non-touching) ranges, sorted, and an integer that indicates |
1080 // which one is the primary selection (the one that's scrolled into | 2016 // which one is the primary selection (the one that's scrolled into |
1081 // view, that getCursor returns, etc). | 2017 // view, that getCursor returns, etc). |
1082 function Selection(ranges, primIndex) { | 2018 function Selection(ranges, primIndex) { |
1083 this.ranges = ranges; | 2019 this.ranges = ranges; |
1084 this.primIndex = primIndex; | 2020 this.primIndex = primIndex; |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1221 setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); | 2157 setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); |
1222 } | 2158 } |
1223 | 2159 |
1224 // Reset the selection to a single range. | 2160 // Reset the selection to a single range. |
1225 function setSimpleSelection(doc, anchor, head, options) { | 2161 function setSimpleSelection(doc, anchor, head, options) { |
1226 setSelection(doc, simpleSelection(anchor, head), options); | 2162 setSelection(doc, simpleSelection(anchor, head), options); |
1227 } | 2163 } |
1228 | 2164 |
1229 // Give beforeSelectionChange handlers a change to influence a | 2165 // Give beforeSelectionChange handlers a change to influence a |
1230 // selection update. | 2166 // selection update. |
1231 function filterSelectionChange(doc, sel) { | 2167 function filterSelectionChange(doc, sel, options) { |
1232 var obj = { | 2168 var obj = { |
1233 ranges: sel.ranges, | 2169 ranges: sel.ranges, |
1234 update: function(ranges) { | 2170 update: function(ranges) { |
1235 this.ranges = []; | 2171 this.ranges = []; |
1236 for (var i = 0; i < ranges.length; i++) | 2172 for (var i = 0; i < ranges.length; i++) |
1237 this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), | 2173 this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), |
1238 clipPos(doc, ranges[i].head)); | 2174 clipPos(doc, ranges[i].head)); |
1239 } | 2175 }, |
| 2176 origin: options && options.origin |
1240 }; | 2177 }; |
1241 signal(doc, "beforeSelectionChange", doc, obj); | 2178 signal(doc, "beforeSelectionChange", doc, obj); |
1242 if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); | 2179 if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); |
1243 if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.rang
es.length - 1); | 2180 if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.rang
es.length - 1); |
1244 else return sel; | 2181 else return sel; |
1245 } | 2182 } |
1246 | 2183 |
1247 function setSelectionReplaceHistory(doc, sel, options) { | 2184 function setSelectionReplaceHistory(doc, sel, options) { |
1248 var done = doc.history.done, last = lst(done); | 2185 var done = doc.history.done, last = lst(done); |
1249 if (last && last.ranges) { | 2186 if (last && last.ranges) { |
1250 done[done.length - 1] = sel; | 2187 done[done.length - 1] = sel; |
1251 setSelectionNoUndo(doc, sel, options); | 2188 setSelectionNoUndo(doc, sel, options); |
1252 } else { | 2189 } else { |
1253 setSelection(doc, sel, options); | 2190 setSelection(doc, sel, options); |
1254 } | 2191 } |
1255 } | 2192 } |
1256 | 2193 |
1257 // Set a new selection. | 2194 // Set a new selection. |
1258 function setSelection(doc, sel, options) { | 2195 function setSelection(doc, sel, options) { |
1259 setSelectionNoUndo(doc, sel, options); | 2196 setSelectionNoUndo(doc, sel, options); |
1260 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options)
; | 2197 addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options)
; |
1261 } | 2198 } |
1262 | 2199 |
1263 function setSelectionNoUndo(doc, sel, options) { | 2200 function setSelectionNoUndo(doc, sel, options) { |
1264 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm,
"beforeSelectionChange")) | 2201 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm,
"beforeSelectionChange")) |
1265 sel = filterSelectionChange(doc, sel); | 2202 sel = filterSelectionChange(doc, sel, options); |
1266 | 2203 |
1267 var bias = options && options.bias || | 2204 var bias = options && options.bias || |
1268 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); | 2205 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); |
1269 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); | 2206 setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); |
1270 | 2207 |
1271 if (!(options && options.scroll === false) && doc.cm) | 2208 if (!(options && options.scroll === false) && doc.cm) |
1272 ensureCursorVisible(doc.cm); | 2209 ensureCursorVisible(doc.cm); |
1273 } | 2210 } |
1274 | 2211 |
1275 function setSelectionInner(doc, sel) { | 2212 function setSelectionInner(doc, sel) { |
(...skipping 13 matching lines...) Expand all Loading... |
1289 function reCheckSelection(doc) { | 2226 function reCheckSelection(doc) { |
1290 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel
_dontScroll); | 2227 setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel
_dontScroll); |
1291 } | 2228 } |
1292 | 2229 |
1293 // Return a selection that does not partially select any atomic | 2230 // Return a selection that does not partially select any atomic |
1294 // ranges. | 2231 // ranges. |
1295 function skipAtomicInSelection(doc, sel, bias, mayClear) { | 2232 function skipAtomicInSelection(doc, sel, bias, mayClear) { |
1296 var out; | 2233 var out; |
1297 for (var i = 0; i < sel.ranges.length; i++) { | 2234 for (var i = 0; i < sel.ranges.length; i++) { |
1298 var range = sel.ranges[i]; | 2235 var range = sel.ranges[i]; |
1299 var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear); | 2236 var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; |
1300 var newHead = skipAtomic(doc, range.head, bias, mayClear); | 2237 var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, may
Clear); |
| 2238 var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear)
; |
1301 if (out || newAnchor != range.anchor || newHead != range.head) { | 2239 if (out || newAnchor != range.anchor || newHead != range.head) { |
1302 if (!out) out = sel.ranges.slice(0, i); | 2240 if (!out) out = sel.ranges.slice(0, i); |
1303 out[i] = new Range(newAnchor, newHead); | 2241 out[i] = new Range(newAnchor, newHead); |
1304 } | 2242 } |
1305 } | 2243 } |
1306 return out ? normalizeSelection(out, sel.primIndex) : sel; | 2244 return out ? normalizeSelection(out, sel.primIndex) : sel; |
1307 } | 2245 } |
1308 | 2246 |
1309 // Ensure a given position is not inside an atomic range. | 2247 function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { |
1310 function skipAtomic(doc, pos, bias, mayClear) { | 2248 var line = getLine(doc, pos.line); |
1311 var flipped = false, curPos = pos; | 2249 if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { |
1312 var dir = bias || 1; | 2250 var sp = line.markedSpans[i], m = sp.marker; |
1313 doc.cantEdit = false; | 2251 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < p
os.ch)) && |
1314 search: for (;;) { | 2252 (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch
))) { |
1315 var line = getLine(doc, curPos.line); | 2253 if (mayClear) { |
1316 if (line.markedSpans) { | 2254 signal(m, "beforeCursorEnter"); |
1317 for (var i = 0; i < line.markedSpans.length; ++i) { | 2255 if (m.explicitlyCleared) { |
1318 var sp = line.markedSpans[i], m = sp.marker; | 2256 if (!line.markedSpans) break; |
1319 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.f
rom < curPos.ch)) && | 2257 else {--i; continue;} |
1320 (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to >
curPos.ch))) { | |
1321 if (mayClear) { | |
1322 signal(m, "beforeCursorEnter"); | |
1323 if (m.explicitlyCleared) { | |
1324 if (!line.markedSpans) break; | |
1325 else {--i; continue;} | |
1326 } | |
1327 } | |
1328 if (!m.atomic) continue; | |
1329 var newPos = m.find(dir < 0 ? -1 : 1); | |
1330 if (cmp(newPos, curPos) == 0) { | |
1331 newPos.ch += dir; | |
1332 if (newPos.ch < 0) { | |
1333 if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.li
ne - 1)); | |
1334 else newPos = null; | |
1335 } else if (newPos.ch > line.text.length) { | |
1336 if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.
line + 1, 0); | |
1337 else newPos = null; | |
1338 } | |
1339 if (!newPos) { | |
1340 if (flipped) { | |
1341 // Driven in a corner -- no valid cursor position found at all | |
1342 // -- try again *with* clearing, if we didn't already | |
1343 if (!mayClear) return skipAtomic(doc, pos, bias, true); | |
1344 // Otherwise, turn off editing until further notice, and retur
n the start of the doc | |
1345 doc.cantEdit = true; | |
1346 return Pos(doc.first, 0); | |
1347 } | |
1348 flipped = true; newPos = pos; dir = -dir; | |
1349 } | |
1350 } | |
1351 curPos = newPos; | |
1352 continue search; | |
1353 } | 2258 } |
1354 } | 2259 } |
| 2260 if (!m.atomic) continue; |
| 2261 |
| 2262 if (oldPos) { |
| 2263 var near = m.find(dir < 0 ? 1 : -1), diff; |
| 2264 if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) |
| 2265 near = movePos(doc, near, -dir, near && near.line == pos.line ? line
: null); |
| 2266 if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (di
r < 0 ? diff < 0 : diff > 0)) |
| 2267 return skipAtomicInner(doc, near, pos, dir, mayClear); |
| 2268 } |
| 2269 |
| 2270 var far = m.find(dir < 0 ? -1 : 1); |
| 2271 if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) |
| 2272 far = movePos(doc, far, dir, far.line == pos.line ? line : null); |
| 2273 return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null; |
1355 } | 2274 } |
1356 return curPos; | 2275 } |
| 2276 return pos; |
| 2277 } |
| 2278 |
| 2279 // Ensure a given position is not inside an atomic range. |
| 2280 function skipAtomic(doc, pos, oldPos, bias, mayClear) { |
| 2281 var dir = bias || 1; |
| 2282 var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || |
| 2283 (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || |
| 2284 skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || |
| 2285 (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)); |
| 2286 if (!found) { |
| 2287 doc.cantEdit = true; |
| 2288 return Pos(doc.first, 0); |
| 2289 } |
| 2290 return found; |
| 2291 } |
| 2292 |
| 2293 function movePos(doc, pos, dir, line) { |
| 2294 if (dir < 0 && pos.ch == 0) { |
| 2295 if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1)); |
| 2296 else return null; |
| 2297 } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length
) { |
| 2298 if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0); |
| 2299 else return null; |
| 2300 } else { |
| 2301 return new Pos(pos.line, pos.ch + dir); |
1357 } | 2302 } |
1358 } | 2303 } |
1359 | 2304 |
1360 // SELECTION DRAWING | 2305 // SELECTION DRAWING |
1361 | 2306 |
1362 // Redraw the selection and/or cursor | 2307 function updateSelection(cm) { |
1363 function drawSelection(cm) { | 2308 cm.display.input.showSelection(cm.display.input.prepareSelection()); |
1364 var display = cm.display, doc = cm.doc, result = {}; | 2309 } |
| 2310 |
| 2311 function prepareSelection(cm, primary) { |
| 2312 var doc = cm.doc, result = {}; |
1365 var curFragment = result.cursors = document.createDocumentFragment(); | 2313 var curFragment = result.cursors = document.createDocumentFragment(); |
1366 var selFragment = result.selection = document.createDocumentFragment(); | 2314 var selFragment = result.selection = document.createDocumentFragment(); |
1367 | 2315 |
1368 for (var i = 0; i < doc.sel.ranges.length; i++) { | 2316 for (var i = 0; i < doc.sel.ranges.length; i++) { |
| 2317 if (primary === false && i == doc.sel.primIndex) continue; |
1369 var range = doc.sel.ranges[i]; | 2318 var range = doc.sel.ranges[i]; |
| 2319 if (range.from().line >= cm.display.viewTo || range.to().line < cm.display
.viewFrom) continue; |
1370 var collapsed = range.empty(); | 2320 var collapsed = range.empty(); |
1371 if (collapsed || cm.options.showCursorWhenSelecting) | 2321 if (collapsed || cm.options.showCursorWhenSelecting) |
1372 drawSelectionCursor(cm, range, curFragment); | 2322 drawSelectionCursor(cm, range.head, curFragment); |
1373 if (!collapsed) | 2323 if (!collapsed) |
1374 drawSelectionRange(cm, range, selFragment); | 2324 drawSelectionRange(cm, range, selFragment); |
1375 } | 2325 } |
1376 | |
1377 // Move the hidden textarea near the cursor to prevent scrolling artifacts | |
1378 if (cm.options.moveInputWithCursor) { | |
1379 var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); | |
1380 var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.l
ineDiv.getBoundingClientRect(); | |
1381 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, | |
1382 headPos.top + lineOff.top - wrapOff.to
p)); | |
1383 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, | |
1384 headPos.left + lineOff.left - wrapOff
.left)); | |
1385 } | |
1386 | |
1387 return result; | 2326 return result; |
1388 } | 2327 } |
1389 | 2328 |
1390 function showSelection(cm, drawn) { | |
1391 removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors); | |
1392 removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection); | |
1393 if (drawn.teTop != null) { | |
1394 cm.display.inputDiv.style.top = drawn.teTop + "px"; | |
1395 cm.display.inputDiv.style.left = drawn.teLeft + "px"; | |
1396 } | |
1397 } | |
1398 | |
1399 function updateSelection(cm) { | |
1400 showSelection(cm, drawSelection(cm)); | |
1401 } | |
1402 | |
1403 // Draws a cursor for the given range | 2329 // Draws a cursor for the given range |
1404 function drawSelectionCursor(cm, range, output) { | 2330 function drawSelectionCursor(cm, head, output) { |
1405 var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.single
CursorHeightPerLine); | 2331 var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursor
HeightPerLine); |
1406 | 2332 |
1407 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); | 2333 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); |
1408 cursor.style.left = pos.left + "px"; | 2334 cursor.style.left = pos.left + "px"; |
1409 cursor.style.top = pos.top + "px"; | 2335 cursor.style.top = pos.top + "px"; |
1410 cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorH
eight + "px"; | 2336 cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorH
eight + "px"; |
1411 | 2337 |
1412 if (pos.other) { | 2338 if (pos.other) { |
1413 // Secondary cursor, shown when on a 'jump' in bi-directional text | 2339 // Secondary cursor, shown when on a 'jump' in bi-directional text |
1414 var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-curs
or CodeMirror-secondarycursor")); | 2340 var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-curs
or CodeMirror-secondarycursor")); |
1415 otherCursor.style.display = ""; | 2341 otherCursor.style.display = ""; |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1519 function highlightWorker(cm) { | 2445 function highlightWorker(cm) { |
1520 var doc = cm.doc; | 2446 var doc = cm.doc; |
1521 if (doc.frontier < doc.first) doc.frontier = doc.first; | 2447 if (doc.frontier < doc.first) doc.frontier = doc.first; |
1522 if (doc.frontier >= cm.display.viewTo) return; | 2448 if (doc.frontier >= cm.display.viewTo) return; |
1523 var end = +new Date + cm.options.workTime; | 2449 var end = +new Date + cm.options.workTime; |
1524 var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); | 2450 var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); |
1525 var changedLines = []; | 2451 var changedLines = []; |
1526 | 2452 |
1527 doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 50
0), function(line) { | 2453 doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 50
0), function(line) { |
1528 if (doc.frontier >= cm.display.viewFrom) { // Visible | 2454 if (doc.frontier >= cm.display.viewFrom) { // Visible |
1529 var oldStyles = line.styles; | 2455 var oldStyles = line.styles, tooLong = line.text.length > cm.options.max
HighlightLength; |
1530 var highlighted = highlightLine(cm, line, state, true); | 2456 var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode,
state) : state, true); |
1531 line.styles = highlighted.styles; | 2457 line.styles = highlighted.styles; |
1532 var oldCls = line.styleClasses, newCls = highlighted.classes; | 2458 var oldCls = line.styleClasses, newCls = highlighted.classes; |
1533 if (newCls) line.styleClasses = newCls; | 2459 if (newCls) line.styleClasses = newCls; |
1534 else if (oldCls) line.styleClasses = null; | 2460 else if (oldCls) line.styleClasses = null; |
1535 var ischange = !oldStyles || oldStyles.length != line.styles.length || | 2461 var ischange = !oldStyles || oldStyles.length != line.styles.length || |
1536 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bg
Class || oldCls.textClass != newCls.textClass); | 2462 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bg
Class || oldCls.textClass != newCls.textClass); |
1537 for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldSt
yles[i] != line.styles[i]; | 2463 for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldSt
yles[i] != line.styles[i]; |
1538 if (ischange) changedLines.push(doc.frontier); | 2464 if (ischange) changedLines.push(doc.frontier); |
1539 line.stateAfter = copyState(doc.mode, state); | 2465 line.stateAfter = tooLong ? state : copyState(doc.mode, state); |
1540 } else { | 2466 } else { |
1541 processLine(cm, line.text, state); | 2467 if (line.text.length <= cm.options.maxHighlightLength) |
| 2468 processLine(cm, line.text, state); |
1542 line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : n
ull; | 2469 line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : n
ull; |
1543 } | 2470 } |
1544 ++doc.frontier; | 2471 ++doc.frontier; |
1545 if (+new Date > end) { | 2472 if (+new Date > end) { |
1546 startWorker(cm, cm.options.workDelay); | 2473 startWorker(cm, cm.options.workDelay); |
1547 return true; | 2474 return true; |
1548 } | 2475 } |
1549 }); | 2476 }); |
1550 if (changedLines.length) runInOp(cm, function() { | 2477 if (changedLines.length) runInOp(cm, function() { |
1551 for (var i = 0; i < changedLines.length; i++) | 2478 for (var i = 0; i < changedLines.length; i++) |
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1676 } | 2603 } |
1677 | 2604 |
1678 // Measurement can be split in two steps, the set-up work that | 2605 // Measurement can be split in two steps, the set-up work that |
1679 // applies to the whole line, and the measurement of the actual | 2606 // applies to the whole line, and the measurement of the actual |
1680 // character. Functions like coordsChar, that need to do a lot of | 2607 // character. Functions like coordsChar, that need to do a lot of |
1681 // measurements in a row, can thus ensure that the set-up work is | 2608 // measurements in a row, can thus ensure that the set-up work is |
1682 // only done once. | 2609 // only done once. |
1683 function prepareMeasureForLine(cm, line) { | 2610 function prepareMeasureForLine(cm, line) { |
1684 var lineN = lineNo(line); | 2611 var lineN = lineNo(line); |
1685 var view = findViewForLine(cm, lineN); | 2612 var view = findViewForLine(cm, lineN); |
1686 if (view && !view.text) | 2613 if (view && !view.text) { |
1687 view = null; | 2614 view = null; |
1688 else if (view && view.changes) | 2615 } else if (view && view.changes) { |
1689 updateLineForChanges(cm, view, lineN, getDimensions(cm)); | 2616 updateLineForChanges(cm, view, lineN, getDimensions(cm)); |
| 2617 cm.curOp.forceUpdate = true; |
| 2618 } |
1690 if (!view) | 2619 if (!view) |
1691 view = updateExternalMeasurement(cm, line); | 2620 view = updateExternalMeasurement(cm, line); |
1692 | 2621 |
1693 var info = mapFromLineView(view, line, lineN); | 2622 var info = mapFromLineView(view, line, lineN); |
1694 return { | 2623 return { |
1695 line: line, view: view, rect: null, | 2624 line: line, view: view, rect: null, |
1696 map: info.map, cache: info.cache, before: info.before, | 2625 map: info.map, cache: info.cache, before: info.before, |
1697 hasHeights: false | 2626 hasHeights: false |
1698 }; | 2627 }; |
1699 } | 2628 } |
(...skipping 15 matching lines...) Expand all Loading... |
1715 found = measureCharInner(cm, prepared, ch, bias); | 2644 found = measureCharInner(cm, prepared, ch, bias); |
1716 if (!found.bogus) prepared.cache[key] = found; | 2645 if (!found.bogus) prepared.cache[key] = found; |
1717 } | 2646 } |
1718 return {left: found.left, right: found.right, | 2647 return {left: found.left, right: found.right, |
1719 top: varHeight ? found.rtop : found.top, | 2648 top: varHeight ? found.rtop : found.top, |
1720 bottom: varHeight ? found.rbottom : found.bottom}; | 2649 bottom: varHeight ? found.rbottom : found.bottom}; |
1721 } | 2650 } |
1722 | 2651 |
1723 var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; | 2652 var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; |
1724 | 2653 |
1725 function measureCharInner(cm, prepared, ch, bias) { | 2654 function nodeAndOffsetInLineMap(map, ch, bias) { |
1726 var map = prepared.map; | |
1727 | |
1728 var node, start, end, collapse; | 2655 var node, start, end, collapse; |
1729 // First, search the line map for the text node corresponding to, | 2656 // First, search the line map for the text node corresponding to, |
1730 // or closest to, the target character. | 2657 // or closest to, the target character. |
1731 for (var i = 0; i < map.length; i += 3) { | 2658 for (var i = 0; i < map.length; i += 3) { |
1732 var mStart = map[i], mEnd = map[i + 1]; | 2659 var mStart = map[i], mEnd = map[i + 1]; |
1733 if (ch < mStart) { | 2660 if (ch < mStart) { |
1734 start = 0; end = 1; | 2661 start = 0; end = 1; |
1735 collapse = "left"; | 2662 collapse = "left"; |
1736 } else if (ch < mEnd) { | 2663 } else if (ch < mEnd) { |
1737 start = ch - mStart; | 2664 start = ch - mStart; |
(...skipping 13 matching lines...) Expand all Loading... |
1751 collapse = "left"; | 2678 collapse = "left"; |
1752 } | 2679 } |
1753 if (bias == "right" && start == mEnd - mStart) | 2680 if (bias == "right" && start == mEnd - mStart) |
1754 while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].i
nsertLeft) { | 2681 while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].i
nsertLeft) { |
1755 node = map[(i += 3) + 2]; | 2682 node = map[(i += 3) + 2]; |
1756 collapse = "right"; | 2683 collapse = "right"; |
1757 } | 2684 } |
1758 break; | 2685 break; |
1759 } | 2686 } |
1760 } | 2687 } |
| 2688 return {node: node, start: start, end: end, collapse: collapse, coverStart:
mStart, coverEnd: mEnd}; |
| 2689 } |
| 2690 |
| 2691 function getUsefulRect(rects, bias) { |
| 2692 var rect = nullRect |
| 2693 if (bias == "left") for (var i = 0; i < rects.length; i++) { |
| 2694 if ((rect = rects[i]).left != rect.right) break |
| 2695 } else for (var i = rects.length - 1; i >= 0; i--) { |
| 2696 if ((rect = rects[i]).left != rect.right) break |
| 2697 } |
| 2698 return rect |
| 2699 } |
| 2700 |
| 2701 function measureCharInner(cm, prepared, ch, bias) { |
| 2702 var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); |
| 2703 var node = place.node, start = place.start, end = place.end, collapse = plac
e.collapse; |
1761 | 2704 |
1762 var rect; | 2705 var rect; |
1763 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve t
he coordinates. | 2706 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve t
he coordinates. |
1764 for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense
rectangles are returned | 2707 for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense
rectangles are returned |
1765 while (start && isExtendingChar(prepared.line.text.charAt(mStart + start
))) --start; | 2708 while (start && isExtendingChar(prepared.line.text.charAt(place.coverSta
rt + start))) --start; |
1766 while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(
mStart + end))) ++end; | 2709 while (place.coverStart + end < place.coverEnd && isExtendingChar(prepar
ed.line.text.charAt(place.coverStart + end))) ++end; |
1767 if (ie && ie_version < 9 && start == 0 && end == mEnd - mStart) { | 2710 if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.
coverStart) |
1768 rect = node.parentNode.getBoundingClientRect(); | 2711 rect = node.parentNode.getBoundingClientRect(); |
1769 } else if (ie && cm.options.lineWrapping) { | 2712 else |
1770 var rects = range(node, start, end).getClientRects(); | 2713 rect = getUsefulRect(range(node, start, end).getClientRects(), bias) |
1771 if (rects.length) | |
1772 rect = rects[bias == "right" ? rects.length - 1 : 0]; | |
1773 else | |
1774 rect = nullRect; | |
1775 } else { | |
1776 rect = range(node, start, end).getBoundingClientRect() || nullRect; | |
1777 } | |
1778 if (rect.left || rect.right || start == 0) break; | 2714 if (rect.left || rect.right || start == 0) break; |
1779 end = start; | 2715 end = start; |
1780 start = start - 1; | 2716 start = start - 1; |
1781 collapse = "right"; | 2717 collapse = "right"; |
1782 } | 2718 } |
1783 if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.mea
sure, rect); | 2719 if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.mea
sure, rect); |
1784 } else { // If it is a widget, simply get the box for the whole widget. | 2720 } else { // If it is a widget, simply get the box for the whole widget. |
1785 if (start > 0) collapse = bias = "right"; | 2721 if (start > 0) collapse = bias = "right"; |
1786 var rects; | 2722 var rects; |
1787 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) | 2723 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) |
(...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1993 | 2929 |
1994 var bidi = getOrder(lineObj), dist = lineObj.text.length; | 2930 var bidi = getOrder(lineObj), dist = lineObj.text.length; |
1995 var from = lineLeft(lineObj), to = lineRight(lineObj); | 2931 var from = lineLeft(lineObj), to = lineRight(lineObj); |
1996 var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside =
wrongLine; | 2932 var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside =
wrongLine; |
1997 | 2933 |
1998 if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); | 2934 if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); |
1999 // Do a binary search between these bounds. | 2935 // Do a binary search between these bounds. |
2000 for (;;) { | 2936 for (;;) { |
2001 if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from
<= 1) { | 2937 if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from
<= 1) { |
2002 var ch = x < fromX || x - fromX <= toX - x ? from : to; | 2938 var ch = x < fromX || x - fromX <= toX - x ? from : to; |
| 2939 var outside = ch == from ? fromOutside : toOutside |
2003 var xDiff = x - (ch == from ? fromX : toX); | 2940 var xDiff = x - (ch == from ? fromX : toX); |
| 2941 // This is a kludge to handle the case where the coordinates |
| 2942 // are after a line-wrapped line. We should replace it with a |
| 2943 // more general handling of cursor positions around line |
| 2944 // breaks. (Issue #4078) |
| 2945 if (toOutside && !bidi && !/\s/.test(lineObj.text.charAt(ch)) && xDiff >
0 && |
| 2946 ch < lineObj.text.length && preparedMeasure.view.measure.heights.len
gth > 1) { |
| 2947 var charSize = measureCharPrepared(cm, preparedMeasure, ch, "right"); |
| 2948 if (innerOff <= charSize.bottom && innerOff >= charSize.top && Math.ab
s(x - charSize.right) < xDiff) { |
| 2949 outside = false |
| 2950 ch++ |
| 2951 xDiff = x - charSize.right |
| 2952 } |
| 2953 } |
2004 while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; | 2954 while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; |
2005 var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, | 2955 var pos = PosWithInfo(lineNo, ch, outside, xDiff < -1 ? -1 : xDiff > 1 ?
1 : 0); |
2006 xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); | |
2007 return pos; | 2956 return pos; |
2008 } | 2957 } |
2009 var step = Math.ceil(dist / 2), middle = from + step; | 2958 var step = Math.ceil(dist / 2), middle = from + step; |
2010 if (bidi) { | 2959 if (bidi) { |
2011 middle = from; | 2960 middle = from; |
2012 for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1)
; | 2961 for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1)
; |
2013 } | 2962 } |
2014 var middleX = getX(middle); | 2963 var middleX = getX(middle); |
2015 if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) t
oX += 1000; dist = step;} | 2964 if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) t
oX += 1000; dist = step;} |
2016 else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= ste
p;} | 2965 else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= ste
p;} |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2069 forceUpdate: false, // Used to force a redraw | 3018 forceUpdate: false, // Used to force a redraw |
2070 updateInput: null, // Whether to reset the input textarea | 3019 updateInput: null, // Whether to reset the input textarea |
2071 typing: false, // Whether this reset should be careful to leave
existing text (for compositing) | 3020 typing: false, // Whether this reset should be careful to leave
existing text (for compositing) |
2072 changeObjs: null, // Accumulated changes, for firing change events | 3021 changeObjs: null, // Accumulated changes, for firing change events |
2073 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on | 3022 cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on |
2074 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been
called already | 3023 cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been
called already |
2075 selectionChanged: false, // Whether the selection needs to be redrawn | 3024 selectionChanged: false, // Whether the selection needs to be redrawn |
2076 updateMaxLine: false, // Set when the widest line needs to be determine
d anew | 3025 updateMaxLine: false, // Set when the widest line needs to be determine
d anew |
2077 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pu
shed to DOM yet | 3026 scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pu
shed to DOM yet |
2078 scrollToPos: null, // Used to scroll to a specific position | 3027 scrollToPos: null, // Used to scroll to a specific position |
| 3028 focus: false, |
2079 id: ++nextOpId // Unique ID | 3029 id: ++nextOpId // Unique ID |
2080 }; | 3030 }; |
2081 if (operationGroup) { | 3031 if (operationGroup) { |
2082 operationGroup.ops.push(cm.curOp); | 3032 operationGroup.ops.push(cm.curOp); |
2083 } else { | 3033 } else { |
2084 cm.curOp.ownsGroup = operationGroup = { | 3034 cm.curOp.ownsGroup = operationGroup = { |
2085 ops: [cm.curOp], | 3035 ops: [cm.curOp], |
2086 delayedCallbacks: [] | 3036 delayedCallbacks: [] |
2087 }; | 3037 }; |
2088 } | 3038 } |
2089 } | 3039 } |
2090 | 3040 |
2091 function fireCallbacksForOps(group) { | 3041 function fireCallbacksForOps(group) { |
2092 // Calls delayed callbacks and cursorActivity handlers until no | 3042 // Calls delayed callbacks and cursorActivity handlers until no |
2093 // new ones appear | 3043 // new ones appear |
2094 var callbacks = group.delayedCallbacks, i = 0; | 3044 var callbacks = group.delayedCallbacks, i = 0; |
2095 do { | 3045 do { |
2096 for (; i < callbacks.length; i++) | 3046 for (; i < callbacks.length; i++) |
2097 callbacks[i](); | 3047 callbacks[i].call(null); |
2098 for (var j = 0; j < group.ops.length; j++) { | 3048 for (var j = 0; j < group.ops.length; j++) { |
2099 var op = group.ops[j]; | 3049 var op = group.ops[j]; |
2100 if (op.cursorActivityHandlers) | 3050 if (op.cursorActivityHandlers) |
2101 while (op.cursorActivityCalled < op.cursorActivityHandlers.length) | 3051 while (op.cursorActivityCalled < op.cursorActivityHandlers.length) |
2102 op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm); | 3052 op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.c
m); |
2103 } | 3053 } |
2104 } while (i < callbacks.length); | 3054 } while (i < callbacks.length); |
2105 } | 3055 } |
2106 | 3056 |
2107 // Finish an operation, updating the display and signalling delayed events | 3057 // Finish an operation, updating the display and signalling delayed events |
2108 function endOperation(cm) { | 3058 function endOperation(cm) { |
2109 var op = cm.curOp, group = op.ownsGroup; | 3059 var op = cm.curOp, group = op.ownsGroup; |
2110 if (!group) return; | 3060 if (!group) return; |
2111 | 3061 |
2112 try { fireCallbacksForOps(group); } | 3062 try { fireCallbacksForOps(group); } |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2162 // updateDisplay_W2 will use these properties to do the actual resizing | 3112 // updateDisplay_W2 will use these properties to do the actual resizing |
2163 if (display.maxLineChanged && !cm.options.lineWrapping) { | 3113 if (display.maxLineChanged && !cm.options.lineWrapping) { |
2164 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.l
ength).left + 3; | 3114 op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.l
ength).left + 3; |
2165 cm.display.sizerWidth = op.adjustWidthTo; | 3115 cm.display.sizerWidth = op.adjustWidthTo; |
2166 op.barMeasure.scrollWidth = | 3116 op.barMeasure.scrollWidth = |
2167 Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adj
ustWidthTo + scrollGap(cm) + cm.display.barWidth); | 3117 Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adj
ustWidthTo + scrollGap(cm) + cm.display.barWidth); |
2168 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo
- displayWidth(cm)); | 3118 op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo
- displayWidth(cm)); |
2169 } | 3119 } |
2170 | 3120 |
2171 if (op.updatedDisplay || op.selectionChanged) | 3121 if (op.updatedDisplay || op.selectionChanged) |
2172 op.newSelectionNodes = drawSelection(cm); | 3122 op.preparedSelection = display.input.prepareSelection(op.focus); |
2173 } | 3123 } |
2174 | 3124 |
2175 function endOperation_W2(op) { | 3125 function endOperation_W2(op) { |
2176 var cm = op.cm; | 3126 var cm = op.cm; |
2177 | 3127 |
2178 if (op.adjustWidthTo != null) { | 3128 if (op.adjustWidthTo != null) { |
2179 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; | 3129 cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; |
2180 if (op.maxScrollLeft < cm.doc.scrollLeft) | 3130 if (op.maxScrollLeft < cm.doc.scrollLeft) |
2181 setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollL
eft), true); | 3131 setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollL
eft), true); |
2182 cm.display.maxLineChanged = false; | 3132 cm.display.maxLineChanged = false; |
2183 } | 3133 } |
2184 | 3134 |
2185 if (op.newSelectionNodes) | 3135 var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus |
| document.hasFocus()) |
2186 showSelection(cm, op.newSelectionNodes); | 3136 if (op.preparedSelection) |
| 3137 cm.display.input.showSelection(op.preparedSelection, takeFocus); |
| 3138 if (op.updatedDisplay || op.startHeight != cm.doc.height) |
| 3139 updateScrollbars(cm, op.barMeasure); |
2187 if (op.updatedDisplay) | 3140 if (op.updatedDisplay) |
2188 setDocumentHeight(cm, op.barMeasure); | 3141 setDocumentHeight(cm, op.barMeasure); |
2189 if (op.updatedDisplay || op.startHeight != cm.doc.height) | |
2190 updateScrollbars(cm, op.barMeasure); | |
2191 | 3142 |
2192 if (op.selectionChanged) restartBlink(cm); | 3143 if (op.selectionChanged) restartBlink(cm); |
2193 | 3144 |
2194 if (cm.state.focused && op.updateInput) | 3145 if (cm.state.focused && op.updateInput) |
2195 resetInput(cm, op.typing); | 3146 cm.display.input.reset(op.typing); |
| 3147 if (takeFocus) ensureFocus(op.cm); |
2196 } | 3148 } |
2197 | 3149 |
2198 function endOperation_finish(op) { | 3150 function endOperation_finish(op) { |
2199 var cm = op.cm, display = cm.display, doc = cm.doc; | 3151 var cm = op.cm, display = cm.display, doc = cm.doc; |
2200 | 3152 |
2201 if (op.updatedDisplay) postUpdateDisplay(cm, op.update); | 3153 if (op.updatedDisplay) postUpdateDisplay(cm, op.update); |
2202 | 3154 |
2203 // Abort mouse wheel delta measurement, when scrolling explicitly | 3155 // Abort mouse wheel delta measurement, when scrolling explicitly |
2204 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft !=
null || op.scrollToPos)) | 3156 if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft !=
null || op.scrollToPos)) |
2205 display.wheelStartX = display.wheelStartY = null; | 3157 display.wheelStartX = display.wheelStartY = null; |
2206 | 3158 |
2207 // Propagate the scroll position to the actual DOM scroller | 3159 // Propagate the scroll position to the actual DOM scroller |
2208 if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || o
p.forceScroll)) { | 3160 if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || o
p.forceScroll)) { |
2209 doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - displ
ay.scroller.clientHeight, op.scrollTop)); | 3161 doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - displ
ay.scroller.clientHeight, op.scrollTop)); |
2210 display.scrollbars.setScrollTop(doc.scrollTop); | 3162 display.scrollbars.setScrollTop(doc.scrollTop); |
2211 display.scroller.scrollTop = doc.scrollTop; | 3163 display.scroller.scrollTop = doc.scrollTop; |
2212 } | 3164 } |
2213 if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft |
| op.forceScroll)) { | 3165 if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft |
| op.forceScroll)) { |
2214 doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displ
ayWidth(cm), op.scrollLeft)); | 3166 doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displ
ay.scroller.clientWidth, op.scrollLeft)); |
2215 display.scrollbars.setScrollLeft(doc.scrollLeft); | 3167 display.scrollbars.setScrollLeft(doc.scrollLeft); |
2216 display.scroller.scrollLeft = doc.scrollLeft; | 3168 display.scroller.scrollLeft = doc.scrollLeft; |
2217 alignHorizontally(cm); | 3169 alignHorizontally(cm); |
2218 } | 3170 } |
2219 // If we need to scroll a specific position into view, do so. | 3171 // If we need to scroll a specific position into view, do so. |
2220 if (op.scrollToPos) { | 3172 if (op.scrollToPos) { |
2221 var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), | 3173 var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), |
2222 clipPos(doc, op.scrollToPos.to), op.scrollT
oPos.margin); | 3174 clipPos(doc, op.scrollToPos.to), op.scrollT
oPos.margin); |
2223 if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coo
rds); | 3175 if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coo
rds); |
2224 } | 3176 } |
2225 | 3177 |
2226 // Fire events for markers that are hidden/unidden by editing or | 3178 // Fire events for markers that are hidden/unidden by editing or |
2227 // undoing | 3179 // undoing |
2228 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; | 3180 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; |
2229 if (hidden) for (var i = 0; i < hidden.length; ++i) | 3181 if (hidden) for (var i = 0; i < hidden.length; ++i) |
2230 if (!hidden[i].lines.length) signal(hidden[i], "hide"); | 3182 if (!hidden[i].lines.length) signal(hidden[i], "hide"); |
2231 if (unhidden) for (var i = 0; i < unhidden.length; ++i) | 3183 if (unhidden) for (var i = 0; i < unhidden.length; ++i) |
2232 if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); | 3184 if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); |
2233 | 3185 |
2234 if (display.wrapper.offsetHeight) | 3186 if (display.wrapper.offsetHeight) |
2235 doc.scrollTop = cm.display.scroller.scrollTop; | 3187 doc.scrollTop = cm.display.scroller.scrollTop; |
2236 | 3188 |
2237 // Fire change events, and delayed event handlers | 3189 // Fire change events, and delayed event handlers |
2238 if (op.changeObjs) | 3190 if (op.changeObjs) |
2239 signal(cm, "changes", cm, op.changeObjs); | 3191 signal(cm, "changes", cm, op.changeObjs); |
| 3192 if (op.update) |
| 3193 op.update.finish(); |
2240 } | 3194 } |
2241 | 3195 |
2242 // Run the given function in an operation | 3196 // Run the given function in an operation |
2243 function runInOp(cm, f) { | 3197 function runInOp(cm, f) { |
2244 if (cm.curOp) return f(); | 3198 if (cm.curOp) return f(); |
2245 startOperation(cm); | 3199 startOperation(cm); |
2246 try { return f(); } | 3200 try { return f(); } |
2247 finally { endOperation(cm); } | 3201 finally { endOperation(cm); } |
2248 } | 3202 } |
2249 // Wraps a function in an operation. Returns the wrapped function. | 3203 // Wraps a function in an operation. Returns the wrapped function. |
(...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2455 // out of date (or nonexistent). | 3409 // out of date (or nonexistent). |
2456 function countDirtyView(cm) { | 3410 function countDirtyView(cm) { |
2457 var view = cm.display.view, dirty = 0; | 3411 var view = cm.display.view, dirty = 0; |
2458 for (var i = 0; i < view.length; i++) { | 3412 for (var i = 0; i < view.length; i++) { |
2459 var lineView = view[i]; | 3413 var lineView = view[i]; |
2460 if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; | 3414 if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; |
2461 } | 3415 } |
2462 return dirty; | 3416 return dirty; |
2463 } | 3417 } |
2464 | 3418 |
2465 // INPUT HANDLING | |
2466 | |
2467 // Poll for input changes, using the normal rate of polling. This | |
2468 // runs as long as the editor is focused. | |
2469 function slowPoll(cm) { | |
2470 if (cm.display.pollingFast) return; | |
2471 cm.display.poll.set(cm.options.pollInterval, function() { | |
2472 readInput(cm); | |
2473 if (cm.state.focused) slowPoll(cm); | |
2474 }); | |
2475 } | |
2476 | |
2477 // When an event has just come in that is likely to add or change | |
2478 // something in the input textarea, we poll faster, to ensure that | |
2479 // the change appears on the screen quickly. | |
2480 function fastPoll(cm) { | |
2481 var missed = false; | |
2482 cm.display.pollingFast = true; | |
2483 function p() { | |
2484 var changed = readInput(cm); | |
2485 if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);} | |
2486 else {cm.display.pollingFast = false; slowPoll(cm);} | |
2487 } | |
2488 cm.display.poll.set(20, p); | |
2489 } | |
2490 | |
2491 // This will be set to an array of strings when copying, so that, | |
2492 // when pasting, we know what kind of selections the copied text | |
2493 // was made out of. | |
2494 var lastCopied = null; | |
2495 | |
2496 // Read input from the textarea, and update the document to match. | |
2497 // When something is selected, it is present in the textarea, and | |
2498 // selected (unless it is huge, in which case a placeholder is | |
2499 // used). When nothing is selected, the cursor sits after previously | |
2500 // seen text (can be empty), which is stored in prevInput (we must | |
2501 // not reset the textarea when typing, because that breaks IME). | |
2502 function readInput(cm) { | |
2503 var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc
; | |
2504 // Since this is called a *lot*, try to bail out as cheaply as | |
2505 // possible when it is clear that nothing happened. hasSelection | |
2506 // will be the case when there is a lot of text in the textarea, | |
2507 // in which case reading its value would be expensive. | |
2508 if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(c
m) || cm.options.disableInput || cm.state.keySeq) | |
2509 return false; | |
2510 // See paste handler for more on the fakedLastChar kludge | |
2511 if (cm.state.pasteIncoming && cm.state.fakedLastChar) { | |
2512 input.value = input.value.substring(0, input.value.length - 1); | |
2513 cm.state.fakedLastChar = false; | |
2514 } | |
2515 var text = input.value; | |
2516 // If nothing changed, bail. | |
2517 if (text == prevInput && !cm.somethingSelected()) return false; | |
2518 // Work around nonsensical selection resetting in IE9/10, and | |
2519 // inexplicable appearance of private area unicode characters on | |
2520 // some key combos in Mac (#2689). | |
2521 if (ie && ie_version >= 9 && cm.display.inputHasSelection === text || | |
2522 mac && /[\uf700-\uf7ff]/.test(text)) { | |
2523 resetInput(cm); | |
2524 return false; | |
2525 } | |
2526 | |
2527 var withOp = !cm.curOp; | |
2528 if (withOp) startOperation(cm); | |
2529 cm.display.shift = false; | |
2530 | |
2531 if (text.charCodeAt(0) == 0x200b && doc.sel == cm.display.selForContextMenu
&& !prevInput) | |
2532 prevInput = "\u200b"; | |
2533 // Find the part of the input that is actually new | |
2534 var same = 0, l = Math.min(prevInput.length, text.length); | |
2535 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++sa
me; | |
2536 var inserted = text.slice(same), textLines = splitLines(inserted); | |
2537 | |
2538 // When pasing N lines into N selections, insert one line per selection | |
2539 var multiPaste = null; | |
2540 if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) { | |
2541 if (lastCopied && lastCopied.join("\n") == inserted) | |
2542 multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastC
opied, splitLines); | |
2543 else if (textLines.length == doc.sel.ranges.length) | |
2544 multiPaste = map(textLines, function(l) { return [l]; }); | |
2545 } | |
2546 | |
2547 // Normal behavior is to insert the new text into every selection | |
2548 for (var i = doc.sel.ranges.length - 1; i >= 0; i--) { | |
2549 var range = doc.sel.ranges[i]; | |
2550 var from = range.from(), to = range.to(); | |
2551 // Handle deletion | |
2552 if (same < prevInput.length) | |
2553 from = Pos(from.line, from.ch - (prevInput.length - same)); | |
2554 // Handle overwrite | |
2555 else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming) | |
2556 to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + ls
t(textLines).length)); | |
2557 var updateInput = cm.curOp.updateInput; | |
2558 var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % m
ultiPaste.length] : textLines, | |
2559 origin: cm.state.pasteIncoming ? "paste" : cm.state.cut
Incoming ? "cut" : "+input"}; | |
2560 makeChange(cm.doc, changeEvent); | |
2561 signalLater(cm, "inputRead", cm, changeEvent); | |
2562 // When an 'electric' character is inserted, immediately trigger a reinden
t | |
2563 if (inserted && !cm.state.pasteIncoming && cm.options.electricChars && | |
2564 cm.options.smartIndent && range.head.ch < 100 && | |
2565 (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) { | |
2566 var mode = cm.getModeAt(range.head); | |
2567 var end = changeEnd(changeEvent); | |
2568 if (mode.electricChars) { | |
2569 for (var j = 0; j < mode.electricChars.length; j++) | |
2570 if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { | |
2571 indentLine(cm, end.line, "smart"); | |
2572 break; | |
2573 } | |
2574 } else if (mode.electricInput) { | |
2575 if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.c
h))) | |
2576 indentLine(cm, end.line, "smart"); | |
2577 } | |
2578 } | |
2579 } | |
2580 ensureCursorVisible(cm); | |
2581 cm.curOp.updateInput = updateInput; | |
2582 cm.curOp.typing = true; | |
2583 | |
2584 // Don't leave long text in the textarea, since it makes further polling slo
w | |
2585 if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.
prevInput = ""; | |
2586 else cm.display.prevInput = text; | |
2587 if (withOp) endOperation(cm); | |
2588 cm.state.pasteIncoming = cm.state.cutIncoming = false; | |
2589 return true; | |
2590 } | |
2591 | |
2592 // Reset the input to correspond to the selection (or to be empty, | |
2593 // when not typing and nothing is selected) | |
2594 function resetInput(cm, typing) { | |
2595 if (cm.display.contextMenuPending) return; | |
2596 var minimal, selected, doc = cm.doc; | |
2597 if (cm.somethingSelected()) { | |
2598 cm.display.prevInput = ""; | |
2599 var range = doc.sel.primary(); | |
2600 minimal = hasCopyEvent && | |
2601 (range.to().line - range.from().line > 100 || (selected = cm.getSelectio
n()).length > 1000); | |
2602 var content = minimal ? "-" : selected || cm.getSelection(); | |
2603 cm.display.input.value = content; | |
2604 if (cm.state.focused) selectInput(cm.display.input); | |
2605 if (ie && ie_version >= 9) cm.display.inputHasSelection = content; | |
2606 } else if (!typing) { | |
2607 cm.display.prevInput = cm.display.input.value = ""; | |
2608 if (ie && ie_version >= 9) cm.display.inputHasSelection = null; | |
2609 } | |
2610 cm.display.inaccurateSelection = minimal; | |
2611 } | |
2612 | |
2613 function focusInput(cm) { | |
2614 if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.displ
ay.input)) | |
2615 cm.display.input.focus(); | |
2616 } | |
2617 | |
2618 function ensureFocus(cm) { | |
2619 if (!cm.state.focused) { focusInput(cm); onFocus(cm); } | |
2620 } | |
2621 | |
2622 function isReadOnly(cm) { | |
2623 return cm.options.readOnly || cm.doc.cantEdit; | |
2624 } | |
2625 | |
2626 // EVENT HANDLERS | 3419 // EVENT HANDLERS |
2627 | 3420 |
2628 // Attach the necessary event handlers when initializing the editor | 3421 // Attach the necessary event handlers when initializing the editor |
2629 function registerEventHandlers(cm) { | 3422 function registerEventHandlers(cm) { |
2630 var d = cm.display; | 3423 var d = cm.display; |
2631 on(d.scroller, "mousedown", operation(cm, onMouseDown)); | 3424 on(d.scroller, "mousedown", operation(cm, onMouseDown)); |
2632 // Older IE's will not fire a second mousedown for a double click | 3425 // Older IE's will not fire a second mousedown for a double click |
2633 if (ie && ie_version < 11) | 3426 if (ie && ie_version < 11) |
2634 on(d.scroller, "dblclick", operation(cm, function(e) { | 3427 on(d.scroller, "dblclick", operation(cm, function(e) { |
2635 if (signalDOMEvent(cm, e)) return; | 3428 if (signalDOMEvent(cm, e)) return; |
2636 var pos = posFromMouse(cm, e); | 3429 var pos = posFromMouse(cm, e); |
2637 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return
; | 3430 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return
; |
2638 e_preventDefault(e); | 3431 e_preventDefault(e); |
2639 var word = cm.findWordAt(pos); | 3432 var word = cm.findWordAt(pos); |
2640 extendSelection(cm.doc, word.anchor, word.head); | 3433 extendSelection(cm.doc, word.anchor, word.head); |
2641 })); | 3434 })); |
2642 else | 3435 else |
2643 on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preven
tDefault(e); }); | 3436 on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preven
tDefault(e); }); |
2644 // Prevent normal selection in the editor (we handle our own) | |
2645 on(d.lineSpace, "selectstart", function(e) { | |
2646 if (!eventInWidget(d, e)) e_preventDefault(e); | |
2647 }); | |
2648 // Some browsers fire contextmenu *after* opening the menu, at | 3437 // Some browsers fire contextmenu *after* opening the menu, at |
2649 // which point we can't mess with it anymore. Context menu is | 3438 // which point we can't mess with it anymore. Context menu is |
2650 // handled in onMouseDown for these browsers. | 3439 // handled in onMouseDown for these browsers. |
2651 if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContext
Menu(cm, e);}); | 3440 if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContext
Menu(cm, e);}); |
2652 | 3441 |
| 3442 // Used to suppress mouse event handling when a touch happens |
| 3443 var touchFinished, prevTouch = {end: 0}; |
| 3444 function finishTouch() { |
| 3445 if (d.activeTouch) { |
| 3446 touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000); |
| 3447 prevTouch = d.activeTouch; |
| 3448 prevTouch.end = +new Date; |
| 3449 } |
| 3450 }; |
| 3451 function isMouseLikeTouchEvent(e) { |
| 3452 if (e.touches.length != 1) return false; |
| 3453 var touch = e.touches[0]; |
| 3454 return touch.radiusX <= 1 && touch.radiusY <= 1; |
| 3455 } |
| 3456 function farAway(touch, other) { |
| 3457 if (other.left == null) return true; |
| 3458 var dx = other.left - touch.left, dy = other.top - touch.top; |
| 3459 return dx * dx + dy * dy > 20 * 20; |
| 3460 } |
| 3461 on(d.scroller, "touchstart", function(e) { |
| 3462 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { |
| 3463 clearTimeout(touchFinished); |
| 3464 var now = +new Date; |
| 3465 d.activeTouch = {start: now, moved: false, |
| 3466 prev: now - prevTouch.end <= 300 ? prevTouch : null}; |
| 3467 if (e.touches.length == 1) { |
| 3468 d.activeTouch.left = e.touches[0].pageX; |
| 3469 d.activeTouch.top = e.touches[0].pageY; |
| 3470 } |
| 3471 } |
| 3472 }); |
| 3473 on(d.scroller, "touchmove", function() { |
| 3474 if (d.activeTouch) d.activeTouch.moved = true; |
| 3475 }); |
| 3476 on(d.scroller, "touchend", function(e) { |
| 3477 var touch = d.activeTouch; |
| 3478 if (touch && !eventInWidget(d, e) && touch.left != null && |
| 3479 !touch.moved && new Date - touch.start < 300) { |
| 3480 var pos = cm.coordsChar(d.activeTouch, "page"), range; |
| 3481 if (!touch.prev || farAway(touch, touch.prev)) // Single tap |
| 3482 range = new Range(pos, pos); |
| 3483 else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double
tap |
| 3484 range = cm.findWordAt(pos); |
| 3485 else // Triple tap |
| 3486 range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1,
0))); |
| 3487 cm.setSelection(range.anchor, range.head); |
| 3488 cm.focus(); |
| 3489 e_preventDefault(e); |
| 3490 } |
| 3491 finishTouch(); |
| 3492 }); |
| 3493 on(d.scroller, "touchcancel", finishTouch); |
| 3494 |
2653 // Sync scrolling between fake scrollbars and real scrollable | 3495 // Sync scrolling between fake scrollbars and real scrollable |
2654 // area, ensure viewport is updated when scrolling. | 3496 // area, ensure viewport is updated when scrolling. |
2655 on(d.scroller, "scroll", function() { | 3497 on(d.scroller, "scroll", function() { |
2656 if (d.scroller.clientHeight) { | 3498 if (d.scroller.clientHeight) { |
2657 setScrollTop(cm, d.scroller.scrollTop); | 3499 setScrollTop(cm, d.scroller.scrollTop); |
2658 setScrollLeft(cm, d.scroller.scrollLeft, true); | 3500 setScrollLeft(cm, d.scroller.scrollLeft, true); |
2659 signal(cm, "scroll", cm); | 3501 signal(cm, "scroll", cm); |
2660 } | 3502 } |
2661 }); | 3503 }); |
2662 | 3504 |
2663 // Listen to wheel events in order to try and update the viewport on time. | 3505 // Listen to wheel events in order to try and update the viewport on time. |
2664 on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); | 3506 on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); |
2665 on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); | 3507 on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); |
2666 | 3508 |
2667 // Prevent wrapper from ever scrolling | 3509 // Prevent wrapper from ever scrolling |
2668 on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollL
eft = 0; }); | 3510 on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollL
eft = 0; }); |
2669 | 3511 |
2670 on(d.input, "keyup", function(e) { onKeyUp.call(cm, e); }); | 3512 d.dragFunctions = { |
2671 on(d.input, "input", function() { | 3513 enter: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);}, |
2672 if (ie && ie_version >= 9 && cm.display.inputHasSelection) cm.display.inpu
tHasSelection = null; | 3514 over: function(e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop
(e); }}, |
2673 readInput(cm); | 3515 start: function(e){onDragStart(cm, e);}, |
2674 }); | 3516 drop: operation(cm, onDrop), |
2675 on(d.input, "keydown", operation(cm, onKeyDown)); | 3517 leave: function(e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }} |
2676 on(d.input, "keypress", operation(cm, onKeyPress)); | 3518 }; |
2677 on(d.input, "focus", bind(onFocus, cm)); | |
2678 on(d.input, "blur", bind(onBlur, cm)); | |
2679 | 3519 |
2680 function drag_(e) { | 3520 var inp = d.input.getField(); |
2681 if (!signalDOMEvent(cm, e)) e_stop(e); | 3521 on(inp, "keyup", function(e) { onKeyUp.call(cm, e); }); |
| 3522 on(inp, "keydown", operation(cm, onKeyDown)); |
| 3523 on(inp, "keypress", operation(cm, onKeyPress)); |
| 3524 on(inp, "focus", bind(onFocus, cm)); |
| 3525 on(inp, "blur", bind(onBlur, cm)); |
| 3526 } |
| 3527 |
| 3528 function dragDropChanged(cm, value, old) { |
| 3529 var wasOn = old && old != CodeMirror.Init; |
| 3530 if (!value != !wasOn) { |
| 3531 var funcs = cm.display.dragFunctions; |
| 3532 var toggle = value ? on : off; |
| 3533 toggle(cm.display.scroller, "dragstart", funcs.start); |
| 3534 toggle(cm.display.scroller, "dragenter", funcs.enter); |
| 3535 toggle(cm.display.scroller, "dragover", funcs.over); |
| 3536 toggle(cm.display.scroller, "dragleave", funcs.leave); |
| 3537 toggle(cm.display.scroller, "drop", funcs.drop); |
2682 } | 3538 } |
2683 if (cm.options.dragDrop) { | |
2684 on(d.scroller, "dragstart", function(e){onDragStart(cm, e);}); | |
2685 on(d.scroller, "dragenter", drag_); | |
2686 on(d.scroller, "dragover", drag_); | |
2687 on(d.scroller, "drop", operation(cm, onDrop)); | |
2688 } | |
2689 on(d.scroller, "paste", function(e) { | |
2690 if (eventInWidget(d, e)) return; | |
2691 cm.state.pasteIncoming = true; | |
2692 focusInput(cm); | |
2693 fastPoll(cm); | |
2694 }); | |
2695 on(d.input, "paste", function() { | |
2696 // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206 | |
2697 // Add a char to the end of textarea before paste occur so that | |
2698 // selection doesn't span to the end of textarea. | |
2699 if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleD
own < 200)) { | |
2700 var start = d.input.selectionStart, end = d.input.selectionEnd; | |
2701 d.input.value += "$"; | |
2702 // The selection end needs to be set before the start, otherwise there | |
2703 // can be an intermediate non-empty selection between the two, which | |
2704 // can override the middle-click paste buffer on linux and cause the | |
2705 // wrong thing to get pasted. | |
2706 d.input.selectionEnd = end; | |
2707 d.input.selectionStart = start; | |
2708 cm.state.fakedLastChar = true; | |
2709 } | |
2710 cm.state.pasteIncoming = true; | |
2711 fastPoll(cm); | |
2712 }); | |
2713 | |
2714 function prepareCopyCut(e) { | |
2715 if (cm.somethingSelected()) { | |
2716 lastCopied = cm.getSelections(); | |
2717 if (d.inaccurateSelection) { | |
2718 d.prevInput = ""; | |
2719 d.inaccurateSelection = false; | |
2720 d.input.value = lastCopied.join("\n"); | |
2721 selectInput(d.input); | |
2722 } | |
2723 } else { | |
2724 var text = [], ranges = []; | |
2725 for (var i = 0; i < cm.doc.sel.ranges.length; i++) { | |
2726 var line = cm.doc.sel.ranges[i].head.line; | |
2727 var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; | |
2728 ranges.push(lineRange); | |
2729 text.push(cm.getRange(lineRange.anchor, lineRange.head)); | |
2730 } | |
2731 if (e.type == "cut") { | |
2732 cm.setSelections(ranges, null, sel_dontScroll); | |
2733 } else { | |
2734 d.prevInput = ""; | |
2735 d.input.value = text.join("\n"); | |
2736 selectInput(d.input); | |
2737 } | |
2738 lastCopied = text; | |
2739 } | |
2740 if (e.type == "cut") cm.state.cutIncoming = true; | |
2741 } | |
2742 on(d.input, "cut", prepareCopyCut); | |
2743 on(d.input, "copy", prepareCopyCut); | |
2744 | |
2745 // Needed to handle Tab key in KHTML | |
2746 if (khtml) on(d.sizer, "mouseup", function() { | |
2747 if (activeElt() == d.input) d.input.blur(); | |
2748 focusInput(cm); | |
2749 }); | |
2750 } | 3539 } |
2751 | 3540 |
2752 // Called when the window resizes | 3541 // Called when the window resizes |
2753 function onResize(cm) { | 3542 function onResize(cm) { |
2754 var d = cm.display; | 3543 var d = cm.display; |
2755 if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapp
er.clientWidth) | 3544 if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapp
er.clientWidth) |
2756 return; | 3545 return; |
2757 // Might be a text scaling operation, clear size caches. | 3546 // Might be a text scaling operation, clear size caches. |
2758 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; | 3547 d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; |
2759 d.scrollbarsClipped = false; | 3548 d.scrollbarsClipped = false; |
(...skipping 11 matching lines...) Expand all Loading... |
2771 } | 3560 } |
2772 } | 3561 } |
2773 | 3562 |
2774 // Given a mouse event, find the corresponding position. If liberal | 3563 // Given a mouse event, find the corresponding position. If liberal |
2775 // is false, it checks whether a gutter or scrollbar was clicked, | 3564 // is false, it checks whether a gutter or scrollbar was clicked, |
2776 // and returns null if it was. forRect is used by rectangular | 3565 // and returns null if it was. forRect is used by rectangular |
2777 // selections, and tries to estimate a character position even for | 3566 // selections, and tries to estimate a character position even for |
2778 // coordinates beyond the right of the text. | 3567 // coordinates beyond the right of the text. |
2779 function posFromMouse(cm, e, liberal, forRect) { | 3568 function posFromMouse(cm, e, liberal, forRect) { |
2780 var display = cm.display; | 3569 var display = cm.display; |
2781 if (!liberal && e_target(e).getAttribute("not-content") == "true") return nu
ll; | 3570 if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return
null; |
2782 | 3571 |
2783 var x, y, space = display.lineSpace.getBoundingClientRect(); | 3572 var x, y, space = display.lineSpace.getBoundingClientRect(); |
2784 // Fails unpredictably on IE[67] when mouse is dragged around quickly. | 3573 // Fails unpredictably on IE[67] when mouse is dragged around quickly. |
2785 try { x = e.clientX - space.left; y = e.clientY - space.top; } | 3574 try { x = e.clientX - space.left; y = e.clientY - space.top; } |
2786 catch (e) { return null; } | 3575 catch (e) { return null; } |
2787 var coords = coordsChar(cm, x, y), line; | 3576 var coords = coordsChar(cm, x, y), line; |
2788 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text
).length == coords.ch) { | 3577 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text
).length == coords.ch) { |
2789 var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.le
ngth; | 3578 var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.le
ngth; |
2790 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display)
.left) / charWidth(cm.display)) - colDiff)); | 3579 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display)
.left) / charWidth(cm.display)) - colDiff)); |
2791 } | 3580 } |
2792 return coords; | 3581 return coords; |
2793 } | 3582 } |
2794 | 3583 |
2795 // A mouse down can be a single click, double click, triple click, | 3584 // A mouse down can be a single click, double click, triple click, |
2796 // start of selection drag, start of text drag, new cursor | 3585 // start of selection drag, start of text drag, new cursor |
2797 // (ctrl-click), rectangle drag (alt-drag), or xwin | 3586 // (ctrl-click), rectangle drag (alt-drag), or xwin |
2798 // middle-click-paste. Or it might be a click on something we should | 3587 // middle-click-paste. Or it might be a click on something we should |
2799 // not interfere with, such as a scrollbar or widget. | 3588 // not interfere with, such as a scrollbar or widget. |
2800 function onMouseDown(e) { | 3589 function onMouseDown(e) { |
2801 if (signalDOMEvent(this, e)) return; | |
2802 var cm = this, display = cm.display; | 3590 var cm = this, display = cm.display; |
| 3591 if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTo
uch()) return; |
2803 display.shift = e.shiftKey; | 3592 display.shift = e.shiftKey; |
2804 | 3593 |
2805 if (eventInWidget(display, e)) { | 3594 if (eventInWidget(display, e)) { |
2806 if (!webkit) { | 3595 if (!webkit) { |
2807 // Briefly turn off draggability, to allow widgets to do | 3596 // Briefly turn off draggability, to allow widgets to do |
2808 // normal dragging things. | 3597 // normal dragging things. |
2809 display.scroller.draggable = false; | 3598 display.scroller.draggable = false; |
2810 setTimeout(function(){display.scroller.draggable = true;}, 100); | 3599 setTimeout(function(){display.scroller.draggable = true;}, 100); |
2811 } | 3600 } |
2812 return; | 3601 return; |
2813 } | 3602 } |
2814 if (clickInGutter(cm, e)) return; | 3603 if (clickInGutter(cm, e)) return; |
2815 var start = posFromMouse(cm, e); | 3604 var start = posFromMouse(cm, e); |
2816 window.focus(); | 3605 window.focus(); |
2817 | 3606 |
2818 switch (e_button(e)) { | 3607 switch (e_button(e)) { |
2819 case 1: | 3608 case 1: |
2820 if (start) | 3609 // #3261: make sure, that we're not starting a second selection |
| 3610 if (cm.state.selectingText) |
| 3611 cm.state.selectingText(e); |
| 3612 else if (start) |
2821 leftButtonDown(cm, e, start); | 3613 leftButtonDown(cm, e, start); |
2822 else if (e_target(e) == display.scroller) | 3614 else if (e_target(e) == display.scroller) |
2823 e_preventDefault(e); | 3615 e_preventDefault(e); |
2824 break; | 3616 break; |
2825 case 2: | 3617 case 2: |
2826 if (webkit) cm.state.lastMiddleDown = +new Date; | 3618 if (webkit) cm.state.lastMiddleDown = +new Date; |
2827 if (start) extendSelection(cm.doc, start); | 3619 if (start) extendSelection(cm.doc, start); |
2828 setTimeout(bind(focusInput, cm), 20); | 3620 setTimeout(function() {display.input.focus();}, 20); |
2829 e_preventDefault(e); | 3621 e_preventDefault(e); |
2830 break; | 3622 break; |
2831 case 3: | 3623 case 3: |
2832 if (captureRightClick) onContextMenu(cm, e); | 3624 if (captureRightClick) onContextMenu(cm, e); |
| 3625 else delayBlurEvent(cm); |
2833 break; | 3626 break; |
2834 } | 3627 } |
2835 } | 3628 } |
2836 | 3629 |
2837 var lastClick, lastDoubleClick; | 3630 var lastClick, lastDoubleClick; |
2838 function leftButtonDown(cm, e, start) { | 3631 function leftButtonDown(cm, e, start) { |
2839 setTimeout(bind(ensureFocus, cm), 0); | 3632 if (ie) setTimeout(bind(ensureFocus, cm), 0); |
| 3633 else cm.curOp.focus = activeElt(); |
2840 | 3634 |
2841 var now = +new Date, type; | 3635 var now = +new Date, type; |
2842 if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleCli
ck.pos, start) == 0) { | 3636 if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleCli
ck.pos, start) == 0) { |
2843 type = "triple"; | 3637 type = "triple"; |
2844 } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, sta
rt) == 0) { | 3638 } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, sta
rt) == 0) { |
2845 type = "double"; | 3639 type = "double"; |
2846 lastDoubleClick = {time: now, pos: start}; | 3640 lastDoubleClick = {time: now, pos: start}; |
2847 } else { | 3641 } else { |
2848 type = "single"; | 3642 type = "single"; |
2849 lastClick = {time: now, pos: start}; | 3643 lastClick = {time: now, pos: start}; |
2850 } | 3644 } |
2851 | 3645 |
2852 var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; | 3646 var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; |
2853 if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && | 3647 if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && |
2854 type == "single" && (contained = sel.contains(start)) > -1 && | 3648 type == "single" && (contained = sel.contains(start)) > -1 && |
2855 !sel.ranges[contained].empty()) | 3649 (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRe
l > 0) && |
| 3650 (cmp(contained.to(), start) > 0 || start.xRel < 0)) |
2856 leftButtonStartDrag(cm, e, start, modifier); | 3651 leftButtonStartDrag(cm, e, start, modifier); |
2857 else | 3652 else |
2858 leftButtonSelect(cm, e, start, type, modifier); | 3653 leftButtonSelect(cm, e, start, type, modifier); |
2859 } | 3654 } |
2860 | 3655 |
2861 // Start a text drag. When it ends, see if any dragging actually | 3656 // Start a text drag. When it ends, see if any dragging actually |
2862 // happen, and treat as a click if it didn't. | 3657 // happen, and treat as a click if it didn't. |
2863 function leftButtonStartDrag(cm, e, start, modifier) { | 3658 function leftButtonStartDrag(cm, e, start, modifier) { |
2864 var display = cm.display; | 3659 var display = cm.display, startTime = +new Date; |
2865 var dragEnd = operation(cm, function(e2) { | 3660 var dragEnd = operation(cm, function(e2) { |
2866 if (webkit) display.scroller.draggable = false; | 3661 if (webkit) display.scroller.draggable = false; |
2867 cm.state.draggingText = false; | 3662 cm.state.draggingText = false; |
2868 off(document, "mouseup", dragEnd); | 3663 off(document, "mouseup", dragEnd); |
2869 off(display.scroller, "drop", dragEnd); | 3664 off(display.scroller, "drop", dragEnd); |
2870 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) <
10) { | 3665 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) <
10) { |
2871 e_preventDefault(e2); | 3666 e_preventDefault(e2); |
2872 if (!modifier) | 3667 if (!modifier && +new Date - 200 < startTime) |
2873 extendSelection(cm.doc, start); | 3668 extendSelection(cm.doc, start); |
2874 focusInput(cm); | 3669 // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3
081) |
2875 // Work around unexplainable focus problem in IE9 (#2127) | 3670 if (webkit || ie && ie_version == 9) |
2876 if (ie && ie_version == 9) | 3671 setTimeout(function() {document.body.focus(); display.input.focus();},
20); |
2877 setTimeout(function() {document.body.focus(); focusInput(cm);}, 20); | 3672 else |
| 3673 display.input.focus(); |
2878 } | 3674 } |
2879 }); | 3675 }); |
2880 // Let the drag handler handle this. | 3676 // Let the drag handler handle this. |
2881 if (webkit) display.scroller.draggable = true; | 3677 if (webkit) display.scroller.draggable = true; |
2882 cm.state.draggingText = dragEnd; | 3678 cm.state.draggingText = dragEnd; |
| 3679 dragEnd.copy = mac ? e.altKey : e.ctrlKey |
2883 // IE's approach to draggable | 3680 // IE's approach to draggable |
2884 if (display.scroller.dragDrop) display.scroller.dragDrop(); | 3681 if (display.scroller.dragDrop) display.scroller.dragDrop(); |
2885 on(document, "mouseup", dragEnd); | 3682 on(document, "mouseup", dragEnd); |
2886 on(display.scroller, "drop", dragEnd); | 3683 on(display.scroller, "drop", dragEnd); |
2887 } | 3684 } |
2888 | 3685 |
2889 // Normal selection, as opposed to text dragging. | 3686 // Normal selection, as opposed to text dragging. |
2890 function leftButtonSelect(cm, e, start, type, addNew) { | 3687 function leftButtonSelect(cm, e, start, type, addNew) { |
2891 var display = cm.display, doc = cm.doc; | 3688 var display = cm.display, doc = cm.doc; |
2892 e_preventDefault(e); | 3689 e_preventDefault(e); |
2893 | 3690 |
2894 var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; | 3691 var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; |
2895 if (addNew && !e.shiftKey) { | 3692 if (addNew && !e.shiftKey) { |
2896 ourIndex = doc.sel.contains(start); | 3693 ourIndex = doc.sel.contains(start); |
2897 if (ourIndex > -1) | 3694 if (ourIndex > -1) |
2898 ourRange = ranges[ourIndex]; | 3695 ourRange = ranges[ourIndex]; |
2899 else | 3696 else |
2900 ourRange = new Range(start, start); | 3697 ourRange = new Range(start, start); |
2901 } else { | 3698 } else { |
2902 ourRange = doc.sel.primary(); | 3699 ourRange = doc.sel.primary(); |
| 3700 ourIndex = doc.sel.primIndex; |
2903 } | 3701 } |
2904 | 3702 |
2905 if (e.altKey) { | 3703 if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) { |
2906 type = "rect"; | 3704 type = "rect"; |
2907 if (!addNew) ourRange = new Range(start, start); | 3705 if (!addNew) ourRange = new Range(start, start); |
2908 start = posFromMouse(cm, e, true, true); | 3706 start = posFromMouse(cm, e, true, true); |
2909 ourIndex = -1; | 3707 ourIndex = -1; |
2910 } else if (type == "double") { | 3708 } else if (type == "double") { |
2911 var word = cm.findWordAt(start); | 3709 var word = cm.findWordAt(start); |
2912 if (cm.display.shift || doc.extend) | 3710 if (cm.display.shift || doc.extend) |
2913 ourRange = extendRange(doc, ourRange, word.anchor, word.head); | 3711 ourRange = extendRange(doc, ourRange, word.anchor, word.head); |
2914 else | 3712 else |
2915 ourRange = word; | 3713 ourRange = word; |
2916 } else if (type == "triple") { | 3714 } else if (type == "triple") { |
2917 var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1,
0))); | 3715 var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1,
0))); |
2918 if (cm.display.shift || doc.extend) | 3716 if (cm.display.shift || doc.extend) |
2919 ourRange = extendRange(doc, ourRange, line.anchor, line.head); | 3717 ourRange = extendRange(doc, ourRange, line.anchor, line.head); |
2920 else | 3718 else |
2921 ourRange = line; | 3719 ourRange = line; |
2922 } else { | 3720 } else { |
2923 ourRange = extendRange(doc, ourRange, start); | 3721 ourRange = extendRange(doc, ourRange, start); |
2924 } | 3722 } |
2925 | 3723 |
2926 if (!addNew) { | 3724 if (!addNew) { |
2927 ourIndex = 0; | 3725 ourIndex = 0; |
2928 setSelection(doc, new Selection([ourRange], 0), sel_mouse); | 3726 setSelection(doc, new Selection([ourRange], 0), sel_mouse); |
2929 startSel = doc.sel; | 3727 startSel = doc.sel; |
2930 } else if (ourIndex == -1) { | 3728 } else if (ourIndex == -1) { |
2931 ourIndex = ranges.length; | 3729 ourIndex = ranges.length; |
2932 setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), | 3730 setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), |
2933 {scroll: false, origin: "*mouse"}); | 3731 {scroll: false, origin: "*mouse"}); |
2934 } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single"
) { | 3732 } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single"
&& !e.shiftKey) { |
2935 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(rang
es.slice(ourIndex + 1)), 0)); | 3733 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(rang
es.slice(ourIndex + 1)), 0), |
| 3734 {scroll: false, origin: "*mouse"}); |
2936 startSel = doc.sel; | 3735 startSel = doc.sel; |
2937 } else { | 3736 } else { |
2938 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); | 3737 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); |
2939 } | 3738 } |
2940 | 3739 |
2941 var lastPos = start; | 3740 var lastPos = start; |
2942 function extendTo(pos) { | 3741 function extendTo(pos) { |
2943 if (cmp(lastPos, pos) == 0) return; | 3742 if (cmp(lastPos, pos) == 0) return; |
2944 lastPos = pos; | 3743 lastPos = pos; |
2945 | 3744 |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2987 // happened in the meantime (clearTimeout isn't reliable -- at | 3786 // happened in the meantime (clearTimeout isn't reliable -- at |
2988 // least on Chrome, the timeouts still happen even when cleared, | 3787 // least on Chrome, the timeouts still happen even when cleared, |
2989 // if the clear happens after their scheduled firing time). | 3788 // if the clear happens after their scheduled firing time). |
2990 var counter = 0; | 3789 var counter = 0; |
2991 | 3790 |
2992 function extend(e) { | 3791 function extend(e) { |
2993 var curCount = ++counter; | 3792 var curCount = ++counter; |
2994 var cur = posFromMouse(cm, e, true, type == "rect"); | 3793 var cur = posFromMouse(cm, e, true, type == "rect"); |
2995 if (!cur) return; | 3794 if (!cur) return; |
2996 if (cmp(cur, lastPos) != 0) { | 3795 if (cmp(cur, lastPos) != 0) { |
2997 ensureFocus(cm); | 3796 cm.curOp.focus = activeElt(); |
2998 extendTo(cur); | 3797 extendTo(cur); |
2999 var visible = visibleLines(display, doc); | 3798 var visible = visibleLines(display, doc); |
3000 if (cur.line >= visible.to || cur.line < visible.from) | 3799 if (cur.line >= visible.to || cur.line < visible.from) |
3001 setTimeout(operation(cm, function(){if (counter == curCount) extend(e)
;}), 150); | 3800 setTimeout(operation(cm, function(){if (counter == curCount) extend(e)
;}), 150); |
3002 } else { | 3801 } else { |
3003 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.
bottom ? 20 : 0; | 3802 var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.
bottom ? 20 : 0; |
3004 if (outside) setTimeout(operation(cm, function() { | 3803 if (outside) setTimeout(operation(cm, function() { |
3005 if (counter != curCount) return; | 3804 if (counter != curCount) return; |
3006 display.scroller.scrollTop += outside; | 3805 display.scroller.scrollTop += outside; |
3007 extend(e); | 3806 extend(e); |
3008 }), 50); | 3807 }), 50); |
3009 } | 3808 } |
3010 } | 3809 } |
3011 | 3810 |
3012 function done(e) { | 3811 function done(e) { |
| 3812 cm.state.selectingText = false; |
3013 counter = Infinity; | 3813 counter = Infinity; |
3014 e_preventDefault(e); | 3814 e_preventDefault(e); |
3015 focusInput(cm); | 3815 display.input.focus(); |
3016 off(document, "mousemove", move); | 3816 off(document, "mousemove", move); |
3017 off(document, "mouseup", up); | 3817 off(document, "mouseup", up); |
3018 doc.history.lastSelOrigin = null; | 3818 doc.history.lastSelOrigin = null; |
3019 } | 3819 } |
3020 | 3820 |
3021 var move = operation(cm, function(e) { | 3821 var move = operation(cm, function(e) { |
3022 if (!e_button(e)) done(e); | 3822 if (!e_button(e)) done(e); |
3023 else extend(e); | 3823 else extend(e); |
3024 }); | 3824 }); |
3025 var up = operation(cm, done); | 3825 var up = operation(cm, done); |
| 3826 cm.state.selectingText = up; |
3026 on(document, "mousemove", move); | 3827 on(document, "mousemove", move); |
3027 on(document, "mouseup", up); | 3828 on(document, "mouseup", up); |
3028 } | 3829 } |
3029 | 3830 |
3030 // Determines whether an event happened in the gutter, and fires the | 3831 // Determines whether an event happened in the gutter, and fires the |
3031 // handlers for the corresponding event. | 3832 // handlers for the corresponding event. |
3032 function gutterEvent(cm, e, type, prevent, signalfn) { | 3833 function gutterEvent(cm, e, type, prevent) { |
3033 try { var mX = e.clientX, mY = e.clientY; } | 3834 try { var mX = e.clientX, mY = e.clientY; } |
3034 catch(e) { return false; } | 3835 catch(e) { return false; } |
3035 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) retu
rn false; | 3836 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) retu
rn false; |
3036 if (prevent) e_preventDefault(e); | 3837 if (prevent) e_preventDefault(e); |
3037 | 3838 |
3038 var display = cm.display; | 3839 var display = cm.display; |
3039 var lineBox = display.lineDiv.getBoundingClientRect(); | 3840 var lineBox = display.lineDiv.getBoundingClientRect(); |
3040 | 3841 |
3041 if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(
e); | 3842 if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(
e); |
3042 mY -= lineBox.top - display.viewOffset; | 3843 mY -= lineBox.top - display.viewOffset; |
3043 | 3844 |
3044 for (var i = 0; i < cm.options.gutters.length; ++i) { | 3845 for (var i = 0; i < cm.options.gutters.length; ++i) { |
3045 var g = display.gutters.childNodes[i]; | 3846 var g = display.gutters.childNodes[i]; |
3046 if (g && g.getBoundingClientRect().right >= mX) { | 3847 if (g && g.getBoundingClientRect().right >= mX) { |
3047 var line = lineAtHeight(cm.doc, mY); | 3848 var line = lineAtHeight(cm.doc, mY); |
3048 var gutter = cm.options.gutters[i]; | 3849 var gutter = cm.options.gutters[i]; |
3049 signalfn(cm, type, cm, line, gutter, e); | 3850 signal(cm, type, cm, line, gutter, e); |
3050 return e_defaultPrevented(e); | 3851 return e_defaultPrevented(e); |
3051 } | 3852 } |
3052 } | 3853 } |
3053 } | 3854 } |
3054 | 3855 |
3055 function clickInGutter(cm, e) { | 3856 function clickInGutter(cm, e) { |
3056 return gutterEvent(cm, e, "gutterClick", true, signalLater); | 3857 return gutterEvent(cm, e, "gutterClick", true); |
3057 } | 3858 } |
3058 | 3859 |
3059 // Kludge to work around strange IE behavior where it'll sometimes | 3860 // Kludge to work around strange IE behavior where it'll sometimes |
3060 // re-fire a series of drag-related events right after the drop (#1551) | 3861 // re-fire a series of drag-related events right after the drop (#1551) |
3061 var lastDrop = 0; | 3862 var lastDrop = 0; |
3062 | 3863 |
3063 function onDrop(e) { | 3864 function onDrop(e) { |
3064 var cm = this; | 3865 var cm = this; |
| 3866 clearDragCursor(cm); |
3065 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) | 3867 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) |
3066 return; | 3868 return; |
3067 e_preventDefault(e); | 3869 e_preventDefault(e); |
3068 if (ie) lastDrop = +new Date; | 3870 if (ie) lastDrop = +new Date; |
3069 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; | 3871 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; |
3070 if (!pos || isReadOnly(cm)) return; | 3872 if (!pos || cm.isReadOnly()) return; |
3071 // Might be a file drop, in which case we simply extract the text | 3873 // Might be a file drop, in which case we simply extract the text |
3072 // and insert it. | 3874 // and insert it. |
3073 if (files && files.length && window.FileReader && window.File) { | 3875 if (files && files.length && window.FileReader && window.File) { |
3074 var n = files.length, text = Array(n), read = 0; | 3876 var n = files.length, text = Array(n), read = 0; |
3075 var loadFile = function(file, i) { | 3877 var loadFile = function(file, i) { |
| 3878 if (cm.options.allowDropFileTypes && |
| 3879 indexOf(cm.options.allowDropFileTypes, file.type) == -1) |
| 3880 return; |
| 3881 |
3076 var reader = new FileReader; | 3882 var reader = new FileReader; |
3077 reader.onload = operation(cm, function() { | 3883 reader.onload = operation(cm, function() { |
3078 text[i] = reader.result; | 3884 var content = reader.result; |
| 3885 if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = ""; |
| 3886 text[i] = content; |
3079 if (++read == n) { | 3887 if (++read == n) { |
3080 pos = clipPos(cm.doc, pos); | 3888 pos = clipPos(cm.doc, pos); |
3081 var change = {from: pos, to: pos, text: splitLines(text.join("\n")),
origin: "paste"}; | 3889 var change = {from: pos, to: pos, |
| 3890 text: cm.doc.splitLines(text.join(cm.doc.lineSeparator
())), |
| 3891 origin: "paste"}; |
3082 makeChange(cm.doc, change); | 3892 makeChange(cm.doc, change); |
3083 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(ch
ange))); | 3893 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(ch
ange))); |
3084 } | 3894 } |
3085 }); | 3895 }); |
3086 reader.readAsText(file); | 3896 reader.readAsText(file); |
3087 }; | 3897 }; |
3088 for (var i = 0; i < n; ++i) loadFile(files[i], i); | 3898 for (var i = 0; i < n; ++i) loadFile(files[i], i); |
3089 } else { // Normal drop | 3899 } else { // Normal drop |
3090 // Don't do a replace if the drop happened inside of the selected text. | 3900 // Don't do a replace if the drop happened inside of the selected text. |
3091 if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { | 3901 if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { |
3092 cm.state.draggingText(e); | 3902 cm.state.draggingText(e); |
3093 // Ensure the editor is re-focused | 3903 // Ensure the editor is re-focused |
3094 setTimeout(bind(focusInput, cm), 20); | 3904 setTimeout(function() {cm.display.input.focus();}, 20); |
3095 return; | 3905 return; |
3096 } | 3906 } |
3097 try { | 3907 try { |
3098 var text = e.dataTransfer.getData("Text"); | 3908 var text = e.dataTransfer.getData("Text"); |
3099 if (text) { | 3909 if (text) { |
3100 if (cm.state.draggingText && !(mac ? e.metaKey : e.ctrlKey)) | 3910 if (cm.state.draggingText && !cm.state.draggingText.copy) |
3101 var selected = cm.listSelections(); | 3911 var selected = cm.listSelections(); |
3102 setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); | 3912 setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); |
3103 if (selected) for (var i = 0; i < selected.length; ++i) | 3913 if (selected) for (var i = 0; i < selected.length; ++i) |
3104 replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag
"); | 3914 replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag
"); |
3105 cm.replaceSelection(text, "around", "paste"); | 3915 cm.replaceSelection(text, "around", "paste"); |
3106 focusInput(cm); | 3916 cm.display.input.focus(); |
3107 } | 3917 } |
3108 } | 3918 } |
3109 catch(e){} | 3919 catch(e){} |
3110 } | 3920 } |
3111 } | 3921 } |
3112 | 3922 |
3113 function onDragStart(cm, e) { | 3923 function onDragStart(cm, e) { |
3114 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e
); return; } | 3924 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e
); return; } |
3115 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; | 3925 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; |
3116 | 3926 |
3117 e.dataTransfer.setData("Text", cm.getSelection()); | 3927 e.dataTransfer.setData("Text", cm.getSelection()); |
| 3928 e.dataTransfer.effectAllowed = "copyMove" |
3118 | 3929 |
3119 // Use dummy image instead of default browsers image. | 3930 // Use dummy image instead of default browsers image. |
3120 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so
we don't do it there. | 3931 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so
we don't do it there. |
3121 if (e.dataTransfer.setDragImage && !safari) { | 3932 if (e.dataTransfer.setDragImage && !safari) { |
3122 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); | 3933 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); |
3123 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAA
AICTAEAOw=="; | 3934 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAA
AICTAEAOw=="; |
3124 if (presto) { | 3935 if (presto) { |
3125 img.width = img.height = 1; | 3936 img.width = img.height = 1; |
3126 cm.display.wrapper.appendChild(img); | 3937 cm.display.wrapper.appendChild(img); |
3127 // Force a relayout, or Opera won't use our image for some obscure reaso
n | 3938 // Force a relayout, or Opera won't use our image for some obscure reaso
n |
3128 img._top = img.offsetTop; | 3939 img._top = img.offsetTop; |
3129 } | 3940 } |
3130 e.dataTransfer.setDragImage(img, 0, 0); | 3941 e.dataTransfer.setDragImage(img, 0, 0); |
3131 if (presto) img.parentNode.removeChild(img); | 3942 if (presto) img.parentNode.removeChild(img); |
3132 } | 3943 } |
3133 } | 3944 } |
3134 | 3945 |
| 3946 function onDragOver(cm, e) { |
| 3947 var pos = posFromMouse(cm, e); |
| 3948 if (!pos) return; |
| 3949 var frag = document.createDocumentFragment(); |
| 3950 drawSelectionCursor(cm, pos, frag); |
| 3951 if (!cm.display.dragCursor) { |
| 3952 cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dr
agcursors"); |
| 3953 cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursor
Div); |
| 3954 } |
| 3955 removeChildrenAndAdd(cm.display.dragCursor, frag); |
| 3956 } |
| 3957 |
| 3958 function clearDragCursor(cm) { |
| 3959 if (cm.display.dragCursor) { |
| 3960 cm.display.lineSpace.removeChild(cm.display.dragCursor); |
| 3961 cm.display.dragCursor = null; |
| 3962 } |
| 3963 } |
| 3964 |
3135 // SCROLL EVENTS | 3965 // SCROLL EVENTS |
3136 | 3966 |
3137 // Sync the scrollable area and scrollbars, ensure the viewport | 3967 // Sync the scrollable area and scrollbars, ensure the viewport |
3138 // covers the visible area. | 3968 // covers the visible area. |
3139 function setScrollTop(cm, val) { | 3969 function setScrollTop(cm, val) { |
3140 if (Math.abs(cm.doc.scrollTop - val) < 2) return; | 3970 if (Math.abs(cm.doc.scrollTop - val) < 2) return; |
3141 cm.doc.scrollTop = val; | 3971 cm.doc.scrollTop = val; |
3142 if (!gecko) updateDisplaySimple(cm, {top: val}); | 3972 if (!gecko) updateDisplaySimple(cm, {top: val}); |
3143 if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = va
l; | 3973 if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = va
l; |
3144 cm.display.scrollbars.setScrollTop(val); | 3974 cm.display.scrollbars.setScrollTop(val); |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3189 delta.x *= wheelPixelsPerUnit; | 4019 delta.x *= wheelPixelsPerUnit; |
3190 delta.y *= wheelPixelsPerUnit; | 4020 delta.y *= wheelPixelsPerUnit; |
3191 return delta; | 4021 return delta; |
3192 }; | 4022 }; |
3193 | 4023 |
3194 function onScrollWheel(cm, e) { | 4024 function onScrollWheel(cm, e) { |
3195 var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; | 4025 var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; |
3196 | 4026 |
3197 var display = cm.display, scroll = display.scroller; | 4027 var display = cm.display, scroll = display.scroller; |
3198 // Quit if there's nothing to scroll here | 4028 // Quit if there's nothing to scroll here |
3199 if (!(dx && scroll.scrollWidth > scroll.clientWidth || | 4029 var canScrollX = scroll.scrollWidth > scroll.clientWidth; |
3200 dy && scroll.scrollHeight > scroll.clientHeight)) return; | 4030 var canScrollY = scroll.scrollHeight > scroll.clientHeight; |
| 4031 if (!(dx && canScrollX || dy && canScrollY)) return; |
3201 | 4032 |
3202 // Webkit browsers on OS X abort momentum scrolls when the target | 4033 // Webkit browsers on OS X abort momentum scrolls when the target |
3203 // of the scroll event is removed from the scrollable element. | 4034 // of the scroll event is removed from the scrollable element. |
3204 // This hack (see related code in patchDisplay) makes sure the | 4035 // This hack (see related code in patchDisplay) makes sure the |
3205 // element is kept around. | 4036 // element is kept around. |
3206 if (dy && mac && webkit) { | 4037 if (dy && mac && webkit) { |
3207 outer: for (var cur = e.target, view = display.view; cur != scroll; cur =
cur.parentNode) { | 4038 outer: for (var cur = e.target, view = display.view; cur != scroll; cur =
cur.parentNode) { |
3208 for (var i = 0; i < view.length; i++) { | 4039 for (var i = 0; i < view.length; i++) { |
3209 if (view[i].node == cur) { | 4040 if (view[i].node == cur) { |
3210 cm.display.currentWheelTarget = cur; | 4041 cm.display.currentWheelTarget = cur; |
3211 break outer; | 4042 break outer; |
3212 } | 4043 } |
3213 } | 4044 } |
3214 } | 4045 } |
3215 } | 4046 } |
3216 | 4047 |
3217 // On some browsers, horizontal scrolling will cause redraws to | 4048 // On some browsers, horizontal scrolling will cause redraws to |
3218 // happen before the gutter has been realigned, causing it to | 4049 // happen before the gutter has been realigned, causing it to |
3219 // wriggle around in a most unseemly way. When we have an | 4050 // wriggle around in a most unseemly way. When we have an |
3220 // estimated pixels/delta value, we just handle horizontal | 4051 // estimated pixels/delta value, we just handle horizontal |
3221 // scrolling entirely here. It'll be slightly off from native, but | 4052 // scrolling entirely here. It'll be slightly off from native, but |
3222 // better than glitching out. | 4053 // better than glitching out. |
3223 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { | 4054 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { |
3224 if (dy) | 4055 if (dy && canScrollY) |
3225 setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixel
sPerUnit, scroll.scrollHeight - scroll.clientHeight))); | 4056 setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixel
sPerUnit, scroll.scrollHeight - scroll.clientHeight))); |
3226 setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixel
sPerUnit, scroll.scrollWidth - scroll.clientWidth))); | 4057 setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixel
sPerUnit, scroll.scrollWidth - scroll.clientWidth))); |
3227 e_preventDefault(e); | 4058 // Only prevent default scrolling if vertical scrolling is |
| 4059 // actually possible. Otherwise, it causes vertical scroll |
| 4060 // jitter on OSX trackpads when deltaX is small and deltaY |
| 4061 // is large (issue #3579) |
| 4062 if (!dy || (dy && canScrollY)) |
| 4063 e_preventDefault(e); |
3228 display.wheelStartX = null; // Abort measurement, if in progress | 4064 display.wheelStartX = null; // Abort measurement, if in progress |
3229 return; | 4065 return; |
3230 } | 4066 } |
3231 | 4067 |
3232 // 'Project' the visible viewport to cover the area that is being | 4068 // 'Project' the visible viewport to cover the area that is being |
3233 // scrolled into view (if we know enough to estimate it). | 4069 // scrolled into view (if we know enough to estimate it). |
3234 if (dy && wheelPixelsPerUnit != null) { | 4070 if (dy && wheelPixelsPerUnit != null) { |
3235 var pixels = dy * wheelPixelsPerUnit; | 4071 var pixels = dy * wheelPixelsPerUnit; |
3236 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; | 4072 var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; |
3237 if (pixels < 0) top = Math.max(0, top + pixels - 50); | 4073 if (pixels < 0) top = Math.max(0, top + pixels - 50); |
(...skipping 25 matching lines...) Expand all Loading... |
3263 // KEY EVENTS | 4099 // KEY EVENTS |
3264 | 4100 |
3265 // Run a handler that was bound to a key. | 4101 // Run a handler that was bound to a key. |
3266 function doHandleBinding(cm, bound, dropShift) { | 4102 function doHandleBinding(cm, bound, dropShift) { |
3267 if (typeof bound == "string") { | 4103 if (typeof bound == "string") { |
3268 bound = commands[bound]; | 4104 bound = commands[bound]; |
3269 if (!bound) return false; | 4105 if (!bound) return false; |
3270 } | 4106 } |
3271 // Ensure previous input has been read, so that the handler sees a | 4107 // Ensure previous input has been read, so that the handler sees a |
3272 // consistent view of the document | 4108 // consistent view of the document |
3273 if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false; | 4109 cm.display.input.ensurePolled(); |
3274 var prevShift = cm.display.shift, done = false; | 4110 var prevShift = cm.display.shift, done = false; |
3275 try { | 4111 try { |
3276 if (isReadOnly(cm)) cm.state.suppressEdits = true; | 4112 if (cm.isReadOnly()) cm.state.suppressEdits = true; |
3277 if (dropShift) cm.display.shift = false; | 4113 if (dropShift) cm.display.shift = false; |
3278 done = bound(cm) != Pass; | 4114 done = bound(cm) != Pass; |
3279 } finally { | 4115 } finally { |
3280 cm.display.shift = prevShift; | 4116 cm.display.shift = prevShift; |
3281 cm.state.suppressEdits = false; | 4117 cm.state.suppressEdits = false; |
3282 } | 4118 } |
3283 return done; | 4119 return done; |
3284 } | 4120 } |
3285 | 4121 |
3286 function lookupKeyForEditor(cm, name, handle) { | 4122 function lookupKeyForEditor(cm, name, handle) { |
3287 for (var i = 0; i < cm.state.keyMaps.length; i++) { | 4123 for (var i = 0; i < cm.state.keyMaps.length; i++) { |
3288 var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); | 4124 var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); |
3289 if (result) return result; | 4125 if (result) return result; |
3290 } | 4126 } |
3291 return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle
, cm)) | 4127 return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle
, cm)) |
3292 || lookupKey(name, cm.options.keyMap, handle, cm); | 4128 || lookupKey(name, cm.options.keyMap, handle, cm); |
3293 } | 4129 } |
3294 | 4130 |
3295 var stopSeq = new Delayed; | 4131 var stopSeq = new Delayed; |
3296 function dispatchKey(cm, name, e, handle) { | 4132 function dispatchKey(cm, name, e, handle) { |
3297 var seq = cm.state.keySeq; | 4133 var seq = cm.state.keySeq; |
3298 if (seq) { | 4134 if (seq) { |
3299 if (isModifierKey(name)) return "handled"; | 4135 if (isModifierKey(name)) return "handled"; |
3300 stopSeq.set(50, function() { | 4136 stopSeq.set(50, function() { |
3301 if (cm.state.keySeq == seq) { | 4137 if (cm.state.keySeq == seq) { |
3302 cm.state.keySeq = null; | 4138 cm.state.keySeq = null; |
3303 resetInput(cm); | 4139 cm.display.input.reset(); |
3304 } | 4140 } |
3305 }); | 4141 }); |
3306 name = seq + " " + name; | 4142 name = seq + " " + name; |
3307 } | 4143 } |
3308 var result = lookupKeyForEditor(cm, name, handle); | 4144 var result = lookupKeyForEditor(cm, name, handle); |
3309 | 4145 |
3310 if (result == "multi") | 4146 if (result == "multi") |
3311 cm.state.keySeq = name; | 4147 cm.state.keySeq = name; |
3312 if (result == "handled") | 4148 if (result == "handled") |
3313 signalLater(cm, "keyHandled", cm, name, e); | 4149 signalLater(cm, "keyHandled", cm, name, e); |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3345 | 4181 |
3346 // Handle a key from the keypress event | 4182 // Handle a key from the keypress event |
3347 function handleCharBinding(cm, e, ch) { | 4183 function handleCharBinding(cm, e, ch) { |
3348 return dispatchKey(cm, "'" + ch + "'", e, | 4184 return dispatchKey(cm, "'" + ch + "'", e, |
3349 function(b) { return doHandleBinding(cm, b, true); }); | 4185 function(b) { return doHandleBinding(cm, b, true); }); |
3350 } | 4186 } |
3351 | 4187 |
3352 var lastStoppedKey = null; | 4188 var lastStoppedKey = null; |
3353 function onKeyDown(e) { | 4189 function onKeyDown(e) { |
3354 var cm = this; | 4190 var cm = this; |
3355 ensureFocus(cm); | 4191 cm.curOp.focus = activeElt(); |
3356 if (signalDOMEvent(cm, e)) return; | 4192 if (signalDOMEvent(cm, e)) return; |
3357 // IE does strange things with escape. | 4193 // IE does strange things with escape. |
3358 if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; | 4194 if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; |
3359 var code = e.keyCode; | 4195 var code = e.keyCode; |
3360 cm.display.shift = code == 16 || e.shiftKey; | 4196 cm.display.shift = code == 16 || e.shiftKey; |
3361 var handled = handleKeyBinding(cm, e); | 4197 var handled = handleKeyBinding(cm, e); |
3362 if (presto) { | 4198 if (presto) { |
3363 lastStoppedKey = handled ? code : null; | 4199 lastStoppedKey = handled ? code : null; |
3364 // Opera has no cut event... we try to at least catch the key combo | 4200 // Opera has no cut event... we try to at least catch the key combo |
3365 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKe
y)) | 4201 if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKe
y)) |
(...skipping 20 matching lines...) Expand all Loading... |
3386 on(document, "mouseover", up); | 4222 on(document, "mouseover", up); |
3387 } | 4223 } |
3388 | 4224 |
3389 function onKeyUp(e) { | 4225 function onKeyUp(e) { |
3390 if (e.keyCode == 16) this.doc.sel.shift = false; | 4226 if (e.keyCode == 16) this.doc.sel.shift = false; |
3391 signalDOMEvent(this, e); | 4227 signalDOMEvent(this, e); |
3392 } | 4228 } |
3393 | 4229 |
3394 function onKeyPress(e) { | 4230 function onKeyPress(e) { |
3395 var cm = this; | 4231 var cm = this; |
3396 if (signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) ret
urn; | 4232 if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e
.altKey || mac && e.metaKey) return; |
3397 var keyCode = e.keyCode, charCode = e.charCode; | 4233 var keyCode = e.keyCode, charCode = e.charCode; |
3398 if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDe
fault(e); return;} | 4234 if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDe
fault(e); return;} |
3399 if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm
, e)) return; | 4235 if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) retur
n; |
3400 var ch = String.fromCharCode(charCode == null ? keyCode : charCode); | 4236 var ch = String.fromCharCode(charCode == null ? keyCode : charCode); |
3401 if (handleCharBinding(cm, e, ch)) return; | 4237 if (handleCharBinding(cm, e, ch)) return; |
3402 if (ie && ie_version >= 9) cm.display.inputHasSelection = null; | 4238 cm.display.input.onKeyPress(e); |
3403 fastPoll(cm); | |
3404 } | 4239 } |
3405 | 4240 |
3406 // FOCUS/BLUR EVENTS | 4241 // FOCUS/BLUR EVENTS |
3407 | 4242 |
| 4243 function delayBlurEvent(cm) { |
| 4244 cm.state.delayingBlurEvent = true; |
| 4245 setTimeout(function() { |
| 4246 if (cm.state.delayingBlurEvent) { |
| 4247 cm.state.delayingBlurEvent = false; |
| 4248 onBlur(cm); |
| 4249 } |
| 4250 }, 100); |
| 4251 } |
| 4252 |
3408 function onFocus(cm) { | 4253 function onFocus(cm) { |
| 4254 if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false; |
| 4255 |
3409 if (cm.options.readOnly == "nocursor") return; | 4256 if (cm.options.readOnly == "nocursor") return; |
3410 if (!cm.state.focused) { | 4257 if (!cm.state.focused) { |
3411 signal(cm, "focus", cm); | 4258 signal(cm, "focus", cm); |
3412 cm.state.focused = true; | 4259 cm.state.focused = true; |
3413 addClass(cm.display.wrapper, "CodeMirror-focused"); | 4260 addClass(cm.display.wrapper, "CodeMirror-focused"); |
3414 // The prevInput test prevents this from firing when a context | 4261 // This test prevents this from firing when a context |
3415 // menu is closed (since the resetInput would kill the | 4262 // menu is closed (since the input reset would kill the |
3416 // select-all detection hack) | 4263 // select-all detection hack) |
3417 if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { | 4264 if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { |
3418 resetInput(cm); | 4265 cm.display.input.reset(); |
3419 if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730 | 4266 if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20)
; // Issue #1730 |
3420 } | 4267 } |
| 4268 cm.display.input.receivedFocus(); |
3421 } | 4269 } |
3422 slowPoll(cm); | |
3423 restartBlink(cm); | 4270 restartBlink(cm); |
3424 } | 4271 } |
3425 function onBlur(cm) { | 4272 function onBlur(cm) { |
| 4273 if (cm.state.delayingBlurEvent) return; |
| 4274 |
3426 if (cm.state.focused) { | 4275 if (cm.state.focused) { |
3427 signal(cm, "blur", cm); | 4276 signal(cm, "blur", cm); |
3428 cm.state.focused = false; | 4277 cm.state.focused = false; |
3429 rmClass(cm.display.wrapper, "CodeMirror-focused"); | 4278 rmClass(cm.display.wrapper, "CodeMirror-focused"); |
3430 } | 4279 } |
3431 clearInterval(cm.display.blinker); | 4280 clearInterval(cm.display.blinker); |
3432 setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 15
0); | 4281 setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 15
0); |
3433 } | 4282 } |
3434 | 4283 |
3435 // CONTEXT MENU HANDLING | 4284 // CONTEXT MENU HANDLING |
3436 | 4285 |
3437 // To make the context menu work, we need to briefly unhide the | 4286 // To make the context menu work, we need to briefly unhide the |
3438 // textarea (making it as unobtrusive as possible) to let the | 4287 // textarea (making it as unobtrusive as possible) to let the |
3439 // right-click take effect on it. | 4288 // right-click take effect on it. |
3440 function onContextMenu(cm, e) { | 4289 function onContextMenu(cm, e) { |
| 4290 if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return; |
3441 if (signalDOMEvent(cm, e, "contextmenu")) return; | 4291 if (signalDOMEvent(cm, e, "contextmenu")) return; |
3442 var display = cm.display; | 4292 cm.display.input.onContextMenu(e); |
3443 if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return; | |
3444 | |
3445 var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; | |
3446 if (!pos || presto) return; // Opera is difficult. | |
3447 | |
3448 // Reset the current text selection only if the click is done outside of the
selection | |
3449 // and 'resetSelectionOnContextMenu' option is true. | |
3450 var reset = cm.options.resetSelectionOnContextMenu; | |
3451 if (reset && cm.doc.sel.contains(pos) == -1) | |
3452 operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); | |
3453 | |
3454 var oldCSS = display.input.style.cssText; | |
3455 display.inputDiv.style.position = "absolute"; | |
3456 display.input.style.cssText = "position: fixed; width: 30px; height: 30px; t
op: " + (e.clientY - 5) + | |
3457 "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + | |
3458 (ie ? "rgba(255, 255, 255, .05)" : "transparent") + | |
3459 "; outline: none; border-width: 0; outline: none; overflow: hidden; opacit
y: .05; filter: alpha(opacity=5);"; | |
3460 if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2
712) | |
3461 focusInput(cm); | |
3462 if (webkit) window.scrollTo(null, oldScrollY); | |
3463 resetInput(cm); | |
3464 // Adds "Select all" to context menu in FF | |
3465 if (!cm.somethingSelected()) display.input.value = display.prevInput = " "; | |
3466 display.contextMenuPending = true; | |
3467 display.selForContextMenu = cm.doc.sel; | |
3468 clearTimeout(display.detectingSelectAll); | |
3469 | |
3470 // Select-all will be greyed out if there's nothing to select, so | |
3471 // this adds a zero-width space so that we can later check whether | |
3472 // it got selected. | |
3473 function prepareSelectAllHack() { | |
3474 if (display.input.selectionStart != null) { | |
3475 var selected = cm.somethingSelected(); | |
3476 var extval = display.input.value = "\u200b" + (selected ? display.input.
value : ""); | |
3477 display.prevInput = selected ? "" : "\u200b"; | |
3478 display.input.selectionStart = 1; display.input.selectionEnd = extval.le
ngth; | |
3479 // Re-set this, in case some other handler touched the | |
3480 // selection in the meantime. | |
3481 display.selForContextMenu = cm.doc.sel; | |
3482 } | |
3483 } | |
3484 function rehide() { | |
3485 display.contextMenuPending = false; | |
3486 display.inputDiv.style.position = "relative"; | |
3487 display.input.style.cssText = oldCSS; | |
3488 if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller
.scrollTop = scrollPos); | |
3489 slowPoll(cm); | |
3490 | |
3491 // Try to detect the user choosing select-all | |
3492 if (display.input.selectionStart != null) { | |
3493 if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); | |
3494 var i = 0, poll = function() { | |
3495 if (display.selForContextMenu == cm.doc.sel && display.input.selection
Start == 0) | |
3496 operation(cm, commands.selectAll)(cm); | |
3497 else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500); | |
3498 else resetInput(cm); | |
3499 }; | |
3500 display.detectingSelectAll = setTimeout(poll, 200); | |
3501 } | |
3502 } | |
3503 | |
3504 if (ie && ie_version >= 9) prepareSelectAllHack(); | |
3505 if (captureRightClick) { | |
3506 e_stop(e); | |
3507 var mouseup = function() { | |
3508 off(window, "mouseup", mouseup); | |
3509 setTimeout(rehide, 20); | |
3510 }; | |
3511 on(window, "mouseup", mouseup); | |
3512 } else { | |
3513 setTimeout(rehide, 50); | |
3514 } | |
3515 } | 4293 } |
3516 | 4294 |
3517 function contextMenuInGutter(cm, e) { | 4295 function contextMenuInGutter(cm, e) { |
3518 if (!hasHandler(cm, "gutterContextMenu")) return false; | 4296 if (!hasHandler(cm, "gutterContextMenu")) return false; |
3519 return gutterEvent(cm, e, "gutterContextMenu", false, signal); | 4297 return gutterEvent(cm, e, "gutterContextMenu", false); |
3520 } | 4298 } |
3521 | 4299 |
3522 // UPDATING | 4300 // UPDATING |
3523 | 4301 |
3524 // Compute the position of the end of a change (its 'to' property | 4302 // Compute the position of the end of a change (its 'to' property |
3525 // refers to the pre-change end). | 4303 // refers to the pre-change end). |
3526 var changeEnd = CodeMirror.changeEnd = function(change) { | 4304 var changeEnd = CodeMirror.changeEnd = function(change) { |
3527 if (!change.text) return change.to; | 4305 if (!change.text) return change.to; |
3528 return Pos(change.from.line + change.text.length - 1, | 4306 return Pos(change.from.line + change.text.length - 1, |
3529 lst(change.text).length + (change.text.length == 1 ? change.from.
ch : 0)); | 4307 lst(change.text).length + (change.text.length == 1 ? change.from.
ch : 0)); |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3637 if (!sharedHist && indexOf(rebased, doc.history) == -1) { | 4415 if (!sharedHist && indexOf(rebased, doc.history) == -1) { |
3638 rebaseHist(doc.history, change); | 4416 rebaseHist(doc.history, change); |
3639 rebased.push(doc.history); | 4417 rebased.push(doc.history); |
3640 } | 4418 } |
3641 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)
); | 4419 makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)
); |
3642 }); | 4420 }); |
3643 } | 4421 } |
3644 | 4422 |
3645 // Revert a change stored in a document's history. | 4423 // Revert a change stored in a document's history. |
3646 function makeChangeFromHistory(doc, type, allowSelectionOnly) { | 4424 function makeChangeFromHistory(doc, type, allowSelectionOnly) { |
3647 if (doc.cm && doc.cm.state.suppressEdits) return; | 4425 if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) return; |
3648 | 4426 |
3649 var hist = doc.history, event, selAfter = doc.sel; | 4427 var hist = doc.history, event, selAfter = doc.sel; |
3650 var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo"
? hist.undone : hist.done; | 4428 var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo"
? hist.undone : hist.done; |
3651 | 4429 |
3652 // Verify that there is a useable event (so that ctrl-z won't | 4430 // Verify that there is a useable event (so that ctrl-z won't |
3653 // needlessly clear selection events) | 4431 // needlessly clear selection events) |
3654 for (var i = 0; i < source.length; i++) { | 4432 for (var i = 0; i < source.length; i++) { |
3655 event = source[i]; | 4433 event = source[i]; |
3656 if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.r
anges) | 4434 if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.r
anges) |
3657 break; | 4435 break; |
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3813 }; | 4591 }; |
3814 if (changeHandler) signalLater(cm, "change", cm, obj); | 4592 if (changeHandler) signalLater(cm, "change", cm, obj); |
3815 if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).pu
sh(obj); | 4593 if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).pu
sh(obj); |
3816 } | 4594 } |
3817 cm.display.selForContextMenu = null; | 4595 cm.display.selForContextMenu = null; |
3818 } | 4596 } |
3819 | 4597 |
3820 function replaceRange(doc, code, from, to, origin) { | 4598 function replaceRange(doc, code, from, to, origin) { |
3821 if (!to) to = from; | 4599 if (!to) to = from; |
3822 if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } | 4600 if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } |
3823 if (typeof code == "string") code = splitLines(code); | 4601 if (typeof code == "string") code = doc.splitLines(code); |
3824 makeChange(doc, {from: from, to: to, text: code, origin: origin}); | 4602 makeChange(doc, {from: from, to: to, text: code, origin: origin}); |
3825 } | 4603 } |
3826 | 4604 |
3827 // SCROLLING THINGS INTO VIEW | 4605 // SCROLLING THINGS INTO VIEW |
3828 | 4606 |
3829 // If an editor sits on the top or bottom of the window, partially | 4607 // If an editor sits on the top or bottom of the window, partially |
3830 // scrolled out of view, this ensures that the cursor is visible. | 4608 // scrolled out of view, this ensures that the cursor is visible. |
3831 function maybeScrollWindow(cm, coords) { | 4609 function maybeScrollWindow(cm, coords) { |
3832 if (signalDOMEvent(cm, "scrollCursorIntoView")) return; | 4610 if (signalDOMEvent(cm, "scrollCursorIntoView")) return; |
3833 | 4611 |
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3992 } | 4770 } |
3993 indentation = Math.max(0, indentation); | 4771 indentation = Math.max(0, indentation); |
3994 | 4772 |
3995 var indentString = "", pos = 0; | 4773 var indentString = "", pos = 0; |
3996 if (cm.options.indentWithTabs) | 4774 if (cm.options.indentWithTabs) |
3997 for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; i
ndentString += "\t";} | 4775 for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; i
ndentString += "\t";} |
3998 if (pos < indentation) indentString += spaceStr(indentation - pos); | 4776 if (pos < indentation) indentString += spaceStr(indentation - pos); |
3999 | 4777 |
4000 if (indentString != curSpaceString) { | 4778 if (indentString != curSpaceString) { |
4001 replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length),
"+input"); | 4779 replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length),
"+input"); |
| 4780 line.stateAfter = null; |
| 4781 return true; |
4002 } else { | 4782 } else { |
4003 // Ensure that, if the cursor was in the whitespace at the start | 4783 // Ensure that, if the cursor was in the whitespace at the start |
4004 // of the line, it is moved to the end of that space. | 4784 // of the line, it is moved to the end of that space. |
4005 for (var i = 0; i < doc.sel.ranges.length; i++) { | 4785 for (var i = 0; i < doc.sel.ranges.length; i++) { |
4006 var range = doc.sel.ranges[i]; | 4786 var range = doc.sel.ranges[i]; |
4007 if (range.head.line == n && range.head.ch < curSpaceString.length) { | 4787 if (range.head.line == n && range.head.ch < curSpaceString.length) { |
4008 var pos = Pos(n, curSpaceString.length); | 4788 var pos = Pos(n, curSpaceString.length); |
4009 replaceOneSelection(doc, i, new Range(pos, pos)); | 4789 replaceOneSelection(doc, i, new Range(pos, pos)); |
4010 break; | 4790 break; |
4011 } | 4791 } |
4012 } | 4792 } |
4013 } | 4793 } |
4014 line.stateAfter = null; | |
4015 } | 4794 } |
4016 | 4795 |
4017 // Utility for applying a change to a line by handle or number, | 4796 // Utility for applying a change to a line by handle or number, |
4018 // returning the number and optionally registering the line as | 4797 // returning the number and optionally registering the line as |
4019 // changed. | 4798 // changed. |
4020 function changeLine(doc, handle, changeType, op) { | 4799 function changeLine(doc, handle, changeType, op) { |
4021 var no = handle, line = handle; | 4800 var no = handle, line = handle; |
4022 if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); | 4801 if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); |
4023 else no = lineNo(handle); | 4802 else no = lineNo(handle); |
4024 if (no == null) return null; | 4803 if (no == null) return null; |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4056 // cross line boundaries), "word" (across next word), or "group" (to | 4835 // cross line boundaries), "word" (across next word), or "group" (to |
4057 // the start of next group of word or non-word-non-whitespace | 4836 // the start of next group of word or non-word-non-whitespace |
4058 // chars). The visually param controls whether, in right-to-left | 4837 // chars). The visually param controls whether, in right-to-left |
4059 // text, direction 1 means to move towards the next index in the | 4838 // text, direction 1 means to move towards the next index in the |
4060 // string, or towards the character to the right of the current | 4839 // string, or towards the character to the right of the current |
4061 // position. The resulting position will have a hitSide=true | 4840 // position. The resulting position will have a hitSide=true |
4062 // property if it reached the end of the document. | 4841 // property if it reached the end of the document. |
4063 function findPosH(doc, pos, dir, unit, visually) { | 4842 function findPosH(doc, pos, dir, unit, visually) { |
4064 var line = pos.line, ch = pos.ch, origDir = dir; | 4843 var line = pos.line, ch = pos.ch, origDir = dir; |
4065 var lineObj = getLine(doc, line); | 4844 var lineObj = getLine(doc, line); |
4066 var possible = true; | |
4067 function findNextLine() { | 4845 function findNextLine() { |
4068 var l = line + dir; | 4846 var l = line + dir; |
4069 if (l < doc.first || l >= doc.first + doc.size) return (possible = false); | 4847 if (l < doc.first || l >= doc.first + doc.size) return false |
4070 line = l; | 4848 line = l; |
4071 return lineObj = getLine(doc, l); | 4849 return lineObj = getLine(doc, l); |
4072 } | 4850 } |
4073 function moveOnce(boundToLine) { | 4851 function moveOnce(boundToLine) { |
4074 var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, tru
e); | 4852 var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, tru
e); |
4075 if (next == null) { | 4853 if (next == null) { |
4076 if (!boundToLine && findNextLine()) { | 4854 if (!boundToLine && findNextLine()) { |
4077 if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); | 4855 if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); |
4078 else ch = dir < 0 ? lineObj.text.length : 0; | 4856 else ch = dir < 0 ? lineObj.text.length : 0; |
4079 } else return (possible = false); | 4857 } else return false |
4080 } else ch = next; | 4858 } else ch = next; |
4081 return true; | 4859 return true; |
4082 } | 4860 } |
4083 | 4861 |
4084 if (unit == "char") moveOnce(); | 4862 if (unit == "char") { |
4085 else if (unit == "column") moveOnce(true); | 4863 moveOnce() |
4086 else if (unit == "word" || unit == "group") { | 4864 } else if (unit == "column") { |
| 4865 moveOnce(true) |
| 4866 } else if (unit == "word" || unit == "group") { |
4087 var sawType = null, group = unit == "group"; | 4867 var sawType = null, group = unit == "group"; |
4088 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); | 4868 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); |
4089 for (var first = true;; first = false) { | 4869 for (var first = true;; first = false) { |
4090 if (dir < 0 && !moveOnce(!first)) break; | 4870 if (dir < 0 && !moveOnce(!first)) break; |
4091 var cur = lineObj.text.charAt(ch) || "\n"; | 4871 var cur = lineObj.text.charAt(ch) || "\n"; |
4092 var type = isWordChar(cur, helper) ? "w" | 4872 var type = isWordChar(cur, helper) ? "w" |
4093 : group && cur == "\n" ? "n" | 4873 : group && cur == "\n" ? "n" |
4094 : !group || /\s/.test(cur) ? null | 4874 : !group || /\s/.test(cur) ? null |
4095 : "p"; | 4875 : "p"; |
4096 if (group && !first && !type) type = "s"; | 4876 if (group && !first && !type) type = "s"; |
4097 if (sawType && sawType != type) { | 4877 if (sawType && sawType != type) { |
4098 if (dir < 0) {dir = 1; moveOnce();} | 4878 if (dir < 0) {dir = 1; moveOnce();} |
4099 break; | 4879 break; |
4100 } | 4880 } |
4101 | 4881 |
4102 if (type) sawType = type; | 4882 if (type) sawType = type; |
4103 if (dir > 0 && !moveOnce(!first)) break; | 4883 if (dir > 0 && !moveOnce(!first)) break; |
4104 } | 4884 } |
4105 } | 4885 } |
4106 var result = skipAtomic(doc, Pos(line, ch), origDir, true); | 4886 var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true); |
4107 if (!possible) result.hitSide = true; | 4887 if (!cmp(pos, result)) result.hitSide = true; |
4108 return result; | 4888 return result; |
4109 } | 4889 } |
4110 | 4890 |
4111 // For relative vertical movement. Dir may be -1 or 1. Unit can be | 4891 // For relative vertical movement. Dir may be -1 or 1. Unit can be |
4112 // "page" or "line". The resulting position will have a hitSide=true | 4892 // "page" or "line". The resulting position will have a hitSide=true |
4113 // property if it reached the end of the document. | 4893 // property if it reached the end of the document. |
4114 function findPosV(cm, pos, dir, unit) { | 4894 function findPosV(cm, pos, dir, unit) { |
4115 var doc = cm.doc, x = pos.left, y; | 4895 var doc = cm.doc, x = pos.left, y; |
4116 if (unit == "page") { | 4896 if (unit == "page") { |
4117 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeigh
t || document.documentElement.clientHeight); | 4897 var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeigh
t || document.documentElement.clientHeight); |
(...skipping 15 matching lines...) Expand all Loading... |
4133 // The publicly visible API. Note that methodOp(f) means | 4913 // The publicly visible API. Note that methodOp(f) means |
4134 // 'wrap f in an operation, performed on its `this` parameter'. | 4914 // 'wrap f in an operation, performed on its `this` parameter'. |
4135 | 4915 |
4136 // This is not the complete set of editor methods. Most of the | 4916 // This is not the complete set of editor methods. Most of the |
4137 // methods defined on the Doc type are also injected into | 4917 // methods defined on the Doc type are also injected into |
4138 // CodeMirror.prototype, for backwards compatibility and | 4918 // CodeMirror.prototype, for backwards compatibility and |
4139 // convenience. | 4919 // convenience. |
4140 | 4920 |
4141 CodeMirror.prototype = { | 4921 CodeMirror.prototype = { |
4142 constructor: CodeMirror, | 4922 constructor: CodeMirror, |
4143 focus: function(){window.focus(); focusInput(this); fastPoll(this);}, | 4923 focus: function(){window.focus(); this.display.input.focus();}, |
4144 | 4924 |
4145 setOption: function(option, value) { | 4925 setOption: function(option, value) { |
4146 var options = this.options, old = options[option]; | 4926 var options = this.options, old = options[option]; |
4147 if (options[option] == value && option != "mode") return; | 4927 if (options[option] == value && option != "mode") return; |
4148 options[option] = value; | 4928 options[option] = value; |
4149 if (optionHandlers.hasOwnProperty(option)) | 4929 if (optionHandlers.hasOwnProperty(option)) |
4150 operation(this, optionHandlers[option])(this, value, old); | 4930 operation(this, optionHandlers[option])(this, value, old); |
4151 }, | 4931 }, |
4152 | 4932 |
4153 getOption: function(option) {return this.options[option];}, | 4933 getOption: function(option) {return this.options[option];}, |
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4244 if (!mode.innerMode) return mode; | 5024 if (!mode.innerMode) return mode; |
4245 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; | 5025 return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; |
4246 }, | 5026 }, |
4247 | 5027 |
4248 getHelper: function(pos, type) { | 5028 getHelper: function(pos, type) { |
4249 return this.getHelpers(pos, type)[0]; | 5029 return this.getHelpers(pos, type)[0]; |
4250 }, | 5030 }, |
4251 | 5031 |
4252 getHelpers: function(pos, type) { | 5032 getHelpers: function(pos, type) { |
4253 var found = []; | 5033 var found = []; |
4254 if (!helpers.hasOwnProperty(type)) return helpers; | 5034 if (!helpers.hasOwnProperty(type)) return found; |
4255 var help = helpers[type], mode = this.getModeAt(pos); | 5035 var help = helpers[type], mode = this.getModeAt(pos); |
4256 if (typeof mode[type] == "string") { | 5036 if (typeof mode[type] == "string") { |
4257 if (help[mode[type]]) found.push(help[mode[type]]); | 5037 if (help[mode[type]]) found.push(help[mode[type]]); |
4258 } else if (mode[type]) { | 5038 } else if (mode[type]) { |
4259 for (var i = 0; i < mode[type].length; i++) { | 5039 for (var i = 0; i < mode[type].length; i++) { |
4260 var val = help[mode[type][i]]; | 5040 var val = help[mode[type][i]]; |
4261 if (val) found.push(val); | 5041 if (val) found.push(val); |
4262 } | 5042 } |
4263 } else if (mode.helperType && help[mode.helperType]) { | 5043 } else if (mode.helperType && help[mode.helperType]) { |
4264 found.push(help[mode.helperType]); | 5044 found.push(help[mode.helperType]); |
(...skipping 29 matching lines...) Expand all Loading... |
4294 coordsChar: function(coords, mode) { | 5074 coordsChar: function(coords, mode) { |
4295 coords = fromCoordSystem(this, coords, mode || "page"); | 5075 coords = fromCoordSystem(this, coords, mode || "page"); |
4296 return coordsChar(this, coords.left, coords.top); | 5076 return coordsChar(this, coords.left, coords.top); |
4297 }, | 5077 }, |
4298 | 5078 |
4299 lineAtHeight: function(height, mode) { | 5079 lineAtHeight: function(height, mode) { |
4300 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top
; | 5080 height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top
; |
4301 return lineAtHeight(this.doc, height + this.display.viewOffset); | 5081 return lineAtHeight(this.doc, height + this.display.viewOffset); |
4302 }, | 5082 }, |
4303 heightAtLine: function(line, mode) { | 5083 heightAtLine: function(line, mode) { |
4304 var end = false, last = this.doc.first + this.doc.size - 1; | 5084 var end = false, lineObj; |
4305 if (line < this.doc.first) line = this.doc.first; | 5085 if (typeof line == "number") { |
4306 else if (line > last) { line = last; end = true; } | 5086 var last = this.doc.first + this.doc.size - 1; |
4307 var lineObj = getLine(this.doc, line); | 5087 if (line < this.doc.first) line = this.doc.first; |
| 5088 else if (line > last) { line = last; end = true; } |
| 5089 lineObj = getLine(this.doc, line); |
| 5090 } else { |
| 5091 lineObj = line; |
| 5092 } |
4308 return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").t
op + | 5093 return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").t
op + |
4309 (end ? this.doc.height - heightAtLine(lineObj) : 0); | 5094 (end ? this.doc.height - heightAtLine(lineObj) : 0); |
4310 }, | 5095 }, |
4311 | 5096 |
4312 defaultTextHeight: function() { return textHeight(this.display); }, | 5097 defaultTextHeight: function() { return textHeight(this.display); }, |
4313 defaultCharWidth: function() { return charWidth(this.display); }, | 5098 defaultCharWidth: function() { return charWidth(this.display); }, |
4314 | 5099 |
4315 setGutterMarker: methodOp(function(line, gutterID, value) { | 5100 setGutterMarker: methodOp(function(line, gutterID, value) { |
4316 return changeLine(this.doc, line, "gutter", function(line) { | 5101 return changeLine(this.doc, line, "gutter", function(line) { |
4317 var markers = line.gutterMarkers || (line.gutterMarkers = {}); | 5102 var markers = line.gutterMarkers || (line.gutterMarkers = {}); |
4318 markers[gutterID] = value; | 5103 markers[gutterID] = value; |
4319 if (!value && isEmpty(markers)) line.gutterMarkers = null; | 5104 if (!value && isEmpty(markers)) line.gutterMarkers = null; |
4320 return true; | 5105 return true; |
4321 }); | 5106 }); |
4322 }), | 5107 }), |
4323 | 5108 |
4324 clearGutter: methodOp(function(gutterID) { | 5109 clearGutter: methodOp(function(gutterID) { |
4325 var cm = this, doc = cm.doc, i = doc.first; | 5110 var cm = this, doc = cm.doc, i = doc.first; |
4326 doc.iter(function(line) { | 5111 doc.iter(function(line) { |
4327 if (line.gutterMarkers && line.gutterMarkers[gutterID]) { | 5112 if (line.gutterMarkers && line.gutterMarkers[gutterID]) { |
4328 line.gutterMarkers[gutterID] = null; | 5113 line.gutterMarkers[gutterID] = null; |
4329 regLineChange(cm, i, "gutter"); | 5114 regLineChange(cm, i, "gutter"); |
4330 if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; | 5115 if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; |
4331 } | 5116 } |
4332 ++i; | 5117 ++i; |
4333 }); | 5118 }); |
4334 }), | 5119 }), |
4335 | 5120 |
4336 addLineWidget: methodOp(function(handle, node, options) { | |
4337 return addLineWidget(this, handle, node, options); | |
4338 }), | |
4339 | |
4340 removeLineWidget: function(widget) { widget.clear(); }, | |
4341 | |
4342 lineInfo: function(line) { | 5121 lineInfo: function(line) { |
4343 if (typeof line == "number") { | 5122 if (typeof line == "number") { |
4344 if (!isLine(this.doc, line)) return null; | 5123 if (!isLine(this.doc, line)) return null; |
4345 var n = line; | 5124 var n = line; |
4346 line = getLine(this.doc, line); | 5125 line = getLine(this.doc, line); |
4347 if (!line) return null; | 5126 if (!line) return null; |
4348 } else { | 5127 } else { |
4349 var n = lineNo(line); | 5128 var n = lineNo(line); |
4350 if (n == null) return null; | 5129 if (n == null) return null; |
4351 } | 5130 } |
4352 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutter
Markers, | 5131 return {line: n, handle: line, text: line.text, gutterMarkers: line.gutter
Markers, |
4353 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.
wrapClass, | 5132 textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.
wrapClass, |
4354 widgets: line.widgets}; | 5133 widgets: line.widgets}; |
4355 }, | 5134 }, |
4356 | 5135 |
4357 getViewport: function() { return {from: this.display.viewFrom, to: this.disp
lay.viewTo};}, | 5136 getViewport: function() { return {from: this.display.viewFrom, to: this.disp
lay.viewTo};}, |
4358 | 5137 |
4359 addWidget: function(pos, node, scroll, vert, horiz) { | 5138 addWidget: function(pos, node, scroll, vert, horiz) { |
4360 var display = this.display; | 5139 var display = this.display; |
4361 pos = cursorCoords(this, clipPos(this.doc, pos)); | 5140 pos = cursorCoords(this, clipPos(this.doc, pos)); |
4362 var top = pos.bottom, left = pos.left; | 5141 var top = pos.bottom, left = pos.left; |
4363 node.style.position = "absolute"; | 5142 node.style.position = "absolute"; |
4364 node.setAttribute("cm-ignore-events", "true"); | 5143 node.setAttribute("cm-ignore-events", "true"); |
| 5144 this.display.input.setUneditable(node); |
4365 display.sizer.appendChild(node); | 5145 display.sizer.appendChild(node); |
4366 if (vert == "over") { | 5146 if (vert == "over") { |
4367 top = pos.top; | 5147 top = pos.top; |
4368 } else if (vert == "above" || vert == "near") { | 5148 } else if (vert == "above" || vert == "near") { |
4369 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), | 5149 var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), |
4370 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWid
th); | 5150 hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWid
th); |
4371 // Default to positioning above (if specified and possible); otherwise d
efault to positioning below | 5151 // Default to positioning above (if specified and possible); otherwise d
efault to positioning below |
4372 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.
top > node.offsetHeight) | 5152 if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.
top > node.offsetHeight) |
4373 top = pos.top - node.offsetHeight; | 5153 top = pos.top - node.offsetHeight; |
4374 else if (pos.bottom + node.offsetHeight <= vspace) | 5154 else if (pos.bottom + node.offsetHeight <= vspace) |
(...skipping 14 matching lines...) Expand all Loading... |
4389 if (scroll) | 5169 if (scroll) |
4390 scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offs
etHeight); | 5170 scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offs
etHeight); |
4391 }, | 5171 }, |
4392 | 5172 |
4393 triggerOnKeyDown: methodOp(onKeyDown), | 5173 triggerOnKeyDown: methodOp(onKeyDown), |
4394 triggerOnKeyPress: methodOp(onKeyPress), | 5174 triggerOnKeyPress: methodOp(onKeyPress), |
4395 triggerOnKeyUp: onKeyUp, | 5175 triggerOnKeyUp: onKeyUp, |
4396 | 5176 |
4397 execCommand: function(cmd) { | 5177 execCommand: function(cmd) { |
4398 if (commands.hasOwnProperty(cmd)) | 5178 if (commands.hasOwnProperty(cmd)) |
4399 return commands[cmd](this); | 5179 return commands[cmd].call(null, this); |
4400 }, | 5180 }, |
4401 | 5181 |
| 5182 triggerElectric: methodOp(function(text) { triggerElectric(this, text); }), |
| 5183 |
4402 findPosH: function(from, amount, unit, visually) { | 5184 findPosH: function(from, amount, unit, visually) { |
4403 var dir = 1; | 5185 var dir = 1; |
4404 if (amount < 0) { dir = -1; amount = -amount; } | 5186 if (amount < 0) { dir = -1; amount = -amount; } |
4405 for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { | 5187 for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { |
4406 cur = findPosH(this.doc, cur, dir, unit, visually); | 5188 cur = findPosH(this.doc, cur, dir, unit, visually); |
4407 if (cur.hitSide) break; | 5189 if (cur.hitSide) break; |
4408 } | 5190 } |
4409 return cur; | 5191 return cur; |
4410 }, | 5192 }, |
4411 | 5193 |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4481 | 5263 |
4482 toggleOverwrite: function(value) { | 5264 toggleOverwrite: function(value) { |
4483 if (value != null && value == this.state.overwrite) return; | 5265 if (value != null && value == this.state.overwrite) return; |
4484 if (this.state.overwrite = !this.state.overwrite) | 5266 if (this.state.overwrite = !this.state.overwrite) |
4485 addClass(this.display.cursorDiv, "CodeMirror-overwrite"); | 5267 addClass(this.display.cursorDiv, "CodeMirror-overwrite"); |
4486 else | 5268 else |
4487 rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); | 5269 rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); |
4488 | 5270 |
4489 signal(this, "overwriteToggle", this, this.state.overwrite); | 5271 signal(this, "overwriteToggle", this, this.state.overwrite); |
4490 }, | 5272 }, |
4491 hasFocus: function() { return activeElt() == this.display.input; }, | 5273 hasFocus: function() { return this.display.input.getField() == activeElt();
}, |
| 5274 isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdi
t); }, |
4492 | 5275 |
4493 scrollTo: methodOp(function(x, y) { | 5276 scrollTo: methodOp(function(x, y) { |
4494 if (x != null || y != null) resolveScrollToPos(this); | 5277 if (x != null || y != null) resolveScrollToPos(this); |
4495 if (x != null) this.curOp.scrollLeft = x; | 5278 if (x != null) this.curOp.scrollLeft = x; |
4496 if (y != null) this.curOp.scrollTop = y; | 5279 if (y != null) this.curOp.scrollTop = y; |
4497 }), | 5280 }), |
4498 getScrollInfo: function() { | 5281 getScrollInfo: function() { |
4499 var scroller = this.display.scroller; | 5282 var scroller = this.display.scroller; |
4500 return {left: scroller.scrollLeft, top: scroller.scrollTop, | 5283 return {left: scroller.scrollLeft, top: scroller.scrollTop, |
4501 height: scroller.scrollHeight - scrollGap(this) - this.display.bar
Height, | 5284 height: scroller.scrollHeight - scrollGap(this) - this.display.bar
Height, |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4557 if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) >
.5) | 5340 if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) >
.5) |
4558 estimateLineHeights(this); | 5341 estimateLineHeights(this); |
4559 signal(this, "refresh", this); | 5342 signal(this, "refresh", this); |
4560 }), | 5343 }), |
4561 | 5344 |
4562 swapDoc: methodOp(function(doc) { | 5345 swapDoc: methodOp(function(doc) { |
4563 var old = this.doc; | 5346 var old = this.doc; |
4564 old.cm = null; | 5347 old.cm = null; |
4565 attachDoc(this, doc); | 5348 attachDoc(this, doc); |
4566 clearCaches(this); | 5349 clearCaches(this); |
4567 resetInput(this); | 5350 this.display.input.reset(); |
4568 this.scrollTo(doc.scrollLeft, doc.scrollTop); | 5351 this.scrollTo(doc.scrollLeft, doc.scrollTop); |
4569 this.curOp.forceScroll = true; | 5352 this.curOp.forceScroll = true; |
4570 signalLater(this, "swapDoc", this, old); | 5353 signalLater(this, "swapDoc", this, old); |
4571 return old; | 5354 return old; |
4572 }), | 5355 }), |
4573 | 5356 |
4574 getInputField: function(){return this.display.input;}, | 5357 getInputField: function(){return this.display.input.getField();}, |
4575 getWrapperElement: function(){return this.display.wrapper;}, | 5358 getWrapperElement: function(){return this.display.wrapper;}, |
4576 getScrollerElement: function(){return this.display.scroller;}, | 5359 getScrollerElement: function(){return this.display.scroller;}, |
4577 getGutterElement: function(){return this.display.gutters;} | 5360 getGutterElement: function(){return this.display.gutters;} |
4578 }; | 5361 }; |
4579 eventMixin(CodeMirror); | 5362 eventMixin(CodeMirror); |
4580 | 5363 |
4581 // OPTION DEFAULTS | 5364 // OPTION DEFAULTS |
4582 | 5365 |
4583 // The default configuration options. | 5366 // The default configuration options. |
4584 var defaults = CodeMirror.defaults = {}; | 5367 var defaults = CodeMirror.defaults = {}; |
(...skipping 20 matching lines...) Expand all Loading... |
4605 }, true); | 5388 }, true); |
4606 | 5389 |
4607 option("indentUnit", 2, loadMode, true); | 5390 option("indentUnit", 2, loadMode, true); |
4608 option("indentWithTabs", false); | 5391 option("indentWithTabs", false); |
4609 option("smartIndent", true); | 5392 option("smartIndent", true); |
4610 option("tabSize", 4, function(cm) { | 5393 option("tabSize", 4, function(cm) { |
4611 resetModeState(cm); | 5394 resetModeState(cm); |
4612 clearCaches(cm); | 5395 clearCaches(cm); |
4613 regChange(cm); | 5396 regChange(cm); |
4614 }, true); | 5397 }, true); |
4615 option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]
/g, function(cm, val) { | 5398 option("lineSeparator", null, function(cm, val) { |
4616 cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\
t"), "g"); | 5399 cm.doc.lineSep = val; |
4617 cm.refresh(); | 5400 if (!val) return; |
4618 }, true); | 5401 var newBreaks = [], lineNo = cm.doc.first; |
| 5402 cm.doc.iter(function(line) { |
| 5403 for (var pos = 0;;) { |
| 5404 var found = line.text.indexOf(val, pos); |
| 5405 if (found == -1) break; |
| 5406 pos = found + val.length; |
| 5407 newBreaks.push(Pos(lineNo, found)); |
| 5408 } |
| 5409 lineNo++; |
| 5410 }); |
| 5411 for (var i = newBreaks.length - 1; i >= 0; i--) |
| 5412 replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i
].ch + val.length)) |
| 5413 }); |
| 5414 option("specialChars", /[\u0000-\u001f\u007f\u00ad\u200b-\u200f\u2028\u2029\uf
eff]/g, function(cm, val, old) { |
| 5415 cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"
), "g"); |
| 5416 if (old != CodeMirror.Init) cm.refresh(); |
| 5417 }); |
4619 option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {
cm.refresh();}, true); | 5418 option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {
cm.refresh();}, true); |
4620 option("electricChars", true); | 5419 option("electricChars", true); |
| 5420 option("inputStyle", mobile ? "contenteditable" : "textarea", function() { |
| 5421 throw new Error("inputStyle can not (yet) be changed in a running editor");
// FIXME |
| 5422 }, true); |
4621 option("rtlMoveVisually", !windows); | 5423 option("rtlMoveVisually", !windows); |
4622 option("wholeLineUpdateBefore", true); | 5424 option("wholeLineUpdateBefore", true); |
4623 | 5425 |
4624 option("theme", "default", function(cm) { | 5426 option("theme", "default", function(cm) { |
4625 themeChanged(cm); | 5427 themeChanged(cm); |
4626 guttersChanged(cm); | 5428 guttersChanged(cm); |
4627 }, true); | 5429 }, true); |
4628 option("keyMap", "default", function(cm, val, old) { | 5430 option("keyMap", "default", function(cm, val, old) { |
4629 var next = getKeyMap(val); | 5431 var next = getKeyMap(val); |
4630 var prev = old != CodeMirror.Init && getKeyMap(old); | 5432 var prev = old != CodeMirror.Init && getKeyMap(old); |
(...skipping 20 matching lines...) Expand all Loading... |
4651 }, true); | 5453 }, true); |
4652 option("lineNumbers", false, function(cm) { | 5454 option("lineNumbers", false, function(cm) { |
4653 setGuttersForLineNumbers(cm.options); | 5455 setGuttersForLineNumbers(cm.options); |
4654 guttersChanged(cm); | 5456 guttersChanged(cm); |
4655 }, true); | 5457 }, true); |
4656 option("firstLineNumber", 1, guttersChanged, true); | 5458 option("firstLineNumber", 1, guttersChanged, true); |
4657 option("lineNumberFormatter", function(integer) {return integer;}, guttersChan
ged, true); | 5459 option("lineNumberFormatter", function(integer) {return integer;}, guttersChan
ged, true); |
4658 option("showCursorWhenSelecting", false, updateSelection, true); | 5460 option("showCursorWhenSelecting", false, updateSelection, true); |
4659 | 5461 |
4660 option("resetSelectionOnContextMenu", true); | 5462 option("resetSelectionOnContextMenu", true); |
| 5463 option("lineWiseCopyCut", true); |
4661 | 5464 |
4662 option("readOnly", false, function(cm, val) { | 5465 option("readOnly", false, function(cm, val) { |
4663 if (val == "nocursor") { | 5466 if (val == "nocursor") { |
4664 onBlur(cm); | 5467 onBlur(cm); |
4665 cm.display.input.blur(); | 5468 cm.display.input.blur(); |
4666 cm.display.disabled = true; | 5469 cm.display.disabled = true; |
4667 } else { | 5470 } else { |
4668 cm.display.disabled = false; | 5471 cm.display.disabled = false; |
4669 if (!val) resetInput(cm); | |
4670 } | 5472 } |
| 5473 cm.display.input.readOnlyChanged(val) |
4671 }); | 5474 }); |
4672 option("disableInput", false, function(cm, val) {if (!val) resetInput(cm);}, t
rue); | 5475 option("disableInput", false, function(cm, val) {if (!val) cm.display.input.re
set();}, true); |
4673 option("dragDrop", true); | 5476 option("dragDrop", true, dragDropChanged); |
| 5477 option("allowDropFileTypes", null); |
4674 | 5478 |
4675 option("cursorBlinkRate", 530); | 5479 option("cursorBlinkRate", 530); |
4676 option("cursorScrollMargin", 0); | 5480 option("cursorScrollMargin", 0); |
4677 option("cursorHeight", 1, updateSelection, true); | 5481 option("cursorHeight", 1, updateSelection, true); |
4678 option("singleCursorHeightPerLine", true, updateSelection, true); | 5482 option("singleCursorHeightPerLine", true, updateSelection, true); |
4679 option("workTime", 100); | 5483 option("workTime", 100); |
4680 option("workDelay", 100); | 5484 option("workDelay", 100); |
4681 option("flattenSpans", true, resetModeState, true); | 5485 option("flattenSpans", true, resetModeState, true); |
4682 option("addModeClass", false, resetModeState, true); | 5486 option("addModeClass", false, resetModeState, true); |
4683 option("pollInterval", 100); | 5487 option("pollInterval", 100); |
4684 option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); | 5488 option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); |
4685 option("historyEventDelay", 1250); | 5489 option("historyEventDelay", 1250); |
4686 option("viewportMargin", 10, function(cm){cm.refresh();}, true); | 5490 option("viewportMargin", 10, function(cm){cm.refresh();}, true); |
4687 option("maxHighlightLength", 10000, resetModeState, true); | 5491 option("maxHighlightLength", 10000, resetModeState, true); |
4688 option("moveInputWithCursor", true, function(cm, val) { | 5492 option("moveInputWithCursor", true, function(cm, val) { |
4689 if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0
; | 5493 if (!val) cm.display.input.resetPosition(); |
4690 }); | 5494 }); |
4691 | 5495 |
4692 option("tabindex", null, function(cm, val) { | 5496 option("tabindex", null, function(cm, val) { |
4693 cm.display.input.tabIndex = val || ""; | 5497 cm.display.input.getField().tabIndex = val || ""; |
4694 }); | 5498 }); |
4695 option("autofocus", null); | 5499 option("autofocus", null); |
4696 | 5500 |
4697 // MODE DEFINITION AND QUERYING | 5501 // MODE DEFINITION AND QUERYING |
4698 | 5502 |
4699 // Known modes, by name and by MIME | 5503 // Known modes, by name and by MIME |
4700 var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; | 5504 var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; |
4701 | 5505 |
4702 // Extra arguments are stored as the mode's dependencies, which is | 5506 // Extra arguments are stored as the mode's dependencies, which is |
4703 // used by (legacy) mechanisms like loadmode.js to automatically | 5507 // used by (legacy) mechanisms like loadmode.js to automatically |
(...skipping 225 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4929 delGroupAfter: function(cm) {cm.deleteH(1, "group");}, | 5733 delGroupAfter: function(cm) {cm.deleteH(1, "group");}, |
4930 indentAuto: function(cm) {cm.indentSelection("smart");}, | 5734 indentAuto: function(cm) {cm.indentSelection("smart");}, |
4931 indentMore: function(cm) {cm.indentSelection("add");}, | 5735 indentMore: function(cm) {cm.indentSelection("add");}, |
4932 indentLess: function(cm) {cm.indentSelection("subtract");}, | 5736 indentLess: function(cm) {cm.indentSelection("subtract");}, |
4933 insertTab: function(cm) {cm.replaceSelection("\t");}, | 5737 insertTab: function(cm) {cm.replaceSelection("\t");}, |
4934 insertSoftTab: function(cm) { | 5738 insertSoftTab: function(cm) { |
4935 var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSiz
e; | 5739 var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSiz
e; |
4936 for (var i = 0; i < ranges.length; i++) { | 5740 for (var i = 0; i < ranges.length; i++) { |
4937 var pos = ranges[i].from(); | 5741 var pos = ranges[i].from(); |
4938 var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); | 5742 var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); |
4939 spaces.push(new Array(tabSize - col % tabSize + 1).join(" ")); | 5743 spaces.push(spaceStr(tabSize - col % tabSize)); |
4940 } | 5744 } |
4941 cm.replaceSelections(spaces); | 5745 cm.replaceSelections(spaces); |
4942 }, | 5746 }, |
4943 defaultTab: function(cm) { | 5747 defaultTab: function(cm) { |
4944 if (cm.somethingSelected()) cm.indentSelection("add"); | 5748 if (cm.somethingSelected()) cm.indentSelection("add"); |
4945 else cm.execCommand("insertTab"); | 5749 else cm.execCommand("insertTab"); |
4946 }, | 5750 }, |
4947 transposeChars: function(cm) { | 5751 transposeChars: function(cm) { |
4948 runInOp(cm, function() { | 5752 runInOp(cm, function() { |
4949 var ranges = cm.listSelections(), newSel = []; | 5753 var ranges = cm.listSelections(), newSel = []; |
4950 for (var i = 0; i < ranges.length; i++) { | 5754 for (var i = 0; i < ranges.length; i++) { |
4951 var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; | 5755 var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; |
4952 if (line) { | 5756 if (line) { |
4953 if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); | 5757 if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); |
4954 if (cur.ch > 0) { | 5758 if (cur.ch > 0) { |
4955 cur = new Pos(cur.line, cur.ch + 1); | 5759 cur = new Pos(cur.line, cur.ch + 1); |
4956 cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), | 5760 cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), |
4957 Pos(cur.line, cur.ch - 2), cur, "+transpose"); | 5761 Pos(cur.line, cur.ch - 2), cur, "+transpose"); |
4958 } else if (cur.line > cm.doc.first) { | 5762 } else if (cur.line > cm.doc.first) { |
4959 var prev = getLine(cm.doc, cur.line - 1).text; | 5763 var prev = getLine(cm.doc, cur.line - 1).text; |
4960 if (prev) | 5764 if (prev) |
4961 cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length
- 1), | 5765 cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + |
| 5766 prev.charAt(prev.length - 1), |
4962 Pos(cur.line - 1, prev.length - 1), Pos(cur.line
, 1), "+transpose"); | 5767 Pos(cur.line - 1, prev.length - 1), Pos(cur.line
, 1), "+transpose"); |
4963 } | 5768 } |
4964 } | 5769 } |
4965 newSel.push(new Range(cur, cur)); | 5770 newSel.push(new Range(cur, cur)); |
4966 } | 5771 } |
4967 cm.setSelections(newSel); | 5772 cm.setSelections(newSel); |
4968 }); | 5773 }); |
4969 }, | 5774 }, |
4970 newlineAndIndent: function(cm) { | 5775 newlineAndIndent: function(cm) { |
4971 runInOp(cm, function() { | 5776 runInOp(cm, function() { |
4972 var len = cm.listSelections().length; | 5777 var len = cm.listSelections().length; |
4973 for (var i = 0; i < len; i++) { | 5778 for (var i = 0; i < len; i++) { |
4974 var range = cm.listSelections()[i]; | 5779 var range = cm.listSelections()[i]; |
4975 cm.replaceRange("\n", range.anchor, range.head, "+input"); | 5780 cm.replaceRange(cm.doc.lineSeparator(), range.anchor, range.head, "+in
put"); |
4976 cm.indentLine(range.from().line + 1, null, true); | 5781 cm.indentLine(range.from().line + 1, null, true); |
4977 ensureCursorVisible(cm); | |
4978 } | 5782 } |
| 5783 ensureCursorVisible(cm); |
4979 }); | 5784 }); |
4980 }, | 5785 }, |
| 5786 openLine: function(cm) {cm.replaceSelection("\n", "start")}, |
4981 toggleOverwrite: function(cm) {cm.toggleOverwrite();} | 5787 toggleOverwrite: function(cm) {cm.toggleOverwrite();} |
4982 }; | 5788 }; |
4983 | 5789 |
4984 | 5790 |
4985 // STANDARD KEYMAPS | 5791 // STANDARD KEYMAPS |
4986 | 5792 |
4987 var keyMap = CodeMirror.keyMap = {}; | 5793 var keyMap = CodeMirror.keyMap = {}; |
4988 | 5794 |
4989 keyMap.basic = { | 5795 keyMap.basic = { |
4990 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goL
ineDown", | 5796 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goL
ineDown", |
(...skipping 14 matching lines...) Expand all Loading... |
5005 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace",
"Shift-Ctrl-R": "replaceAll", | 5811 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace",
"Shift-Ctrl-R": "replaceAll", |
5006 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", | 5812 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", |
5007 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSe
lection", | 5813 "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSe
lection", |
5008 fallthrough: "basic" | 5814 fallthrough: "basic" |
5009 }; | 5815 }; |
5010 // Very basic readline/emacs-style bindings, which are standard on Mac. | 5816 // Very basic readline/emacs-style bindings, which are standard on Mac. |
5011 keyMap.emacsy = { | 5817 keyMap.emacsy = { |
5012 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl
-N": "goLineDown", | 5818 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl
-N": "goLineDown", |
5013 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctr
l-E": "goLineEnd", | 5819 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctr
l-E": "goLineEnd", |
5014 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter"
, "Ctrl-H": "delCharBefore", | 5820 "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter"
, "Ctrl-H": "delCharBefore", |
5015 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLi
ne", "Ctrl-T": "transposeChars" | 5821 "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLi
ne", "Ctrl-T": "transposeChars", |
| 5822 "Ctrl-O": "openLine" |
5016 }; | 5823 }; |
5017 keyMap.macDefault = { | 5824 keyMap.macDefault = { |
5018 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z":
"redo", "Cmd-Y": "redo", | 5825 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z":
"redo", "Cmd-Y": "redo", |
5019 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cm
d-Down": "goDocEnd", "Alt-Left": "goGroupLeft", | 5826 "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cm
d-Down": "goDocEnd", "Alt-Left": "goGroupLeft", |
5020 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineR
ight", "Alt-Backspace": "delGroupBefore", | 5827 "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineR
ight", "Alt-Backspace": "delGroupBefore", |
5021 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S
": "save", "Cmd-F": "find", | 5828 "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S
": "save", "Cmd-F": "find", |
5022 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shi
ft-Cmd-Alt-F": "replaceAll", | 5829 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shi
ft-Cmd-Alt-F": "replaceAll", |
5023 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLi
neLeft", "Cmd-Delete": "delWrappedLineRight", | 5830 "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLi
neLeft", "Cmd-Delete": "delWrappedLineRight", |
5024 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocS
tart", "Ctrl-Down": "goDocEnd", | 5831 "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocS
tart", "Ctrl-Down": "goDocEnd", |
5025 fallthrough: ["basic", "emacsy"] | 5832 fallthrough: ["basic", "emacsy"] |
(...skipping 29 matching lines...) Expand all Loading... |
5055 var copy = {}; | 5862 var copy = {}; |
5056 for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) { | 5863 for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) { |
5057 var value = keymap[keyname]; | 5864 var value = keymap[keyname]; |
5058 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue; | 5865 if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue; |
5059 if (value == "...") { delete keymap[keyname]; continue; } | 5866 if (value == "...") { delete keymap[keyname]; continue; } |
5060 | 5867 |
5061 var keys = map(keyname.split(" "), normalizeKeyName); | 5868 var keys = map(keyname.split(" "), normalizeKeyName); |
5062 for (var i = 0; i < keys.length; i++) { | 5869 for (var i = 0; i < keys.length; i++) { |
5063 var val, name; | 5870 var val, name; |
5064 if (i == keys.length - 1) { | 5871 if (i == keys.length - 1) { |
5065 name = keyname; | 5872 name = keys.join(" "); |
5066 val = value; | 5873 val = value; |
5067 } else { | 5874 } else { |
5068 name = keys.slice(0, i + 1).join(" "); | 5875 name = keys.slice(0, i + 1).join(" "); |
5069 val = "..."; | 5876 val = "..."; |
5070 } | 5877 } |
5071 var prev = copy[name]; | 5878 var prev = copy[name]; |
5072 if (!prev) copy[name] = val; | 5879 if (!prev) copy[name] = val; |
5073 else if (prev != val) throw new Error("Inconsistent bindings for " + nam
e); | 5880 else if (prev != val) throw new Error("Inconsistent bindings for " + nam
e); |
5074 } | 5881 } |
5075 delete keymap[keyname]; | 5882 delete keymap[keyname]; |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5114 return name; | 5921 return name; |
5115 }; | 5922 }; |
5116 | 5923 |
5117 function getKeyMap(val) { | 5924 function getKeyMap(val) { |
5118 return typeof val == "string" ? keyMap[val] : val; | 5925 return typeof val == "string" ? keyMap[val] : val; |
5119 } | 5926 } |
5120 | 5927 |
5121 // FROMTEXTAREA | 5928 // FROMTEXTAREA |
5122 | 5929 |
5123 CodeMirror.fromTextArea = function(textarea, options) { | 5930 CodeMirror.fromTextArea = function(textarea, options) { |
5124 if (!options) options = {}; | 5931 options = options ? copyObj(options) : {}; |
5125 options.value = textarea.value; | 5932 options.value = textarea.value; |
5126 if (!options.tabindex && textarea.tabindex) | 5933 if (!options.tabindex && textarea.tabIndex) |
5127 options.tabindex = textarea.tabindex; | 5934 options.tabindex = textarea.tabIndex; |
5128 if (!options.placeholder && textarea.placeholder) | 5935 if (!options.placeholder && textarea.placeholder) |
5129 options.placeholder = textarea.placeholder; | 5936 options.placeholder = textarea.placeholder; |
5130 // Set autofocus to true if this textarea is focused, or if it has | 5937 // Set autofocus to true if this textarea is focused, or if it has |
5131 // autofocus and no other element is focused. | 5938 // autofocus and no other element is focused. |
5132 if (options.autofocus == null) { | 5939 if (options.autofocus == null) { |
5133 var hasFocus = activeElt(); | 5940 var hasFocus = activeElt(); |
5134 options.autofocus = hasFocus == textarea || | 5941 options.autofocus = hasFocus == textarea || |
5135 textarea.getAttribute("autofocus") != null && hasFocus == document.body; | 5942 textarea.getAttribute("autofocus") != null && hasFocus == document.body; |
5136 } | 5943 } |
5137 | 5944 |
5138 function save() {textarea.value = cm.getValue();} | 5945 function save() {textarea.value = cm.getValue();} |
5139 if (textarea.form) { | 5946 if (textarea.form) { |
5140 on(textarea.form, "submit", save); | 5947 on(textarea.form, "submit", save); |
5141 // Deplorable hack to make the submit method do the right thing. | 5948 // Deplorable hack to make the submit method do the right thing. |
5142 if (!options.leaveSubmitMethodAlone) { | 5949 if (!options.leaveSubmitMethodAlone) { |
5143 var form = textarea.form, realSubmit = form.submit; | 5950 var form = textarea.form, realSubmit = form.submit; |
5144 try { | 5951 try { |
5145 var wrappedSubmit = form.submit = function() { | 5952 var wrappedSubmit = form.submit = function() { |
5146 save(); | 5953 save(); |
5147 form.submit = realSubmit; | 5954 form.submit = realSubmit; |
5148 form.submit(); | 5955 form.submit(); |
5149 form.submit = wrappedSubmit; | 5956 form.submit = wrappedSubmit; |
5150 }; | 5957 }; |
5151 } catch(e) {} | 5958 } catch(e) {} |
5152 } | 5959 } |
5153 } | 5960 } |
5154 | 5961 |
| 5962 options.finishInit = function(cm) { |
| 5963 cm.save = save; |
| 5964 cm.getTextArea = function() { return textarea; }; |
| 5965 cm.toTextArea = function() { |
| 5966 cm.toTextArea = isNaN; // Prevent this from being ran twice |
| 5967 save(); |
| 5968 textarea.parentNode.removeChild(cm.getWrapperElement()); |
| 5969 textarea.style.display = ""; |
| 5970 if (textarea.form) { |
| 5971 off(textarea.form, "submit", save); |
| 5972 if (typeof textarea.form.submit == "function") |
| 5973 textarea.form.submit = realSubmit; |
| 5974 } |
| 5975 }; |
| 5976 }; |
| 5977 |
5155 textarea.style.display = "none"; | 5978 textarea.style.display = "none"; |
5156 var cm = CodeMirror(function(node) { | 5979 var cm = CodeMirror(function(node) { |
5157 textarea.parentNode.insertBefore(node, textarea.nextSibling); | 5980 textarea.parentNode.insertBefore(node, textarea.nextSibling); |
5158 }, options); | 5981 }, options); |
5159 cm.save = save; | |
5160 cm.getTextArea = function() { return textarea; }; | |
5161 cm.toTextArea = function() { | |
5162 cm.toTextArea = isNaN; // Prevent this from being ran twice | |
5163 save(); | |
5164 textarea.parentNode.removeChild(cm.getWrapperElement()); | |
5165 textarea.style.display = ""; | |
5166 if (textarea.form) { | |
5167 off(textarea.form, "submit", save); | |
5168 if (typeof textarea.form.submit == "function") | |
5169 textarea.form.submit = realSubmit; | |
5170 } | |
5171 }; | |
5172 return cm; | 5982 return cm; |
5173 }; | 5983 }; |
5174 | 5984 |
5175 // STRING STREAM | 5985 // STRING STREAM |
5176 | 5986 |
5177 // Fed to the mode parsers, provides helper functions to make | 5987 // Fed to the mode parsers, provides helper functions to make |
5178 // parsers more succinct. | 5988 // parsers more succinct. |
5179 | 5989 |
5180 var StringStream = CodeMirror.StringStream = function(string, tabSize) { | 5990 var StringStream = CodeMirror.StringStream = function(string, tabSize) { |
5181 this.pos = this.start = 0; | 5991 this.pos = this.start = 0; |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5254 // Created with markText and setBookmark methods. A TextMarker is a | 6064 // Created with markText and setBookmark methods. A TextMarker is a |
5255 // handle that can be used to clear or find a marked position in the | 6065 // handle that can be used to clear or find a marked position in the |
5256 // document. Line objects hold arrays (markedSpans) containing | 6066 // document. Line objects hold arrays (markedSpans) containing |
5257 // {from, to, marker} object pointing to such marker objects, and | 6067 // {from, to, marker} object pointing to such marker objects, and |
5258 // indicating that such a marker is present on that line. Multiple | 6068 // indicating that such a marker is present on that line. Multiple |
5259 // lines may point to the same marker when it spans across lines. | 6069 // lines may point to the same marker when it spans across lines. |
5260 // The spans will have null for their from/to properties when the | 6070 // The spans will have null for their from/to properties when the |
5261 // marker continues beyond the start/end of the line. Markers have | 6071 // marker continues beyond the start/end of the line. Markers have |
5262 // links back to the lines they currently touch. | 6072 // links back to the lines they currently touch. |
5263 | 6073 |
| 6074 var nextMarkerId = 0; |
| 6075 |
5264 var TextMarker = CodeMirror.TextMarker = function(doc, type) { | 6076 var TextMarker = CodeMirror.TextMarker = function(doc, type) { |
5265 this.lines = []; | 6077 this.lines = []; |
5266 this.type = type; | 6078 this.type = type; |
5267 this.doc = doc; | 6079 this.doc = doc; |
| 6080 this.id = ++nextMarkerId; |
5268 }; | 6081 }; |
5269 eventMixin(TextMarker); | 6082 eventMixin(TextMarker); |
5270 | 6083 |
5271 // Clear the marker. | 6084 // Clear the marker. |
5272 TextMarker.prototype.clear = function() { | 6085 TextMarker.prototype.clear = function() { |
5273 if (this.explicitlyCleared) return; | 6086 if (this.explicitlyCleared) return; |
5274 var cm = this.doc.cm, withOp = cm && !cm.curOp; | 6087 var cm = this.doc.cm, withOp = cm && !cm.curOp; |
5275 if (withOp) startOperation(cm); | 6088 if (withOp) startOperation(cm); |
5276 if (hasHandler(this, "clear")) { | 6089 if (hasHandler(this, "clear")) { |
5277 var found = this.find(); | 6090 var found = this.find(); |
(...skipping 483 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5761 function conflictingCollapsedRange(doc, lineNo, from, to, marker) { | 6574 function conflictingCollapsedRange(doc, lineNo, from, to, marker) { |
5762 var line = getLine(doc, lineNo); | 6575 var line = getLine(doc, lineNo); |
5763 var sps = sawCollapsedSpans && line.markedSpans; | 6576 var sps = sawCollapsedSpans && line.markedSpans; |
5764 if (sps) for (var i = 0; i < sps.length; ++i) { | 6577 if (sps) for (var i = 0; i < sps.length; ++i) { |
5765 var sp = sps[i]; | 6578 var sp = sps[i]; |
5766 if (!sp.marker.collapsed) continue; | 6579 if (!sp.marker.collapsed) continue; |
5767 var found = sp.marker.find(0); | 6580 var found = sp.marker.find(0); |
5768 var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(ma
rker); | 6581 var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(ma
rker); |
5769 var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker
); | 6582 var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker
); |
5770 if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; | 6583 if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; |
5771 if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight
&& marker.inclusiveLeft)) || | 6584 if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cm
p(found.to, from) >= 0 : cmp(found.to, from) > 0) || |
5772 fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft &
& marker.inclusiveRight))) | 6585 fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cm
p(found.from, to) <= 0 : cmp(found.from, to) < 0)) |
5773 return true; | 6586 return true; |
5774 } | 6587 } |
5775 } | 6588 } |
5776 | 6589 |
5777 // A visual line is a line as drawn on the screen. Folding, for | 6590 // A visual line is a line as drawn on the screen. Folding, for |
5778 // example, can cause multiple logical lines to appear on the same | 6591 // example, can cause multiple logical lines to appear on the same |
5779 // visual line. This finds the start of the visual line that the | 6592 // visual line. This finds the start of the visual line that the |
5780 // given line is part of (usually that is the line itself). | 6593 // given line is part of (usually that is the line itself). |
5781 function visualLine(line) { | 6594 function visualLine(line) { |
5782 var merged; | 6595 var merged; |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5841 (sp.to == null || sp.to != span.from) && | 6654 (sp.to == null || sp.to != span.from) && |
5842 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && | 6655 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && |
5843 lineIsHiddenInner(doc, line, sp)) return true; | 6656 lineIsHiddenInner(doc, line, sp)) return true; |
5844 } | 6657 } |
5845 } | 6658 } |
5846 | 6659 |
5847 // LINE WIDGETS | 6660 // LINE WIDGETS |
5848 | 6661 |
5849 // Line widgets are block elements displayed above or below a line. | 6662 // Line widgets are block elements displayed above or below a line. |
5850 | 6663 |
5851 var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { | 6664 var LineWidget = CodeMirror.LineWidget = function(doc, node, options) { |
5852 if (options) for (var opt in options) if (options.hasOwnProperty(opt)) | 6665 if (options) for (var opt in options) if (options.hasOwnProperty(opt)) |
5853 this[opt] = options[opt]; | 6666 this[opt] = options[opt]; |
5854 this.cm = cm; | 6667 this.doc = doc; |
5855 this.node = node; | 6668 this.node = node; |
5856 }; | 6669 }; |
5857 eventMixin(LineWidget); | 6670 eventMixin(LineWidget); |
5858 | 6671 |
5859 function adjustScrollWhenAboveVisible(cm, line, diff) { | 6672 function adjustScrollWhenAboveVisible(cm, line, diff) { |
5860 if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollT
op)) | 6673 if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollT
op)) |
5861 addToScrollPos(cm, null, diff); | 6674 addToScrollPos(cm, null, diff); |
5862 } | 6675 } |
5863 | 6676 |
5864 LineWidget.prototype.clear = function() { | 6677 LineWidget.prototype.clear = function() { |
5865 var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line
); | 6678 var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(
line); |
5866 if (no == null || !ws) return; | 6679 if (no == null || !ws) return; |
5867 for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); | 6680 for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); |
5868 if (!ws.length) line.widgets = null; | 6681 if (!ws.length) line.widgets = null; |
5869 var height = widgetHeight(this); | 6682 var height = widgetHeight(this); |
5870 runInOp(cm, function() { | 6683 updateLineHeight(line, Math.max(0, line.height - height)); |
| 6684 if (cm) runInOp(cm, function() { |
5871 adjustScrollWhenAboveVisible(cm, line, -height); | 6685 adjustScrollWhenAboveVisible(cm, line, -height); |
5872 regLineChange(cm, no, "widget"); | 6686 regLineChange(cm, no, "widget"); |
5873 updateLineHeight(line, Math.max(0, line.height - height)); | |
5874 }); | 6687 }); |
5875 }; | 6688 }; |
5876 LineWidget.prototype.changed = function() { | 6689 LineWidget.prototype.changed = function() { |
5877 var oldH = this.height, cm = this.cm, line = this.line; | 6690 var oldH = this.height, cm = this.doc.cm, line = this.line; |
5878 this.height = null; | 6691 this.height = null; |
5879 var diff = widgetHeight(this) - oldH; | 6692 var diff = widgetHeight(this) - oldH; |
5880 if (!diff) return; | 6693 if (!diff) return; |
5881 runInOp(cm, function() { | 6694 updateLineHeight(line, line.height + diff); |
| 6695 if (cm) runInOp(cm, function() { |
5882 cm.curOp.forceUpdate = true; | 6696 cm.curOp.forceUpdate = true; |
5883 adjustScrollWhenAboveVisible(cm, line, diff); | 6697 adjustScrollWhenAboveVisible(cm, line, diff); |
5884 updateLineHeight(line, line.height + diff); | |
5885 }); | 6698 }); |
5886 }; | 6699 }; |
5887 | 6700 |
5888 function widgetHeight(widget) { | 6701 function widgetHeight(widget) { |
5889 if (widget.height != null) return widget.height; | 6702 if (widget.height != null) return widget.height; |
| 6703 var cm = widget.doc.cm; |
| 6704 if (!cm) return 0; |
5890 if (!contains(document.body, widget.node)) { | 6705 if (!contains(document.body, widget.node)) { |
5891 var parentStyle = "position: relative;"; | 6706 var parentStyle = "position: relative;"; |
5892 if (widget.coverGutter) | 6707 if (widget.coverGutter) |
5893 parentStyle += "margin-left: -" + widget.cm.display.gutters.offsetWidth
+ "px;"; | 6708 parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"
; |
5894 if (widget.noHScroll) | 6709 if (widget.noHScroll) |
5895 parentStyle += "width: " + widget.cm.display.wrapper.clientWidth + "px;"
; | 6710 parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; |
5896 removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node],
null, parentStyle)); | 6711 removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, p
arentStyle)); |
5897 } | 6712 } |
5898 return widget.height = widget.node.offsetHeight; | 6713 return widget.height = widget.node.parentNode.offsetHeight; |
5899 } | 6714 } |
5900 | 6715 |
5901 function addLineWidget(cm, handle, node, options) { | 6716 function addLineWidget(doc, handle, node, options) { |
5902 var widget = new LineWidget(cm, node, options); | 6717 var widget = new LineWidget(doc, node, options); |
5903 if (widget.noHScroll) cm.display.alignWidgets = true; | 6718 var cm = doc.cm; |
5904 changeLine(cm.doc, handle, "widget", function(line) { | 6719 if (cm && widget.noHScroll) cm.display.alignWidgets = true; |
| 6720 changeLine(doc, handle, "widget", function(line) { |
5905 var widgets = line.widgets || (line.widgets = []); | 6721 var widgets = line.widgets || (line.widgets = []); |
5906 if (widget.insertAt == null) widgets.push(widget); | 6722 if (widget.insertAt == null) widgets.push(widget); |
5907 else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insert
At)), 0, widget); | 6723 else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insert
At)), 0, widget); |
5908 widget.line = line; | 6724 widget.line = line; |
5909 if (!lineIsHidden(cm.doc, line)) { | 6725 if (cm && !lineIsHidden(doc, line)) { |
5910 var aboveVisible = heightAtLine(line) < cm.doc.scrollTop; | 6726 var aboveVisible = heightAtLine(line) < doc.scrollTop; |
5911 updateLineHeight(line, line.height + widgetHeight(widget)); | 6727 updateLineHeight(line, line.height + widgetHeight(widget)); |
5912 if (aboveVisible) addToScrollPos(cm, null, widget.height); | 6728 if (aboveVisible) addToScrollPos(cm, null, widget.height); |
5913 cm.curOp.forceUpdate = true; | 6729 cm.curOp.forceUpdate = true; |
5914 } | 6730 } |
5915 return true; | 6731 return true; |
5916 }); | 6732 }); |
5917 return widget; | 6733 return widget; |
5918 } | 6734 } |
5919 | 6735 |
5920 // LINE DATA STRUCTURE | 6736 // LINE DATA STRUCTURE |
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
6076 } | 6892 } |
6077 } | 6893 } |
6078 }, lineClasses); | 6894 }, lineClasses); |
6079 } | 6895 } |
6080 | 6896 |
6081 return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ?
lineClasses : null}; | 6897 return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ?
lineClasses : null}; |
6082 } | 6898 } |
6083 | 6899 |
6084 function getLineStyles(cm, line, updateFrontier) { | 6900 function getLineStyles(cm, line, updateFrontier) { |
6085 if (!line.styles || line.styles[0] != cm.state.modeGen) { | 6901 if (!line.styles || line.styles[0] != cm.state.modeGen) { |
6086 var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm,
lineNo(line))); | 6902 var state = getStateBefore(cm, lineNo(line)); |
| 6903 var result = highlightLine(cm, line, line.text.length > cm.options.maxHigh
lightLength ? copyState(cm.doc.mode, state) : state); |
| 6904 line.stateAfter = state; |
6087 line.styles = result.styles; | 6905 line.styles = result.styles; |
6088 if (result.classes) line.styleClasses = result.classes; | 6906 if (result.classes) line.styleClasses = result.classes; |
6089 else if (line.styleClasses) line.styleClasses = null; | 6907 else if (line.styleClasses) line.styleClasses = null; |
6090 if (updateFrontier === cm.doc.frontier) cm.doc.frontier++; | 6908 if (updateFrontier === cm.doc.frontier) cm.doc.frontier++; |
6091 } | 6909 } |
6092 return line.styles; | 6910 return line.styles; |
6093 } | 6911 } |
6094 | 6912 |
6095 // Lightweight form of highlight -- proceed over this line and | 6913 // Lightweight form of highlight -- proceed over this line and |
6096 // update state, but don't save a style array. Used for lines that | 6914 // update state, but don't save a style array. Used for lines that |
6097 // aren't currently visible. | 6915 // aren't currently visible. |
6098 function processLine(cm, text, state, startAt) { | 6916 function processLine(cm, text, state, startAt) { |
6099 var mode = cm.doc.mode; | 6917 var mode = cm.doc.mode; |
6100 var stream = new StringStream(text, cm.options.tabSize); | 6918 var stream = new StringStream(text, cm.options.tabSize); |
6101 stream.start = stream.pos = startAt || 0; | 6919 stream.start = stream.pos = startAt || 0; |
6102 if (text == "") callBlankLine(mode, state); | 6920 if (text == "") callBlankLine(mode, state); |
6103 while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { | 6921 while (!stream.eol()) { |
6104 readToken(mode, stream, state); | 6922 readToken(mode, stream, state); |
6105 stream.start = stream.pos; | 6923 stream.start = stream.pos; |
6106 } | 6924 } |
6107 } | 6925 } |
6108 | 6926 |
6109 // Convert a style as returned by a mode (either null, or a string | 6927 // Convert a style as returned by a mode (either null, or a string |
6110 // containing one or more styles) to a CSS style. This is cached, | 6928 // containing one or more styles) to a CSS style. This is cached, |
6111 // and also looks for line-wide styles. | 6929 // and also looks for line-wide styles. |
6112 var styleToClassCache = {}, styleToClassCacheWithMode = {}; | 6930 var styleToClassCache = {}, styleToClassCacheWithMode = {}; |
6113 function interpretTokenStyle(style, options) { | 6931 function interpretTokenStyle(style, options) { |
6114 if (!style || /^\s*$/.test(style)) return null; | 6932 if (!style || /^\s*$/.test(style)) return null; |
6115 var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassC
ache; | 6933 var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassC
ache; |
6116 return cache[style] || | 6934 return cache[style] || |
6117 (cache[style] = style.replace(/\S+/g, "cm-$&")); | 6935 (cache[style] = style.replace(/\S+/g, "cm-$&")); |
6118 } | 6936 } |
6119 | 6937 |
6120 // Render the DOM representation of the text of a line. Also builds | 6938 // Render the DOM representation of the text of a line. Also builds |
6121 // up a 'line map', which points at the DOM nodes that represent | 6939 // up a 'line map', which points at the DOM nodes that represent |
6122 // specific stretches of text, and is used by the measuring code. | 6940 // specific stretches of text, and is used by the measuring code. |
6123 // The returned object contains the DOM node, this map, and | 6941 // The returned object contains the DOM node, this map, and |
6124 // information about line-wide styles that were set by the mode. | 6942 // information about line-wide styles that were set by the mode. |
6125 function buildLineContent(cm, lineView) { | 6943 function buildLineContent(cm, lineView) { |
6126 // The padding-right forces the element to have a 'border', which | 6944 // The padding-right forces the element to have a 'border', which |
6127 // is needed on Webkit to be able to get line-level bounding | 6945 // is needed on Webkit to be able to get line-level bounding |
6128 // rectangles for it (in measureChar). | 6946 // rectangles for it (in measureChar). |
6129 var content = elt("span", null, null, webkit ? "padding-right: .1px" : null)
; | 6947 var content = elt("span", null, null, webkit ? "padding-right: .1px" : null)
; |
6130 var builder = {pre: elt("pre", [content]), content: content, col: 0, pos: 0,
cm: cm}; | 6948 var builder = {pre: elt("pre", [content], "CodeMirror-line"), content: conte
nt, |
| 6949 col: 0, pos: 0, cm: cm, |
| 6950 trailingSpace: false, |
| 6951 splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}; |
6131 lineView.measure = {}; | 6952 lineView.measure = {}; |
6132 | 6953 |
6133 // Iterate over the logical lines that make up this visual line. | 6954 // Iterate over the logical lines that make up this visual line. |
6134 for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { | 6955 for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { |
6135 var line = i ? lineView.rest[i - 1] : lineView.line, order; | 6956 var line = i ? lineView.rest[i - 1] : lineView.line, order; |
6136 builder.pos = 0; | 6957 builder.pos = 0; |
6137 builder.addToken = buildToken; | 6958 builder.addToken = buildToken; |
6138 // Optionally wire in some hacks into the token-rendering | 6959 // Optionally wire in some hacks into the token-rendering |
6139 // algorithm, to deal with browser quirks. | 6960 // algorithm, to deal with browser quirks. |
6140 if ((ie || webkit) && cm.getOption("lineWrapping")) | |
6141 builder.addToken = buildTokenSplitSpaces(builder.addToken); | |
6142 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) | 6961 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) |
6143 builder.addToken = buildTokenBadBidi(builder.addToken, order); | 6962 builder.addToken = buildTokenBadBidi(builder.addToken, order); |
6144 builder.map = []; | 6963 builder.map = []; |
6145 var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineN
o(line); | 6964 var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineN
o(line); |
6146 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpda
te)); | 6965 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpda
te)); |
6147 if (line.styleClasses) { | 6966 if (line.styleClasses) { |
6148 if (line.styleClasses.bgClass) | 6967 if (line.styleClasses.bgClass) |
6149 builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgCla
ss || ""); | 6968 builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgCla
ss || ""); |
6150 if (line.styleClasses.textClass) | 6969 if (line.styleClasses.textClass) |
6151 builder.textClass = joinClasses(line.styleClasses.textClass, builder.t
extClass || ""); | 6970 builder.textClass = joinClasses(line.styleClasses.textClass, builder.t
extClass || ""); |
6152 } | 6971 } |
6153 | 6972 |
6154 // Ensure at least a single node is present, for measuring. | 6973 // Ensure at least a single node is present, for measuring. |
6155 if (builder.map.length == 0) | 6974 if (builder.map.length == 0) |
6156 builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.d
isplay.measure))); | 6975 builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.d
isplay.measure))); |
6157 | 6976 |
6158 // Store the map and a cache object for the current logical line | 6977 // Store the map and a cache object for the current logical line |
6159 if (i == 0) { | 6978 if (i == 0) { |
6160 lineView.measure.map = builder.map; | 6979 lineView.measure.map = builder.map; |
6161 lineView.measure.cache = {}; | 6980 lineView.measure.cache = {}; |
6162 } else { | 6981 } else { |
6163 (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map
); | 6982 (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map
); |
6164 (lineView.measure.caches || (lineView.measure.caches = [])).push({}); | 6983 (lineView.measure.caches || (lineView.measure.caches = [])).push({}); |
6165 } | 6984 } |
6166 } | 6985 } |
6167 | 6986 |
6168 // See issue #2901 | 6987 // See issue #2901 |
6169 if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className)) | 6988 if (webkit) { |
6170 builder.content.className = "cm-tab-wrap-hack"; | 6989 var last = builder.content.lastChild |
| 6990 if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.query
Selector(".cm-tab"))) |
| 6991 builder.content.className = "cm-tab-wrap-hack"; |
| 6992 } |
6171 | 6993 |
6172 signal(cm, "renderLine", cm, lineView.line, builder.pre); | 6994 signal(cm, "renderLine", cm, lineView.line, builder.pre); |
6173 if (builder.pre.className) | 6995 if (builder.pre.className) |
6174 builder.textClass = joinClasses(builder.pre.className, builder.textClass |
| ""); | 6996 builder.textClass = joinClasses(builder.pre.className, builder.textClass |
| ""); |
6175 | 6997 |
6176 return builder; | 6998 return builder; |
6177 } | 6999 } |
6178 | 7000 |
6179 function defaultSpecialCharPlaceholder(ch) { | 7001 function defaultSpecialCharPlaceholder(ch) { |
6180 var token = elt("span", "\u2022", "cm-invalidchar"); | 7002 var token = elt("span", "\u2022", "cm-invalidchar"); |
6181 token.title = "\\u" + ch.charCodeAt(0).toString(16); | 7003 token.title = "\\u" + ch.charCodeAt(0).toString(16); |
| 7004 token.setAttribute("aria-label", token.title); |
6182 return token; | 7005 return token; |
6183 } | 7006 } |
6184 | 7007 |
6185 // Build up the DOM representation for a single token, and add it to | 7008 // Build up the DOM representation for a single token, and add it to |
6186 // the line map. Takes care to render special characters separately. | 7009 // the line map. Takes care to render special characters separately. |
6187 function buildToken(builder, text, style, startStyle, endStyle, title, css) { | 7010 function buildToken(builder, text, style, startStyle, endStyle, title, css) { |
6188 if (!text) return; | 7011 if (!text) return; |
6189 var special = builder.cm.options.specialChars, mustWrap = false; | 7012 var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSp
ace) : text |
| 7013 var special = builder.cm.state.specialChars, mustWrap = false; |
6190 if (!special.test(text)) { | 7014 if (!special.test(text)) { |
6191 builder.col += text.length; | 7015 builder.col += text.length; |
6192 var content = document.createTextNode(text); | 7016 var content = document.createTextNode(displayText); |
6193 builder.map.push(builder.pos, builder.pos + text.length, content); | 7017 builder.map.push(builder.pos, builder.pos + text.length, content); |
6194 if (ie && ie_version < 9) mustWrap = true; | 7018 if (ie && ie_version < 9) mustWrap = true; |
6195 builder.pos += text.length; | 7019 builder.pos += text.length; |
6196 } else { | 7020 } else { |
6197 var content = document.createDocumentFragment(), pos = 0; | 7021 var content = document.createDocumentFragment(), pos = 0; |
6198 while (true) { | 7022 while (true) { |
6199 special.lastIndex = pos; | 7023 special.lastIndex = pos; |
6200 var m = special.exec(text); | 7024 var m = special.exec(text); |
6201 var skipped = m ? m.index - pos : text.length - pos; | 7025 var skipped = m ? m.index - pos : text.length - pos; |
6202 if (skipped) { | 7026 if (skipped) { |
6203 var txt = document.createTextNode(text.slice(pos, pos + skipped)); | 7027 var txt = document.createTextNode(displayText.slice(pos, pos + skipped
)); |
6204 if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); | 7028 if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); |
6205 else content.appendChild(txt); | 7029 else content.appendChild(txt); |
6206 builder.map.push(builder.pos, builder.pos + skipped, txt); | 7030 builder.map.push(builder.pos, builder.pos + skipped, txt); |
6207 builder.col += skipped; | 7031 builder.col += skipped; |
6208 builder.pos += skipped; | 7032 builder.pos += skipped; |
6209 } | 7033 } |
6210 if (!m) break; | 7034 if (!m) break; |
6211 pos += skipped + 1; | 7035 pos += skipped + 1; |
6212 if (m[0] == "\t") { | 7036 if (m[0] == "\t") { |
6213 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder
.col % tabSize; | 7037 var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder
.col % tabSize; |
6214 var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"
)); | 7038 var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"
)); |
| 7039 txt.setAttribute("role", "presentation"); |
| 7040 txt.setAttribute("cm-text", "\t"); |
6215 builder.col += tabWidth; | 7041 builder.col += tabWidth; |
| 7042 } else if (m[0] == "\r" || m[0] == "\n") { |
| 7043 var txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\
u2424", "cm-invalidchar")); |
| 7044 txt.setAttribute("cm-text", m[0]); |
| 7045 builder.col += 1; |
6216 } else { | 7046 } else { |
6217 var txt = builder.cm.options.specialCharPlaceholder(m[0]); | 7047 var txt = builder.cm.options.specialCharPlaceholder(m[0]); |
| 7048 txt.setAttribute("cm-text", m[0]); |
6218 if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); | 7049 if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); |
6219 else content.appendChild(txt); | 7050 else content.appendChild(txt); |
6220 builder.col += 1; | 7051 builder.col += 1; |
6221 } | 7052 } |
6222 builder.map.push(builder.pos, builder.pos + 1, txt); | 7053 builder.map.push(builder.pos, builder.pos + 1, txt); |
6223 builder.pos++; | 7054 builder.pos++; |
6224 } | 7055 } |
6225 } | 7056 } |
| 7057 builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 |
6226 if (style || startStyle || endStyle || mustWrap || css) { | 7058 if (style || startStyle || endStyle || mustWrap || css) { |
6227 var fullStyle = style || ""; | 7059 var fullStyle = style || ""; |
6228 if (startStyle) fullStyle += startStyle; | 7060 if (startStyle) fullStyle += startStyle; |
6229 if (endStyle) fullStyle += endStyle; | 7061 if (endStyle) fullStyle += endStyle; |
6230 var token = elt("span", [content], fullStyle, css); | 7062 var token = elt("span", [content], fullStyle, css); |
6231 if (title) token.title = title; | 7063 if (title) token.title = title; |
6232 return builder.content.appendChild(token); | 7064 return builder.content.appendChild(token); |
6233 } | 7065 } |
6234 builder.content.appendChild(content); | 7066 builder.content.appendChild(content); |
6235 } | 7067 } |
6236 | 7068 |
6237 function buildTokenSplitSpaces(inner) { | 7069 function splitSpaces(text, trailingBefore) { |
6238 function split(old) { | 7070 if (text.length > 1 && !/ /.test(text)) return text |
6239 var out = " "; | 7071 var spaceBefore = trailingBefore, result = "" |
6240 for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; | 7072 for (var i = 0; i < text.length; i++) { |
6241 out += " "; | 7073 var ch = text.charAt(i) |
6242 return out; | 7074 if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i
+ 1) == 32)) |
| 7075 ch = "\u00a0" |
| 7076 result += ch |
| 7077 spaceBefore = ch == " " |
6243 } | 7078 } |
6244 return function(builder, text, style, startStyle, endStyle, title) { | 7079 return result |
6245 inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle,
title); | |
6246 }; | |
6247 } | 7080 } |
6248 | 7081 |
6249 // Work around nonsense dimensions being reported for stretches of | 7082 // Work around nonsense dimensions being reported for stretches of |
6250 // right-to-left text. | 7083 // right-to-left text. |
6251 function buildTokenBadBidi(inner, order) { | 7084 function buildTokenBadBidi(inner, order) { |
6252 return function(builder, text, style, startStyle, endStyle, title) { | 7085 return function(builder, text, style, startStyle, endStyle, title, css) { |
6253 style = style ? style + " cm-force-border" : "cm-force-border"; | 7086 style = style ? style + " cm-force-border" : "cm-force-border"; |
6254 var start = builder.pos, end = start + text.length; | 7087 var start = builder.pos, end = start + text.length; |
6255 for (;;) { | 7088 for (;;) { |
6256 // Find the part that overlaps with the start of this text | 7089 // Find the part that overlaps with the start of this text |
6257 for (var i = 0; i < order.length; i++) { | 7090 for (var i = 0; i < order.length; i++) { |
6258 var part = order[i]; | 7091 var part = order[i]; |
6259 if (part.to > start && part.from <= start) break; | 7092 if (part.to > start && part.from <= start) break; |
6260 } | 7093 } |
6261 if (part.to >= end) return inner(builder, text, style, startStyle, endSt
yle, title); | 7094 if (part.to >= end) return inner(builder, text, style, startStyle, endSt
yle, title, css); |
6262 inner(builder, text.slice(0, part.to - start), style, startStyle, null,
title); | 7095 inner(builder, text.slice(0, part.to - start), style, startStyle, null,
title, css); |
6263 startStyle = null; | 7096 startStyle = null; |
6264 text = text.slice(part.to - start); | 7097 text = text.slice(part.to - start); |
6265 start = part.to; | 7098 start = part.to; |
6266 } | 7099 } |
6267 }; | 7100 }; |
6268 } | 7101 } |
6269 | 7102 |
6270 function buildCollapsedSpan(builder, size, marker, ignoreWidget) { | 7103 function buildCollapsedSpan(builder, size, marker, ignoreWidget) { |
6271 var widget = !ignoreWidget && marker.widgetNode; | 7104 var widget = !ignoreWidget && marker.widgetNode; |
| 7105 if (widget) builder.map.push(builder.pos, builder.pos + size, widget); |
| 7106 if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { |
| 7107 if (!widget) |
| 7108 widget = builder.content.appendChild(document.createElement("span")); |
| 7109 widget.setAttribute("cm-marker", marker.id); |
| 7110 } |
6272 if (widget) { | 7111 if (widget) { |
6273 builder.map.push(builder.pos, builder.pos + size, widget); | 7112 builder.cm.display.input.setUneditable(widget); |
6274 builder.content.appendChild(widget); | 7113 builder.content.appendChild(widget); |
6275 } | 7114 } |
6276 builder.pos += size; | 7115 builder.pos += size; |
| 7116 builder.trailingSpace = false |
6277 } | 7117 } |
6278 | 7118 |
6279 // Outputs a number of spans to make up a line, taking highlighting | 7119 // Outputs a number of spans to make up a line, taking highlighting |
6280 // and marked text into account. | 7120 // and marked text into account. |
6281 function insertLineContent(line, builder, styles) { | 7121 function insertLineContent(line, builder, styles) { |
6282 var spans = line.markedSpans, allText = line.text, at = 0; | 7122 var spans = line.markedSpans, allText = line.text, at = 0; |
6283 if (!spans) { | 7123 if (!spans) { |
6284 for (var i = 1; i < styles.length; i+=2) | 7124 for (var i = 1; i < styles.length; i+=2) |
6285 builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTo
kenStyle(styles[i+1], builder.cm.options)); | 7125 builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTo
kenStyle(styles[i+1], builder.cm.options)); |
6286 return; | 7126 return; |
6287 } | 7127 } |
6288 | 7128 |
6289 var len = allText.length, pos = 0, i = 1, text = "", style, css; | 7129 var len = allText.length, pos = 0, i = 1, text = "", style, css; |
6290 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapse
d; | 7130 var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapse
d; |
6291 for (;;) { | 7131 for (;;) { |
6292 if (nextChange == pos) { // Update current marker set | 7132 if (nextChange == pos) { // Update current marker set |
6293 spanStyle = spanEndStyle = spanStartStyle = title = css = ""; | 7133 spanStyle = spanEndStyle = spanStartStyle = title = css = ""; |
6294 collapsed = null; nextChange = Infinity; | 7134 collapsed = null; nextChange = Infinity; |
6295 var foundBookmarks = []; | 7135 var foundBookmarks = [], endStyles |
6296 for (var j = 0; j < spans.length; ++j) { | 7136 for (var j = 0; j < spans.length; ++j) { |
6297 var sp = spans[j], m = sp.marker; | 7137 var sp = spans[j], m = sp.marker; |
6298 if (sp.from <= pos && (sp.to == null || sp.to > pos)) { | 7138 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { |
6299 if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanE
ndStyle = ""; } | 7139 foundBookmarks.push(m); |
| 7140 } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collap
sed && sp.to == pos && sp.from == pos)) { |
| 7141 if (sp.to != null && sp.to != pos && nextChange > sp.to) { |
| 7142 nextChange = sp.to; |
| 7143 spanEndStyle = ""; |
| 7144 } |
6300 if (m.className) spanStyle += " " + m.className; | 7145 if (m.className) spanStyle += " " + m.className; |
6301 if (m.css) css = m.css; | 7146 if (m.css) css = (css ? css + ";" : "") + m.css; |
6302 if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startS
tyle; | 7147 if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startS
tyle; |
6303 if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endSt
yle; | 7148 if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = []
)).push(m.endStyle, sp.to) |
6304 if (m.title && !title) title = m.title; | 7149 if (m.title && !title) title = m.title; |
6305 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.
marker, m) < 0)) | 7150 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.
marker, m) < 0)) |
6306 collapsed = sp; | 7151 collapsed = sp; |
6307 } else if (sp.from > pos && nextChange > sp.from) { | 7152 } else if (sp.from > pos && nextChange > sp.from) { |
6308 nextChange = sp.from; | 7153 nextChange = sp.from; |
6309 } | 7154 } |
6310 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookm
arks.push(m); | |
6311 } | 7155 } |
| 7156 if (endStyles) for (var j = 0; j < endStyles.length; j += 2) |
| 7157 if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j] |
| 7158 |
| 7159 if (!collapsed || collapsed.from == pos) for (var j = 0; j < foundBookma
rks.length; ++j) |
| 7160 buildCollapsedSpan(builder, 0, foundBookmarks[j]); |
6312 if (collapsed && (collapsed.from || 0) == pos) { | 7161 if (collapsed && (collapsed.from || 0) == pos) { |
6313 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapse
d.to) - pos, | 7162 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapse
d.to) - pos, |
6314 collapsed.marker, collapsed.from == null); | 7163 collapsed.marker, collapsed.from == null); |
6315 if (collapsed.to == null) return; | 7164 if (collapsed.to == null) return; |
| 7165 if (collapsed.to == pos) collapsed = false; |
6316 } | 7166 } |
6317 if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookma
rks.length; ++j) | |
6318 buildCollapsedSpan(builder, 0, foundBookmarks[j]); | |
6319 } | 7167 } |
6320 if (pos >= len) break; | 7168 if (pos >= len) break; |
6321 | 7169 |
6322 var upto = Math.min(len, nextChange); | 7170 var upto = Math.min(len, nextChange); |
6323 while (true) { | 7171 while (true) { |
6324 if (text) { | 7172 if (text) { |
6325 var end = pos + text.length; | 7173 var end = pos + text.length; |
6326 if (!collapsed) { | 7174 if (!collapsed) { |
6327 var tokenText = end > upto ? text.slice(0, upto - pos) : text; | 7175 var tokenText = end > upto ? text.slice(0, upto - pos) : text; |
6328 builder.addToken(builder, tokenText, style ? style + spanStyle : spa
nStyle, | 7176 builder.addToken(builder, tokenText, style ? style + spanStyle : spa
nStyle, |
(...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
6494 for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(l
ines); | 7342 for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(l
ines); |
6495 }, | 7343 }, |
6496 insertInner: function(at, lines, height) { | 7344 insertInner: function(at, lines, height) { |
6497 this.size += lines.length; | 7345 this.size += lines.length; |
6498 this.height += height; | 7346 this.height += height; |
6499 for (var i = 0; i < this.children.length; ++i) { | 7347 for (var i = 0; i < this.children.length; ++i) { |
6500 var child = this.children[i], sz = child.chunkSize(); | 7348 var child = this.children[i], sz = child.chunkSize(); |
6501 if (at <= sz) { | 7349 if (at <= sz) { |
6502 child.insertInner(at, lines, height); | 7350 child.insertInner(at, lines, height); |
6503 if (child.lines && child.lines.length > 50) { | 7351 if (child.lines && child.lines.length > 50) { |
6504 while (child.lines.length > 50) { | 7352 // To avoid memory thrashing when child.lines is huge (e.g. first vi
ew of a large file), it's never spliced. |
6505 var spilled = child.lines.splice(child.lines.length - 25, 25); | 7353 // Instead, small slices are taken. They're taken in order because s
equential memory accesses are fastest. |
6506 var newleaf = new LeafChunk(spilled); | 7354 var remaining = child.lines.length % 25 + 25 |
6507 child.height -= newleaf.height; | 7355 for (var pos = remaining; pos < child.lines.length;) { |
6508 this.children.splice(i + 1, 0, newleaf); | 7356 var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)); |
6509 newleaf.parent = this; | 7357 child.height -= leaf.height; |
| 7358 this.children.splice(++i, 0, leaf); |
| 7359 leaf.parent = this; |
6510 } | 7360 } |
| 7361 child.lines = child.lines.slice(0, remaining); |
6511 this.maybeSpill(); | 7362 this.maybeSpill(); |
6512 } | 7363 } |
6513 break; | 7364 break; |
6514 } | 7365 } |
6515 at -= sz; | 7366 at -= sz; |
6516 } | 7367 } |
6517 }, | 7368 }, |
6518 // When a node has grown, check whether it should be split. | 7369 // When a node has grown, check whether it should be split. |
6519 maybeSpill: function() { | 7370 maybeSpill: function() { |
6520 if (this.children.length <= 10) return; | 7371 if (this.children.length <= 10) return; |
6521 var me = this; | 7372 var me = this; |
6522 do { | 7373 do { |
6523 var spilled = me.children.splice(me.children.length - 5, 5); | 7374 var spilled = me.children.splice(me.children.length - 5, 5); |
6524 var sibling = new BranchChunk(spilled); | 7375 var sibling = new BranchChunk(spilled); |
6525 if (!me.parent) { // Become the parent node | 7376 if (!me.parent) { // Become the parent node |
6526 var copy = new BranchChunk(me.children); | 7377 var copy = new BranchChunk(me.children); |
6527 copy.parent = me; | 7378 copy.parent = me; |
6528 me.children = [copy, sibling]; | 7379 me.children = [copy, sibling]; |
6529 me = copy; | 7380 me = copy; |
6530 } else { | 7381 } else { |
6531 me.size -= sibling.size; | 7382 me.size -= sibling.size; |
6532 me.height -= sibling.height; | 7383 me.height -= sibling.height; |
6533 var myIndex = indexOf(me.parent.children, me); | 7384 var myIndex = indexOf(me.parent.children, me); |
6534 me.parent.children.splice(myIndex + 1, 0, sibling); | 7385 me.parent.children.splice(myIndex + 1, 0, sibling); |
6535 } | 7386 } |
6536 sibling.parent = me.parent; | 7387 sibling.parent = me.parent; |
6537 } while (me.children.length > 10); | 7388 } while (me.children.length > 10); |
6538 me.parent.maybeSpill(); | 7389 me.parent.maybeSpill(); |
6539 }, | 7390 }, |
6540 iterN: function(at, n, op) { | 7391 iterN: function(at, n, op) { |
6541 for (var i = 0; i < this.children.length; ++i) { | 7392 for (var i = 0; i < this.children.length; ++i) { |
6542 var child = this.children[i], sz = child.chunkSize(); | 7393 var child = this.children[i], sz = child.chunkSize(); |
6543 if (at < sz) { | 7394 if (at < sz) { |
6544 var used = Math.min(n, sz - at); | 7395 var used = Math.min(n, sz - at); |
6545 if (child.iterN(at, used, op)) return true; | 7396 if (child.iterN(at, used, op)) return true; |
6546 if ((n -= used) == 0) break; | 7397 if ((n -= used) == 0) break; |
6547 at = 0; | 7398 at = 0; |
6548 } else at -= sz; | 7399 } else at -= sz; |
6549 } | 7400 } |
6550 } | 7401 } |
6551 }; | 7402 }; |
6552 | 7403 |
6553 var nextDocId = 0; | 7404 var nextDocId = 0; |
6554 var Doc = CodeMirror.Doc = function(text, mode, firstLine) { | 7405 var Doc = CodeMirror.Doc = function(text, mode, firstLine, lineSep) { |
6555 if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); | 7406 if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep); |
6556 if (firstLine == null) firstLine = 0; | 7407 if (firstLine == null) firstLine = 0; |
6557 | 7408 |
6558 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); | 7409 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); |
6559 this.first = firstLine; | 7410 this.first = firstLine; |
6560 this.scrollTop = this.scrollLeft = 0; | 7411 this.scrollTop = this.scrollLeft = 0; |
6561 this.cantEdit = false; | 7412 this.cantEdit = false; |
6562 this.cleanGeneration = 1; | 7413 this.cleanGeneration = 1; |
6563 this.frontier = firstLine; | 7414 this.frontier = firstLine; |
6564 var start = Pos(firstLine, 0); | 7415 var start = Pos(firstLine, 0); |
6565 this.sel = simpleSelection(start); | 7416 this.sel = simpleSelection(start); |
6566 this.history = new History(null); | 7417 this.history = new History(null); |
6567 this.id = ++nextDocId; | 7418 this.id = ++nextDocId; |
6568 this.modeOption = mode; | 7419 this.modeOption = mode; |
| 7420 this.lineSep = lineSep; |
| 7421 this.extend = false; |
6569 | 7422 |
6570 if (typeof text == "string") text = splitLines(text); | 7423 if (typeof text == "string") text = this.splitLines(text); |
6571 updateDoc(this, {from: start, to: start, text: text}); | 7424 updateDoc(this, {from: start, to: start, text: text}); |
6572 setSelection(this, simpleSelection(start), sel_dontScroll); | 7425 setSelection(this, simpleSelection(start), sel_dontScroll); |
6573 }; | 7426 }; |
6574 | 7427 |
6575 Doc.prototype = createObj(BranchChunk.prototype, { | 7428 Doc.prototype = createObj(BranchChunk.prototype, { |
6576 constructor: Doc, | 7429 constructor: Doc, |
6577 // Iterate over the document. Supports two forms -- with only one | 7430 // Iterate over the document. Supports two forms -- with only one |
6578 // argument, it calls that for each line in the document. With | 7431 // argument, it calls that for each line in the document. With |
6579 // three, it iterates over the range given by the first two (with | 7432 // three, it iterates over the range given by the first two (with |
6580 // the second being non-inclusive). | 7433 // the second being non-inclusive). |
6581 iter: function(from, to, op) { | 7434 iter: function(from, to, op) { |
6582 if (op) this.iterN(from - this.first, to - from, op); | 7435 if (op) this.iterN(from - this.first, to - from, op); |
6583 else this.iterN(this.first, this.first + this.size, from); | 7436 else this.iterN(this.first, this.first + this.size, from); |
6584 }, | 7437 }, |
6585 | 7438 |
6586 // Non-public interface for adding and removing lines. | 7439 // Non-public interface for adding and removing lines. |
6587 insert: function(at, lines) { | 7440 insert: function(at, lines) { |
6588 var height = 0; | 7441 var height = 0; |
6589 for (var i = 0; i < lines.length; ++i) height += lines[i].height; | 7442 for (var i = 0; i < lines.length; ++i) height += lines[i].height; |
6590 this.insertInner(at - this.first, lines, height); | 7443 this.insertInner(at - this.first, lines, height); |
6591 }, | 7444 }, |
6592 remove: function(at, n) { this.removeInner(at - this.first, n); }, | 7445 remove: function(at, n) { this.removeInner(at - this.first, n); }, |
6593 | 7446 |
6594 // From here, the methods are part of the public interface. Most | 7447 // From here, the methods are part of the public interface. Most |
6595 // are also available from CodeMirror (editor) instances. | 7448 // are also available from CodeMirror (editor) instances. |
6596 | 7449 |
6597 getValue: function(lineSep) { | 7450 getValue: function(lineSep) { |
6598 var lines = getLines(this, this.first, this.first + this.size); | 7451 var lines = getLines(this, this.first, this.first + this.size); |
6599 if (lineSep === false) return lines; | 7452 if (lineSep === false) return lines; |
6600 return lines.join(lineSep || "\n"); | 7453 return lines.join(lineSep || this.lineSeparator()); |
6601 }, | 7454 }, |
6602 setValue: docMethodOp(function(code) { | 7455 setValue: docMethodOp(function(code) { |
6603 var top = Pos(this.first, 0), last = this.first + this.size - 1; | 7456 var top = Pos(this.first, 0), last = this.first + this.size - 1; |
6604 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length
), | 7457 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length
), |
6605 text: splitLines(code), origin: "setValue", full: true},
true); | 7458 text: this.splitLines(code), origin: "setValue", full: t
rue}, true); |
6606 setSelection(this, simpleSelection(top)); | 7459 setSelection(this, simpleSelection(top)); |
6607 }), | 7460 }), |
6608 replaceRange: function(code, from, to, origin) { | 7461 replaceRange: function(code, from, to, origin) { |
6609 from = clipPos(this, from); | 7462 from = clipPos(this, from); |
6610 to = to ? clipPos(this, to) : from; | 7463 to = to ? clipPos(this, to) : from; |
6611 replaceRange(this, code, from, to, origin); | 7464 replaceRange(this, code, from, to, origin); |
6612 }, | 7465 }, |
6613 getRange: function(from, to, lineSep) { | 7466 getRange: function(from, to, lineSep) { |
6614 var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); | 7467 var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); |
6615 if (lineSep === false) return lines; | 7468 if (lineSep === false) return lines; |
6616 return lines.join(lineSep || "\n"); | 7469 return lines.join(lineSep || this.lineSeparator()); |
6617 }, | 7470 }, |
6618 | 7471 |
6619 getLine: function(line) {var l = this.getLineHandle(line); return l && l.tex
t;}, | 7472 getLine: function(line) {var l = this.getLineHandle(line); return l && l.tex
t;}, |
6620 | 7473 |
6621 getLineHandle: function(line) {if (isLine(this, line)) return getLine(this,
line);}, | 7474 getLineHandle: function(line) {if (isLine(this, line)) return getLine(this,
line);}, |
6622 getLineNumber: function(line) {return lineNo(line);}, | 7475 getLineNumber: function(line) {return lineNo(line);}, |
6623 | 7476 |
6624 getLineHandleVisualStart: function(line) { | 7477 getLineHandleVisualStart: function(line) { |
6625 if (typeof line == "number") line = getLine(this, line); | 7478 if (typeof line == "number") line = getLine(this, line); |
6626 return visualLine(line); | 7479 return visualLine(line); |
(...skipping 19 matching lines...) Expand all Loading... |
6646 setCursor: docMethodOp(function(line, ch, options) { | 7499 setCursor: docMethodOp(function(line, ch, options) { |
6647 setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line,
ch || 0) : line), null, options); | 7500 setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line,
ch || 0) : line), null, options); |
6648 }), | 7501 }), |
6649 setSelection: docMethodOp(function(anchor, head, options) { | 7502 setSelection: docMethodOp(function(anchor, head, options) { |
6650 setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anch
or), options); | 7503 setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anch
or), options); |
6651 }), | 7504 }), |
6652 extendSelection: docMethodOp(function(head, other, options) { | 7505 extendSelection: docMethodOp(function(head, other, options) { |
6653 extendSelection(this, clipPos(this, head), other && clipPos(this, other),
options); | 7506 extendSelection(this, clipPos(this, head), other && clipPos(this, other),
options); |
6654 }), | 7507 }), |
6655 extendSelections: docMethodOp(function(heads, options) { | 7508 extendSelections: docMethodOp(function(heads, options) { |
6656 extendSelections(this, clipPosArray(this, heads, options)); | 7509 extendSelections(this, clipPosArray(this, heads), options); |
6657 }), | 7510 }), |
6658 extendSelectionsBy: docMethodOp(function(f, options) { | 7511 extendSelectionsBy: docMethodOp(function(f, options) { |
6659 extendSelections(this, map(this.sel.ranges, f), options); | 7512 var heads = map(this.sel.ranges, f); |
| 7513 extendSelections(this, clipPosArray(this, heads), options); |
6660 }), | 7514 }), |
6661 setSelections: docMethodOp(function(ranges, primary, options) { | 7515 setSelections: docMethodOp(function(ranges, primary, options) { |
6662 if (!ranges.length) return; | 7516 if (!ranges.length) return; |
6663 for (var i = 0, out = []; i < ranges.length; i++) | 7517 for (var i = 0, out = []; i < ranges.length; i++) |
6664 out[i] = new Range(clipPos(this, ranges[i].anchor), | 7518 out[i] = new Range(clipPos(this, ranges[i].anchor), |
6665 clipPos(this, ranges[i].head)); | 7519 clipPos(this, ranges[i].head)); |
6666 if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIn
dex); | 7520 if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIn
dex); |
6667 setSelection(this, normalizeSelection(out, primary), options); | 7521 setSelection(this, normalizeSelection(out, primary), options); |
6668 }), | 7522 }), |
6669 addSelection: docMethodOp(function(anchor, head, options) { | 7523 addSelection: docMethodOp(function(anchor, head, options) { |
6670 var ranges = this.sel.ranges.slice(0); | 7524 var ranges = this.sel.ranges.slice(0); |
6671 ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)
)); | 7525 ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)
)); |
6672 setSelection(this, normalizeSelection(ranges, ranges.length - 1), options)
; | 7526 setSelection(this, normalizeSelection(ranges, ranges.length - 1), options)
; |
6673 }), | 7527 }), |
6674 | 7528 |
6675 getSelection: function(lineSep) { | 7529 getSelection: function(lineSep) { |
6676 var ranges = this.sel.ranges, lines; | 7530 var ranges = this.sel.ranges, lines; |
6677 for (var i = 0; i < ranges.length; i++) { | 7531 for (var i = 0; i < ranges.length; i++) { |
6678 var sel = getBetween(this, ranges[i].from(), ranges[i].to()); | 7532 var sel = getBetween(this, ranges[i].from(), ranges[i].to()); |
6679 lines = lines ? lines.concat(sel) : sel; | 7533 lines = lines ? lines.concat(sel) : sel; |
6680 } | 7534 } |
6681 if (lineSep === false) return lines; | 7535 if (lineSep === false) return lines; |
6682 else return lines.join(lineSep || "\n"); | 7536 else return lines.join(lineSep || this.lineSeparator()); |
6683 }, | 7537 }, |
6684 getSelections: function(lineSep) { | 7538 getSelections: function(lineSep) { |
6685 var parts = [], ranges = this.sel.ranges; | 7539 var parts = [], ranges = this.sel.ranges; |
6686 for (var i = 0; i < ranges.length; i++) { | 7540 for (var i = 0; i < ranges.length; i++) { |
6687 var sel = getBetween(this, ranges[i].from(), ranges[i].to()); | 7541 var sel = getBetween(this, ranges[i].from(), ranges[i].to()); |
6688 if (lineSep !== false) sel = sel.join(lineSep || "\n"); | 7542 if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator()); |
6689 parts[i] = sel; | 7543 parts[i] = sel; |
6690 } | 7544 } |
6691 return parts; | 7545 return parts; |
6692 }, | 7546 }, |
6693 replaceSelection: function(code, collapse, origin) { | 7547 replaceSelection: function(code, collapse, origin) { |
6694 var dup = []; | 7548 var dup = []; |
6695 for (var i = 0; i < this.sel.ranges.length; i++) | 7549 for (var i = 0; i < this.sel.ranges.length; i++) |
6696 dup[i] = code; | 7550 dup[i] = code; |
6697 this.replaceSelections(dup, collapse, origin || "+input"); | 7551 this.replaceSelections(dup, collapse, origin || "+input"); |
6698 }, | 7552 }, |
6699 replaceSelections: docMethodOp(function(code, collapse, origin) { | 7553 replaceSelections: docMethodOp(function(code, collapse, origin) { |
6700 var changes = [], sel = this.sel; | 7554 var changes = [], sel = this.sel; |
6701 for (var i = 0; i < sel.ranges.length; i++) { | 7555 for (var i = 0; i < sel.ranges.length; i++) { |
6702 var range = sel.ranges[i]; | 7556 var range = sel.ranges[i]; |
6703 changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[
i]), origin: origin}; | 7557 changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(
code[i]), origin: origin}; |
6704 } | 7558 } |
6705 var newSel = collapse && collapse != "end" && computeReplacedSel(this, cha
nges, collapse); | 7559 var newSel = collapse && collapse != "end" && computeReplacedSel(this, cha
nges, collapse); |
6706 for (var i = changes.length - 1; i >= 0; i--) | 7560 for (var i = changes.length - 1; i >= 0; i--) |
6707 makeChange(this, changes[i]); | 7561 makeChange(this, changes[i]); |
6708 if (newSel) setSelectionReplaceHistory(this, newSel); | 7562 if (newSel) setSelectionReplaceHistory(this, newSel); |
6709 else if (this.cm) ensureCursorVisible(this.cm); | 7563 else if (this.cm) ensureCursorVisible(this.cm); |
6710 }), | 7564 }), |
6711 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), | 7565 undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), |
6712 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), | 7566 redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), |
6713 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", t
rue);}), | 7567 undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", t
rue);}), |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
6768 else { | 7622 else { |
6769 var found = cur.match(classTest(cls)); | 7623 var found = cur.match(classTest(cls)); |
6770 if (!found) return false; | 7624 if (!found) return false; |
6771 var end = found.index + found[0].length; | 7625 var end = found.index + found[0].length; |
6772 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.l
ength ? "" : " ") + cur.slice(end) || null; | 7626 line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.l
ength ? "" : " ") + cur.slice(end) || null; |
6773 } | 7627 } |
6774 return true; | 7628 return true; |
6775 }); | 7629 }); |
6776 }), | 7630 }), |
6777 | 7631 |
| 7632 addLineWidget: docMethodOp(function(handle, node, options) { |
| 7633 return addLineWidget(this, handle, node, options); |
| 7634 }), |
| 7635 removeLineWidget: function(widget) { widget.clear(); }, |
| 7636 |
6778 markText: function(from, to, options) { | 7637 markText: function(from, to, options) { |
6779 return markText(this, clipPos(this, from), clipPos(this, to), options, "ra
nge"); | 7638 return markText(this, clipPos(this, from), clipPos(this, to), options, opt
ions && options.type || "range"); |
6780 }, | 7639 }, |
6781 setBookmark: function(pos, options) { | 7640 setBookmark: function(pos, options) { |
6782 var realOpts = {replacedWith: options && (options.nodeType == null ? optio
ns.widget : options), | 7641 var realOpts = {replacedWith: options && (options.nodeType == null ? optio
ns.widget : options), |
6783 insertLeft: options && options.insertLeft, | 7642 insertLeft: options && options.insertLeft, |
6784 clearWhenEmpty: false, shared: options && options.shared}; | 7643 clearWhenEmpty: false, shared: options && options.shared, |
| 7644 handleMouseEvents: options && options.handleMouseEvents}; |
6785 pos = clipPos(this, pos); | 7645 pos = clipPos(this, pos); |
6786 return markText(this, pos, pos, realOpts, "bookmark"); | 7646 return markText(this, pos, pos, realOpts, "bookmark"); |
6787 }, | 7647 }, |
6788 findMarksAt: function(pos) { | 7648 findMarksAt: function(pos) { |
6789 pos = clipPos(this, pos); | 7649 pos = clipPos(this, pos); |
6790 var markers = [], spans = getLine(this, pos.line).markedSpans; | 7650 var markers = [], spans = getLine(this, pos.line).markedSpans; |
6791 if (spans) for (var i = 0; i < spans.length; ++i) { | 7651 if (spans) for (var i = 0; i < spans.length; ++i) { |
6792 var span = spans[i]; | 7652 var span = spans[i]; |
6793 if ((span.from == null || span.from <= pos.ch) && | 7653 if ((span.from == null || span.from <= pos.ch) && |
6794 (span.to == null || span.to >= pos.ch)) | 7654 (span.to == null || span.to >= pos.ch)) |
6795 markers.push(span.marker.parent || span.marker); | 7655 markers.push(span.marker.parent || span.marker); |
6796 } | 7656 } |
6797 return markers; | 7657 return markers; |
6798 }, | 7658 }, |
6799 findMarks: function(from, to, filter) { | 7659 findMarks: function(from, to, filter) { |
6800 from = clipPos(this, from); to = clipPos(this, to); | 7660 from = clipPos(this, from); to = clipPos(this, to); |
6801 var found = [], lineNo = from.line; | 7661 var found = [], lineNo = from.line; |
6802 this.iter(from.line, to.line + 1, function(line) { | 7662 this.iter(from.line, to.line + 1, function(line) { |
6803 var spans = line.markedSpans; | 7663 var spans = line.markedSpans; |
6804 if (spans) for (var i = 0; i < spans.length; i++) { | 7664 if (spans) for (var i = 0; i < spans.length; i++) { |
6805 var span = spans[i]; | 7665 var span = spans[i]; |
6806 if (!(lineNo == from.line && from.ch > span.to || | 7666 if (!(span.to != null && lineNo == from.line && from.ch >= span.to || |
6807 span.from == null && lineNo != from.line|| | 7667 span.from == null && lineNo != from.line || |
6808 lineNo == to.line && span.from > to.ch) && | 7668 span.from != null && lineNo == to.line && span.from >= to.ch) && |
6809 (!filter || filter(span.marker))) | 7669 (!filter || filter(span.marker))) |
6810 found.push(span.marker.parent || span.marker); | 7670 found.push(span.marker.parent || span.marker); |
6811 } | 7671 } |
6812 ++lineNo; | 7672 ++lineNo; |
6813 }); | 7673 }); |
6814 return found; | 7674 return found; |
6815 }, | 7675 }, |
6816 getAllMarks: function() { | 7676 getAllMarks: function() { |
6817 var markers = []; | 7677 var markers = []; |
6818 this.iter(function(line) { | 7678 this.iter(function(line) { |
6819 var sps = line.markedSpans; | 7679 var sps = line.markedSpans; |
6820 if (sps) for (var i = 0; i < sps.length; ++i) | 7680 if (sps) for (var i = 0; i < sps.length; ++i) |
6821 if (sps[i].from != null) markers.push(sps[i].marker); | 7681 if (sps[i].from != null) markers.push(sps[i].marker); |
6822 }); | 7682 }); |
6823 return markers; | 7683 return markers; |
6824 }, | 7684 }, |
6825 | 7685 |
6826 posFromIndex: function(off) { | 7686 posFromIndex: function(off) { |
6827 var ch, lineNo = this.first; | 7687 var ch, lineNo = this.first, sepSize = this.lineSeparator().length; |
6828 this.iter(function(line) { | 7688 this.iter(function(line) { |
6829 var sz = line.text.length + 1; | 7689 var sz = line.text.length + sepSize; |
6830 if (sz > off) { ch = off; return true; } | 7690 if (sz > off) { ch = off; return true; } |
6831 off -= sz; | 7691 off -= sz; |
6832 ++lineNo; | 7692 ++lineNo; |
6833 }); | 7693 }); |
6834 return clipPos(this, Pos(lineNo, ch)); | 7694 return clipPos(this, Pos(lineNo, ch)); |
6835 }, | 7695 }, |
6836 indexFromPos: function (coords) { | 7696 indexFromPos: function (coords) { |
6837 coords = clipPos(this, coords); | 7697 coords = clipPos(this, coords); |
6838 var index = coords.ch; | 7698 var index = coords.ch; |
6839 if (coords.line < this.first || coords.ch < 0) return 0; | 7699 if (coords.line < this.first || coords.ch < 0) return 0; |
| 7700 var sepSize = this.lineSeparator().length; |
6840 this.iter(this.first, coords.line, function (line) { | 7701 this.iter(this.first, coords.line, function (line) { |
6841 index += line.text.length + 1; | 7702 index += line.text.length + sepSize; |
6842 }); | 7703 }); |
6843 return index; | 7704 return index; |
6844 }, | 7705 }, |
6845 | 7706 |
6846 copy: function(copyHistory) { | 7707 copy: function(copyHistory) { |
6847 var doc = new Doc(getLines(this, this.first, this.first + this.size), this
.modeOption, this.first); | 7708 var doc = new Doc(getLines(this, this.first, this.first + this.size), |
| 7709 this.modeOption, this.first, this.lineSep); |
6848 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; | 7710 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; |
6849 doc.sel = this.sel; | 7711 doc.sel = this.sel; |
6850 doc.extend = false; | 7712 doc.extend = false; |
6851 if (copyHistory) { | 7713 if (copyHistory) { |
6852 doc.history.undoDepth = this.history.undoDepth; | 7714 doc.history.undoDepth = this.history.undoDepth; |
6853 doc.setHistory(this.getHistory()); | 7715 doc.setHistory(this.getHistory()); |
6854 } | 7716 } |
6855 return doc; | 7717 return doc; |
6856 }, | 7718 }, |
6857 | 7719 |
6858 linkedDoc: function(options) { | 7720 linkedDoc: function(options) { |
6859 if (!options) options = {}; | 7721 if (!options) options = {}; |
6860 var from = this.first, to = this.first + this.size; | 7722 var from = this.first, to = this.first + this.size; |
6861 if (options.from != null && options.from > from) from = options.from; | 7723 if (options.from != null && options.from > from) from = options.from; |
6862 if (options.to != null && options.to < to) to = options.to; | 7724 if (options.to != null && options.to < to) to = options.to; |
6863 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOpti
on, from); | 7725 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOpti
on, from, this.lineSep); |
6864 if (options.sharedHist) copy.history = this.history; | 7726 if (options.sharedHist) copy.history = this.history; |
6865 (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.s
haredHist}); | 7727 (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.s
haredHist}); |
6866 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}
]; | 7728 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}
]; |
6867 copySharedMarkers(copy, findSharedMarkers(this)); | 7729 copySharedMarkers(copy, findSharedMarkers(this)); |
6868 return copy; | 7730 return copy; |
6869 }, | 7731 }, |
6870 unlinkDoc: function(other) { | 7732 unlinkDoc: function(other) { |
6871 if (other instanceof CodeMirror) other = other.doc; | 7733 if (other instanceof CodeMirror) other = other.doc; |
6872 if (this.linked) for (var i = 0; i < this.linked.length; ++i) { | 7734 if (this.linked) for (var i = 0; i < this.linked.length; ++i) { |
6873 var link = this.linked[i]; | 7735 var link = this.linked[i]; |
6874 if (link.doc != other) continue; | 7736 if (link.doc != other) continue; |
6875 this.linked.splice(i, 1); | 7737 this.linked.splice(i, 1); |
6876 other.unlinkDoc(this); | 7738 other.unlinkDoc(this); |
6877 detachSharedMarkers(findSharedMarkers(this)); | 7739 detachSharedMarkers(findSharedMarkers(this)); |
6878 break; | 7740 break; |
6879 } | 7741 } |
6880 // If the histories were shared, split them again | 7742 // If the histories were shared, split them again |
6881 if (other.history == this.history) { | 7743 if (other.history == this.history) { |
6882 var splitIds = [other.id]; | 7744 var splitIds = [other.id]; |
6883 linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); | 7745 linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); |
6884 other.history = new History(null); | 7746 other.history = new History(null); |
6885 other.history.done = copyHistoryArray(this.history.done, splitIds); | 7747 other.history.done = copyHistoryArray(this.history.done, splitIds); |
6886 other.history.undone = copyHistoryArray(this.history.undone, splitIds); | 7748 other.history.undone = copyHistoryArray(this.history.undone, splitIds); |
6887 } | 7749 } |
6888 }, | 7750 }, |
6889 iterLinkedDocs: function(f) {linkedDocs(this, f);}, | 7751 iterLinkedDocs: function(f) {linkedDocs(this, f);}, |
6890 | 7752 |
6891 getMode: function() {return this.mode;}, | 7753 getMode: function() {return this.mode;}, |
6892 getEditor: function() {return this.cm;} | 7754 getEditor: function() {return this.cm;}, |
| 7755 |
| 7756 splitLines: function(str) { |
| 7757 if (this.lineSep) return str.split(this.lineSep); |
| 7758 return splitLinesAuto(str); |
| 7759 }, |
| 7760 lineSeparator: function() { return this.lineSep || "\n"; } |
6893 }); | 7761 }); |
6894 | 7762 |
6895 // Public alias. | 7763 // Public alias. |
6896 Doc.prototype.eachLine = Doc.prototype.iter; | 7764 Doc.prototype.eachLine = Doc.prototype.iter; |
6897 | 7765 |
6898 // Set up methods on CodeMirror's prototype to redirect to the editor's docume
nt. | 7766 // Set up methods on CodeMirror's prototype to redirect to the editor's docume
nt. |
6899 var dontDelegate = "iter insert remove copy getEditor".split(" "); | 7767 var dontDelegate = "iter insert remove copy getEditor constructor".split(" "); |
6900 for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && inde
xOf(dontDelegate, prop) < 0) | 7768 for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && inde
xOf(dontDelegate, prop) < 0) |
6901 CodeMirror.prototype[prop] = (function(method) { | 7769 CodeMirror.prototype[prop] = (function(method) { |
6902 return function() {return method.apply(this.doc, arguments);}; | 7770 return function() {return method.apply(this.doc, arguments);}; |
6903 })(Doc.prototype[prop]); | 7771 })(Doc.prototype[prop]); |
6904 | 7772 |
6905 eventMixin(Doc); | 7773 eventMixin(Doc); |
6906 | 7774 |
6907 // Call f for all linked documents. | 7775 // Call f for all linked documents. |
6908 function linkedDocs(doc, f, sharedHistOnly) { | 7776 function linkedDocs(doc, f, sharedHistOnly) { |
6909 function propagate(doc, skip, sharedHist) { | 7777 function propagate(doc, skip, sharedHist) { |
(...skipping 412 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
7322 emitter.addEventListener(type, f, false); | 8190 emitter.addEventListener(type, f, false); |
7323 else if (emitter.attachEvent) | 8191 else if (emitter.attachEvent) |
7324 emitter.attachEvent("on" + type, f); | 8192 emitter.attachEvent("on" + type, f); |
7325 else { | 8193 else { |
7326 var map = emitter._handlers || (emitter._handlers = {}); | 8194 var map = emitter._handlers || (emitter._handlers = {}); |
7327 var arr = map[type] || (map[type] = []); | 8195 var arr = map[type] || (map[type] = []); |
7328 arr.push(f); | 8196 arr.push(f); |
7329 } | 8197 } |
7330 }; | 8198 }; |
7331 | 8199 |
| 8200 var noHandlers = [] |
| 8201 function getHandlers(emitter, type, copy) { |
| 8202 var arr = emitter._handlers && emitter._handlers[type] |
| 8203 if (copy) return arr && arr.length > 0 ? arr.slice() : noHandlers |
| 8204 else return arr || noHandlers |
| 8205 } |
| 8206 |
7332 var off = CodeMirror.off = function(emitter, type, f) { | 8207 var off = CodeMirror.off = function(emitter, type, f) { |
7333 if (emitter.removeEventListener) | 8208 if (emitter.removeEventListener) |
7334 emitter.removeEventListener(type, f, false); | 8209 emitter.removeEventListener(type, f, false); |
7335 else if (emitter.detachEvent) | 8210 else if (emitter.detachEvent) |
7336 emitter.detachEvent("on" + type, f); | 8211 emitter.detachEvent("on" + type, f); |
7337 else { | 8212 else { |
7338 var arr = emitter._handlers && emitter._handlers[type]; | 8213 var handlers = getHandlers(emitter, type, false) |
7339 if (!arr) return; | 8214 for (var i = 0; i < handlers.length; ++i) |
7340 for (var i = 0; i < arr.length; ++i) | 8215 if (handlers[i] == f) { handlers.splice(i, 1); break; } |
7341 if (arr[i] == f) { arr.splice(i, 1); break; } | |
7342 } | 8216 } |
7343 }; | 8217 }; |
7344 | 8218 |
7345 var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { | 8219 var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { |
7346 var arr = emitter._handlers && emitter._handlers[type]; | 8220 var handlers = getHandlers(emitter, type, true) |
7347 if (!arr) return; | 8221 if (!handlers.length) return; |
7348 var args = Array.prototype.slice.call(arguments, 2); | 8222 var args = Array.prototype.slice.call(arguments, 2); |
7349 for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); | 8223 for (var i = 0; i < handlers.length; ++i) handlers[i].apply(null, args); |
7350 }; | 8224 }; |
7351 | 8225 |
7352 var orphanDelayedCallbacks = null; | 8226 var orphanDelayedCallbacks = null; |
7353 | 8227 |
7354 // Often, we want to signal events at a point where we are in the | 8228 // Often, we want to signal events at a point where we are in the |
7355 // middle of some work, but don't want the handler to start calling | 8229 // middle of some work, but don't want the handler to start calling |
7356 // other methods on the editor, which might be in an inconsistent | 8230 // other methods on the editor, which might be in an inconsistent |
7357 // state or simply not expect any other events to happen. | 8231 // state or simply not expect any other events to happen. |
7358 // signalLater looks whether there are any handlers, and schedules | 8232 // signalLater looks whether there are any handlers, and schedules |
7359 // them to be executed when the last operation ends, or, if no | 8233 // them to be executed when the last operation ends, or, if no |
7360 // operation is active, when a timeout fires. | 8234 // operation is active, when a timeout fires. |
7361 function signalLater(emitter, type /*, values...*/) { | 8235 function signalLater(emitter, type /*, values...*/) { |
7362 var arr = emitter._handlers && emitter._handlers[type]; | 8236 var arr = getHandlers(emitter, type, false) |
7363 if (!arr) return; | 8237 if (!arr.length) return; |
7364 var args = Array.prototype.slice.call(arguments, 2), list; | 8238 var args = Array.prototype.slice.call(arguments, 2), list; |
7365 if (operationGroup) { | 8239 if (operationGroup) { |
7366 list = operationGroup.delayedCallbacks; | 8240 list = operationGroup.delayedCallbacks; |
7367 } else if (orphanDelayedCallbacks) { | 8241 } else if (orphanDelayedCallbacks) { |
7368 list = orphanDelayedCallbacks; | 8242 list = orphanDelayedCallbacks; |
7369 } else { | 8243 } else { |
7370 list = orphanDelayedCallbacks = []; | 8244 list = orphanDelayedCallbacks = []; |
7371 setTimeout(fireOrphanDelayed, 0); | 8245 setTimeout(fireOrphanDelayed, 0); |
7372 } | 8246 } |
7373 function bnd(f) {return function(){f.apply(null, args);};}; | 8247 function bnd(f) {return function(){f.apply(null, args);};}; |
(...skipping 19 matching lines...) Expand all Loading... |
7393 | 8267 |
7394 function signalCursorActivity(cm) { | 8268 function signalCursorActivity(cm) { |
7395 var arr = cm._handlers && cm._handlers.cursorActivity; | 8269 var arr = cm._handlers && cm._handlers.cursorActivity; |
7396 if (!arr) return; | 8270 if (!arr) return; |
7397 var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandler
s = []); | 8271 var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandler
s = []); |
7398 for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) | 8272 for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) |
7399 set.push(arr[i]); | 8273 set.push(arr[i]); |
7400 } | 8274 } |
7401 | 8275 |
7402 function hasHandler(emitter, type) { | 8276 function hasHandler(emitter, type) { |
7403 var arr = emitter._handlers && emitter._handlers[type]; | 8277 return getHandlers(emitter, type).length > 0 |
7404 return arr && arr.length > 0; | |
7405 } | 8278 } |
7406 | 8279 |
7407 // Add on and off methods to a constructor's prototype, to make | 8280 // Add on and off methods to a constructor's prototype, to make |
7408 // registering events on such objects more convenient. | 8281 // registering events on such objects more convenient. |
7409 function eventMixin(ctor) { | 8282 function eventMixin(ctor) { |
7410 ctor.prototype.on = function(type, f) {on(this, type, f);}; | 8283 ctor.prototype.on = function(type, f) {on(this, type, f);}; |
7411 ctor.prototype.off = function(type, f) {off(this, type, f);}; | 8284 ctor.prototype.off = function(type, f) {off(this, type, f);}; |
7412 } | 8285 } |
7413 | 8286 |
7414 // MISC UTILITIES | 8287 // MISC UTILITIES |
(...skipping 26 matching lines...) Expand all Loading... |
7441 if (nextTab < 0 || nextTab >= end) | 8314 if (nextTab < 0 || nextTab >= end) |
7442 return n + (end - i); | 8315 return n + (end - i); |
7443 n += nextTab - i; | 8316 n += nextTab - i; |
7444 n += tabSize - (n % tabSize); | 8317 n += tabSize - (n % tabSize); |
7445 i = nextTab + 1; | 8318 i = nextTab + 1; |
7446 } | 8319 } |
7447 }; | 8320 }; |
7448 | 8321 |
7449 // The inverse of countColumn -- find the offset that corresponds to | 8322 // The inverse of countColumn -- find the offset that corresponds to |
7450 // a particular column. | 8323 // a particular column. |
7451 function findColumn(string, goal, tabSize) { | 8324 var findColumn = CodeMirror.findColumn = function(string, goal, tabSize) { |
7452 for (var pos = 0, col = 0;;) { | 8325 for (var pos = 0, col = 0;;) { |
7453 var nextTab = string.indexOf("\t", pos); | 8326 var nextTab = string.indexOf("\t", pos); |
7454 if (nextTab == -1) nextTab = string.length; | 8327 if (nextTab == -1) nextTab = string.length; |
7455 var skipped = nextTab - pos; | 8328 var skipped = nextTab - pos; |
7456 if (nextTab == string.length || col + skipped >= goal) | 8329 if (nextTab == string.length || col + skipped >= goal) |
7457 return pos + Math.min(skipped, goal - col); | 8330 return pos + Math.min(skipped, goal - col); |
7458 col += nextTab - pos; | 8331 col += nextTab - pos; |
7459 col += tabSize - (col % tabSize); | 8332 col += tabSize - (col % tabSize); |
7460 pos = nextTab + 1; | 8333 pos = nextTab + 1; |
7461 if (col >= goal) return pos; | 8334 if (col >= goal) return pos; |
(...skipping 19 matching lines...) Expand all Loading... |
7481 for (var i = 0; i < array.length; ++i) | 8354 for (var i = 0; i < array.length; ++i) |
7482 if (array[i] == elt) return i; | 8355 if (array[i] == elt) return i; |
7483 return -1; | 8356 return -1; |
7484 } | 8357 } |
7485 function map(array, f) { | 8358 function map(array, f) { |
7486 var out = []; | 8359 var out = []; |
7487 for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); | 8360 for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); |
7488 return out; | 8361 return out; |
7489 } | 8362 } |
7490 | 8363 |
| 8364 function nothing() {} |
| 8365 |
7491 function createObj(base, props) { | 8366 function createObj(base, props) { |
7492 var inst; | 8367 var inst; |
7493 if (Object.create) { | 8368 if (Object.create) { |
7494 inst = Object.create(base); | 8369 inst = Object.create(base); |
7495 } else { | 8370 } else { |
7496 var ctor = function() {}; | 8371 nothing.prototype = base; |
7497 ctor.prototype = base; | 8372 inst = new nothing(); |
7498 inst = new ctor(); | |
7499 } | 8373 } |
7500 if (props) copyObj(props, inst); | 8374 if (props) copyObj(props, inst); |
7501 return inst; | 8375 return inst; |
7502 }; | 8376 }; |
7503 | 8377 |
7504 function copyObj(obj, target, overwrite) { | 8378 function copyObj(obj, target, overwrite) { |
7505 if (!target) target = {}; | 8379 if (!target) target = {}; |
7506 for (var prop in obj) | 8380 for (var prop in obj) |
7507 if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProp
erty(prop))) | 8381 if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProp
erty(prop))) |
7508 target[prop] = obj[prop]; | 8382 target[prop] = obj[prop]; |
7509 return target; | 8383 return target; |
7510 } | 8384 } |
7511 | 8385 |
7512 function bind(f) { | 8386 function bind(f) { |
7513 var args = Array.prototype.slice.call(arguments, 1); | 8387 var args = Array.prototype.slice.call(arguments, 1); |
7514 return function(){return f.apply(null, args);}; | 8388 return function(){return f.apply(null, args);}; |
7515 } | 8389 } |
7516 | 8390 |
7517 var nonASCIISingleCaseWordChar = /[\u00df\u0590-\u05f4\u0600-\u06ff\u3040-\u30
9f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; | 8391 var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u304
0-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; |
7518 var isWordCharBasic = CodeMirror.isWordChar = function(ch) { | 8392 var isWordCharBasic = CodeMirror.isWordChar = function(ch) { |
7519 return /\w/.test(ch) || ch > "\x80" && | 8393 return /\w/.test(ch) || ch > "\x80" && |
7520 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(c
h)); | 8394 (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(c
h)); |
7521 }; | 8395 }; |
7522 function isWordChar(ch, helper) { | 8396 function isWordChar(ch, helper) { |
7523 if (!helper) return isWordCharBasic(ch); | 8397 if (!helper) return isWordCharBasic(ch); |
7524 if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; | 8398 if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; |
7525 return helper.test(ch); | 8399 return helper.test(ch); |
7526 } | 8400 } |
7527 | 8401 |
(...skipping 15 matching lines...) Expand all Loading... |
7543 function elt(tag, content, className, style) { | 8417 function elt(tag, content, className, style) { |
7544 var e = document.createElement(tag); | 8418 var e = document.createElement(tag); |
7545 if (className) e.className = className; | 8419 if (className) e.className = className; |
7546 if (style) e.style.cssText = style; | 8420 if (style) e.style.cssText = style; |
7547 if (typeof content == "string") e.appendChild(document.createTextNode(conten
t)); | 8421 if (typeof content == "string") e.appendChild(document.createTextNode(conten
t)); |
7548 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(con
tent[i]); | 8422 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(con
tent[i]); |
7549 return e; | 8423 return e; |
7550 } | 8424 } |
7551 | 8425 |
7552 var range; | 8426 var range; |
7553 if (document.createRange) range = function(node, start, end) { | 8427 if (document.createRange) range = function(node, start, end, endNode) { |
7554 var r = document.createRange(); | 8428 var r = document.createRange(); |
7555 r.setEnd(node, end); | 8429 r.setEnd(endNode || node, end); |
7556 r.setStart(node, start); | 8430 r.setStart(node, start); |
7557 return r; | 8431 return r; |
7558 }; | 8432 }; |
7559 else range = function(node, start, end) { | 8433 else range = function(node, start, end) { |
7560 var r = document.body.createTextRange(); | 8434 var r = document.body.createTextRange(); |
7561 try { r.moveToElementText(node.parentNode); } | 8435 try { r.moveToElementText(node.parentNode); } |
7562 catch(e) { return r; } | 8436 catch(e) { return r; } |
7563 r.collapse(true); | 8437 r.collapse(true); |
7564 r.moveEnd("character", end); | 8438 r.moveEnd("character", end); |
7565 r.moveStart("character", start); | 8439 r.moveStart("character", start); |
7566 return r; | 8440 return r; |
7567 }; | 8441 }; |
7568 | 8442 |
7569 function removeChildren(e) { | 8443 function removeChildren(e) { |
7570 for (var count = e.childNodes.length; count > 0; --count) | 8444 for (var count = e.childNodes.length; count > 0; --count) |
7571 e.removeChild(e.firstChild); | 8445 e.removeChild(e.firstChild); |
7572 return e; | 8446 return e; |
7573 } | 8447 } |
7574 | 8448 |
7575 function removeChildrenAndAdd(parent, e) { | 8449 function removeChildrenAndAdd(parent, e) { |
7576 return removeChildren(parent).appendChild(e); | 8450 return removeChildren(parent).appendChild(e); |
7577 } | 8451 } |
7578 | 8452 |
7579 function contains(parent, child) { | 8453 var contains = CodeMirror.contains = function(parent, child) { |
| 8454 if (child.nodeType == 3) // Android browser always returns false when child
is a textnode |
| 8455 child = child.parentNode; |
7580 if (parent.contains) | 8456 if (parent.contains) |
7581 return parent.contains(child); | 8457 return parent.contains(child); |
7582 while (child = child.parentNode) | 8458 do { |
| 8459 if (child.nodeType == 11) child = child.host; |
7583 if (child == parent) return true; | 8460 if (child == parent) return true; |
| 8461 } while (child = child.parentNode); |
| 8462 }; |
| 8463 |
| 8464 function activeElt() { |
| 8465 var activeElement = document.activeElement; |
| 8466 while (activeElement && activeElement.root && activeElement.root.activeEleme
nt) |
| 8467 activeElement = activeElement.root.activeElement; |
| 8468 return activeElement; |
7584 } | 8469 } |
7585 | |
7586 function activeElt() { return document.activeElement; } | |
7587 // Older versions of IE throws unspecified error when touching | 8470 // Older versions of IE throws unspecified error when touching |
7588 // document.activeElement in some cases (during loading, in iframe) | 8471 // document.activeElement in some cases (during loading, in iframe) |
7589 if (ie && ie_version < 11) activeElt = function() { | 8472 if (ie && ie_version < 11) activeElt = function() { |
7590 try { return document.activeElement; } | 8473 try { return document.activeElement; } |
7591 catch(e) { return document.body; } | 8474 catch(e) { return document.body; } |
7592 }; | 8475 }; |
7593 | 8476 |
7594 function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*")
; } | 8477 function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*")
; } |
7595 var rmClass = CodeMirror.rmClass = function(node, cls) { | 8478 var rmClass = CodeMirror.rmClass = function(node, cls) { |
7596 var current = node.className; | 8479 var current = node.className; |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
7659 }(); | 8542 }(); |
7660 | 8543 |
7661 var zwspSupported; | 8544 var zwspSupported; |
7662 function zeroWidthElement(measure) { | 8545 function zeroWidthElement(measure) { |
7663 if (zwspSupported == null) { | 8546 if (zwspSupported == null) { |
7664 var test = elt("span", "\u200b"); | 8547 var test = elt("span", "\u200b"); |
7665 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("
x")])); | 8548 removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("
x")])); |
7666 if (measure.firstChild.offsetHeight != 0) | 8549 if (measure.firstChild.offsetHeight != 0) |
7667 zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie &
& ie_version < 8); | 8550 zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie &
& ie_version < 8); |
7668 } | 8551 } |
7669 if (zwspSupported) return elt("span", "\u200b"); | 8552 var node = zwspSupported ? elt("span", "\u200b") : |
7670 else return elt("span", "\u00a0", null, "display: inline-block; width: 1px;
margin-right: -1px"); | 8553 elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-rig
ht: -1px"); |
| 8554 node.setAttribute("cm-text", ""); |
| 8555 return node; |
7671 } | 8556 } |
7672 | 8557 |
7673 // Feature-detect IE's crummy client rect reporting for bidi text | 8558 // Feature-detect IE's crummy client rect reporting for bidi text |
7674 var badBidiRects; | 8559 var badBidiRects; |
7675 function hasBadBidiRects(measure) { | 8560 function hasBadBidiRects(measure) { |
7676 if (badBidiRects != null) return badBidiRects; | 8561 if (badBidiRects != null) return badBidiRects; |
7677 var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"))
; | 8562 var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"))
; |
7678 var r0 = range(txt, 0, 1).getBoundingClientRect(); | 8563 var r0 = range(txt, 0, 1).getBoundingClientRect(); |
| 8564 var r1 = range(txt, 1, 2).getBoundingClientRect(); |
| 8565 removeChildren(measure); |
7679 if (!r0 || r0.left == r0.right) return false; // Safari returns null in some
cases (#2780) | 8566 if (!r0 || r0.left == r0.right) return false; // Safari returns null in some
cases (#2780) |
7680 var r1 = range(txt, 1, 2).getBoundingClientRect(); | |
7681 return badBidiRects = (r1.right - r0.right < 3); | 8567 return badBidiRects = (r1.right - r0.right < 3); |
7682 } | 8568 } |
7683 | 8569 |
7684 // See if "".split is the broken IE version, if so, provide an | 8570 // See if "".split is the broken IE version, if so, provide an |
7685 // alternative way to split lines. | 8571 // alternative way to split lines. |
7686 var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? fun
ction(string) { | 8572 var splitLinesAuto = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ?
function(string) { |
7687 var pos = 0, result = [], l = string.length; | 8573 var pos = 0, result = [], l = string.length; |
7688 while (pos <= l) { | 8574 while (pos <= l) { |
7689 var nl = string.indexOf("\n", pos); | 8575 var nl = string.indexOf("\n", pos); |
7690 if (nl == -1) nl = string.length; | 8576 if (nl == -1) nl = string.length; |
7691 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); | 8577 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); |
7692 var rt = line.indexOf("\r"); | 8578 var rt = line.indexOf("\r"); |
7693 if (rt != -1) { | 8579 if (rt != -1) { |
7694 result.push(line.slice(0, rt)); | 8580 result.push(line.slice(0, rt)); |
7695 pos += rt + 1; | 8581 pos += rt + 1; |
7696 } else { | 8582 } else { |
(...skipping 25 matching lines...) Expand all Loading... |
7722 function hasBadZoomedRects(measure) { | 8608 function hasBadZoomedRects(measure) { |
7723 if (badZoomedRects != null) return badZoomedRects; | 8609 if (badZoomedRects != null) return badZoomedRects; |
7724 var node = removeChildrenAndAdd(measure, elt("span", "x")); | 8610 var node = removeChildrenAndAdd(measure, elt("span", "x")); |
7725 var normal = node.getBoundingClientRect(); | 8611 var normal = node.getBoundingClientRect(); |
7726 var fromRange = range(node, 0, 1).getBoundingClientRect(); | 8612 var fromRange = range(node, 0, 1).getBoundingClientRect(); |
7727 return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; | 8613 return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; |
7728 } | 8614 } |
7729 | 8615 |
7730 // KEY NAMES | 8616 // KEY NAMES |
7731 | 8617 |
7732 var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift"
, 17: "Ctrl", 18: "Alt", | 8618 var keyNames = CodeMirror.keyNames = { |
7733 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "Page
Up", 34: "PageDown", 35: "End", | 8619 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl",
18: "Alt", |
7734 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44:
"PrintScrn", 45: "Insert", | 8620 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "Page
Down", 35: "End", |
7735 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod
", 107: "=", 109: "-", 127: "Delete", | 8621 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn",
45: "Insert", |
7736 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 19
1: "/", 192: "`", 219: "[", 220: "\\", | 8622 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", |
7737 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left",
63235: "Right", 63272: "Delete", | 8623 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", |
7738 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown
", 63302: "Insert"}; | 8624 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "
`", 219: "[", 220: "\\", |
7739 CodeMirror.keyNames = keyNames; | 8625 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right
", 63272: "Delete", |
| 8626 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Ins
ert" |
| 8627 }; |
7740 (function() { | 8628 (function() { |
7741 // Number keys | 8629 // Number keys |
7742 for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i)
; | 8630 for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i)
; |
7743 // Alphabetic keys | 8631 // Alphabetic keys |
7744 for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); | 8632 for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); |
7745 // Function keys | 8633 // Function keys |
7746 for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F"
+ i; | 8634 for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F"
+ i; |
7747 })(); | 8635 })(); |
7748 | 8636 |
7749 // BIDI HELPERS | 8637 // BIDI HELPERS |
(...skipping 273 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
8023 } | 8911 } |
8024 } | 8912 } |
8025 if (order[0].level == 1 && (m = str.match(/^\s+/))) { | 8913 if (order[0].level == 1 && (m = str.match(/^\s+/))) { |
8026 order[0].from = m[0].length; | 8914 order[0].from = m[0].length; |
8027 order.unshift(new BidiSpan(0, 0, m[0].length)); | 8915 order.unshift(new BidiSpan(0, 0, m[0].length)); |
8028 } | 8916 } |
8029 if (lst(order).level == 1 && (m = str.match(/\s+$/))) { | 8917 if (lst(order).level == 1 && (m = str.match(/\s+$/))) { |
8030 lst(order).to -= m[0].length; | 8918 lst(order).to -= m[0].length; |
8031 order.push(new BidiSpan(0, len - m[0].length, len)); | 8919 order.push(new BidiSpan(0, len - m[0].length, len)); |
8032 } | 8920 } |
| 8921 if (order[0].level == 2) |
| 8922 order.unshift(new BidiSpan(1, order[0].to, order[0].to)); |
8033 if (order[0].level != lst(order).level) | 8923 if (order[0].level != lst(order).level) |
8034 order.push(new BidiSpan(order[0].level, len, len)); | 8924 order.push(new BidiSpan(order[0].level, len, len)); |
8035 | 8925 |
8036 return order; | 8926 return order; |
8037 }; | 8927 }; |
8038 })(); | 8928 })(); |
8039 | 8929 |
8040 // THE END | 8930 // THE END |
8041 | 8931 |
8042 CodeMirror.version = "4.12.0"; | 8932 CodeMirror.version = "5.17.1"; |
8043 | 8933 |
8044 return CodeMirror; | 8934 return CodeMirror; |
8045 }); | 8935 }); |
OLD | NEW |