@samitouri / QOS-React / commits / c8bab76091

switched to codemirror's jsx mode (#6898)

Keyan Zhang committed May 26, 2016 at 23:33 UTC c8bab76091e0686fb07912b0ce7fe427966bc42f
8 files changed +6536 -2698
docs/_css/_solarized.scss
+4 -6
@@ -37,7 +37,7 @@ html * {
37 border-bottom: 1px dotted #cb4b16;
38 }
39 .cm-s-solarized-light span.cm-keyword {
40 - color: #268bd2;
40 + color: #859900;
41 }
42 .cm-s-solarized-light span.cm-atom {
43 color: #2aa198;
@@ -61,7 +61,7 @@ html * {
61 color: #93a1a1;
62 }
63 .cm-s-solarized-light span.cm-property {
64 - color: #637c84;
64 + color: #657b83;
65 }
66 .cm-s-solarized-light span.cm-operator {
67 color: #657b83;
@@ -74,14 +74,13 @@ html * {
74 border-bottom: 1px dotted #cb4b16;
75 }
76 .cm-s-solarized-light span.cm-bracket {
77 - color: #cb4b16;
77 + color: #268bd2;
78 }
79 .cm-s-solarized-light span.cm-tag {
80 - color: #657b83;
80 + color: #268bd2;
81 }
82 .cm-s-solarized-light span.cm-attribute {
83 color: #586e75;
84 - font-weight: bold;
84 }
85 .cm-s-solarized-light span.cm-meta {
86 color: #268bd2;
@@ -166,7 +165,6 @@ html * {
165 }
166 .cm-s-solarized-dark span.cm-attribute {
167 color: #93a1a1;
169 - font-weight: bold;
168 }
169 .cm-s-solarized-dark span.cm-meta {
170 color: #268bd2;
docs/_js/live_editor.js
+1 -1
@@ -22,7 +22,7 @@ var CodeMirrorEditor = React.createClass({
22 if (IS_MOBILE) return;
23
24 this.editor = CodeMirror.fromTextArea(ReactDOM.findDOMNode(this.refs.editor), {
25 - mode: 'javascript',
25 + mode: 'jsx',
26 lineNumbers: this.props.lineNumbers,
27 lineWrapping: true,
28 smartIndent: false, // javascript mode does bad things with jsx indents
docs/_layouts/default.html
+2
@@ -32,6 +32,8 @@
32 <script type="text/javascript" src="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js"></script>
33 <script src="/react/js/codemirror.js"></script>
34 <script src="/react/js/javascript.js"></script>
35 + <script src="/react/js/xml.js"></script>
36 + <script src="/react/js/jsx.js"></script>
37 <script src="/react/js/react.js"></script>
38 <script src="/react/js/react-dom.js"></script>
39 <script src="/react/js/babel-browser.min.js"></script>
docs/css/codemirror.css
+141 -40
@@ -18,7 +18,7 @@
18 padding: 0 14px; /* Horizontal padding of content */
19 }
20
21 -.CodeMirror-scrollbar-filler {
21 +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
22 background-color: white; /* The little square between H and V scrollbars */
23 }
24
@@ -27,6 +27,7 @@
27 .CodeMirror-gutters {
28 border-right: 1px solid #ddd;
29 background-color: #f7f7f7;
30 + white-space: nowrap;
31 }
32 .CodeMirror-linenumbers {}
33 .CodeMirror-linenumber {
@@ -34,68 +35,110 @@
35 min-width: 20px;
36 text-align: right;
37 color: #999;
38 + white-space: nowrap;
39 }
40
41 +.CodeMirror-guttermarker { color: black; }
42 +.CodeMirror-guttermarker-subtle { color: #999; }
43 +
44 /* CURSOR */
45
41 -.CodeMirror div.CodeMirror-cursor {
46 +.CodeMirror-cursor {
47 border-left: 1px solid black;
48 + border-right: none;
49 + width: 0;
50 }
51 /* Shown when moving in bi-directional text */
52 .CodeMirror div.CodeMirror-secondarycursor {
53 border-left: 1px solid silver;
54 }
48 -.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
55 +.cm-fat-cursor .CodeMirror-cursor {
56 width: auto;
50 - border: 0;
51 - background: transparent;
52 - background: rgba(0, 200, 0, .4);
53 - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800);
57 + border: 0 !important;
58 + background: #7e7;
59 }
55 -/* Kludge to turn off filter in ie9+, which also accepts rgba */
56 -.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor:not(#nonsense_id) {
57 - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
60 +.cm-fat-cursor div.CodeMirror-cursors {
61 + z-index: 1;
62 +}
63 +
64 +.cm-animate-fat-cursor {
65 + width: auto;
66 + border: 0;
67 + -webkit-animation: blink 1.06s steps(1) infinite;
68 + -moz-animation: blink 1.06s steps(1) infinite;
69 + animation: blink 1.06s steps(1) infinite;
70 + background-color: #7e7;
71 +}
72 +@-moz-keyframes blink {
73 + 0% {}
74 + 50% { background-color: transparent; }
75 + 100% {}
76 +}
77 +@-webkit-keyframes blink {
78 + 0% {}
79 + 50% { background-color: transparent; }
80 + 100% {}
81 +}
82 +@keyframes blink {
83 + 0% {}
84 + 50% { background-color: transparent; }
85 + 100% {}
86 }
87 +
88 /* Can style cursor different in overwrite (non-insert) mode */
60 -.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
89 +.CodeMirror-overwrite .CodeMirror-cursor {}
90 +
91 +.cm-tab { display: inline-block; text-decoration: inherit; }
92 +
93 +.CodeMirror-ruler {
94 + border-left: 1px solid #ccc;
95 + position: absolute;
96 +}
97
98 /* DEFAULT THEME */
99
100 +.cm-s-default .cm-header {color: blue;}
101 +.cm-s-default .cm-quote {color: #090;}
102 +.cm-negative {color: #d44;}
103 +.cm-positive {color: #292;}
104 +.cm-header, .cm-strong {font-weight: bold;}
105 +.cm-em {font-style: italic;}
106 +.cm-link {text-decoration: underline;}
107 +.cm-strikethrough {text-decoration: line-through;}
108 +
109 .cm-s-default .cm-keyword {color: #708;}
110 .cm-s-default .cm-atom {color: #219;}
111 .cm-s-default .cm-number {color: #164;}
112 .cm-s-default .cm-def {color: #00f;}
68 -.cm-s-default .cm-variable {color: black;}
113 +.cm-s-default .cm-variable,
114 +.cm-s-default .cm-punctuation,
115 +.cm-s-default .cm-property,
116 +.cm-s-default .cm-operator {}
117 .cm-s-default .cm-variable-2 {color: #05a;}
118 .cm-s-default .cm-variable-3 {color: #085;}
71 -.cm-s-default .cm-property {color: black;}
72 -.cm-s-default .cm-operator {color: black;}
119 .cm-s-default .cm-comment {color: #a50;}
120 .cm-s-default .cm-string {color: #a11;}
121 .cm-s-default .cm-string-2 {color: #f50;}
122 .cm-s-default .cm-meta {color: #555;}
77 -.cm-s-default .cm-error {color: #f00;}
123 .cm-s-default .cm-qualifier {color: #555;}
124 .cm-s-default .cm-builtin {color: #30a;}
125 .cm-s-default .cm-bracket {color: #997;}
126 .cm-s-default .cm-tag {color: #170;}
127 .cm-s-default .cm-attribute {color: #00c;}
83 -.cm-s-default .cm-header {color: blue;}
84 -.cm-s-default .cm-quote {color: #090;}
128 .cm-s-default .cm-hr {color: #999;}
129 .cm-s-default .cm-link {color: #00c;}
130
88 -.cm-negative {color: #d44;}
89 -.cm-positive {color: #292;}
90 -.cm-header, .cm-strong {font-weight: bold;}
91 -.cm-em {font-style: italic;}
92 -.cm-emstrong {font-style: italic; font-weight: bold;}
93 -.cm-link {text-decoration: underline;}
94 -
131 +.cm-s-default .cm-error {color: #f00;}
132 .cm-invalidchar {color: #f00;}
133
134 +.CodeMirror-composing { border-bottom: 2px solid; }
135 +
136 +/* Default styles for common addons */
137 +
138 div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
139 div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
140 +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
141 +.CodeMirror-activeline-background {background: #e8f2ff;}
142
143 /* STOP */
144
@@ -103,28 +146,30 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
146 the editor. You probably shouldn't touch them. */
147
148 .CodeMirror {
106 - line-height: 1;
149 position: relative;
150 overflow: hidden;
151 + background: white;
152 }
153
154 .CodeMirror-scroll {
155 + overflow: scroll !important; /* Things will break if this is overridden */
156 /* 30px is the magic margin used to hide the element's real scrollbars */
113 - /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */
157 + /* See overflow: hidden in .CodeMirror */
158 margin-bottom: -30px; margin-right: -30px;
115 - padding-bottom: 30px; padding-right: 30px;
159 + padding-bottom: 30px;
160 height: 100%;
161 outline: none; /* Prevent dragging from highlighting the element */
162 position: relative;
163 }
164 .CodeMirror-sizer {
165 position: relative;
166 + border-right: 30px solid transparent;
167 }
168
169 /* The fake, visible scrollbars. Used to force redraw during scrolling
170 before actual scrolling happens, thus preventing shaking and
171 flickering artifacts. */
127 -.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler {
172 +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
173 position: absolute;
174 z-index: 6;
175 display: none;
@@ -141,33 +186,55 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
186 }
187 .CodeMirror-scrollbar-filler {
188 right: 0; bottom: 0;
144 - z-index: 6;
189 +}
190 +.CodeMirror-gutter-filler {
191 + left: 0; bottom: 0;
192 }
193
194 .CodeMirror-gutters {
195 position: absolute; left: 0; top: 0;
149 - height: 100%;
196 + min-height: 100%;
197 z-index: 3;
198 }
199 .CodeMirror-gutter {
200 + white-space: normal;
201 height: 100%;
202 display: inline-block;
203 + vertical-align: top;
204 + margin-bottom: -30px;
205 /* Hack to make IE7 behave */
206 *zoom:1;
207 *display:inline;
208 }
209 +.CodeMirror-gutter-wrapper {
210 + position: absolute;
211 + z-index: 4;
212 + background: none !important;
213 + border: none !important;
214 +}
215 +.CodeMirror-gutter-background {
216 + position: absolute;
217 + top: 0; bottom: 0;
218 + z-index: 4;
219 +}
220 .CodeMirror-gutter-elt {
221 position: absolute;
222 cursor: default;
223 z-index: 4;
224 }
225 +.CodeMirror-gutter-wrapper {
226 + -webkit-user-select: none;
227 + -moz-user-select: none;
228 + user-select: none;
229 +}
230
231 .CodeMirror-lines {
232 cursor: text;
233 + min-height: 1px; /* prevents collapsing before first draw */
234 }
235 .CodeMirror pre {
236 /* Reset some styles that the rest of the page might have set */
170 - -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0;
237 + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
238 border-width: 0;
239 background: transparent;
240 font-family: inherit;
@@ -180,12 +247,16 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
247 z-index: 2;
248 position: relative;
249 overflow: visible;
250 + -webkit-tap-highlight-color: transparent;
251 + -webkit-font-variant-ligatures: none;
252 + font-variant-ligatures: none;
253 }
254 .CodeMirror-wrap pre {
255 word-wrap: break-word;
256 white-space: pre-wrap;
257 word-break: normal;
258 }
259 +
260 .CodeMirror-linebackground {
261 position: absolute;
262 left: 0; right: 0; top: 0; bottom: 0;
@@ -198,30 +269,51 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
269 overflow: auto;
270 }
271
201 -.CodeMirror-wrap .CodeMirror-scroll {
202 - overflow-x: hidden;
272 +.CodeMirror-widget {}
273 +
274 +.CodeMirror-code {
275 + outline: none;
276 +}
277 +
278 +/* Force content-box sizing for the elements where we expect it */
279 +.CodeMirror-scroll,
280 +.CodeMirror-sizer,
281 +.CodeMirror-gutter,
282 +.CodeMirror-gutters,
283 +.CodeMirror-linenumber {
284 + -moz-box-sizing: content-box;
285 + box-sizing: content-box;
286 }
287
288 .CodeMirror-measure {
289 position: absolute;
207 - width: 100%; height: 0px;
290 + width: 100%;
291 + height: 0;
292 overflow: hidden;
293 visibility: hidden;
294 }
295 +
296 +.CodeMirror-cursor { position: absolute; }
297 .CodeMirror-measure pre { position: static; }
298
213 -.CodeMirror div.CodeMirror-cursor {
214 - position: absolute;
299 +div.CodeMirror-cursors {
300 visibility: hidden;
216 - border-right: none;
217 - width: 0;
301 + position: relative;
302 + z-index: 3;
303 +}
304 +div.CodeMirror-dragcursors {
305 + visibility: visible;
306 }
219 -.CodeMirror-focused div.CodeMirror-cursor {
307 +
308 +.CodeMirror-focused div.CodeMirror-cursors {
309 visibility: visible;
310 }
311
312 .CodeMirror-selected { background: #d9d9d9; }
313 .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
314 +.CodeMirror-crosshair { cursor: crosshair; }
315 +.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
316 +.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
317
318 .cm-searching {
319 background: #ffa;
@@ -231,9 +323,18 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
323 /* IE7 hack to prevent it from returning funny offsetTops on the spans */
324 .CodeMirror span { *vertical-align: text-bottom; }
325
326 +/* Used to force a border model for a node */
327 +.cm-force-border { padding-right: .1px; }
328 +
329 @media print {
330 /* Hide the cursor when printing */
236 - .CodeMirror div.CodeMirror-cursor {
331 + .CodeMirror div.CodeMirror-cursors {
332 visibility: hidden;
333 }
334 }
335 +
336 +/* See issue #2901 */
337 +.cm-tab-wrap-hack:after { content: ''; }
338 +
339 +/* Help users use markselection to safely style text background */
340 +span.CodeMirror-selectedtext { background: none; }
docs/js/codemirror.js
+5387 -2512
@@ -1,181 +1,209 @@
1 +// CodeMirror, copyright (c) by Marijn Haverbeke and others
2 +// Distributed under an MIT license: http://codemirror.net/LICENSE
3 +
4 +// This is CodeMirror (http://codemirror.net), a code editor
5 +// implemented in JavaScript on top of the browser's DOM.
6 +//
7 +// You can find some technical background for some of the code below
8 +// at http://marijnhaverbeke.nl/blog/#cm-internals .
9 +
10 // CodeMirror is the only global var we claim
11 window.CodeMirror = (function() {
12 "use strict";
13
14 // BROWSER SNIFFING
15
7 - // Crude, but necessary to handle a number of hard-to-feature-detect
8 - // bugs and behavior differences.
9 - var gecko = /gecko\/\d/i.test(navigator.userAgent);
10 - // IE11 currently doesn't count as 'ie', since it has almost none of
11 - // the same bugs as earlier versions. Use ie_gt10 to handle
12 - // incompatibilities in that version.
13 - var ie = /MSIE \d/.test(navigator.userAgent);
14 - var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);
15 - var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
16 - var ie_gt10 = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent);
17 - var webkit = /WebKit\//.test(navigator.userAgent);
18 - var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
19 - var chrome = /Chrome\//.test(navigator.userAgent);
20 - var opera = /Opera\//.test(navigator.userAgent);
16 + // Kludges for bugs and behavior differences that can't be feature
17 + // detected are enabled based on userAgent etc sniffing.
18 + var userAgent = navigator.userAgent;
19 + var platform = navigator.platform;
20 +
21 + var gecko = /gecko\/\d/i.test(userAgent);
22 + var ie_upto10 = /MSIE \d/.test(userAgent);
23 + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent);
24 + var ie = ie_upto10 || ie_11up;
25 + var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]);
26 + var webkit = /WebKit\//.test(userAgent);
27 + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent);
28 + var chrome = /Chrome\//.test(userAgent);
29 + var presto = /Opera\//.test(userAgent);
30 var safari = /Apple Computer/.test(navigator.vendor);
22 - var khtml = /KHTML\//.test(navigator.userAgent);
23 - var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
24 - var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
25 - var phantom = /PhantomJS/.test(navigator.userAgent);
31 + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent);
32 + var phantom = /PhantomJS/.test(userAgent);
33
27 - var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
34 + var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent);
35 // This is woefully incomplete. Suggestions for alternative methods welcome.
29 - var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
30 - var mac = ios || /Mac/.test(navigator.platform);
31 - var windows = /win/i.test(navigator.platform);
32 -
33 - var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
34 - if (opera_version) opera_version = Number(opera_version[1]);
35 - if (opera_version && opera_version >= 15) { opera = false; webkit = true; }
36 + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent);
37 + var mac = ios || /Mac/.test(platform);
38 + var chromeOS = /\bCrOS\b/.test(userAgent);
39 + var windows = /win/i.test(platform);
40 +
41 + var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/);
42 + if (presto_version) presto_version = Number(presto_version[1]);
43 + if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
44 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
37 - var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
38 - var captureMiddleClick = gecko || (ie && !ie_lt9);
45 + var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));
46 + var captureRightClick = gecko || (ie && ie_version >= 9);
47
40 - // Optimize some code when these features are not used
48 + // Optimize some code when these features are not used.
49 var sawReadOnlySpans = false, sawCollapsedSpans = false;
50
43 - // CONSTRUCTOR
51 + // EDITOR CONSTRUCTOR
52 +
53 + // A CodeMirror instance represents an editor. This is the object
54 + // that user code is usually dealing with.
55
56 function CodeMirror(place, options) {
57 if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
58
48 - this.options = options = options || {};
59 + this.options = options = options ? copyObj(options) : {};
60 // Determine effective options based on given values and defaults.
50 - for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
51 - options[opt] = defaults[opt];
61 + copyObj(defaults, options, false);
62 setGuttersForLineNumbers(options);
63
54 - var docStart = typeof options.value == "string" ? 0 : options.value.first;
55 - var display = this.display = makeDisplay(place, docStart);
64 + var doc = options.value;
65 + if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator);
66 + this.doc = doc;
67 +
68 + var input = new CodeMirror.inputStyles[options.inputStyle](this);
69 + var display = this.display = new Display(place, doc, input);
70 display.wrapper.CodeMirror = this;
71 updateGutters(this);
58 - if (options.autofocus && !mobile) focusInput(this);
59 -
60 - this.state = {keyMaps: [],
61 - overlays: [],
62 - modeGen: 0,
63 - overwrite: false, focused: false,
64 - suppressEdits: false, pasteIncoming: false,
65 - draggingText: false,
66 - highlight: new Delayed()};
67 -
72 themeChanged(this);
73 if (options.lineWrapping)
74 this.display.wrapper.className += " CodeMirror-wrap";
75 + if (options.autofocus && !mobile) display.input.focus();
76 + initScrollbars(this);
77 +
78 + this.state = {
79 + keyMaps: [], // stores maps added by addKeyMap
80 + overlays: [], // highlighting overlays, as added by addOverlay
81 + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
82 + overwrite: false,
83 + delayingBlurEvent: false,
84 + focused: false,
85 + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
86 + pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll
87 + selectingText: false,
88 + draggingText: false,
89 + highlight: new Delayed(), // stores highlight worker timeout
90 + keySeq: null, // Unfinished key sequence
91 + specialChars: null
92 + };
93
72 - var doc = options.value;
73 - if (typeof doc == "string") doc = new Doc(options.value, options.mode);
74 - operation(this, attachDoc)(this, doc);
94 + var cm = this;
95
96 // Override magic textarea content restore that IE sometimes does
97 // on our hidden textarea on reload
78 - if (ie) setTimeout(bind(resetInput, this, true), 20);
98 + if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20);
99
100 registerEventHandlers(this);
81 - // IE throws unspecified error in certain cases, when
82 - // trying to access activeElement before onload
83 - var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }
84 - if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);
85 - else onBlur(this);
101 + ensureGlobalHandlers();
102
87 - operation(this, function() {
88 - for (var opt in optionHandlers)
89 - if (optionHandlers.propertyIsEnumerable(opt))
90 - optionHandlers[opt](this, options[opt], Init);
91 - for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
92 - })();
103 + startOperation(this);
104 + this.curOp.forceUpdate = true;
105 + attachDoc(this, doc);
106 +
107 + if ((options.autofocus && !mobile) || cm.hasFocus())
108 + setTimeout(bind(onFocus, this), 20);
109 + else
110 + onBlur(this);
111 +
112 + for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
113 + optionHandlers[opt](this, options[opt], Init);
114 + maybeUpdateLineNumberWidth(this);
115 + if (options.finishInit) options.finishInit(this);
116 + for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
117 + endOperation(this);
118 + // Suppress optimizelegibility in Webkit, since it breaks text
119 + // measuring on line wrapping boundaries.
120 + if (webkit && options.lineWrapping &&
121 + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility")
122 + display.lineDiv.style.textRendering = "auto";
123 }
124
125 // DISPLAY CONSTRUCTOR
126
97 - function makeDisplay(place, docStart) {
98 - var d = {};
99 -
100 - var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");
101 - if (webkit) input.style.width = "1000px";
102 - else input.setAttribute("wrap", "off");
103 - // if border: 0; -- iOS fails to open keyboard (issue #1287)
104 - if (ios) input.style.border = "1px solid black";
105 - input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
106 -
107 - // Wraps and hides input textarea
108 - d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
109 - // The actual fake scrollbars.
110 - d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
111 - d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
127 + // The display handles the DOM integration, both for input reading
128 + // and content drawing. It holds references to DOM nodes and
129 + // display-related state.
130 +
131 + function Display(place, doc, input) {
132 + var d = this;
133 + this.input = input;
134 +
135 + // Covers bottom-right square when both scrollbars are present.
136 d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
137 + d.scrollbarFiller.setAttribute("cm-not-content", "true");
138 + // Covers bottom of gutter when coverGutterNextToScrollbar is on
139 + // and h scrollbar is present.
140 d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
114 - // DIVs containing the selection and the actual code
141 + d.gutterFiller.setAttribute("cm-not-content", "true");
142 + // Will contain the actual code, positioned to cover the viewport.
143 d.lineDiv = elt("div", null, "CodeMirror-code");
144 + // Elements are added to these to represent selection and cursors.
145 d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
117 - // Blinky cursor, and element used to ensure cursor fits at the end of a line
118 - d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
119 - // Secondary cursor, shown when on a 'jump' in bi-directional text
120 - d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
121 - // Used to measure text size
146 + d.cursorDiv = elt("div", null, "CodeMirror-cursors");
147 + // A visibility: hidden element used to find the size of things.
148 d.measure = elt("div", null, "CodeMirror-measure");
149 + // When lines outside of the viewport are measured, they are drawn in this.
150 + d.lineMeasure = elt("div", null, "CodeMirror-measure");
151 // Wraps everything that needs to exist inside the vertically-padded coordinate system
124 - d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],
125 - null, "position: relative; outline: none");
126 - // Moved around its parent to cover visible view
152 + d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],
153 + null, "position: relative; outline: none");
154 + // Moved around its parent to cover visible view.
155 d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
128 - // Set to the height of the text, causes scrolling
156 + // Set to the height of the document, allowing scrolling.
157 d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
130 - // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
131 - d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
132 - // Will contain the gutters, if any
158 + d.sizerWidth = null;
159 + // Behavior of elts with overflow: auto and padding is
160 + // inconsistent across browsers. This is used to ensure the
161 + // scrollable area is big enough.
162 + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;");
163 + // Will contain the gutters, if any.
164 d.gutters = elt("div", null, "CodeMirror-gutters");
165 d.lineGutter = null;
135 - // Provides scrolling
166 + // Actual scrollable element.
167 d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
168 d.scroller.setAttribute("tabIndex", "-1");
169 // The element in which the editor lives.
139 - d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
140 - d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
141 - // Work around IE7 z-index bug
142 - if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
143 - if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
144 -
145 - // Needed to hide big blue blinking cursor on Mobile Safari
146 - if (ios) input.style.width = "0px";
147 - if (!webkit) d.scroller.draggable = true;
148 - // Needed to handle Tab key in KHTML
149 - if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
150 - // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
151 - else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
152 -
153 - // Current visible range (may be bigger than the view window).
154 - d.viewOffset = d.lastSizeC = 0;
155 - d.showingFrom = d.showingTo = docStart;
170 + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
171 +
172 + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
173 + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
174 + if (!webkit && !(gecko && mobile)) d.scroller.draggable = true;
175 +
176 + if (place) {
177 + if (place.appendChild) place.appendChild(d.wrapper);
178 + else place(d.wrapper);
179 + }
180 +
181 + // Current rendered range (may be bigger than the view window).
182 + d.viewFrom = d.viewTo = doc.first;
183 + d.reportedViewFrom = d.reportedViewTo = doc.first;
184 + // Information about the rendered lines.
185 + d.view = [];
186 + d.renderedView = null;
187 + // Holds info about a single rendered line when it was rendered
188 + // for measurement, while not in view.
189 + d.externalMeasured = null;
190 + // Empty space (in pixels) above the view
191 + d.viewOffset = 0;
192 + d.lastWrapHeight = d.lastWrapWidth = 0;
193 + d.updateLineNumbers = null;
194 +
195 + d.nativeBarWidth = d.barHeight = d.barWidth = 0;
196 + d.scrollbarsClipped = false;
197
198 // Used to only resize the line number gutter when necessary (when
199 // the amount of lines crosses a boundary that makes its width change)
200 d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
160 - // See readInput and resetInput
161 - d.prevInput = "";
162 - // Set to true when a non-horizontal-scrolling widget is added. As
163 - // an optimization, widget aligning is skipped when d is false.
201 + // Set to true when a non-horizontal-scrolling line widget is
202 + // added. As an optimization, line widget aligning is skipped when
203 + // this is false.
204 d.alignWidgets = false;
165 - // Flag that indicates whether we currently expect input to appear
166 - // (after some event like 'keypress' or 'input') and are polling
167 - // intensively.
168 - d.pollingFast = false;
169 - // Self-resetting timeout for the poller
170 - d.poll = new Delayed();
205
172 - d.cachedCharWidth = d.cachedTextHeight = null;
173 - d.measureLineCache = [];
174 - d.measureLineCachePos = 0;
175 -
176 - // Tracks when resetInput has punted to just putting a short
177 - // string instead of the (large) selection.
178 - d.inaccurateSelection = false;
206 + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
207
208 // Tracks the maximum line length so that the horizontal scrollbar
209 // can be kept static when scrolling.
@@ -186,7 +214,16 @@ window.CodeMirror = (function() {
214 // Used for measuring wheel scrolling granularity
215 d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
216
189 - return d;
217 + // True when shift is held down.
218 + d.shift = false;
219 +
220 + // Used to track whether anything happened since the context menu
221 + // was opened.
222 + d.selForContextMenu = null;
223 +
224 + d.activeTouch = null;
225 +
226 + input.init(d);
227 }
228
229 // STATE UPDATES
@@ -195,6 +232,10 @@ window.CodeMirror = (function() {
232
233 function loadMode(cm) {
234 cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
235 + resetModeState(cm);
236 + }
237 +
238 + function resetModeState(cm) {
239 cm.doc.iter(function(line) {
240 if (line.stateAfter) line.stateAfter = null;
241 if (line.styles) line.styles = null;
@@ -207,11 +248,12 @@ window.CodeMirror = (function() {
248
249 function wrappingChanged(cm) {
250 if (cm.options.lineWrapping) {
210 - cm.display.wrapper.className += " CodeMirror-wrap";
251 + addClass(cm.display.wrapper, "CodeMirror-wrap");
252 cm.display.sizer.style.minWidth = "";
253 + cm.display.sizerWidth = null;
254 } else {
213 - cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
214 - computeMaxLength(cm);
255 + rmClass(cm.display.wrapper, "CodeMirror-wrap");
256 + findMaxLine(cm);
257 }
258 estimateLineHeights(cm);
259 regChange(cm);
@@ -219,16 +261,24 @@ window.CodeMirror = (function() {
261 setTimeout(function(){updateScrollbars(cm);}, 100);
262 }
263
264 + // Returns a function that estimates the height of a line, to use as
265 + // first approximation until the line becomes visible (and is thus
266 + // properly measurable).
267 function estimateHeight(cm) {
268 var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
269 var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
270 return function(line) {
226 - if (lineIsHidden(cm.doc, line))
227 - return 0;
228 - else if (wrapping)
229 - return (Math.ceil(line.text.length / perLine) || 1) * th;
271 + if (lineIsHidden(cm.doc, line)) return 0;
272 +
273 + var widgetsHeight = 0;
274 + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) {
275 + if (line.widgets[i].height) widgetsHeight += line.widgets[i].height;
276 + }
277 +
278 + if (wrapping)
279 + return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th;
280 else
231 - return th;
281 + return widgetsHeight + th;
282 };
283 }
284
@@ -240,14 +290,6 @@ window.CodeMirror = (function() {
290 });
291 }
292
243 - function keyMapChanged(cm) {
244 - var map = keyMap[cm.options.keyMap], style = map.style;
245 - cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
246 - (style ? " cm-keymap-" + style : "");
247 - if (cm.state.disableInput && !map.disableInput) resetInput(cm, true);
248 - cm.state.disableInput = map.disableInput;
249 - }
250 -
293 function themeChanged(cm) {
294 cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
295 cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
@@ -260,6 +302,8 @@ window.CodeMirror = (function() {
302 setTimeout(function(){alignHorizontally(cm);}, 20);
303 }
304
305 + // Rebuild the gutter elements, ensure the margin to the left of the
306 + // code matches their width.
307 function updateGutters(cm) {
308 var gutters = cm.display.gutters, specs = cm.options.gutters;
309 removeChildren(gutters);
@@ -272,33 +316,43 @@ window.CodeMirror = (function() {
316 }
317 }
318 gutters.style.display = i ? "" : "none";
319 + updateGutterSpace(cm);
320 }
321
277 - function lineLength(doc, line) {
322 + function updateGutterSpace(cm) {
323 + var width = cm.display.gutters.offsetWidth;
324 + cm.display.sizer.style.marginLeft = width + "px";
325 + }
326 +
327 + // Compute the character length of a line, taking into account
328 + // collapsed ranges (see markText) that might hide parts, and join
329 + // other lines onto it.
330 + function lineLength(line) {
331 if (line.height == 0) return 0;
332 var len = line.text.length, merged, cur = line;
333 while (merged = collapsedSpanAtStart(cur)) {
281 - var found = merged.find();
282 - cur = getLine(doc, found.from.line);
334 + var found = merged.find(0, true);
335 + cur = found.from.line;
336 len += found.from.ch - found.to.ch;
337 }
338 cur = line;
339 while (merged = collapsedSpanAtEnd(cur)) {
287 - var found = merged.find();
340 + var found = merged.find(0, true);
341 len -= cur.text.length - found.from.ch;
289 - cur = getLine(doc, found.to.line);
342 + cur = found.to.line;
343 len += cur.text.length - found.to.ch;
344 }
345 return len;
346 }
347
295 - function computeMaxLength(cm) {
348 + // Find the longest line in the document.
349 + function findMaxLine(cm) {
350 var d = cm.display, doc = cm.doc;
351 d.maxLine = getLine(doc, doc.first);
298 - d.maxLineLength = lineLength(doc, d.maxLine);
352 + d.maxLineLength = lineLength(d.maxLine);
353 d.maxLineChanged = true;
354 doc.iter(function(line) {
301 - var len = lineLength(doc, line);
355 + var len = lineLength(line);
356 if (len > d.maxLineLength) {
357 d.maxLineLength = len;
358 d.maxLine = line;
@@ -320,73 +374,231 @@ window.CodeMirror = (function() {
374
375 // SCROLLBARS
376
323 - // Re-synchronize the fake scrollbars with the actual size of the
324 - // content. Optionally force a scrollTop.
325 - function updateScrollbars(cm) {
326 - var d = cm.display, docHeight = cm.doc.height;
327 - var totalHeight = docHeight + paddingVert(d);
328 - d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
329 - d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px";
330 - var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
331 - var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1);
332 - var needsV = scrollHeight > (d.scroller.clientHeight + 1);
333 - if (needsV) {
334 - d.scrollbarV.style.display = "block";
335 - d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
336 - d.scrollbarV.firstChild.style.height =
337 - (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
338 - } else {
339 - d.scrollbarV.style.display = "";
340 - d.scrollbarV.firstChild.style.height = "0";
341 - }
342 - if (needsH) {
343 - d.scrollbarH.style.display = "block";
344 - d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
345 - d.scrollbarH.firstChild.style.width =
346 - (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
347 - } else {
348 - d.scrollbarH.style.display = "";
349 - d.scrollbarH.firstChild.style.width = "0";
377 + // Prepare DOM reads needed to update the scrollbars. Done in one
378 + // shot to minimize update/measure roundtrips.
379 + function measureForScrollbars(cm) {
380 + var d = cm.display, gutterW = d.gutters.offsetWidth;
381 + var docH = Math.round(cm.doc.height + paddingVert(cm.display));
382 + return {
383 + clientHeight: d.scroller.clientHeight,
384 + viewHeight: d.wrapper.clientHeight,
385 + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth,
386 + viewWidth: d.wrapper.clientWidth,
387 + barLeft: cm.options.fixedGutter ? gutterW : 0,
388 + docHeight: docH,
389 + scrollHeight: docH + scrollGap(cm) + d.barHeight,
390 + nativeBarWidth: d.nativeBarWidth,
391 + gutterWidth: gutterW
392 + };
393 + }
394 +
395 + function NativeScrollbars(place, scroll, cm) {
396 + this.cm = cm;
397 + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
398 + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
399 + place(vert); place(horiz);
400 +
401 + on(vert, "scroll", function() {
402 + if (vert.clientHeight) scroll(vert.scrollTop, "vertical");
403 + });
404 + on(horiz, "scroll", function() {
405 + if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal");
406 + });
407 +
408 + this.checkedZeroWidth = false;
409 + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
410 + if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px";
411 + }
412 +
413 + NativeScrollbars.prototype = copyObj({
414 + update: function(measure) {
415 + var needsH = measure.scrollWidth > measure.clientWidth + 1;
416 + var needsV = measure.scrollHeight > measure.clientHeight + 1;
417 + var sWidth = measure.nativeBarWidth;
418 +
419 + if (needsV) {
420 + this.vert.style.display = "block";
421 + this.vert.style.bottom = needsH ? sWidth + "px" : "0";
422 + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0);
423 + // A bug in IE8 can cause this value to be negative, so guard it.
424 + this.vert.firstChild.style.height =
425 + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px";
426 + } else {
427 + this.vert.style.display = "";
428 + this.vert.firstChild.style.height = "0";
429 + }
430 +
431 + if (needsH) {
432 + this.horiz.style.display = "block";
433 + this.horiz.style.right = needsV ? sWidth + "px" : "0";
434 + this.horiz.style.left = measure.barLeft + "px";
435 + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0);
436 + this.horiz.firstChild.style.width =
437 + (measure.scrollWidth - measure.clientWidth + totalWidth) + "px";
438 + } else {
439 + this.horiz.style.display = "";
440 + this.horiz.firstChild.style.width = "0";
441 + }
442 +
443 + if (!this.checkedZeroWidth && measure.clientHeight > 0) {
444 + if (sWidth == 0) this.zeroWidthHack();
445 + this.checkedZeroWidth = true;
446 + }
447 +
448 + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0};
449 + },
450 + setScrollLeft: function(pos) {
451 + if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos;
452 + if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz);
453 + },
454 + setScrollTop: function(pos) {
455 + if (this.vert.scrollTop != pos) this.vert.scrollTop = pos;
456 + if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert);
457 + },
458 + zeroWidthHack: function() {
459 + var w = mac && !mac_geMountainLion ? "12px" : "18px";
460 + this.horiz.style.height = this.vert.style.width = w;
461 + this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none";
462 + this.disableHoriz = new Delayed;
463 + this.disableVert = new Delayed;
464 + },
465 + enableZeroWidthBar: function(bar, delay) {
466 + bar.style.pointerEvents = "auto";
467 + function maybeDisable() {
468 + // To find out whether the scrollbar is still visible, we
469 + // check whether the element under the pixel in the bottom
470 + // left corner of the scrollbar box is the scrollbar box
471 + // itself (when the bar is still visible) or its filler child
472 + // (when the bar is hidden). If it is still visible, we keep
473 + // it enabled, if it's hidden, we disable pointer events.
474 + var box = bar.getBoundingClientRect();
475 + var elt = document.elementFromPoint(box.left + 1, box.bottom - 1);
476 + if (elt != bar) bar.style.pointerEvents = "none";
477 + else delay.set(1000, maybeDisable);
478 + }
479 + delay.set(1000, maybeDisable);
480 + },
481 + clear: function() {
482 + var parent = this.horiz.parentNode;
483 + parent.removeChild(this.horiz);
484 + parent.removeChild(this.vert);
485 }
351 - if (needsH && needsV) {
486 + }, NativeScrollbars.prototype);
487 +
488 + function NullScrollbars() {}
489 +
490 + NullScrollbars.prototype = copyObj({
491 + update: function() { return {bottom: 0, right: 0}; },
492 + setScrollLeft: function() {},
493 + setScrollTop: function() {},
494 + clear: function() {}
495 + }, NullScrollbars.prototype);
496 +
497 + CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars};
498 +
499 + function initScrollbars(cm) {
500 + if (cm.display.scrollbars) {
501 + cm.display.scrollbars.clear();
502 + if (cm.display.scrollbars.addClass)
503 + rmClass(cm.display.wrapper, cm.display.scrollbars.addClass);
504 + }
505 +
506 + cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node) {
507 + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller);
508 + // Prevent clicks in the scrollbars from killing focus
509 + on(node, "mousedown", function() {
510 + if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0);
511 + });
512 + node.setAttribute("cm-not-content", "true");
513 + }, function(pos, axis) {
514 + if (axis == "horizontal") setScrollLeft(cm, pos);
515 + else setScrollTop(cm, pos);
516 + }, cm);
517 + if (cm.display.scrollbars.addClass)
518 + addClass(cm.display.wrapper, cm.display.scrollbars.addClass);
519 + }
520 +
521 + function updateScrollbars(cm, measure) {
522 + if (!measure) measure = measureForScrollbars(cm);
523 + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight;
524 + updateScrollbarsInner(cm, measure);
525 + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) {
526 + if (startWidth != cm.display.barWidth && cm.options.lineWrapping)
527 + updateHeightsInViewport(cm);
528 + updateScrollbarsInner(cm, measureForScrollbars(cm));
529 + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight;
530 + }
531 + }
532 +
533 + // Re-synchronize the fake scrollbars with the actual size of the
534 + // content.
535 + function updateScrollbarsInner(cm, measure) {
536 + var d = cm.display;
537 + var sizes = d.scrollbars.update(measure);
538 +
539 + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px";
540 + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px";
541 + d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"
542 +
543 + if (sizes.right && sizes.bottom) {
544 d.scrollbarFiller.style.display = "block";
353 - d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
545 + d.scrollbarFiller.style.height = sizes.bottom + "px";
546 + d.scrollbarFiller.style.width = sizes.right + "px";
547 } else d.scrollbarFiller.style.display = "";
355 - if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
548 + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
549 d.gutterFiller.style.display = "block";
357 - d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px";
358 - d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
550 + d.gutterFiller.style.height = sizes.bottom + "px";
551 + d.gutterFiller.style.width = measure.gutterWidth + "px";
552 } else d.gutterFiller.style.display = "";
360 -
361 - if (mac_geLion && scrollbarWidth(d.measure) === 0) {
362 - d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
363 - d.scrollbarV.style.pointerEvents = d.scrollbarH.style.pointerEvents = "none";
364 - }
553 }
554
367 - function visibleLines(display, doc, viewPort) {
368 - var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
369 - if (typeof viewPort == "number") top = viewPort;
370 - else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
555 + // Compute the lines that are visible in a given viewport (defaults
556 + // the the current scroll position). viewport may contain top,
557 + // height, and ensure (see op.scrollToPos) properties.
558 + function visibleLines(display, doc, viewport) {
559 + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop;
560 top = Math.floor(top - paddingTop(display));
372 - var bottom = Math.ceil(top + height);
373 - return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
561 + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight;
562 +
563 + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);
564 + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
565 + // forces those lines into the viewport (if possible).
566 + if (viewport && viewport.ensure) {
567 + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line;
568 + if (ensureFrom < from) {
569 + from = ensureFrom;
570 + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight);
571 + } else if (Math.min(ensureTo, doc.lastLine()) >= to) {
572 + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight);
573 + to = ensureTo;
574 + }
575 + }
576 + return {from: from, to: Math.max(to, from + 1)};
577 }
578
579 // LINE NUMBERS
580
581 + // Re-align line numbers and gutter marks to compensate for
582 + // horizontal scrolling.
583 function alignHorizontally(cm) {
379 - var display = cm.display;
584 + var display = cm.display, view = display.view;
585 if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
586 var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
382 - var gutterW = display.gutters.offsetWidth, l = comp + "px";
383 - for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
384 - for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
587 + var gutterW = display.gutters.offsetWidth, left = comp + "px";
588 + for (var i = 0; i < view.length; i++) if (!view[i].hidden) {
589 + if (cm.options.fixedGutter && view[i].gutter)
590 + view[i].gutter.style.left = left;
591 + var align = view[i].alignable;
592 + if (align) for (var j = 0; j < align.length; j++)
593 + align[j].style.left = left;
594 }
595 if (cm.options.fixedGutter)
596 display.gutters.style.left = (comp + gutterW) + "px";
597 }
598
599 + // Used to ensure that the line number gutter is still the right
600 + // size for the current document size. Returns true when an update
601 + // is needed.
602 function maybeUpdateLineNumberWidth(cm) {
603 if (!cm.options.lineNumbers) return false;
604 var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
@@ -395,431 +607,1751 @@ window.CodeMirror = (function() {
607 "CodeMirror-linenumber CodeMirror-gutter-elt"));
608 var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
609 display.lineGutter.style.width = "";
398 - display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
610 + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1;
611 display.lineNumWidth = display.lineNumInnerWidth + padding;
612 display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
613 display.lineGutter.style.width = display.lineNumWidth + "px";
614 + updateGutterSpace(cm);
615 return true;
616 }
404 - return false;
617 + return false;
618 + }
619 +
620 + function lineNumberFor(options, i) {
621 + return String(options.lineNumberFormatter(i + options.firstLineNumber));
622 + }
623 +
624 + // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
625 + // but using getBoundingClientRect to get a sub-pixel-accurate
626 + // result.
627 + function compensateForHScroll(display) {
628 + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left;
629 + }
630 +
631 + // DISPLAY DRAWING
632 +
633 + function DisplayUpdate(cm, viewport, force) {
634 + var display = cm.display;
635 +
636 + this.viewport = viewport;
637 + // Store some values that we'll need later (but don't want to force a relayout for)
638 + this.visible = visibleLines(display, cm.doc, viewport);
639 + this.editorIsHidden = !display.wrapper.offsetWidth;
640 + this.wrapperHeight = display.wrapper.clientHeight;
641 + this.wrapperWidth = display.wrapper.clientWidth;
642 + this.oldDisplayWidth = displayWidth(cm);
643 + this.force = force;
644 + this.dims = getDimensions(cm);
645 + this.events = [];
646 + }
647 +
648 + DisplayUpdate.prototype.signal = function(emitter, type) {
649 + if (hasHandler(emitter, type))
650 + this.events.push(arguments);
651 + };
652 + DisplayUpdate.prototype.finish = function() {
653 + for (var i = 0; i < this.events.length; i++)
654 + signal.apply(null, this.events[i]);
655 + };
656 +
657 + function maybeClipScrollbars(cm) {
658 + var display = cm.display;
659 + if (!display.scrollbarsClipped && display.scroller.offsetWidth) {
660 + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth;
661 + display.heightForcer.style.height = scrollGap(cm) + "px";
662 + display.sizer.style.marginBottom = -display.nativeBarWidth + "px";
663 + display.sizer.style.borderRightWidth = scrollGap(cm) + "px";
664 + display.scrollbarsClipped = true;
665 + }
666 + }
667 +
668 + // Does the actual updating of the line display. Bails out
669 + // (returning false) when there is nothing to be done and forced is
670 + // false.
671 + function updateDisplayIfNeeded(cm, update) {
672 + var display = cm.display, doc = cm.doc;
673 +
674 + if (update.editorIsHidden) {
675 + resetView(cm);
676 + return false;
677 + }
678 +
679 + // Bail out if the visible area is already rendered and nothing changed.
680 + if (!update.force &&
681 + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
682 + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
683 + display.renderedView == display.view && countDirtyView(cm) == 0)
684 + return false;
685 +
686 + if (maybeUpdateLineNumberWidth(cm)) {
687 + resetView(cm);
688 + update.dims = getDimensions(cm);
689 + }
690 +
691 + // Compute a suitable new viewport (from & to)
692 + var end = doc.first + doc.size;
693 + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);
694 + var to = Math.min(end, update.visible.to + cm.options.viewportMargin);
695 + if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom);
696 + if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo);
697 + if (sawCollapsedSpans) {
698 + from = visualLineNo(cm.doc, from);
699 + to = visualLineEndNo(cm.doc, to);
700 + }
701 +
702 + var different = from != display.viewFrom || to != display.viewTo ||
703 + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;
704 + adjustView(cm, from, to);
705 +
706 + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));
707 + // Position the mover div to align with the current scroll position
708 + cm.display.mover.style.top = display.viewOffset + "px";
709 +
710 + var toUpdate = countDirtyView(cm);
711 + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
712 + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
713 + return false;
714 +
715 + // For big changes, we hide the enclosing element during the
716 + // update, since that speeds up the operations on most browsers.
717 + var focused = activeElt();
718 + if (toUpdate > 4) display.lineDiv.style.display = "none";
719 + patchDisplay(cm, display.updateLineNumbers, update.dims);
720 + if (toUpdate > 4) display.lineDiv.style.display = "";
721 + display.renderedView = display.view;
722 + // There might have been a widget with a focused element that got
723 + // hidden or updated, if so re-focus it.
724 + if (focused && activeElt() != focused && focused.offsetHeight) focused.focus();
725 +
726 + // Prevent selection and cursors from interfering with the scroll
727 + // width and height.
728 + removeChildren(display.cursorDiv);
729 + removeChildren(display.selectionDiv);
730 + display.gutters.style.height = display.sizer.style.minHeight = 0;
731 +
732 + if (different) {
733 + display.lastWrapHeight = update.wrapperHeight;
734 + display.lastWrapWidth = update.wrapperWidth;
735 + startWorker(cm, 400);
736 + }
737 +
738 + display.updateLineNumbers = null;
739 +
740 + return true;
741 + }
742 +
743 + function postUpdateDisplay(cm, update) {
744 + var viewport = update.viewport;
745 +
746 + for (var first = true;; first = false) {
747 + if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {
748 + // Clip forced viewport to actual scrollable area.
749 + if (viewport && viewport.top != null)
750 + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)};
751 + // Updated line heights might result in the drawn area not
752 + // actually covering the viewport. Keep looping until it does.
753 + update.visible = visibleLines(cm.display, cm.doc, viewport);
754 + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)
755 + break;
756 + }
757 + if (!updateDisplayIfNeeded(cm, update)) break;
758 + updateHeightsInViewport(cm);
759 + var barMeasure = measureForScrollbars(cm);
760 + updateSelection(cm);
761 + updateScrollbars(cm, barMeasure);
762 + setDocumentHeight(cm, barMeasure);
763 + }
764 +
765 + update.signal(cm, "update", cm);
766 + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {
767 + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
768 + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo;
769 + }
770 + }
771 +
772 + function updateDisplaySimple(cm, viewport) {
773 + var update = new DisplayUpdate(cm, viewport);
774 + if (updateDisplayIfNeeded(cm, update)) {
775 + updateHeightsInViewport(cm);
776 + postUpdateDisplay(cm, update);
777 + var barMeasure = measureForScrollbars(cm);
778 + updateSelection(cm);
779 + updateScrollbars(cm, barMeasure);
780 + setDocumentHeight(cm, barMeasure);
781 + update.finish();
782 + }
783 + }
784 +
785 + function setDocumentHeight(cm, measure) {
786 + cm.display.sizer.style.minHeight = measure.docHeight + "px";
787 + cm.display.heightForcer.style.top = measure.docHeight + "px";
788 + cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px";
789 + }
790 +
791 + // Read the actual heights of the rendered lines, and update their
792 + // stored heights to match.
793 + function updateHeightsInViewport(cm) {
794 + var display = cm.display;
795 + var prevBottom = display.lineDiv.offsetTop;
796 + for (var i = 0; i < display.view.length; i++) {
797 + var cur = display.view[i], height;
798 + if (cur.hidden) continue;
799 + if (ie && ie_version < 8) {
800 + var bot = cur.node.offsetTop + cur.node.offsetHeight;
801 + height = bot - prevBottom;
802 + prevBottom = bot;
803 + } else {
804 + var box = cur.node.getBoundingClientRect();
805 + height = box.bottom - box.top;
806 + }
807 + var diff = cur.line.height - height;
808 + if (height < 2) height = textHeight(display);
809 + if (diff > .001 || diff < -.001) {
810 + updateLineHeight(cur.line, height);
811 + updateWidgetHeight(cur.line);
812 + if (cur.rest) for (var j = 0; j < cur.rest.length; j++)
813 + updateWidgetHeight(cur.rest[j]);
814 + }
815 + }
816 + }
817 +
818 + // Read and store the height of line widgets associated with the
819 + // given line.
820 + function updateWidgetHeight(line) {
821 + if (line.widgets) for (var i = 0; i < line.widgets.length; ++i)
822 + line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight;
823 + }
824 +
825 + // Do a bulk-read of the DOM positions and sizes needed to draw the
826 + // view, so that we don't interleave reading and writing to the DOM.
827 + function getDimensions(cm) {
828 + var d = cm.display, left = {}, width = {};
829 + var gutterLeft = d.gutters.clientLeft;
830 + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
831 + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft;
832 + width[cm.options.gutters[i]] = n.clientWidth;
833 + }
834 + return {fixedPos: compensateForHScroll(d),
835 + gutterTotalWidth: d.gutters.offsetWidth,
836 + gutterLeft: left,
837 + gutterWidth: width,
838 + wrapperWidth: d.wrapper.clientWidth};
839 + }
840 +
841 + // Sync the actual display DOM structure with display.view, removing
842 + // nodes for lines that are no longer in view, and creating the ones
843 + // that are not there yet, and updating the ones that are out of
844 + // date.
845 + function patchDisplay(cm, updateNumbersFrom, dims) {
846 + var display = cm.display, lineNumbers = cm.options.lineNumbers;
847 + var container = display.lineDiv, cur = container.firstChild;
848 +
849 + function rm(node) {
850 + var next = node.nextSibling;
851 + // Works around a throw-scroll bug in OS X Webkit
852 + if (webkit && mac && cm.display.currentWheelTarget == node)
853 + node.style.display = "none";
854 + else
855 + node.parentNode.removeChild(node);
856 + return next;
857 + }
858 +
859 + var view = display.view, lineN = display.viewFrom;
860 + // Loop over the elements in the view, syncing cur (the DOM nodes
861 + // in display.lineDiv) with the view as we go.
862 + for (var i = 0; i < view.length; i++) {
863 + var lineView = view[i];
864 + if (lineView.hidden) {
865 + } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet
866 + var node = buildLineElement(cm, lineView, lineN, dims);
867 + container.insertBefore(node, cur);
868 + } else { // Already drawn
869 + while (cur != lineView.node) cur = rm(cur);
870 + var updateNumber = lineNumbers && updateNumbersFrom != null &&
871 + updateNumbersFrom <= lineN && lineView.lineNumber;
872 + if (lineView.changes) {
873 + if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false;
874 + updateLineForChanges(cm, lineView, lineN, dims);
875 + }
876 + if (updateNumber) {
877 + removeChildren(lineView.lineNumber);
878 + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)));
879 + }
880 + cur = lineView.node.nextSibling;
881 + }
882 + lineN += lineView.size;
883 + }
884 + while (cur) cur = rm(cur);
885 + }
886 +
887 + // When an aspect of a line changes, a string is added to
888 + // lineView.changes. This updates the relevant part of the line's
889 + // DOM structure.
890 + function updateLineForChanges(cm, lineView, lineN, dims) {
891 + for (var j = 0; j < lineView.changes.length; j++) {
892 + var type = lineView.changes[j];
893 + if (type == "text") updateLineText(cm, lineView);
894 + else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims);
895 + else if (type == "class") updateLineClasses(lineView);
896 + else if (type == "widget") updateLineWidgets(cm, lineView, dims);
897 + }
898 + lineView.changes = null;
899 + }
900 +
901 + // Lines with gutter elements, widgets or a background class need to
902 + // be wrapped, and have the extra elements added to the wrapper div
903 + function ensureLineWrapped(lineView) {
904 + if (lineView.node == lineView.text) {
905 + lineView.node = elt("div", null, null, "position: relative");
906 + if (lineView.text.parentNode)
907 + lineView.text.parentNode.replaceChild(lineView.node, lineView.text);
908 + lineView.node.appendChild(lineView.text);
909 + if (ie && ie_version < 8) lineView.node.style.zIndex = 2;
910 + }
911 + return lineView.node;
912 + }
913 +
914 + function updateLineBackground(lineView) {
915 + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass;
916 + if (cls) cls += " CodeMirror-linebackground";
917 + if (lineView.background) {
918 + if (cls) lineView.background.className = cls;
919 + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; }
920 + } else if (cls) {
921 + var wrap = ensureLineWrapped(lineView);
922 + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild);
923 + }
924 + }
925 +
926 + // Wrapper around buildLineContent which will reuse the structure
927 + // in display.externalMeasured when possible.
928 + function getLineContent(cm, lineView) {
929 + var ext = cm.display.externalMeasured;
930 + if (ext && ext.line == lineView.line) {
931 + cm.display.externalMeasured = null;
932 + lineView.measure = ext.measure;
933 + return ext.built;
934 + }
935 + return buildLineContent(cm, lineView);
936 + }
937 +
938 + // Redraw the line's text. Interacts with the background and text
939 + // classes because the mode may output tokens that influence these
940 + // classes.
941 + function updateLineText(cm, lineView) {
942 + var cls = lineView.text.className;
943 + var built = getLineContent(cm, lineView);
944 + if (lineView.text == lineView.node) lineView.node = built.pre;
945 + lineView.text.parentNode.replaceChild(built.pre, lineView.text);
946 + lineView.text = built.pre;
947 + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {
948 + lineView.bgClass = built.bgClass;
949 + lineView.textClass = built.textClass;
950 + updateLineClasses(lineView);
951 + } else if (cls) {
952 + lineView.text.className = cls;
953 + }
954 + }
955 +
956 + function updateLineClasses(lineView) {
957 + updateLineBackground(lineView);
958 + if (lineView.line.wrapClass)
959 + ensureLineWrapped(lineView).className = lineView.line.wrapClass;
960 + else if (lineView.node != lineView.text)
961 + lineView.node.className = "";
962 + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass;
963 + lineView.text.className = textClass || "";
964 + }
965 +
966 + function updateLineGutter(cm, lineView, lineN, dims) {
967 + if (lineView.gutter) {
968 + lineView.node.removeChild(lineView.gutter);
969 + lineView.gutter = null;
970 + }
971 + if (lineView.gutterBackground) {
972 + lineView.node.removeChild(lineView.gutterBackground);
973 + lineView.gutterBackground = null;
974 + }
975 + if (lineView.line.gutterClass) {
976 + var wrap = ensureLineWrapped(lineView);
977 + lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass,
978 + "left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) +
979 + "px; width: " + dims.gutterTotalWidth + "px");
980 + wrap.insertBefore(lineView.gutterBackground, lineView.text);
981 + }
982 + var markers = lineView.line.gutterMarkers;
983 + if (cm.options.lineNumbers || markers) {
984 + var wrap = ensureLineWrapped(lineView);
985 + var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " +
986 + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px");
987 + cm.display.input.setUneditable(gutterWrap);
988 + wrap.insertBefore(gutterWrap, lineView.text);
989 + if (lineView.line.gutterClass)
990 + gutterWrap.className += " " + lineView.line.gutterClass;
991 + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
992 + lineView.lineNumber = gutterWrap.appendChild(
993 + elt("div", lineNumberFor(cm.options, lineN),
994 + "CodeMirror-linenumber CodeMirror-gutter-elt",
995 + "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
996 + + cm.display.lineNumInnerWidth + "px"));
997 + if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) {
998 + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
999 + if (found)
1000 + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
1001 + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
1002 + }
1003 + }
1004 + }
1005 +
1006 + function updateLineWidgets(cm, lineView, dims) {
1007 + if (lineView.alignable) lineView.alignable = null;
1008 + for (var node = lineView.node.firstChild, next; node; node = next) {
1009 + var next = node.nextSibling;
1010 + if (node.className == "CodeMirror-linewidget")
1011 + lineView.node.removeChild(node);
1012 + }
1013 + insertLineWidgets(cm, lineView, dims);
1014 + }
1015 +
1016 + // Build a line's DOM representation from scratch
1017 + function buildLineElement(cm, lineView, lineN, dims) {
1018 + var built = getLineContent(cm, lineView);
1019 + lineView.text = lineView.node = built.pre;
1020 + if (built.bgClass) lineView.bgClass = built.bgClass;
1021 + if (built.textClass) lineView.textClass = built.textClass;
1022 +
1023 + updateLineClasses(lineView);
1024 + updateLineGutter(cm, lineView, lineN, dims);
1025 + insertLineWidgets(cm, lineView, dims);
1026 + return lineView.node;
1027 + }
1028 +
1029 + // A lineView may contain multiple logical lines (when merged by
1030 + // collapsed spans). The widgets for all of them need to be drawn.
1031 + function insertLineWidgets(cm, lineView, dims) {
1032 + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true);
1033 + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
1034 + insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false);
1035 + }
1036 +
1037 + function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {
1038 + if (!line.widgets) return;
1039 + var wrap = ensureLineWrapped(lineView);
1040 + for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
1041 + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
1042 + if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true");
1043 + positionLineWidget(widget, node, lineView, dims);
1044 + cm.display.input.setUneditable(node);
1045 + if (allowAbove && widget.above)
1046 + wrap.insertBefore(node, lineView.gutter || lineView.text);
1047 + else
1048 + wrap.appendChild(node);
1049 + signalLater(widget, "redraw");
1050 + }
1051 + }
1052 +
1053 + function positionLineWidget(widget, node, lineView, dims) {
1054 + if (widget.noHScroll) {
1055 + (lineView.alignable || (lineView.alignable = [])).push(node);
1056 + var width = dims.wrapperWidth;
1057 + node.style.left = dims.fixedPos + "px";
1058 + if (!widget.coverGutter) {
1059 + width -= dims.gutterTotalWidth;
1060 + node.style.paddingLeft = dims.gutterTotalWidth + "px";
1061 + }
1062 + node.style.width = width + "px";
1063 + }
1064 + if (widget.coverGutter) {
1065 + node.style.zIndex = 5;
1066 + node.style.position = "relative";
1067 + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
1068 + }
1069 + }
1070 +
1071 + // POSITION OBJECT
1072 +
1073 + // A Pos instance represents a position within the text.
1074 + var Pos = CodeMirror.Pos = function(line, ch) {
1075 + if (!(this instanceof Pos)) return new Pos(line, ch);
1076 + this.line = line; this.ch = ch;
1077 + };
1078 +
1079 + // Compare two positions, return 0 if they are the same, a negative
1080 + // number when a is less, and a positive number otherwise.
1081 + var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; };
1082 +
1083 + function copyPos(x) {return Pos(x.line, x.ch);}
1084 + function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
1085 + function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }
1086 +
1087 + // INPUT HANDLING
1088 +
1089 + function ensureFocus(cm) {
1090 + if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
1091 + }
1092 +
1093 + // This will be set to a {lineWise: bool, text: [string]} object, so
1094 + // that, when pasting, we know what kind of selections the copied
1095 + // text was made out of.
1096 + var lastCopied = null;
1097 +
1098 + function applyTextInput(cm, inserted, deleted, sel, origin) {
1099 + var doc = cm.doc;
1100 + cm.display.shift = false;
1101 + if (!sel) sel = doc.sel;
1102 +
1103 + var paste = cm.state.pasteIncoming || origin == "paste";
1104 + var textLines = doc.splitLines(inserted), multiPaste = null
1105 + // When pasing N lines into N selections, insert one line per selection
1106 + if (paste && sel.ranges.length > 1) {
1107 + if (lastCopied && lastCopied.text.join("\n") == inserted) {
1108 + if (sel.ranges.length % lastCopied.text.length == 0) {
1109 + multiPaste = [];
1110 + for (var i = 0; i < lastCopied.text.length; i++)
1111 + multiPaste.push(doc.splitLines(lastCopied.text[i]));
1112 + }
1113 + } else if (textLines.length == sel.ranges.length) {
1114 + multiPaste = map(textLines, function(l) { return [l]; });
1115 + }
1116 + }
1117 +
1118 + // Normal behavior is to insert the new text into every selection
1119 + for (var i = sel.ranges.length - 1; i >= 0; i--) {
1120 + var range = sel.ranges[i];
1121 + var from = range.from(), to = range.to();
1122 + if (range.empty()) {
1123 + if (deleted && deleted > 0) // Handle deletion
1124 + from = Pos(from.line, from.ch - deleted);
1125 + else if (cm.state.overwrite && !paste) // Handle overwrite
1126 + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
1127 + else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted)
1128 + from = to = Pos(from.line, 0)
1129 + }
1130 + var updateInput = cm.curOp.updateInput;
1131 + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines,
1132 + origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")};
1133 + makeChange(cm.doc, changeEvent);
1134 + signalLater(cm, "inputRead", cm, changeEvent);
1135 + }
1136 + if (inserted && !paste)
1137 + triggerElectric(cm, inserted);
1138 +
1139 + ensureCursorVisible(cm);
1140 + cm.curOp.updateInput = updateInput;
1141 + cm.curOp.typing = true;
1142 + cm.state.pasteIncoming = cm.state.cutIncoming = false;
1143 + }
1144 +
1145 + function handlePaste(e, cm) {
1146 + var pasted = e.clipboardData && e.clipboardData.getData("text/plain");
1147 + if (pasted) {
1148 + e.preventDefault();
1149 + if (!cm.isReadOnly() && !cm.options.disableInput)
1150 + runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); });
1151 + return true;
1152 + }
1153 + }
1154 +
1155 + function triggerElectric(cm, inserted) {
1156 + // When an 'electric' character is inserted, immediately trigger a reindent
1157 + if (!cm.options.electricChars || !cm.options.smartIndent) return;
1158 + var sel = cm.doc.sel;
1159 +
1160 + for (var i = sel.ranges.length - 1; i >= 0; i--) {
1161 + var range = sel.ranges[i];
1162 + if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) continue;
1163 + var mode = cm.getModeAt(range.head);
1164 + var indented = false;
1165 + if (mode.electricChars) {
1166 + for (var j = 0; j < mode.electricChars.length; j++)
1167 + if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
1168 + indented = indentLine(cm, range.head.line, "smart");
1169 + break;
1170 + }
1171 + } else if (mode.electricInput) {
1172 + if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch)))
1173 + indented = indentLine(cm, range.head.line, "smart");
1174 + }
1175 + if (indented) signalLater(cm, "electricInput", cm, range.head.line);
1176 + }
1177 + }
1178 +
1179 + function copyableRanges(cm) {
1180 + var text = [], ranges = [];
1181 + for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
1182 + var line = cm.doc.sel.ranges[i].head.line;
1183 + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
1184 + ranges.push(lineRange);
1185 + text.push(cm.getRange(lineRange.anchor, lineRange.head));
1186 + }
1187 + return {text: text, ranges: ranges};
1188 + }
1189 +
1190 + function disableBrowserMagic(field) {
1191 + field.setAttribute("autocorrect", "off");
1192 + field.setAttribute("autocapitalize", "off");
1193 + field.setAttribute("spellcheck", "false");
1194 + }
1195 +
1196 + // TEXTAREA INPUT STYLE
1197 +
1198 + function TextareaInput(cm) {
1199 + this.cm = cm;
1200 + // See input.poll and input.reset
1201 + this.prevInput = "";
1202 +
1203 + // Flag that indicates whether we expect input to appear real soon
1204 + // now (after some event like 'keypress' or 'input') and are
1205 + // polling intensively.
1206 + this.pollingFast = false;
1207 + // Self-resetting timeout for the poller
1208 + this.polling = new Delayed();
1209 + // Tracks when input.reset has punted to just putting a short
1210 + // string into the textarea instead of the full selection.
1211 + this.inaccurateSelection = false;
1212 + // Used to work around IE issue with selection being forgotten when focus moves away from textarea
1213 + this.hasSelection = false;
1214 + this.composing = null;
1215 + };
1216 +
1217 + function hiddenTextarea() {
1218 + var te = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
1219 + var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
1220 + // The textarea is kept positioned near the cursor to prevent the
1221 + // fact that it'll be scrolled into view on input from scrolling
1222 + // our fake cursor out of view. On webkit, when wrap=off, paste is
1223 + // very slow. So make the area wide instead.
1224 + if (webkit) te.style.width = "1000px";
1225 + else te.setAttribute("wrap", "off");
1226 + // If border: 0; -- iOS fails to open keyboard (issue #1287)
1227 + if (ios) te.style.border = "1px solid black";
1228 + disableBrowserMagic(te);
1229 + return div;
1230 + }
1231 +
1232 + TextareaInput.prototype = copyObj({
1233 + init: function(display) {
1234 + var input = this, cm = this.cm;
1235 +
1236 + // Wraps and hides input textarea
1237 + var div = this.wrapper = hiddenTextarea();
1238 + // The semihidden textarea that is focused when the editor is
1239 + // focused, and receives input.
1240 + var te = this.textarea = div.firstChild;
1241 + display.wrapper.insertBefore(div, display.wrapper.firstChild);
1242 +
1243 + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)
1244 + if (ios) te.style.width = "0px";
1245 +
1246 + on(te, "input", function() {
1247 + if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = null;
1248 + input.poll();
1249 + });
1250 +
1251 + on(te, "paste", function(e) {
1252 + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return
1253 +
1254 + cm.state.pasteIncoming = true;
1255 + input.fastPoll();
1256 + });
1257 +
1258 + function prepareCopyCut(e) {
1259 + if (signalDOMEvent(cm, e)) return
1260 + if (cm.somethingSelected()) {
1261 + lastCopied = {lineWise: false, text: cm.getSelections()};
1262 + if (input.inaccurateSelection) {
1263 + input.prevInput = "";
1264 + input.inaccurateSelection = false;
1265 + te.value = lastCopied.text.join("\n");
1266 + selectInput(te);
1267 + }
1268 + } else if (!cm.options.lineWiseCopyCut) {
1269 + return;
1270 + } else {
1271 + var ranges = copyableRanges(cm);
1272 + lastCopied = {lineWise: true, text: ranges.text};
1273 + if (e.type == "cut") {
1274 + cm.setSelections(ranges.ranges, null, sel_dontScroll);
1275 + } else {
1276 + input.prevInput = "";
1277 + te.value = ranges.text.join("\n");
1278 + selectInput(te);
1279 + }
1280 + }
1281 + if (e.type == "cut") cm.state.cutIncoming = true;
1282 + }
1283 + on(te, "cut", prepareCopyCut);
1284 + on(te, "copy", prepareCopyCut);
1285 +
1286 + on(display.scroller, "paste", function(e) {
1287 + if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return;
1288 + cm.state.pasteIncoming = true;
1289 + input.focus();
1290 + });
1291 +
1292 + // Prevent normal selection in the editor (we handle our own)
1293 + on(display.lineSpace, "selectstart", function(e) {
1294 + if (!eventInWidget(display, e)) e_preventDefault(e);
1295 + });
1296 +
1297 + on(te, "compositionstart", function() {
1298 + var start = cm.getCursor("from");
1299 + if (input.composing) input.composing.range.clear()
1300 + input.composing = {
1301 + start: start,
1302 + range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"})
1303 + };
1304 + });
1305 + on(te, "compositionend", function() {
1306 + if (input.composing) {
1307 + input.poll();
1308 + input.composing.range.clear();
1309 + input.composing = null;
1310 + }
1311 + });
1312 + },
1313 +
1314 + prepareSelection: function() {
1315 + // Redraw the selection and/or cursor
1316 + var cm = this.cm, display = cm.display, doc = cm.doc;
1317 + var result = prepareSelection(cm);
1318 +
1319 + // Move the hidden textarea near the cursor to prevent scrolling artifacts
1320 + if (cm.options.moveInputWithCursor) {
1321 + var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
1322 + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
1323 + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
1324 + headPos.top + lineOff.top - wrapOff.top));
1325 + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
1326 + headPos.left + lineOff.left - wrapOff.left));
1327 + }
1328 +
1329 + return result;
1330 + },
1331 +
1332 + showSelection: function(drawn) {
1333 + var cm = this.cm, display = cm.display;
1334 + removeChildrenAndAdd(display.cursorDiv, drawn.cursors);
1335 + removeChildrenAndAdd(display.selectionDiv, drawn.selection);
1336 + if (drawn.teTop != null) {
1337 + this.wrapper.style.top = drawn.teTop + "px";
1338 + this.wrapper.style.left = drawn.teLeft + "px";
1339 + }
1340 + },
1341 +
1342 + // Reset the input to correspond to the selection (or to be empty,
1343 + // when not typing and nothing is selected)
1344 + reset: function(typing) {
1345 + if (this.contextMenuPending) return;
1346 + var minimal, selected, cm = this.cm, doc = cm.doc;
1347 + if (cm.somethingSelected()) {
1348 + this.prevInput = "";
1349 + var range = doc.sel.primary();
1350 + minimal = hasCopyEvent &&
1351 + (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
1352 + var content = minimal ? "-" : selected || cm.getSelection();
1353 + this.textarea.value = content;
1354 + if (cm.state.focused) selectInput(this.textarea);
1355 + if (ie && ie_version >= 9) this.hasSelection = content;
1356 + } else if (!typing) {
1357 + this.prevInput = this.textarea.value = "";
1358 + if (ie && ie_version >= 9) this.hasSelection = null;
1359 + }
1360 + this.inaccurateSelection = minimal;
1361 + },
1362 +
1363 + getField: function() { return this.textarea; },
1364 +
1365 + supportsTouch: function() { return false; },
1366 +
1367 + focus: function() {
1368 + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) {
1369 + try { this.textarea.focus(); }
1370 + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM
1371 + }
1372 + },
1373 +
1374 + blur: function() { this.textarea.blur(); },
1375 +
1376 + resetPosition: function() {
1377 + this.wrapper.style.top = this.wrapper.style.left = 0;
1378 + },
1379 +
1380 + receivedFocus: function() { this.slowPoll(); },
1381 +
1382 + // Poll for input changes, using the normal rate of polling. This
1383 + // runs as long as the editor is focused.
1384 + slowPoll: function() {
1385 + var input = this;
1386 + if (input.pollingFast) return;
1387 + input.polling.set(this.cm.options.pollInterval, function() {
1388 + input.poll();
1389 + if (input.cm.state.focused) input.slowPoll();
1390 + });
1391 + },
1392 +
1393 + // When an event has just come in that is likely to add or change
1394 + // something in the input textarea, we poll faster, to ensure that
1395 + // the change appears on the screen quickly.
1396 + fastPoll: function() {
1397 + var missed = false, input = this;
1398 + input.pollingFast = true;
1399 + function p() {
1400 + var changed = input.poll();
1401 + if (!changed && !missed) {missed = true; input.polling.set(60, p);}
1402 + else {input.pollingFast = false; input.slowPoll();}
1403 + }
1404 + input.polling.set(20, p);
1405 + },
1406 +
1407 + // Read input from the textarea, and update the document to match.
1408 + // When something is selected, it is present in the textarea, and
1409 + // selected (unless it is huge, in which case a placeholder is
1410 + // used). When nothing is selected, the cursor sits after previously
1411 + // seen text (can be empty), which is stored in prevInput (we must
1412 + // not reset the textarea when typing, because that breaks IME).
1413 + poll: function() {
1414 + var cm = this.cm, input = this.textarea, prevInput = this.prevInput;
1415 + // Since this is called a *lot*, try to bail out as cheaply as
1416 + // possible when it is clear that nothing happened. hasSelection
1417 + // will be the case when there is a lot of text in the textarea,
1418 + // in which case reading its value would be expensive.
1419 + if (this.contextMenuPending || !cm.state.focused ||
1420 + (hasSelection(input) && !prevInput && !this.composing) ||
1421 + cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
1422 + return false;
1423 +
1424 + var text = input.value;
1425 + // If nothing changed, bail.
1426 + if (text == prevInput && !cm.somethingSelected()) return false;
1427 + // Work around nonsensical selection resetting in IE9/10, and
1428 + // inexplicable appearance of private area unicode characters on
1429 + // some key combos in Mac (#2689).
1430 + if (ie && ie_version >= 9 && this.hasSelection === text ||
1431 + mac && /[\uf700-\uf7ff]/.test(text)) {
1432 + cm.display.input.reset();
1433 + return false;
1434 + }
1435 +
1436 + if (cm.doc.sel == cm.display.selForContextMenu) {
1437 + var first = text.charCodeAt(0);
1438 + if (first == 0x200b && !prevInput) prevInput = "\u200b";
1439 + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); }
1440 + }
1441 + // Find the part of the input that is actually new
1442 + var same = 0, l = Math.min(prevInput.length, text.length);
1443 + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
1444 +
1445 + var self = this;
1446 + runInOp(cm, function() {
1447 + applyTextInput(cm, text.slice(same), prevInput.length - same,
1448 + null, self.composing ? "*compose" : null);
1449 +
1450 + // Don't leave long text in the textarea, since it makes further polling slow
1451 + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = "";
1452 + else self.prevInput = text;
1453 +
1454 + if (self.composing) {
1455 + self.composing.range.clear();
1456 + self.composing.range = cm.markText(self.composing.start, cm.getCursor("to"),
1457 + {className: "CodeMirror-composing"});
1458 + }
1459 + });
1460 + return true;
1461 + },
1462 +
1463 + ensurePolled: function() {
1464 + if (this.pollingFast && this.poll()) this.pollingFast = false;
1465 + },
1466 +
1467 + onKeyPress: function() {
1468 + if (ie && ie_version >= 9) this.hasSelection = null;
1469 + this.fastPoll();
1470 + },
1471 +
1472 + onContextMenu: function(e) {
1473 + var input = this, cm = input.cm, display = cm.display, te = input.textarea;
1474 + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
1475 + if (!pos || presto) return; // Opera is difficult.
1476 +
1477 + // Reset the current text selection only if the click is done outside of the selection
1478 + // and 'resetSelectionOnContextMenu' option is true.
1479 + var reset = cm.options.resetSelectionOnContextMenu;
1480 + if (reset && cm.doc.sel.contains(pos) == -1)
1481 + operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll);
1482 +
1483 + var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText;
1484 + input.wrapper.style.cssText = "position: absolute"
1485 + var wrapperBox = input.wrapper.getBoundingClientRect()
1486 + te.style.cssText = "position: absolute; width: 30px; height: 30px; top: " + (e.clientY - wrapperBox.top - 5) +
1487 + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px; z-index: 1000; background: " +
1488 + (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
1489 + "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1490 + if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712)
1491 + display.input.focus();
1492 + if (webkit) window.scrollTo(null, oldScrollY);
1493 + display.input.reset();
1494 + // Adds "Select all" to context menu in FF
1495 + if (!cm.somethingSelected()) te.value = input.prevInput = " ";
1496 + input.contextMenuPending = true;
1497 + display.selForContextMenu = cm.doc.sel;
1498 + clearTimeout(display.detectingSelectAll);
1499 +
1500 + // Select-all will be greyed out if there's nothing to select, so
1501 + // this adds a zero-width space so that we can later check whether
1502 + // it got selected.
1503 + function prepareSelectAllHack() {
1504 + if (te.selectionStart != null) {
1505 + var selected = cm.somethingSelected();
1506 + var extval = "\u200b" + (selected ? te.value : "");
1507 + te.value = "\u21da"; // Used to catch context-menu undo
1508 + te.value = extval;
1509 + input.prevInput = selected ? "" : "\u200b";
1510 + te.selectionStart = 1; te.selectionEnd = extval.length;
1511 + // Re-set this, in case some other handler touched the
1512 + // selection in the meantime.
1513 + display.selForContextMenu = cm.doc.sel;
1514 + }
1515 + }
1516 + function rehide() {
1517 + input.contextMenuPending = false;
1518 + input.wrapper.style.cssText = oldWrapperCSS
1519 + te.style.cssText = oldCSS;
1520 + if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos);
1521 +
1522 + // Try to detect the user choosing select-all
1523 + if (te.selectionStart != null) {
1524 + if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
1525 + var i = 0, poll = function() {
1526 + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&
1527 + te.selectionEnd > 0 && input.prevInput == "\u200b")
1528 + operation(cm, commands.selectAll)(cm);
1529 + else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
1530 + else display.input.reset();
1531 + };
1532 + display.detectingSelectAll = setTimeout(poll, 200);
1533 + }
1534 + }
1535 +
1536 + if (ie && ie_version >= 9) prepareSelectAllHack();
1537 + if (captureRightClick) {
1538 + e_stop(e);
1539 + var mouseup = function() {
1540 + off(window, "mouseup", mouseup);
1541 + setTimeout(rehide, 20);
1542 + };
1543 + on(window, "mouseup", mouseup);
1544 + } else {
1545 + setTimeout(rehide, 50);
1546 + }
1547 + },
1548 +
1549 + readOnlyChanged: function(val) {
1550 + if (!val) this.reset();
1551 + },
1552 +
1553 + setUneditable: nothing,
1554 +
1555 + needsContentAttribute: false
1556 + }, TextareaInput.prototype);
1557 +
1558 + // CONTENTEDITABLE INPUT STYLE
1559 +
1560 + function ContentEditableInput(cm) {
1561 + this.cm = cm;
1562 + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null;
1563 + this.polling = new Delayed();
1564 + this.gracePeriod = false;
1565 + }
1566 +
1567 + ContentEditableInput.prototype = copyObj({
1568 + init: function(display) {
1569 + var input = this, cm = input.cm;
1570 + var div = input.div = display.lineDiv;
1571 + disableBrowserMagic(div);
1572 +
1573 + on(div, "paste", function(e) {
1574 + if (!signalDOMEvent(cm, e)) handlePaste(e, cm);
1575 + })
1576 +
1577 + on(div, "compositionstart", function(e) {
1578 + var data = e.data;
1579 + input.composing = {sel: cm.doc.sel, data: data, startData: data};
1580 + if (!data) return;
1581 + var prim = cm.doc.sel.primary();
1582 + var line = cm.getLine(prim.head.line);
1583 + var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length));
1584 + if (found > -1 && found <= prim.head.ch)
1585 + input.composing.sel = simpleSelection(Pos(prim.head.line, found),
1586 + Pos(prim.head.line, found + data.length));
1587 + });
1588 + on(div, "compositionupdate", function(e) {
1589 + input.composing.data = e.data;
1590 + });
1591 + on(div, "compositionend", function(e) {
1592 + var ours = input.composing;
1593 + if (!ours) return;
1594 + if (e.data != ours.startData && !/\u200b/.test(e.data))
1595 + ours.data = e.data;
1596 + // Need a small delay to prevent other code (input event,
1597 + // selection polling) from doing damage when fired right after
1598 + // compositionend.
1599 + setTimeout(function() {
1600 + if (!ours.handled)
1601 + input.applyComposition(ours);
1602 + if (input.composing == ours)
1603 + input.composing = null;
1604 + }, 50);
1605 + });
1606 +
1607 + on(div, "touchstart", function() {
1608 + input.forceCompositionEnd();
1609 + });
1610 +
1611 + on(div, "input", function() {
1612 + if (input.composing) return;
1613 + if (cm.isReadOnly() || !input.pollContent())
1614 + runInOp(input.cm, function() {regChange(cm);});
1615 + });
1616 +
1617 + function onCopyCut(e) {
1618 + if (signalDOMEvent(cm, e)) return
1619 + if (cm.somethingSelected()) {
1620 + lastCopied = {lineWise: false, text: cm.getSelections()};
1621 + if (e.type == "cut") cm.replaceSelection("", null, "cut");
1622 + } else if (!cm.options.lineWiseCopyCut) {
1623 + return;
1624 + } else {
1625 + var ranges = copyableRanges(cm);
1626 + lastCopied = {lineWise: true, text: ranges.text};
1627 + if (e.type == "cut") {
1628 + cm.operation(function() {
1629 + cm.setSelections(ranges.ranges, 0, sel_dontScroll);
1630 + cm.replaceSelection("", null, "cut");
1631 + });
1632 + }
1633 + }
1634 + // iOS exposes the clipboard API, but seems to discard content inserted into it
1635 + if (e.clipboardData && !ios) {
1636 + e.preventDefault();
1637 + e.clipboardData.clearData();
1638 + e.clipboardData.setData("text/plain", lastCopied.text.join("\n"));
1639 + } else {
1640 + // Old-fashioned briefly-focus-a-textarea hack
1641 + var kludge = hiddenTextarea(), te = kludge.firstChild;
1642 + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
1643 + te.value = lastCopied.text.join("\n");
1644 + var hadFocus = document.activeElement;
1645 + selectInput(te);
1646 + setTimeout(function() {
1647 + cm.display.lineSpace.removeChild(kludge);
1648 + hadFocus.focus();
1649 + }, 50);
1650 + }
1651 + }
1652 + on(div, "copy", onCopyCut);
1653 + on(div, "cut", onCopyCut);
1654 + },
1655 +
1656 + prepareSelection: function() {
1657 + var result = prepareSelection(this.cm, false);
1658 + result.focus = this.cm.state.focused;
1659 + return result;
1660 + },
1661 +
1662 + showSelection: function(info, takeFocus) {
1663 + if (!info || !this.cm.display.view.length) return;
1664 + if (info.focus || takeFocus) this.showPrimarySelection();
1665 + this.showMultipleSelections(info);
1666 + },
1667 +
1668 + showPrimarySelection: function() {
1669 + var sel = window.getSelection(), prim = this.cm.doc.sel.primary();
1670 + var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset);
1671 + var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset);
1672 + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&
1673 + cmp(minPos(curAnchor, curFocus), prim.from()) == 0 &&
1674 + cmp(maxPos(curAnchor, curFocus), prim.to()) == 0)
1675 + return;
1676 +
1677 + var start = posToDOM(this.cm, prim.from());
1678 + var end = posToDOM(this.cm, prim.to());
1679 + if (!start && !end) return;
1680 +
1681 + var view = this.cm.display.view;
1682 + var old = sel.rangeCount && sel.getRangeAt(0);
1683 + if (!start) {
1684 + start = {node: view[0].measure.map[2], offset: 0};
1685 + } else if (!end) { // FIXME dangerously hacky
1686 + var measure = view[view.length - 1].measure;
1687 + var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map;
1688 + end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]};
1689 + }
1690 +
1691 + try { var rng = range(start.node, start.offset, end.offset, end.node); }
1692 + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
1693 + if (rng) {
1694 + if (!gecko && this.cm.state.focused) {
1695 + sel.collapse(start.node, start.offset);
1696 + if (!rng.collapsed) sel.addRange(rng);
1697 + } else {
1698 + sel.removeAllRanges();
1699 + sel.addRange(rng);
1700 + }
1701 + if (old && sel.anchorNode == null) sel.addRange(old);
1702 + else if (gecko) this.startGracePeriod();
1703 + }
1704 + this.rememberSelection();
1705 + },
1706 +
1707 + startGracePeriod: function() {
1708 + var input = this;
1709 + clearTimeout(this.gracePeriod);
1710 + this.gracePeriod = setTimeout(function() {
1711 + input.gracePeriod = false;
1712 + if (input.selectionChanged())
1713 + input.cm.operation(function() { input.cm.curOp.selectionChanged = true; });
1714 + }, 20);
1715 + },
1716 +
1717 + showMultipleSelections: function(info) {
1718 + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors);
1719 + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection);
1720 + },
1721 +
1722 + rememberSelection: function() {
1723 + var sel = window.getSelection();
1724 + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset;
1725 + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset;
1726 + },
1727 +
1728 + selectionInEditor: function() {
1729 + var sel = window.getSelection();
1730 + if (!sel.rangeCount) return false;
1731 + var node = sel.getRangeAt(0).commonAncestorContainer;
1732 + return contains(this.div, node);
1733 + },
1734 +
1735 + focus: function() {
1736 + if (this.cm.options.readOnly != "nocursor") this.div.focus();
1737 + },
1738 + blur: function() { this.div.blur(); },
1739 + getField: function() { return this.div; },
1740 +
1741 + supportsTouch: function() { return true; },
1742 +
1743 + receivedFocus: function() {
1744 + var input = this;
1745 + if (this.selectionInEditor())
1746 + this.pollSelection();
1747 + else
1748 + runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; });
1749 +
1750 + function poll() {
1751 + if (input.cm.state.focused) {
1752 + input.pollSelection();
1753 + input.polling.set(input.cm.options.pollInterval, poll);
1754 + }
1755 + }
1756 + this.polling.set(this.cm.options.pollInterval, poll);
1757 + },
1758 +
1759 + selectionChanged: function() {
1760 + var sel = window.getSelection();
1761 + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
1762 + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset;
1763 + },
1764 +
1765 + pollSelection: function() {
1766 + if (!this.composing && !this.gracePeriod && this.selectionChanged()) {
1767 + var sel = window.getSelection(), cm = this.cm;
1768 + this.rememberSelection();
1769 + var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);
1770 + var head = domToPos(cm, sel.focusNode, sel.focusOffset);
1771 + if (anchor && head) runInOp(cm, function() {
1772 + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll);
1773 + if (anchor.bad || head.bad) cm.curOp.selectionChanged = true;
1774 + });
1775 + }
1776 + },
1777 +
1778 + pollContent: function() {
1779 + var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary();
1780 + var from = sel.from(), to = sel.to();
1781 + if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false;
1782 +
1783 + var fromIndex;
1784 + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {
1785 + var fromLine = lineNo(display.view[0].line);
1786 + var fromNode = display.view[0].node;
1787 + } else {
1788 + var fromLine = lineNo(display.view[fromIndex].line);
1789 + var fromNode = display.view[fromIndex - 1].node.nextSibling;
1790 + }
1791 + var toIndex = findViewIndex(cm, to.line);
1792 + if (toIndex == display.view.length - 1) {
1793 + var toLine = display.viewTo - 1;
1794 + var toNode = display.lineDiv.lastChild;
1795 + } else {
1796 + var toLine = lineNo(display.view[toIndex + 1].line) - 1;
1797 + var toNode = display.view[toIndex + 1].node.previousSibling;
1798 + }
1799 +
1800 + var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine));
1801 + var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length));
1802 + while (newText.length > 1 && oldText.length > 1) {
1803 + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; }
1804 + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; }
1805 + else break;
1806 + }
1807 +
1808 + var cutFront = 0, cutEnd = 0;
1809 + var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length);
1810 + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))
1811 + ++cutFront;
1812 + var newBot = lst(newText), oldBot = lst(oldText);
1813 + var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),
1814 + oldBot.length - (oldText.length == 1 ? cutFront : 0));
1815 + while (cutEnd < maxCutEnd &&
1816 + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))
1817 + ++cutEnd;
1818 +
1819 + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd);
1820 + newText[0] = newText[0].slice(cutFront);
1821 +
1822 + var chFrom = Pos(fromLine, cutFront);
1823 + var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0);
1824 + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {
1825 + replaceRange(cm.doc, newText, chFrom, chTo, "+input");
1826 + return true;
1827 + }
1828 + },
1829 +
1830 + ensurePolled: function() {
1831 + this.forceCompositionEnd();
1832 + },
1833 + reset: function() {
1834 + this.forceCompositionEnd();
1835 + },
1836 + forceCompositionEnd: function() {
1837 + if (!this.composing || this.composing.handled) return;
1838 + this.applyComposition(this.composing);
1839 + this.composing.handled = true;
1840 + this.div.blur();
1841 + this.div.focus();
1842 + },
1843 + applyComposition: function(composing) {
1844 + if (this.cm.isReadOnly())
1845 + operation(this.cm, regChange)(this.cm)
1846 + else if (composing.data && composing.data != composing.startData)
1847 + operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel);
1848 + },
1849 +
1850 + setUneditable: function(node) {
1851 + node.contentEditable = "false"
1852 + },
1853 +
1854 + onKeyPress: function(e) {
1855 + e.preventDefault();
1856 + if (!this.cm.isReadOnly())
1857 + operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0);
1858 + },
1859 +
1860 + readOnlyChanged: function(val) {
1861 + this.div.contentEditable = String(val != "nocursor")
1862 + },
1863 +
1864 + onContextMenu: nothing,
1865 + resetPosition: nothing,
1866 +
1867 + needsContentAttribute: true
1868 + }, ContentEditableInput.prototype);
1869 +
1870 + function posToDOM(cm, pos) {
1871 + var view = findViewForLine(cm, pos.line);
1872 + if (!view || view.hidden) return null;
1873 + var line = getLine(cm.doc, pos.line);
1874 + var info = mapFromLineView(view, line, pos.line);
1875 +
1876 + var order = getOrder(line), side = "left";
1877 + if (order) {
1878 + var partPos = getBidiPartAt(order, pos.ch);
1879 + side = partPos % 2 ? "right" : "left";
1880 + }
1881 + var result = nodeAndOffsetInLineMap(info.map, pos.ch, side);
1882 + result.offset = result.collapse == "right" ? result.end : result.start;
1883 + return result;
1884 + }
1885 +
1886 + function badPos(pos, bad) { if (bad) pos.bad = true; return pos; }
1887 +
1888 + function domToPos(cm, node, offset) {
1889 + var lineNode;
1890 + if (node == cm.display.lineDiv) {
1891 + lineNode = cm.display.lineDiv.childNodes[offset];
1892 + if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true);
1893 + node = null; offset = 0;
1894 + } else {
1895 + for (lineNode = node;; lineNode = lineNode.parentNode) {
1896 + if (!lineNode || lineNode == cm.display.lineDiv) return null;
1897 + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break;
1898 + }
1899 + }
1900 + for (var i = 0; i < cm.display.view.length; i++) {
1901 + var lineView = cm.display.view[i];
1902 + if (lineView.node == lineNode)
1903 + return locateNodeInLineView(lineView, node, offset);
1904 + }
1905 + }
1906 +
1907 + function locateNodeInLineView(lineView, node, offset) {
1908 + var wrapper = lineView.text.firstChild, bad = false;
1909 + if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true);
1910 + if (node == wrapper) {
1911 + bad = true;
1912 + node = wrapper.childNodes[offset];
1913 + offset = 0;
1914 + if (!node) {
1915 + var line = lineView.rest ? lst(lineView.rest) : lineView.line;
1916 + return badPos(Pos(lineNo(line), line.text.length), bad);
1917 + }
1918 + }
1919 +
1920 + var textNode = node.nodeType == 3 ? node : null, topNode = node;
1921 + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
1922 + textNode = node.firstChild;
1923 + if (offset) offset = textNode.nodeValue.length;
1924 + }
1925 + while (topNode.parentNode != wrapper) topNode = topNode.parentNode;
1926 + var measure = lineView.measure, maps = measure.maps;
1927 +
1928 + function find(textNode, topNode, offset) {
1929 + for (var i = -1; i < (maps ? maps.length : 0); i++) {
1930 + var map = i < 0 ? measure.map : maps[i];
1931 + for (var j = 0; j < map.length; j += 3) {
1932 + var curNode = map[j + 2];
1933 + if (curNode == textNode || curNode == topNode) {
1934 + var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]);
1935 + var ch = map[j] + offset;
1936 + if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)];
1937 + return Pos(line, ch);
1938 + }
1939 + }
1940 + }
1941 + }
1942 + var found = find(textNode, topNode, offset);
1943 + if (found) return badPos(found, bad);
1944 +
1945 + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems
1946 + for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) {
1947 + found = find(after, after.firstChild, 0);
1948 + if (found)
1949 + return badPos(Pos(found.line, found.ch - dist), bad);
1950 + else
1951 + dist += after.textContent.length;
1952 + }
1953 + for (var before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) {
1954 + found = find(before, before.firstChild, -1);
1955 + if (found)
1956 + return badPos(Pos(found.line, found.ch + dist), bad);
1957 + else
1958 + dist += after.textContent.length;
1959 + }
1960 + }
1961 +
1962 + function domTextBetween(cm, from, to, fromLine, toLine) {
1963 + var text = "", closing = false, lineSep = cm.doc.lineSeparator();
1964 + function recognizeMarker(id) { return function(marker) { return marker.id == id; }; }
1965 + function walk(node) {
1966 + if (node.nodeType == 1) {
1967 + var cmText = node.getAttribute("cm-text");
1968 + if (cmText != null) {
1969 + if (cmText == "") cmText = node.textContent.replace(/\u200b/g, "");
1970 + text += cmText;
1971 + return;
1972 + }
1973 + var markerID = node.getAttribute("cm-marker"), range;
1974 + if (markerID) {
1975 + var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID));
1976 + if (found.length && (range = found[0].find()))
1977 + text += getBetween(cm.doc, range.from, range.to).join(lineSep);
1978 + return;
1979 + }
1980 + if (node.getAttribute("contenteditable") == "false") return;
1981 + for (var i = 0; i < node.childNodes.length; i++)
1982 + walk(node.childNodes[i]);
1983 + if (/^(pre|div|p)$/i.test(node.nodeName))
1984 + closing = true;
1985 + } else if (node.nodeType == 3) {
1986 + var val = node.nodeValue;
1987 + if (!val) return;
1988 + if (closing) {
1989 + text += lineSep;
1990 + closing = false;
1991 + }
1992 + text += val;
1993 + }
1994 + }
1995 + for (;;) {
1996 + walk(from);
1997 + if (from == to) break;
1998 + from = from.nextSibling;
1999 + }
2000 + return text;
2001 }
2002
407 - function lineNumberFor(options, i) {
408 - return String(options.lineNumberFormatter(i + options.firstLineNumber));
409 - }
410 - function compensateForHScroll(display) {
411 - return getRect(display.scroller).left - getRect(display.sizer).left;
412 - }
2003 + CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput};
2004
414 - // DISPLAY DRAWING
2005 + // SELECTION / CURSOR
2006
416 - function updateDisplay(cm, changes, viewPort, forced) {
417 - var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated;
418 - var visible = visibleLines(cm.display, cm.doc, viewPort);
419 - for (var first = true;; first = false) {
420 - var oldWidth = cm.display.scroller.clientWidth;
421 - if (!updateDisplayInner(cm, changes, visible, forced)) break;
422 - updated = true;
423 - changes = [];
424 - updateSelection(cm);
425 - updateScrollbars(cm);
426 - if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) {
427 - forced = true;
428 - continue;
2007 + // Selection objects are immutable. A new one is created every time
2008 + // the selection changes. A selection is one or more non-overlapping
2009 + // (and non-touching) ranges, sorted, and an integer that indicates
2010 + // which one is the primary selection (the one that's scrolled into
2011 + // view, that getCursor returns, etc).
2012 + function Selection(ranges, primIndex) {
2013 + this.ranges = ranges;
2014 + this.primIndex = primIndex;
2015 + }
2016 +
2017 + Selection.prototype = {
2018 + primary: function() { return this.ranges[this.primIndex]; },
2019 + equals: function(other) {
2020 + if (other == this) return true;
2021 + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false;
2022 + for (var i = 0; i < this.ranges.length; i++) {
2023 + var here = this.ranges[i], there = other.ranges[i];
2024 + if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false;
2025 + }
2026 + return true;
2027 + },
2028 + deepCopy: function() {
2029 + for (var out = [], i = 0; i < this.ranges.length; i++)
2030 + out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head));
2031 + return new Selection(out, this.primIndex);
2032 + },
2033 + somethingSelected: function() {
2034 + for (var i = 0; i < this.ranges.length; i++)
2035 + if (!this.ranges[i].empty()) return true;
2036 + return false;
2037 + },
2038 + contains: function(pos, end) {
2039 + if (!end) end = pos;
2040 + for (var i = 0; i < this.ranges.length; i++) {
2041 + var range = this.ranges[i];
2042 + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
2043 + return i;
2044 }
430 - forced = false;
2045 + return -1;
2046 + }
2047 + };
2048
432 - // Clip forced viewport to actual scrollable area
433 - if (viewPort)
434 - viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight,
435 - typeof viewPort == "number" ? viewPort : viewPort.top);
436 - visible = visibleLines(cm.display, cm.doc, viewPort);
437 - if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo)
438 - break;
2049 + function Range(anchor, head) {
2050 + this.anchor = anchor; this.head = head;
2051 + }
2052 +
2053 + Range.prototype = {
2054 + from: function() { return minPos(this.anchor, this.head); },
2055 + to: function() { return maxPos(this.anchor, this.head); },
2056 + empty: function() {
2057 + return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch;
2058 }
2059 + };
2060
441 - if (updated) {
442 - signalLater(cm, "update", cm);
443 - if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
444 - signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
2061 + // Take an unsorted, potentially overlapping set of ranges, and
2062 + // build a selection out of it. 'Consumes' ranges array (modifying
2063 + // it).
2064 + function normalizeSelection(ranges, primIndex) {
2065 + var prim = ranges[primIndex];
2066 + ranges.sort(function(a, b) { return cmp(a.from(), b.from()); });
2067 + primIndex = indexOf(ranges, prim);
2068 + for (var i = 1; i < ranges.length; i++) {
2069 + var cur = ranges[i], prev = ranges[i - 1];
2070 + if (cmp(prev.to(), cur.from()) >= 0) {
2071 + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to());
2072 + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head;
2073 + if (i <= primIndex) --primIndex;
2074 + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));
2075 + }
2076 }
446 - return updated;
2077 + return new Selection(ranges, primIndex);
2078 }
2079
449 - // Uses a set of changes plus the current scroll position to
450 - // determine which DOM updates have to be made, and makes the
451 - // updates.
452 - function updateDisplayInner(cm, changes, visible, forced) {
453 - var display = cm.display, doc = cm.doc;
454 - if (!display.wrapper.offsetWidth) {
455 - display.showingFrom = display.showingTo = doc.first;
456 - display.viewOffset = 0;
457 - return;
458 - }
2080 + function simpleSelection(anchor, head) {
2081 + return new Selection([new Range(anchor, head || anchor)], 0);
2082 + }
2083
460 - // Bail out if the visible area is already rendered and nothing changed.
461 - if (!forced && changes.length == 0 &&
462 - visible.from > display.showingFrom && visible.to < display.showingTo)
463 - return;
2084 + // Most of the external API clips given positions to make sure they
2085 + // actually exist within the document.
2086 + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
2087 + function clipPos(doc, pos) {
2088 + if (pos.line < doc.first) return Pos(doc.first, 0);
2089 + var last = doc.first + doc.size - 1;
2090 + if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
2091 + return clipToLen(pos, getLine(doc, pos.line).text.length);
2092 + }
2093 + function clipToLen(pos, linelen) {
2094 + var ch = pos.ch;
2095 + if (ch == null || ch > linelen) return Pos(pos.line, linelen);
2096 + else if (ch < 0) return Pos(pos.line, 0);
2097 + else return pos;
2098 + }
2099 + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
2100 + function clipPosArray(doc, array) {
2101 + for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]);
2102 + return out;
2103 + }
2104
465 - if (maybeUpdateLineNumberWidth(cm))
466 - changes = [{from: doc.first, to: doc.first + doc.size}];
467 - var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
468 - display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
2105 + // SELECTION UPDATES
2106
470 - // Used to determine which lines need their line numbers updated
471 - var positionsChangedFrom = Infinity;
472 - if (cm.options.lineNumbers)
473 - for (var i = 0; i < changes.length; ++i)
474 - if (changes[i].diff && changes[i].from < positionsChangedFrom) { positionsChangedFrom = changes[i].from; }
2107 + // The 'scroll' parameter given to many of these indicated whether
2108 + // the new cursor position should be scrolled into view after
2109 + // modifying the selection.
2110
476 - var end = doc.first + doc.size;
477 - var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
478 - var to = Math.min(end, visible.to + cm.options.viewportMargin);
479 - if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom);
480 - if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo);
481 - if (sawCollapsedSpans) {
482 - from = lineNo(visualLine(doc, getLine(doc, from)));
483 - while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to;
484 - }
485 -
486 - // Create a range of theoretically intact lines, and punch holes
487 - // in that using the change info.
488 - var intact = [{from: Math.max(display.showingFrom, doc.first),
489 - to: Math.min(display.showingTo, end)}];
490 - if (intact[0].from >= intact[0].to) intact = [];
491 - else intact = computeIntact(intact, changes);
492 - // When merged lines are present, we might have to reduce the
493 - // intact ranges because changes in continued fragments of the
494 - // intact lines do require the lines to be redrawn.
495 - if (sawCollapsedSpans)
496 - for (var i = 0; i < intact.length; ++i) {
497 - var range = intact[i], merged;
498 - while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) {
499 - var newTo = merged.find().from.line;
500 - if (newTo > range.from) range.to = newTo;
501 - else { intact.splice(i--, 1); break; }
2111 + // If shift is held or the extend flag is set, extends a range to
2112 + // include a given position (and optionally a second position).
2113 + // Otherwise, simply returns the range between the given positions.
2114 + // Used for cursor motion and such.
2115 + function extendRange(doc, range, head, other) {
2116 + if (doc.cm && doc.cm.display.shift || doc.extend) {
2117 + var anchor = range.anchor;
2118 + if (other) {
2119 + var posBefore = cmp(head, anchor) < 0;
2120 + if (posBefore != (cmp(other, anchor) < 0)) {
2121 + anchor = head;
2122 + head = other;
2123 + } else if (posBefore != (cmp(head, other) < 0)) {
2124 + head = other;
2125 }
2126 }
504 -
505 - // Clip off the parts that won't be visible
506 - var intactLines = 0;
507 - for (var i = 0; i < intact.length; ++i) {
508 - var range = intact[i];
509 - if (range.from < from) range.from = from;
510 - if (range.to > to) range.to = to;
511 - if (range.from >= range.to) intact.splice(i--, 1);
512 - else intactLines += range.to - range.from;
513 - }
514 - if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
515 - updateViewOffset(cm);
516 - return;
517 - }
518 - intact.sort(function(a, b) {return a.from - b.from;});
519 -
520 - // Avoid crashing on IE's "unspecified error" when in iframes
521 - try {
522 - var focused = document.activeElement;
523 - } catch(e) {}
524 - if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
525 - patchDisplay(cm, from, to, intact, positionsChangedFrom);
526 - display.lineDiv.style.display = "";
527 - if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus();
528 -
529 - var different = from != display.showingFrom || to != display.showingTo ||
530 - display.lastSizeC != display.wrapper.clientHeight;
531 - // This is just a bogus formula that detects when the editor is
532 - // resized or the font size changes.
533 - if (different) {
534 - display.lastSizeC = display.wrapper.clientHeight;
535 - startWorker(cm, 400);
2127 + return new Range(anchor, head);
2128 + } else {
2129 + return new Range(other || head, head);
2130 }
537 - display.showingFrom = from; display.showingTo = to;
2131 + }
2132
539 - display.gutters.style.height = "";
540 - updateHeightsInViewport(cm);
541 - updateViewOffset(cm);
2133 + // Extend the primary selection range, discard the rest.
2134 + function extendSelection(doc, head, other, options) {
2135 + setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options);
2136 + }
2137
543 - return true;
2138 + // Extend all selections (pos is an array of selections with length
2139 + // equal the number of selections)
2140 + function extendSelections(doc, heads, options) {
2141 + for (var out = [], i = 0; i < doc.sel.ranges.length; i++)
2142 + out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null);
2143 + var newSel = normalizeSelection(out, doc.sel.primIndex);
2144 + setSelection(doc, newSel, options);
2145 }
2146
546 - function updateHeightsInViewport(cm) {
547 - var display = cm.display;
548 - var prevBottom = display.lineDiv.offsetTop;
549 - for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
550 - if (ie_lt8) {
551 - var bot = node.offsetTop + node.offsetHeight;
552 - height = bot - prevBottom;
553 - prevBottom = bot;
554 - } else {
555 - var box = getRect(node);
556 - height = box.bottom - box.top;
557 - }
558 - var diff = node.lineObj.height - height;
559 - if (height < 2) height = textHeight(display);
560 - if (diff > .001 || diff < -.001) {
561 - updateLineHeight(node.lineObj, height);
562 - var widgets = node.lineObj.widgets;
563 - if (widgets) for (var i = 0; i < widgets.length; ++i)
564 - widgets[i].height = widgets[i].node.offsetHeight;
565 - }
566 - }
2147 + // Updates a single range in the selection.
2148 + function replaceOneSelection(doc, i, range, options) {
2149 + var ranges = doc.sel.ranges.slice(0);
2150 + ranges[i] = range;
2151 + setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options);
2152 }
2153
569 - function updateViewOffset(cm) {
570 - var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom));
571 - // Position the mover div to align with the current virtual scroll position
572 - cm.display.mover.style.top = off + "px";
2154 + // Reset the selection to a single range.
2155 + function setSimpleSelection(doc, anchor, head, options) {
2156 + setSelection(doc, simpleSelection(anchor, head), options);
2157 }
2158
575 - function computeIntact(intact, changes) {
576 - for (var i = 0, l = changes.length || 0; i < l; ++i) {
577 - var change = changes[i], intact2 = [], diff = change.diff || 0;
578 - for (var j = 0, l2 = intact.length; j < l2; ++j) {
579 - var range = intact[j];
580 - if (change.to <= range.from && change.diff) {
581 - intact2.push({from: range.from + diff, to: range.to + diff});
582 - } else if (change.to <= range.from || change.from >= range.to) {
583 - intact2.push(range);
584 - } else {
585 - if (change.from > range.from)
586 - intact2.push({from: range.from, to: change.from});
587 - if (change.to < range.to)
588 - intact2.push({from: change.to + diff, to: range.to + diff});
589 - }
590 - }
591 - intact = intact2;
592 - }
593 - return intact;
2159 + // Give beforeSelectionChange handlers a change to influence a
2160 + // selection update.
2161 + function filterSelectionChange(doc, sel, options) {
2162 + var obj = {
2163 + ranges: sel.ranges,
2164 + update: function(ranges) {
2165 + this.ranges = [];
2166 + for (var i = 0; i < ranges.length; i++)
2167 + this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
2168 + clipPos(doc, ranges[i].head));
2169 + },
2170 + origin: options && options.origin
2171 + };
2172 + signal(doc, "beforeSelectionChange", doc, obj);
2173 + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
2174 + if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1);
2175 + else return sel;
2176 }
2177
596 - function getDimensions(cm) {
597 - var d = cm.display, left = {}, width = {};
598 - for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
599 - left[cm.options.gutters[i]] = n.offsetLeft;
600 - width[cm.options.gutters[i]] = n.offsetWidth;
2178 + function setSelectionReplaceHistory(doc, sel, options) {
2179 + var done = doc.history.done, last = lst(done);
2180 + if (last && last.ranges) {
2181 + done[done.length - 1] = sel;
2182 + setSelectionNoUndo(doc, sel, options);
2183 + } else {
2184 + setSelection(doc, sel, options);
2185 }
602 - return {fixedPos: compensateForHScroll(d),
603 - gutterTotalWidth: d.gutters.offsetWidth,
604 - gutterLeft: left,
605 - gutterWidth: width,
606 - wrapperWidth: d.wrapper.clientWidth};
2186 }
2187
609 - function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
610 - var dims = getDimensions(cm);
611 - var display = cm.display, lineNumbers = cm.options.lineNumbers;
612 - if (!intact.length && (!webkit || !cm.display.currentWheelTarget))
613 - removeChildren(display.lineDiv);
614 - var container = display.lineDiv, cur = container.firstChild;
2188 + // Set a new selection.
2189 + function setSelection(doc, sel, options) {
2190 + setSelectionNoUndo(doc, sel, options);
2191 + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);
2192 + }
2193
616 - function rm(node) {
617 - var next = node.nextSibling;
618 - if (webkit && mac && cm.display.currentWheelTarget == node) {
619 - node.style.display = "none";
620 - node.lineObj = null;
621 - } else {
622 - node.parentNode.removeChild(node);
623 - }
624 - return next;
625 - }
2194 + function setSelectionNoUndo(doc, sel, options) {
2195 + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
2196 + sel = filterSelectionChange(doc, sel, options);
2197
627 - var nextIntact = intact.shift(), lineN = from;
628 - cm.doc.iter(from, to, function(line) {
629 - if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
630 - if (lineIsHidden(cm.doc, line)) {
631 - if (line.height != 0) updateLineHeight(line, 0);
632 - if (line.widgets && cur && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) {
633 - var w = line.widgets[i];
634 - if (w.showIfHidden) {
635 - var prev = cur.previousSibling;
636 - if (/pre/i.test(prev.nodeName)) {
637 - var wrap = elt("div", null, null, "position: relative");
638 - prev.parentNode.replaceChild(wrap, prev);
639 - wrap.appendChild(prev);
640 - prev = wrap;
641 - }
642 - var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget"));
643 - if (!w.handleMouseEvents) wnode.ignoreEvents = true;
644 - positionLineWidget(w, wnode, prev, dims);
645 - }
646 - }
647 - } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
648 - // This line is intact. Skip to the actual node. Update its
649 - // line number if needed.
650 - while (cur.lineObj != line) cur = rm(cur);
651 - if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber)
652 - setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN));
653 - cur = cur.nextSibling;
654 - } else {
655 - // For lines with widgets, make an attempt to find and reuse
656 - // the existing element, so that widgets aren't needlessly
657 - // removed and re-inserted into the dom
658 - if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling)
659 - if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; }
660 - // This line needs to be generated.
661 - var lineNode = buildLineElement(cm, line, lineN, dims, reuse);
662 - if (lineNode != reuse) {
663 - container.insertBefore(lineNode, cur);
664 - } else {
665 - while (cur != reuse) cur = rm(cur);
666 - cur = cur.nextSibling;
667 - }
2198 + var bias = options && options.bias ||
2199 + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
2200 + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
2201
669 - lineNode.lineObj = line;
670 - }
671 - ++lineN;
672 - });
673 - while (cur) cur = rm(cur);
2202 + if (!(options && options.scroll === false) && doc.cm)
2203 + ensureCursorVisible(doc.cm);
2204 }
2205
676 - function buildLineElement(cm, line, lineNo, dims, reuse) {
677 - var built = buildLineContent(cm, line), lineElement = built.pre;
678 - var markers = line.gutterMarkers, display = cm.display, wrap;
2206 + function setSelectionInner(doc, sel) {
2207 + if (sel.equals(doc.sel)) return;
2208
680 - var bgClass = built.bgClass ? built.bgClass + " " + (line.bgClass || "") : line.bgClass;
681 - if (!cm.options.lineNumbers && !markers && !bgClass && !line.wrapClass && !line.widgets)
682 - return lineElement;
2209 + doc.sel = sel;
2210
684 - // Lines with gutter elements, widgets or a background class need
685 - // to be wrapped again, and have the extra elements added to the
686 - // wrapper div
2211 + if (doc.cm) {
2212 + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
2213 + signalCursorActivity(doc.cm);
2214 + }
2215 + signalLater(doc, "cursorActivity", doc);
2216 + }
2217
688 - if (reuse) {
689 - reuse.alignable = null;
690 - var isOk = true, widgetsSeen = 0, insertBefore = null;
691 - for (var n = reuse.firstChild, next; n; n = next) {
692 - next = n.nextSibling;
693 - if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
694 - reuse.removeChild(n);
695 - } else {
696 - for (var i = 0; i < line.widgets.length; ++i) {
697 - var widget = line.widgets[i];
698 - if (widget.node == n.firstChild) {
699 - if (!widget.above && !insertBefore) insertBefore = n;
700 - positionLineWidget(widget, n, reuse, dims);
701 - ++widgetsSeen;
702 - break;
703 - }
704 - }
705 - if (i == line.widgets.length) { isOk = false; break; }
706 - }
2218 + // Verify that the selection does not partially select any atomic
2219 + // marked ranges.
2220 + function reCheckSelection(doc) {
2221 + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll);
2222 + }
2223 +
2224 + // Return a selection that does not partially select any atomic
2225 + // ranges.
2226 + function skipAtomicInSelection(doc, sel, bias, mayClear) {
2227 + var out;
2228 + for (var i = 0; i < sel.ranges.length; i++) {
2229 + var range = sel.ranges[i];
2230 + var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];
2231 + var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear);
2232 + var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear);
2233 + if (out || newAnchor != range.anchor || newHead != range.head) {
2234 + if (!out) out = sel.ranges.slice(0, i);
2235 + out[i] = new Range(newAnchor, newHead);
2236 }
708 - reuse.insertBefore(lineElement, insertBefore);
709 - if (isOk && widgetsSeen == line.widgets.length) {
710 - wrap = reuse;
711 - reuse.className = line.wrapClass || "";
712 - }
713 - }
714 - if (!wrap) {
715 - wrap = elt("div", null, line.wrapClass, "position: relative");
716 - wrap.appendChild(lineElement);
2237 }
718 - // Kludge to make sure the styled element lies behind the selection (by z-index)
719 - if (bgClass)
720 - wrap.insertBefore(elt("div", null, bgClass + " CodeMirror-linebackground"), wrap.firstChild);
721 - if (cm.options.lineNumbers || markers) {
722 - var gutterWrap = wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " +
723 - (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
724 - lineElement);
725 - if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);
726 - if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
727 - wrap.lineNumber = gutterWrap.appendChild(
728 - elt("div", lineNumberFor(cm.options, lineNo),
729 - "CodeMirror-linenumber CodeMirror-gutter-elt",
730 - "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
731 - + display.lineNumInnerWidth + "px"));
732 - if (markers)
733 - for (var k = 0; k < cm.options.gutters.length; ++k) {
734 - var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
735 - if (found)
736 - gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
737 - dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
2238 + return out ? normalizeSelection(out, sel.primIndex) : sel;
2239 + }
2240 +
2241 + function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
2242 + var line = getLine(doc, pos.line);
2243 + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
2244 + var sp = line.markedSpans[i], m = sp.marker;
2245 + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&
2246 + (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) {
2247 + if (mayClear) {
2248 + signal(m, "beforeCursorEnter");
2249 + if (m.explicitlyCleared) {
2250 + if (!line.markedSpans) break;
2251 + else {--i; continue;}
2252 + }
2253 }
2254 + if (!m.atomic) continue;
2255 +
2256 + if (oldPos) {
2257 + var near = m.find(dir < 0 ? 1 : -1), diff;
2258 + if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft)
2259 + near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null);
2260 + if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))
2261 + return skipAtomicInner(doc, near, pos, dir, mayClear);
2262 + }
2263 +
2264 + var far = m.find(dir < 0 ? -1 : 1);
2265 + if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight)
2266 + far = movePos(doc, far, dir, far.line == pos.line ? line : null);
2267 + return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null;
2268 + }
2269 }
740 - if (ie_lt8) wrap.style.zIndex = 2;
741 - if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
742 - var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
743 - if (!widget.handleMouseEvents) node.ignoreEvents = true;
744 - positionLineWidget(widget, node, wrap, dims);
745 - if (widget.above)
746 - wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
747 - else
748 - wrap.appendChild(node);
749 - signalLater(widget, "redraw");
750 - }
751 - return wrap;
2270 + return pos;
2271 }
2272
754 - function positionLineWidget(widget, node, wrap, dims) {
755 - if (widget.noHScroll) {
756 - (wrap.alignable || (wrap.alignable = [])).push(node);
757 - var width = dims.wrapperWidth;
758 - node.style.left = dims.fixedPos + "px";
759 - if (!widget.coverGutter) {
760 - width -= dims.gutterTotalWidth;
761 - node.style.paddingLeft = dims.gutterTotalWidth + "px";
762 - }
763 - node.style.width = width + "px";
2273 + // Ensure a given position is not inside an atomic range.
2274 + function skipAtomic(doc, pos, oldPos, bias, mayClear) {
2275 + var dir = bias || 1;
2276 + var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
2277 + (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
2278 + skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
2279 + (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true));
2280 + if (!found) {
2281 + doc.cantEdit = true;
2282 + return Pos(doc.first, 0);
2283 }
765 - if (widget.coverGutter) {
766 - node.style.zIndex = 5;
767 - node.style.position = "relative";
768 - if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
2284 + return found;
2285 + }
2286 +
2287 + function movePos(doc, pos, dir, line) {
2288 + if (dir < 0 && pos.ch == 0) {
2289 + if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1));
2290 + else return null;
2291 + } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {
2292 + if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0);
2293 + else return null;
2294 + } else {
2295 + return new Pos(pos.line, pos.ch + dir);
2296 }
2297 }
2298
772 - // SELECTION / CURSOR
2299 + // SELECTION DRAWING
2300
2301 function updateSelection(cm) {
775 - var display = cm.display;
776 - var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to);
777 - if (collapsed || cm.options.showCursorWhenSelecting)
778 - updateSelectionCursor(cm);
779 - else
780 - display.cursor.style.display = display.otherCursor.style.display = "none";
781 - if (!collapsed)
782 - updateSelectionRange(cm);
783 - else
784 - display.selectionDiv.style.display = "none";
2302 + cm.display.input.showSelection(cm.display.input.prepareSelection());
2303 + }
2304
786 - // Move the hidden textarea near the cursor to prevent scrolling artifacts
787 - if (cm.options.moveInputWithCursor) {
788 - var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
789 - var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
790 - display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
791 - headPos.top + lineOff.top - wrapOff.top)) + "px";
792 - display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
793 - headPos.left + lineOff.left - wrapOff.left)) + "px";
2305 + function prepareSelection(cm, primary) {
2306 + var doc = cm.doc, result = {};
2307 + var curFragment = result.cursors = document.createDocumentFragment();
2308 + var selFragment = result.selection = document.createDocumentFragment();
2309 +
2310 + for (var i = 0; i < doc.sel.ranges.length; i++) {
2311 + if (primary === false && i == doc.sel.primIndex) continue;
2312 + var range = doc.sel.ranges[i];
2313 + if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue;
2314 + var collapsed = range.empty();
2315 + if (collapsed || cm.options.showCursorWhenSelecting)
2316 + drawSelectionCursor(cm, range.head, curFragment);
2317 + if (!collapsed)
2318 + drawSelectionRange(cm, range, selFragment);
2319 }
2320 + return result;
2321 }
2322
797 - // No selection, plain cursor
798 - function updateSelectionCursor(cm) {
799 - var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div");
800 - display.cursor.style.left = pos.left + "px";
801 - display.cursor.style.top = pos.top + "px";
802 - display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
803 - display.cursor.style.display = "";
2323 + // Draws a cursor for the given range
2324 + function drawSelectionCursor(cm, head, output) {
2325 + var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine);
2326 +
2327 + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
2328 + cursor.style.left = pos.left + "px";
2329 + cursor.style.top = pos.top + "px";
2330 + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
2331
2332 if (pos.other) {
806 - display.otherCursor.style.display = "";
807 - display.otherCursor.style.left = pos.other.left + "px";
808 - display.otherCursor.style.top = pos.other.top + "px";
809 - display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
810 - } else { display.otherCursor.style.display = "none"; }
2333 + // Secondary cursor, shown when on a 'jump' in bi-directional text
2334 + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"));
2335 + otherCursor.style.display = "";
2336 + otherCursor.style.left = pos.other.left + "px";
2337 + otherCursor.style.top = pos.other.top + "px";
2338 + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
2339 + }
2340 }
2341
813 - // Highlight selection
814 - function updateSelectionRange(cm) {
815 - var display = cm.display, doc = cm.doc, sel = cm.doc.sel;
2342 + // Draws the given range as a highlighted selection
2343 + function drawSelectionRange(cm, range, output) {
2344 + var display = cm.display, doc = cm.doc;
2345 var fragment = document.createDocumentFragment();
817 - var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
2346 + var padding = paddingH(cm.display), leftSide = padding.left;
2347 + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right;
2348
2349 function add(left, top, width, bottom) {
2350 if (top < 0) top = 0;
2351 + top = Math.round(top);
2352 + bottom = Math.round(bottom);
2353 fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
822 - "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) +
2354 + "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) +
2355 "px; height: " + (bottom - top) + "px"));
2356 }
2357
@@ -842,44 +2374,44 @@ window.CodeMirror = (function() {
2374 left = leftPos.left;
2375 right = rightPos.right;
2376 }
845 - if (fromArg == null && from == 0) left = pl;
2377 + if (fromArg == null && from == 0) left = leftSide;
2378 if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
2379 add(left, leftPos.top, null, leftPos.bottom);
848 - left = pl;
2380 + left = leftSide;
2381 if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
2382 }
851 - if (toArg == null && to == lineLen) right = clientWidth;
2383 + if (toArg == null && to == lineLen) right = rightSide;
2384 if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
2385 start = leftPos;
2386 if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
2387 end = rightPos;
856 - if (left < pl + 1) left = pl;
2388 + if (left < leftSide + 1) left = leftSide;
2389 add(left, rightPos.top, right - left, rightPos.bottom);
2390 });
2391 return {start: start, end: end};
2392 }
2393
862 - if (sel.from.line == sel.to.line) {
863 - drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
2394 + var sFrom = range.from(), sTo = range.to();
2395 + if (sFrom.line == sTo.line) {
2396 + drawForLine(sFrom.line, sFrom.ch, sTo.ch);
2397 } else {
865 - var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line);
866 - var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine);
867 - var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end;
868 - var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start;
2398 + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);
2399 + var singleVLine = visualLine(fromLine) == visualLine(toLine);
2400 + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end;
2401 + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start;
2402 if (singleVLine) {
2403 if (leftEnd.top < rightStart.top - 2) {
2404 add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
872 - add(pl, rightStart.top, rightStart.left, rightStart.bottom);
2405 + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);
2406 } else {
2407 add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
2408 }
2409 }
2410 if (leftEnd.bottom < rightStart.top)
878 - add(pl, leftEnd.bottom, null, rightStart.top);
2411 + add(leftSide, leftEnd.bottom, null, rightStart.top);
2412 }
2413
881 - removeChildrenAndAdd(display.selectionDiv, fragment);
882 - display.selectionDiv.style.display = "";
2414 + output.appendChild(fragment);
2415 }
2416
2417 // Cursor-blinking
@@ -888,40 +2420,46 @@ window.CodeMirror = (function() {
2420 var display = cm.display;
2421 clearInterval(display.blinker);
2422 var on = true;
891 - display.cursor.style.visibility = display.otherCursor.style.visibility = "";
2423 + display.cursorDiv.style.visibility = "";
2424 if (cm.options.cursorBlinkRate > 0)
2425 display.blinker = setInterval(function() {
894 - display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
2426 + display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden";
2427 }, cm.options.cursorBlinkRate);
2428 + else if (cm.options.cursorBlinkRate < 0)
2429 + display.cursorDiv.style.visibility = "hidden";
2430 }
2431
2432 // HIGHLIGHT WORKER
2433
2434 function startWorker(cm, time) {
901 - if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo)
2435 + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo)
2436 cm.state.highlight.set(time, bind(highlightWorker, cm));
2437 }
2438
2439 function highlightWorker(cm) {
2440 var doc = cm.doc;
2441 if (doc.frontier < doc.first) doc.frontier = doc.first;
908 - if (doc.frontier >= cm.display.showingTo) return;
2442 + if (doc.frontier >= cm.display.viewTo) return;
2443 var end = +new Date + cm.options.workTime;
2444 var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
911 - var changed = [], prevChange;
912 - doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {
913 - if (doc.frontier >= cm.display.showingFrom) { // Visible
914 - var oldStyles = line.styles;
915 - line.styles = highlightLine(cm, line, state, true);
916 - var ischange = !oldStyles || oldStyles.length != line.styles.length;
2445 + var changedLines = [];
2446 +
2447 + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) {
2448 + if (doc.frontier >= cm.display.viewFrom) { // Visible
2449 + var oldStyles = line.styles, tooLong = line.text.length > cm.options.maxHighlightLength;
2450 + var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, state) : state, true);
2451 + line.styles = highlighted.styles;
2452 + var oldCls = line.styleClasses, newCls = highlighted.classes;
2453 + if (newCls) line.styleClasses = newCls;
2454 + else if (oldCls) line.styleClasses = null;
2455 + var ischange = !oldStyles || oldStyles.length != line.styles.length ||
2456 + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass);
2457 for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
918 - if (ischange) {
919 - if (prevChange && prevChange.end == doc.frontier) prevChange.end++;
920 - else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1});
921 - }
922 - line.stateAfter = copyState(doc.mode, state);
2458 + if (ischange) changedLines.push(doc.frontier);
2459 + line.stateAfter = tooLong ? state : copyState(doc.mode, state);
2460 } else {
924 - processLine(cm, line.text, state);
2461 + if (line.text.length <= cm.options.maxHighlightLength)
2462 + processLine(cm, line.text, state);
2463 line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
2464 }
2465 ++doc.frontier;
@@ -930,11 +2468,10 @@ window.CodeMirror = (function() {
2468 return true;
2469 }
2470 });
933 - if (changed.length)
934 - operation(cm, function() {
935 - for (var i = 0; i < changed.length; ++i)
936 - regChange(this, changed[i].start, changed[i].end);
937 - })();
2471 + if (changedLines.length) runInOp(cm, function() {
2472 + for (var i = 0; i < changedLines.length; i++)
2473 + regLineChange(cm, changedLines[i], "text");
2474 + });
2475 }
2476
2477 // Finds the line to start with when starting a parse. Tries to
@@ -966,7 +2503,7 @@ window.CodeMirror = (function() {
2503 else state = copyState(doc.mode, state);
2504 doc.iter(pos, n, function(line) {
2505 processLine(cm, line.text, state);
969 - var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;
2506 + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo;
2507 line.stateAfter = save ? copyState(doc.mode, state) : null;
2508 ++pos;
2509 });
@@ -978,183 +2515,261 @@ window.CodeMirror = (function() {
2515
2516 function paddingTop(display) {return display.lineSpace.offsetTop;}
2517 function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
981 - function paddingLeft(display) {
982 - var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x"));
983 - return e.offsetLeft;
984 - }
985 -
986 - function measureChar(cm, line, ch, data, bias) {
987 - var dir = -1;
988 - data = data || measureLine(cm, line);
989 - if (data.crude) {
990 - var left = data.left + ch * data.width;
991 - return {left: left, right: left + data.width, top: data.top, bottom: data.bottom};
992 - }
993 -
994 - for (var pos = ch;; pos += dir) {
995 - var r = data[pos];
996 - if (r) break;
997 - if (dir < 0 && pos == 0) dir = 1;
998 - }
999 - bias = pos > ch ? "left" : pos < ch ? "right" : bias;
1000 - if (bias == "left" && r.leftSide) r = r.leftSide;
1001 - else if (bias == "right" && r.rightSide) r = r.rightSide;
1002 - return {left: pos < ch ? r.right : r.left,
1003 - right: pos > ch ? r.left : r.right,
1004 - top: r.top,
1005 - bottom: r.bottom};
1006 - }
1007 -
1008 - function findCachedMeasurement(cm, line) {
1009 - var cache = cm.display.measureLineCache;
1010 - for (var i = 0; i < cache.length; ++i) {
1011 - var memo = cache[i];
1012 - if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
1013 - cm.display.scroller.clientWidth == memo.width &&
1014 - memo.classes == line.textClass + "|" + line.wrapClass)
1015 - return memo;
1016 - }
1017 - }
1018 -
1019 - function clearCachedMeasurement(cm, line) {
1020 - var exists = findCachedMeasurement(cm, line);
1021 - if (exists) exists.text = exists.measure = exists.markedSpans = null;
1022 - }
1023 -
1024 - function measureLine(cm, line) {
1025 - // First look in the cache
1026 - var cached = findCachedMeasurement(cm, line);
1027 - if (cached) return cached.measure;
1028 -
1029 - // Failing that, recompute and store result in cache
1030 - var measure = measureLineInner(cm, line);
1031 - var cache = cm.display.measureLineCache;
1032 - var memo = {text: line.text, width: cm.display.scroller.clientWidth,
1033 - markedSpans: line.markedSpans, measure: measure,
1034 - classes: line.textClass + "|" + line.wrapClass};
1035 - if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;
1036 - else cache.push(memo);
1037 - return measure;
1038 - }
1039 -
1040 - function measureLineInner(cm, line) {
1041 - if (!cm.options.lineWrapping && line.text.length >= cm.options.crudeMeasuringFrom)
1042 - return crudelyMeasureLine(cm, line);
1043 -
1044 - var display = cm.display, measure = emptyArray(line.text.length);
1045 - var pre = buildLineContent(cm, line, measure, true).pre;
1046 -
1047 - // IE does not cache element positions of inline elements between
1048 - // calls to getBoundingClientRect. This makes the loop below,
1049 - // which gathers the positions of all the characters on the line,
1050 - // do an amount of layout work quadratic to the number of
1051 - // characters. When line wrapping is off, we try to improve things
1052 - // by first subdividing the line into a bunch of inline blocks, so
1053 - // that IE can reuse most of the layout information from caches
1054 - // for those blocks. This does interfere with line wrapping, so it
1055 - // doesn't work when wrapping is on, but in that case the
1056 - // situation is slightly better, since IE does cache line-wrapping
1057 - // information and only recomputes per-line.
1058 - if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
1059 - var fragment = document.createDocumentFragment();
1060 - var chunk = 10, n = pre.childNodes.length;
1061 - for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
1062 - var wrap = elt("div", null, null, "display: inline-block");
1063 - for (var j = 0; j < chunk && n; ++j) {
1064 - wrap.appendChild(pre.firstChild);
1065 - --n;
2518 + function paddingH(display) {
2519 + if (display.cachedPaddingH) return display.cachedPaddingH;
2520 + var e = removeChildrenAndAdd(display.measure, elt("pre", "x"));
2521 + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;
2522 + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};
2523 + if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data;
2524 + return data;
2525 + }
2526 +
2527 + function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; }
2528 + function displayWidth(cm) {
2529 + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth;
2530 + }
2531 + function displayHeight(cm) {
2532 + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight;
2533 + }
2534 +
2535 + // Ensure the lineView.wrapping.heights array is populated. This is
2536 + // an array of bottom offsets for the lines that make up a drawn
2537 + // line. When lineWrapping is on, there might be more than one
2538 + // height.
2539 + function ensureLineHeights(cm, lineView, rect) {
2540 + var wrapping = cm.options.lineWrapping;
2541 + var curWidth = wrapping && displayWidth(cm);
2542 + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
2543 + var heights = lineView.measure.heights = [];
2544 + if (wrapping) {
2545 + lineView.measure.width = curWidth;
2546 + var rects = lineView.text.firstChild.getClientRects();
2547 + for (var i = 0; i < rects.length - 1; i++) {
2548 + var cur = rects[i], next = rects[i + 1];
2549 + if (Math.abs(cur.bottom - next.bottom) > 2)
2550 + heights.push((cur.bottom + next.top) / 2 - rect.top);
2551 }
1067 - fragment.appendChild(wrap);
2552 }
1069 - pre.appendChild(fragment);
1070 - }
2553 + heights.push(rect.bottom - rect.top);
2554 + }
2555 + }
2556 +
2557 + // Find a line map (mapping character offsets to text nodes) and a
2558 + // measurement cache for the given line number. (A line view might
2559 + // contain multiple lines when collapsed ranges are present.)
2560 + function mapFromLineView(lineView, line, lineN) {
2561 + if (lineView.line == line)
2562 + return {map: lineView.measure.map, cache: lineView.measure.cache};
2563 + for (var i = 0; i < lineView.rest.length; i++)
2564 + if (lineView.rest[i] == line)
2565 + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]};
2566 + for (var i = 0; i < lineView.rest.length; i++)
2567 + if (lineNo(lineView.rest[i]) > lineN)
2568 + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true};
2569 + }
2570 +
2571 + // Render a line into the hidden node display.externalMeasured. Used
2572 + // when measurement is needed for a line that's not in the viewport.
2573 + function updateExternalMeasurement(cm, line) {
2574 + line = visualLine(line);
2575 + var lineN = lineNo(line);
2576 + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);
2577 + view.lineN = lineN;
2578 + var built = view.built = buildLineContent(cm, view);
2579 + view.text = built.pre;
2580 + removeChildrenAndAdd(cm.display.lineMeasure, built.pre);
2581 + return view;
2582 + }
2583 +
2584 + // Get a {top, bottom, left, right} box (in line-local coordinates)
2585 + // for a given character.
2586 + function measureChar(cm, line, ch, bias) {
2587 + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias);
2588 + }
2589 +
2590 + // Find a line view that corresponds to the given line number.
2591 + function findViewForLine(cm, lineN) {
2592 + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
2593 + return cm.display.view[findViewIndex(cm, lineN)];
2594 + var ext = cm.display.externalMeasured;
2595 + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
2596 + return ext;
2597 + }
2598 +
2599 + // Measurement can be split in two steps, the set-up work that
2600 + // applies to the whole line, and the measurement of the actual
2601 + // character. Functions like coordsChar, that need to do a lot of
2602 + // measurements in a row, can thus ensure that the set-up work is
2603 + // only done once.
2604 + function prepareMeasureForLine(cm, line) {
2605 + var lineN = lineNo(line);
2606 + var view = findViewForLine(cm, lineN);
2607 + if (view && !view.text) {
2608 + view = null;
2609 + } else if (view && view.changes) {
2610 + updateLineForChanges(cm, view, lineN, getDimensions(cm));
2611 + cm.curOp.forceUpdate = true;
2612 + }
2613 + if (!view)
2614 + view = updateExternalMeasurement(cm, line);
2615 +
2616 + var info = mapFromLineView(view, line, lineN);
2617 + return {
2618 + line: line, view: view, rect: null,
2619 + map: info.map, cache: info.cache, before: info.before,
2620 + hasHeights: false
2621 + };
2622 + }
2623
1072 - removeChildrenAndAdd(display.measure, pre);
2624 + // Given a prepared measurement object, measures the position of an
2625 + // actual character (or fetches it from the cache).
2626 + function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
2627 + if (prepared.before) ch = -1;
2628 + var key = ch + (bias || ""), found;
2629 + if (prepared.cache.hasOwnProperty(key)) {
2630 + found = prepared.cache[key];
2631 + } else {
2632 + if (!prepared.rect)
2633 + prepared.rect = prepared.view.text.getBoundingClientRect();
2634 + if (!prepared.hasHeights) {
2635 + ensureLineHeights(cm, prepared.view, prepared.rect);
2636 + prepared.hasHeights = true;
2637 + }
2638 + found = measureCharInner(cm, prepared, ch, bias);
2639 + if (!found.bogus) prepared.cache[key] = found;
2640 + }
2641 + return {left: found.left, right: found.right,
2642 + top: varHeight ? found.rtop : found.top,
2643 + bottom: varHeight ? found.rbottom : found.bottom};
2644 + }
2645 +
2646 + var nullRect = {left: 0, right: 0, top: 0, bottom: 0};
2647 +
2648 + function nodeAndOffsetInLineMap(map, ch, bias) {
2649 + var node, start, end, collapse;
2650 + // First, search the line map for the text node corresponding to,
2651 + // or closest to, the target character.
2652 + for (var i = 0; i < map.length; i += 3) {
2653 + var mStart = map[i], mEnd = map[i + 1];
2654 + if (ch < mStart) {
2655 + start = 0; end = 1;
2656 + collapse = "left";
2657 + } else if (ch < mEnd) {
2658 + start = ch - mStart;
2659 + end = start + 1;
2660 + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {
2661 + end = mEnd - mStart;
2662 + start = end - 1;
2663 + if (ch >= mEnd) collapse = "right";
2664 + }
2665 + if (start != null) {
2666 + node = map[i + 2];
2667 + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
2668 + collapse = bias;
2669 + if (bias == "left" && start == 0)
2670 + while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {
2671 + node = map[(i -= 3) + 2];
2672 + collapse = "left";
2673 + }
2674 + if (bias == "right" && start == mEnd - mStart)
2675 + while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) {
2676 + node = map[(i += 3) + 2];
2677 + collapse = "right";
2678 + }
2679 + break;
2680 + }
2681 + }
2682 + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd};
2683 + }
2684
1074 - var outer = getRect(display.lineDiv);
1075 - var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
1076 - // Work around an IE7/8 bug where it will sometimes have randomly
1077 - // replaced our pre with a clone at this point.
1078 - if (ie_lt9 && display.measure.first != pre)
1079 - removeChildrenAndAdd(display.measure, pre);
2685 + function measureCharInner(cm, prepared, ch, bias) {
2686 + var place = nodeAndOffsetInLineMap(prepared.map, ch, bias);
2687 + var node = place.node, start = place.start, end = place.end, collapse = place.collapse;
2688
1081 - function measureRect(rect) {
1082 - var top = rect.top - outer.top, bot = rect.bottom - outer.top;
1083 - if (bot > maxBot) bot = maxBot;
1084 - if (top < 0) top = 0;
1085 - for (var i = vranges.length - 2; i >= 0; i -= 2) {
1086 - var rtop = vranges[i], rbot = vranges[i+1];
1087 - if (rtop > bot || rbot < top) continue;
1088 - if (rtop <= top && rbot >= bot ||
1089 - top <= rtop && bot >= rbot ||
1090 - Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
1091 - vranges[i] = Math.min(top, rtop);
1092 - vranges[i+1] = Math.max(bot, rbot);
1093 - break;
1094 - }
1095 - }
1096 - if (i < 0) { i = vranges.length; vranges.push(top, bot); }
1097 - return {left: rect.left - outer.left,
1098 - right: rect.right - outer.left,
1099 - top: i, bottom: null};
1100 - }
1101 - function finishRect(rect) {
1102 - rect.bottom = vranges[rect.top+1];
1103 - rect.top = vranges[rect.top];
1104 - }
1105 -
1106 - for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
1107 - var node = cur, rect = null;
1108 - // A widget might wrap, needs special care
1109 - if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) {
1110 - if (cur.firstChild.nodeType == 1) node = cur.firstChild;
1111 - var rects = node.getClientRects();
1112 - if (rects.length > 1) {
1113 - rect = data[i] = measureRect(rects[0]);
1114 - rect.rightSide = measureRect(rects[rects.length - 1]);
2689 + var rect;
2690 + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
2691 + for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned
2692 + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start;
2693 + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end;
2694 + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) {
2695 + rect = node.parentNode.getBoundingClientRect();
2696 + } else if (ie && cm.options.lineWrapping) {
2697 + var rects = range(node, start, end).getClientRects();
2698 + if (rects.length)
2699 + rect = rects[bias == "right" ? rects.length - 1 : 0];
2700 + else
2701 + rect = nullRect;
2702 + } else {
2703 + rect = range(node, start, end).getBoundingClientRect() || nullRect;
2704 }
2705 + if (rect.left || rect.right || start == 0) break;
2706 + end = start;
2707 + start = start - 1;
2708 + collapse = "right";
2709 }
1117 - if (!rect) rect = data[i] = measureRect(getRect(node));
1118 - if (cur.measureRight) rect.right = getRect(cur.measureRight).left - outer.left;
1119 - if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide));
2710 + if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect);
2711 + } else { // If it is a widget, simply get the box for the whole widget.
2712 + if (start > 0) collapse = bias = "right";
2713 + var rects;
2714 + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
2715 + rect = rects[bias == "right" ? rects.length - 1 : 0];
2716 + else
2717 + rect = node.getBoundingClientRect();
2718 }
1121 - removeChildren(cm.display.measure);
1122 - for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
1123 - finishRect(cur);
1124 - if (cur.leftSide) finishRect(cur.leftSide);
1125 - if (cur.rightSide) finishRect(cur.rightSide);
2719 + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {
2720 + var rSpan = node.parentNode.getClientRects()[0];
2721 + if (rSpan)
2722 + rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom};
2723 + else
2724 + rect = nullRect;
2725 }
1127 - return data;
2726 +
2727 + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top;
2728 + var mid = (rtop + rbot) / 2;
2729 + var heights = prepared.view.measure.heights;
2730 + for (var i = 0; i < heights.length - 1; i++)
2731 + if (mid < heights[i]) break;
2732 + var top = i ? heights[i - 1] : 0, bot = heights[i];
2733 + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left,
2734 + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left,
2735 + top: top, bottom: bot};
2736 + if (!rect.left && !rect.right) result.bogus = true;
2737 + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; }
2738 +
2739 + return result;
2740 }
2741
1130 - function crudelyMeasureLine(cm, line) {
1131 - var copy = new Line(line.text.slice(0, 100), null);
1132 - if (line.textClass) copy.textClass = line.textClass;
1133 - var measure = measureLineInner(cm, copy);
1134 - var left = measureChar(cm, copy, 0, measure, "left");
1135 - var right = measureChar(cm, copy, 99, measure, "right");
1136 - return {crude: true, top: left.top, left: left.left, bottom: left.bottom, width: (right.right - left.left) / 100};
2742 + // Work around problem with bounding client rects on ranges being
2743 + // returned incorrectly when zoomed on IE10 and below.
2744 + function maybeUpdateRectForZooming(measure, rect) {
2745 + if (!window.screen || screen.logicalXDPI == null ||
2746 + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
2747 + return rect;
2748 + var scaleX = screen.logicalXDPI / screen.deviceXDPI;
2749 + var scaleY = screen.logicalYDPI / screen.deviceYDPI;
2750 + return {left: rect.left * scaleX, right: rect.right * scaleX,
2751 + top: rect.top * scaleY, bottom: rect.bottom * scaleY};
2752 }
2753
1139 - function measureLineWidth(cm, line) {
1140 - var hasBadSpan = false;
1141 - if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) {
1142 - var sp = line.markedSpans[i];
1143 - if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
2754 + function clearLineMeasurementCacheFor(lineView) {
2755 + if (lineView.measure) {
2756 + lineView.measure.cache = {};
2757 + lineView.measure.heights = null;
2758 + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
2759 + lineView.measure.caches[i] = {};
2760 }
1145 - var cached = !hasBadSpan && findCachedMeasurement(cm, line);
1146 - if (cached || line.text.length >= cm.options.crudeMeasuringFrom)
1147 - return measureChar(cm, line, line.text.length, cached && cached.measure, "right").right;
2761 + }
2762
1149 - var pre = buildLineContent(cm, line, null, true).pre;
1150 - var end = pre.appendChild(zeroWidthElement(cm.display.measure));
1151 - removeChildrenAndAdd(cm.display.measure, pre);
1152 - return getRect(end).right - getRect(cm.display.lineDiv).left;
2763 + function clearLineMeasurementCache(cm) {
2764 + cm.display.externalMeasure = null;
2765 + removeChildren(cm.display.lineMeasure);
2766 + for (var i = 0; i < cm.display.view.length; i++)
2767 + clearLineMeasurementCacheFor(cm.display.view[i]);
2768 }
2769
2770 function clearCaches(cm) {
1156 - cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
1157 - cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
2771 + clearLineMeasurementCache(cm);
2772 + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null;
2773 if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
2774 cm.display.lineNumChars = null;
2775 }
@@ -1162,7 +2777,10 @@ window.CodeMirror = (function() {
2777 function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
2778 function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
2779
1165 - // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
2780 + // Converts a {top, bottom, left, right} box from line-local
2781 + // coordinates into another coordinate system. Context may be one of
2782 + // "line", "div" (display.lineDiv), "local"/null (editor), "window",
2783 + // or "page".
2784 function intoCoordSystem(cm, lineObj, rect, context) {
2785 if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
2786 var size = widgetHeight(lineObj.widgets[i]);
@@ -1170,11 +2788,11 @@ window.CodeMirror = (function() {
2788 }
2789 if (context == "line") return rect;
2790 if (!context) context = "local";
1173 - var yOff = heightAtLine(cm, lineObj);
2791 + var yOff = heightAtLine(lineObj);
2792 if (context == "local") yOff += paddingTop(cm.display);
2793 else yOff -= cm.display.viewOffset;
2794 if (context == "page" || context == "window") {
1177 - var lOff = getRect(cm.display.lineSpace);
2795 + var lOff = cm.display.lineSpace.getBoundingClientRect();
2796 yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
2797 var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
2798 rect.left += xOff; rect.right += xOff;
@@ -1183,8 +2801,8 @@ window.CodeMirror = (function() {
2801 return rect;
2802 }
2803
1186 - // Context may be "window", "page", "div", or "local"/null
1187 - // Result is in "div" coords
2804 + // Coverts a box from "div" coords to another coordinate system.
2805 + // Context may be "window", "page", "div", or "local"/null.
2806 function fromCoordSystem(cm, coords, context) {
2807 if (context == "div") return coords;
2808 var left = coords.left, top = coords.top;
@@ -1193,25 +2811,28 @@ window.CodeMirror = (function() {
2811 left -= pageScrollX();
2812 top -= pageScrollY();
2813 } else if (context == "local" || !context) {
1196 - var localBox = getRect(cm.display.sizer);
2814 + var localBox = cm.display.sizer.getBoundingClientRect();
2815 left += localBox.left;
2816 top += localBox.top;
2817 }
2818
1201 - var lineSpaceBox = getRect(cm.display.lineSpace);
2819 + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();
2820 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
2821 }
2822
2823 function charCoords(cm, pos, context, lineObj, bias) {
2824 if (!lineObj) lineObj = getLine(cm.doc, pos.line);
1207 - return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context);
2825 + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context);
2826 }
2827
1210 - function cursorCoords(cm, pos, context, lineObj, measurement) {
2828 + // Returns a box for a given cursor position, which may have an
2829 + // 'other' property containing the position of the secondary cursor
2830 + // on a bidi boundary.
2831 + function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {
2832 lineObj = lineObj || getLine(cm.doc, pos.line);
1212 - if (!measurement) measurement = measureLine(cm, lineObj);
2833 + if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj);
2834 function get(ch, right) {
1214 - var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left");
2835 + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight);
2836 if (right) m.left = m.right; else m.right = m.left;
2837 return intoCoordSystem(cm, lineObj, m, context);
2838 }
@@ -1237,43 +2858,59 @@ window.CodeMirror = (function() {
2858 return val;
2859 }
2860
2861 + // Used to cheaply estimate the coordinates for a position. Used for
2862 + // intermediate scroll updates.
2863 + function estimateCoords(cm, pos) {
2864 + var left = 0, pos = clipPos(cm.doc, pos);
2865 + if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch;
2866 + var lineObj = getLine(cm.doc, pos.line);
2867 + var top = heightAtLine(lineObj) + paddingTop(cm.display);
2868 + return {left: left, right: left, top: top, bottom: top + lineObj.height};
2869 + }
2870 +
2871 + // Positions returned by coordsChar contain some extra information.
2872 + // xRel is the relative x position of the input coordinates compared
2873 + // to the found position (so xRel > 0 means the coordinates are to
2874 + // the right of the character position, for example). When outside
2875 + // is true, that means the coordinates lie outside the line's
2876 + // vertical range.
2877 function PosWithInfo(line, ch, outside, xRel) {
1241 - var pos = new Pos(line, ch);
2878 + var pos = Pos(line, ch);
2879 pos.xRel = xRel;
2880 if (outside) pos.outside = true;
2881 return pos;
2882 }
2883
1247 - // Coords must be lineSpace-local
2884 + // Compute the character position closest to the given coordinates.
2885 + // Input must be lineSpace-local ("div" coordinate system).
2886 function coordsChar(cm, x, y) {
2887 var doc = cm.doc;
2888 y += cm.display.viewOffset;
2889 if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
1252 - var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
1253 - if (lineNo > last)
2890 + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
2891 + if (lineN > last)
2892 return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
2893 if (x < 0) x = 0;
2894
2895 + var lineObj = getLine(doc, lineN);
2896 for (;;) {
1258 - var lineObj = getLine(doc, lineNo);
1259 - var found = coordsCharInner(cm, lineObj, lineNo, x, y);
2897 + var found = coordsCharInner(cm, lineObj, lineN, x, y);
2898 var merged = collapsedSpanAtEnd(lineObj);
1261 - var mergedPos = merged && merged.find();
2899 + var mergedPos = merged && merged.find(0, true);
2900 if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
1263 - lineNo = mergedPos.to.line;
2901 + lineN = lineNo(lineObj = mergedPos.to.line);
2902 else
2903 return found;
2904 }
2905 }
2906
2907 function coordsCharInner(cm, lineObj, lineNo, x, y) {
1270 - var innerOff = y - heightAtLine(cm, lineObj);
2908 + var innerOff = y - heightAtLine(lineObj);
2909 var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
1272 - var measurement = measureLine(cm, lineObj);
2910 + var preparedMeasure = prepareMeasureForLine(cm, lineObj);
2911
2912 function getX(ch) {
1275 - var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
1276 - lineObj, measurement);
2913 + var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure);
2914 wrongLine = true;
2915 if (innerOff > sp.bottom) return sp.left - adjust;
2916 else if (innerOff < sp.top) return sp.left + adjust;
@@ -1291,9 +2928,9 @@ window.CodeMirror = (function() {
2928 if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
2929 var ch = x < fromX || x - fromX <= toX - x ? from : to;
2930 var xDiff = x - (ch == from ? fromX : toX);
1294 - while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
2931 + while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
2932 var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
1296 - xDiff < 0 ? -1 : xDiff ? 1 : 0);
2933 + xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
2934 return pos;
2935 }
2936 var step = Math.ceil(dist / 2), middle = from + step;
@@ -1308,6 +2945,7 @@ window.CodeMirror = (function() {
2945 }
2946
2947 var measureText;
2948 + // Compute the default text height.
2949 function textHeight(display) {
2950 if (display.cachedTextHeight != null) return display.cachedTextHeight;
2951 if (measureText == null) {
@@ -1327,259 +2965,513 @@ window.CodeMirror = (function() {
2965 return height || 1;
2966 }
2967
2968 + // Compute the default character width.
2969 function charWidth(display) {
2970 if (display.cachedCharWidth != null) return display.cachedCharWidth;
1332 - var anchor = elt("span", "x");
2971 + var anchor = elt("span", "xxxxxxxxxx");
2972 var pre = elt("pre", [anchor]);
2973 removeChildrenAndAdd(display.measure, pre);
1335 - var width = anchor.offsetWidth;
2974 + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;
2975 if (width > 2) display.cachedCharWidth = width;
2976 return width || 10;
2977 }
2978
2979 // OPERATIONS
2980
1342 - // Operations are used to wrap changes in such a way that each
1343 - // change won't have to update the cursor and display (which would
1344 - // be awkward, slow, and error-prone), but instead updates are
1345 - // batched and then all combined and executed at once.
2981 + // Operations are used to wrap a series of changes to the editor
2982 + // state in such a way that each change won't have to update the
2983 + // cursor and display (which would be awkward, slow, and
2984 + // error-prone). Instead, display updates are batched and then all
2985 + // combined and executed at once.
2986 +
2987 + var operationGroup = null;
2988
2989 var nextOpId = 0;
2990 + // Start a new operation.
2991 function startOperation(cm) {
2992 cm.curOp = {
1350 - // An array of ranges of lines that have to be updated. See
1351 - // updateDisplay.
1352 - changes: [],
1353 - forceUpdate: false,
1354 - updateInput: null,
1355 - userSelChange: null,
1356 - textChanged: null,
1357 - selectionChanged: false,
1358 - cursorActivity: false,
1359 - updateMaxLine: false,
1360 - updateScrollPos: false,
1361 - id: ++nextOpId
2993 + cm: cm,
2994 + viewChanged: false, // Flag that indicates that lines might need to be redrawn
2995 + startHeight: cm.doc.height, // Used to detect need to update scrollbar
2996 + forceUpdate: false, // Used to force a redraw
2997 + updateInput: null, // Whether to reset the input textarea
2998 + typing: false, // Whether this reset should be careful to leave existing text (for compositing)
2999 + changeObjs: null, // Accumulated changes, for firing change events
3000 + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
3001 + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already
3002 + selectionChanged: false, // Whether the selection needs to be redrawn
3003 + updateMaxLine: false, // Set when the widest line needs to be determined anew
3004 + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
3005 + scrollToPos: null, // Used to scroll to a specific position
3006 + focus: false,
3007 + id: ++nextOpId // Unique ID
3008 };
1363 - if (!delayedCallbackDepth++) delayedCallbacks = [];
3009 + if (operationGroup) {
3010 + operationGroup.ops.push(cm.curOp);
3011 + } else {
3012 + cm.curOp.ownsGroup = operationGroup = {
3013 + ops: [cm.curOp],
3014 + delayedCallbacks: []
3015 + };
3016 + }
3017 + }
3018 +
3019 + function fireCallbacksForOps(group) {
3020 + // Calls delayed callbacks and cursorActivity handlers until no
3021 + // new ones appear
3022 + var callbacks = group.delayedCallbacks, i = 0;
3023 + do {
3024 + for (; i < callbacks.length; i++)
3025 + callbacks[i].call(null);
3026 + for (var j = 0; j < group.ops.length; j++) {
3027 + var op = group.ops[j];
3028 + if (op.cursorActivityHandlers)
3029 + while (op.cursorActivityCalled < op.cursorActivityHandlers.length)
3030 + op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm);
3031 + }
3032 + } while (i < callbacks.length);
3033 }
3034
3035 + // Finish an operation, updating the display and signalling delayed events
3036 function endOperation(cm) {
1367 - var op = cm.curOp, doc = cm.doc, display = cm.display;
1368 - cm.curOp = null;
1369 -
1370 - if (op.updateMaxLine) computeMaxLength(cm);
1371 - if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) {
1372 - var width = measureLineWidth(cm, display.maxLine);
1373 - display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
1374 - display.maxLineChanged = false;
1375 - var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
1376 - if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos)
1377 - setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
1378 - }
1379 - var newScrollPos, updated;
1380 - if (op.updateScrollPos) {
1381 - newScrollPos = op.updateScrollPos;
1382 - } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible
1383 - var coords = cursorCoords(cm, doc.sel.head);
1384 - newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
1385 - }
1386 - if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) {
1387 - updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate);
1388 - if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;
1389 - }
1390 - if (!updated && op.selectionChanged) updateSelection(cm);
1391 - if (op.updateScrollPos) {
1392 - var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, newScrollPos.scrollTop));
1393 - var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, newScrollPos.scrollLeft));
1394 - display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top;
1395 - display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left;
1396 - alignHorizontally(cm);
1397 - if (op.scrollToPos)
1398 - scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from),
1399 - clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin);
1400 - } else if (newScrollPos) {
1401 - scrollCursorIntoView(cm);
3037 + var op = cm.curOp, group = op.ownsGroup;
3038 + if (!group) return;
3039 +
3040 + try { fireCallbacksForOps(group); }
3041 + finally {
3042 + operationGroup = null;
3043 + for (var i = 0; i < group.ops.length; i++)
3044 + group.ops[i].cm.curOp = null;
3045 + endOperations(group);
3046 + }
3047 + }
3048 +
3049 + // The DOM updates done when an operation finishes are batched so
3050 + // that the minimum number of relayouts are required.
3051 + function endOperations(group) {
3052 + var ops = group.ops;
3053 + for (var i = 0; i < ops.length; i++) // Read DOM
3054 + endOperation_R1(ops[i]);
3055 + for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
3056 + endOperation_W1(ops[i]);
3057 + for (var i = 0; i < ops.length; i++) // Read DOM
3058 + endOperation_R2(ops[i]);
3059 + for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
3060 + endOperation_W2(ops[i]);
3061 + for (var i = 0; i < ops.length; i++) // Read DOM
3062 + endOperation_finish(ops[i]);
3063 + }
3064 +
3065 + function endOperation_R1(op) {
3066 + var cm = op.cm, display = cm.display;
3067 + maybeClipScrollbars(cm);
3068 + if (op.updateMaxLine) findMaxLine(cm);
3069 +
3070 + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||
3071 + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
3072 + op.scrollToPos.to.line >= display.viewTo) ||
3073 + display.maxLineChanged && cm.options.lineWrapping;
3074 + op.update = op.mustUpdate &&
3075 + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);
3076 + }
3077 +
3078 + function endOperation_W1(op) {
3079 + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update);
3080 + }
3081 +
3082 + function endOperation_R2(op) {
3083 + var cm = op.cm, display = cm.display;
3084 + if (op.updatedDisplay) updateHeightsInViewport(cm);
3085 +
3086 + op.barMeasure = measureForScrollbars(cm);
3087 +
3088 + // If the max line changed since it was last measured, measure it,
3089 + // and ensure the document's width matches it.
3090 + // updateDisplay_W2 will use these properties to do the actual resizing
3091 + if (display.maxLineChanged && !cm.options.lineWrapping) {
3092 + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3;
3093 + cm.display.sizerWidth = op.adjustWidthTo;
3094 + op.barMeasure.scrollWidth =
3095 + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth);
3096 + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm));
3097 + }
3098 +
3099 + if (op.updatedDisplay || op.selectionChanged)
3100 + op.preparedSelection = display.input.prepareSelection(op.focus);
3101 + }
3102 +
3103 + function endOperation_W2(op) {
3104 + var cm = op.cm;
3105 +
3106 + if (op.adjustWidthTo != null) {
3107 + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px";
3108 + if (op.maxScrollLeft < cm.doc.scrollLeft)
3109 + setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true);
3110 + cm.display.maxLineChanged = false;
3111 }
3112 +
3113 + var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus())
3114 + if (op.preparedSelection)
3115 + cm.display.input.showSelection(op.preparedSelection, takeFocus);
3116 + if (op.updatedDisplay || op.startHeight != cm.doc.height)
3117 + updateScrollbars(cm, op.barMeasure);
3118 + if (op.updatedDisplay)
3119 + setDocumentHeight(cm, op.barMeasure);
3120 +
3121 if (op.selectionChanged) restartBlink(cm);
3122
3123 if (cm.state.focused && op.updateInput)
1406 - resetInput(cm, op.userSelChange);
3124 + cm.display.input.reset(op.typing);
3125 + if (takeFocus) ensureFocus(op.cm);
3126 + }
3127 +
3128 + function endOperation_finish(op) {
3129 + var cm = op.cm, display = cm.display, doc = cm.doc;
3130
3131 + if (op.updatedDisplay) postUpdateDisplay(cm, op.update);
3132 +
3133 + // Abort mouse wheel delta measurement, when scrolling explicitly
3134 + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))
3135 + display.wheelStartX = display.wheelStartY = null;
3136 +
3137 + // Propagate the scroll position to the actual DOM scroller
3138 + if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) {
3139 + doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop));
3140 + display.scrollbars.setScrollTop(doc.scrollTop);
3141 + display.scroller.scrollTop = doc.scrollTop;
3142 + }
3143 + if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) {
3144 + doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft));
3145 + display.scrollbars.setScrollLeft(doc.scrollLeft);
3146 + display.scroller.scrollLeft = doc.scrollLeft;
3147 + alignHorizontally(cm);
3148 + }
3149 + // If we need to scroll a specific position into view, do so.
3150 + if (op.scrollToPos) {
3151 + var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
3152 + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin);
3153 + if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords);
3154 + }
3155 +
3156 + // Fire events for markers that are hidden/unidden by editing or
3157 + // undoing
3158 var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
3159 if (hidden) for (var i = 0; i < hidden.length; ++i)
3160 if (!hidden[i].lines.length) signal(hidden[i], "hide");
3161 if (unhidden) for (var i = 0; i < unhidden.length; ++i)
3162 if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
3163
1414 - var delayed;
1415 - if (!--delayedCallbackDepth) {
1416 - delayed = delayedCallbacks;
1417 - delayedCallbacks = null;
1418 - }
1419 - if (op.textChanged)
1420 - signal(cm, "change", cm, op.textChanged);
1421 - if (op.cursorActivity) signal(cm, "cursorActivity", cm);
1422 - if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
3164 + if (display.wrapper.offsetHeight)
3165 + doc.scrollTop = cm.display.scroller.scrollTop;
3166 +
3167 + // Fire change events, and delayed event handlers
3168 + if (op.changeObjs)
3169 + signal(cm, "changes", cm, op.changeObjs);
3170 + if (op.update)
3171 + op.update.finish();
3172 }
3173
3174 + // Run the given function in an operation
3175 + function runInOp(cm, f) {
3176 + if (cm.curOp) return f();
3177 + startOperation(cm);
3178 + try { return f(); }
3179 + finally { endOperation(cm); }
3180 + }
3181 // Wraps a function in an operation. Returns the wrapped function.
1426 - function operation(cm1, f) {
3182 + function operation(cm, f) {
3183 return function() {
1428 - var cm = cm1 || this, withOp = !cm.curOp;
1429 - if (withOp) startOperation(cm);
1430 - try { var result = f.apply(cm, arguments); }
1431 - finally { if (withOp) endOperation(cm); }
1432 - return result;
3184 + if (cm.curOp) return f.apply(cm, arguments);
3185 + startOperation(cm);
3186 + try { return f.apply(cm, arguments); }
3187 + finally { endOperation(cm); }
3188 };
3189 }
1435 - function docOperation(f) {
3190 + // Used to add methods to editor and doc instances, wrapping them in
3191 + // operations.
3192 + function methodOp(f) {
3193 return function() {
1437 - var withOp = this.cm && !this.cm.curOp, result;
1438 - if (withOp) startOperation(this.cm);
1439 - try { result = f.apply(this, arguments); }
1440 - finally { if (withOp) endOperation(this.cm); }
1441 - return result;
3194 + if (this.curOp) return f.apply(this, arguments);
3195 + startOperation(this);
3196 + try { return f.apply(this, arguments); }
3197 + finally { endOperation(this); }
3198 };
3199 }
1444 - function runInOp(cm, f) {
1445 - var withOp = !cm.curOp, result;
1446 - if (withOp) startOperation(cm);
1447 - try { result = f(); }
1448 - finally { if (withOp) endOperation(cm); }
1449 - return result;
3200 + function docMethodOp(f) {
3201 + return function() {
3202 + var cm = this.cm;
3203 + if (!cm || cm.curOp) return f.apply(this, arguments);
3204 + startOperation(cm);
3205 + try { return f.apply(this, arguments); }
3206 + finally { endOperation(cm); }
3207 + };
3208 + }
3209 +
3210 + // VIEW TRACKING
3211 +
3212 + // These objects are used to represent the visible (currently drawn)
3213 + // part of the document. A LineView may correspond to multiple
3214 + // logical lines, if those are connected by collapsed ranges.
3215 + function LineView(doc, line, lineN) {
3216 + // The starting line
3217 + this.line = line;
3218 + // Continuing lines, if any
3219 + this.rest = visualLineContinued(line);
3220 + // Number of logical lines in this visual line
3221 + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;
3222 + this.node = this.text = null;
3223 + this.hidden = lineIsHidden(doc, line);
3224 + }
3225 +
3226 + // Create a range of LineView objects for the given lines.
3227 + function buildViewArray(cm, from, to) {
3228 + var array = [], nextPos;
3229 + for (var pos = from; pos < to; pos = nextPos) {
3230 + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);
3231 + nextPos = pos + view.size;
3232 + array.push(view);
3233 + }
3234 + return array;
3235 }
3236
3237 + // Updates the display.view data structure for a given change to the
3238 + // document. From and to are in pre-change coordinates. Lendiff is
3239 + // the amount of lines added or subtracted by the change. This is
3240 + // used for changes that span multiple lines, or change the way
3241 + // lines are divided into visual lines. regLineChange (below)
3242 + // registers single-line changes.
3243 function regChange(cm, from, to, lendiff) {
3244 if (from == null) from = cm.doc.first;
3245 if (to == null) to = cm.doc.first + cm.doc.size;
1455 - cm.curOp.changes.push({from: from, to: to, diff: lendiff});
1456 - }
1457 -
1458 - // INPUT HANDLING
1459 -
1460 - function slowPoll(cm) {
1461 - if (cm.display.pollingFast) return;
1462 - cm.display.poll.set(cm.options.pollInterval, function() {
1463 - readInput(cm);
1464 - if (cm.state.focused) slowPoll(cm);
1465 - });
1466 - }
3246 + if (!lendiff) lendiff = 0;
3247
1468 - function fastPoll(cm) {
1469 - var missed = false;
1470 - cm.display.pollingFast = true;
1471 - function p() {
1472 - var changed = readInput(cm);
1473 - if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
1474 - else {cm.display.pollingFast = false; slowPoll(cm);}
1475 - }
1476 - cm.display.poll.set(20, p);
1477 - }
1478 -
1479 - // prevInput is a hack to work with IME. If we reset the textarea
1480 - // on every change, that breaks IME. So we look for changes
1481 - // compared to the previous content instead. (Modern browsers have
1482 - // events that indicate IME taking place, but these are not widely
1483 - // supported or compatible enough yet to rely on.)
1484 - function readInput(cm) {
1485 - var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
1486 - if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false;
1487 - if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
1488 - input.value = input.value.substring(0, input.value.length - 1);
1489 - cm.state.fakedLastChar = false;
1490 - }
1491 - var text = input.value;
1492 - if (text == prevInput && posEq(sel.from, sel.to)) return false;
1493 - if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {
1494 - resetInput(cm, true);
1495 - return false;
3248 + var display = cm.display;
3249 + if (lendiff && to < display.viewTo &&
3250 + (display.updateLineNumbers == null || display.updateLineNumbers > from))
3251 + display.updateLineNumbers = from;
3252 +
3253 + cm.curOp.viewChanged = true;
3254 +
3255 + if (from >= display.viewTo) { // Change after
3256 + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
3257 + resetView(cm);
3258 + } else if (to <= display.viewFrom) { // Change before
3259 + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
3260 + resetView(cm);
3261 + } else {
3262 + display.viewFrom += lendiff;
3263 + display.viewTo += lendiff;
3264 + }
3265 + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
3266 + resetView(cm);
3267 + } else if (from <= display.viewFrom) { // Top overlap
3268 + var cut = viewCuttingPoint(cm, to, to + lendiff, 1);
3269 + if (cut) {
3270 + display.view = display.view.slice(cut.index);
3271 + display.viewFrom = cut.lineN;
3272 + display.viewTo += lendiff;
3273 + } else {
3274 + resetView(cm);
3275 + }
3276 + } else if (to >= display.viewTo) { // Bottom overlap
3277 + var cut = viewCuttingPoint(cm, from, from, -1);
3278 + if (cut) {
3279 + display.view = display.view.slice(0, cut.index);
3280 + display.viewTo = cut.lineN;
3281 + } else {
3282 + resetView(cm);
3283 + }
3284 + } else { // Gap in the middle
3285 + var cutTop = viewCuttingPoint(cm, from, from, -1);
3286 + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);
3287 + if (cutTop && cutBot) {
3288 + display.view = display.view.slice(0, cutTop.index)
3289 + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
3290 + .concat(display.view.slice(cutBot.index));
3291 + display.viewTo += lendiff;
3292 + } else {
3293 + resetView(cm);
3294 + }
3295 }
3296
1498 - var withOp = !cm.curOp;
1499 - if (withOp) startOperation(cm);
1500 - sel.shift = false;
1501 - var same = 0, l = Math.min(prevInput.length, text.length);
1502 - while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
1503 - var from = sel.from, to = sel.to;
1504 - var inserted = text.slice(same);
1505 - if (same < prevInput.length)
1506 - from = Pos(from.line, from.ch - (prevInput.length - same));
1507 - else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
1508 - to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + inserted.length));
1509 -
1510 - var updateInput = cm.curOp.updateInput;
1511 - var changeEvent = {from: from, to: to, text: splitLines(inserted),
1512 - origin: cm.state.pasteIncoming ? "paste" : "+input"};
1513 - makeChange(cm.doc, changeEvent, "end");
1514 - cm.curOp.updateInput = updateInput;
1515 - signalLater(cm, "inputRead", cm, changeEvent);
1516 - if (!cm.state.pasteIncoming && cm.options.electricChars &&
1517 - cm.options.smartIndent && sel.head.ch < 100) {
1518 - var electric = cm.getModeAt(sel.head).electricChars;
1519 - if (electric) for (var i = 0; i < electric.length; i++)
1520 - if (inserted.indexOf(electric.charAt(i)) > -1) {
1521 - indentLine(cm, sel.head.line, "smart");
1522 - break;
1523 - }
3297 + var ext = display.externalMeasured;
3298 + if (ext) {
3299 + if (to < ext.lineN)
3300 + ext.lineN += lendiff;
3301 + else if (from < ext.lineN + ext.size)
3302 + display.externalMeasured = null;
3303 + }
3304 + }
3305 +
3306 + // Register a change to a single line. Type must be one of "text",
3307 + // "gutter", "class", "widget"
3308 + function regLineChange(cm, line, type) {
3309 + cm.curOp.viewChanged = true;
3310 + var display = cm.display, ext = cm.display.externalMeasured;
3311 + if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
3312 + display.externalMeasured = null;
3313 +
3314 + if (line < display.viewFrom || line >= display.viewTo) return;
3315 + var lineView = display.view[findViewIndex(cm, line)];
3316 + if (lineView.node == null) return;
3317 + var arr = lineView.changes || (lineView.changes = []);
3318 + if (indexOf(arr, type) == -1) arr.push(type);
3319 + }
3320 +
3321 + // Clear the view.
3322 + function resetView(cm) {
3323 + cm.display.viewFrom = cm.display.viewTo = cm.doc.first;
3324 + cm.display.view = [];
3325 + cm.display.viewOffset = 0;
3326 + }
3327 +
3328 + // Find the view element corresponding to a given line. Return null
3329 + // when the line isn't visible.
3330 + function findViewIndex(cm, n) {
3331 + if (n >= cm.display.viewTo) return null;
3332 + n -= cm.display.viewFrom;
3333 + if (n < 0) return null;
3334 + var view = cm.display.view;
3335 + for (var i = 0; i < view.length; i++) {
3336 + n -= view[i].size;
3337 + if (n < 0) return i;
3338 + }
3339 + }
3340 +
3341 + function viewCuttingPoint(cm, oldN, newN, dir) {
3342 + var index = findViewIndex(cm, oldN), diff, view = cm.display.view;
3343 + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
3344 + return {index: index, lineN: newN};
3345 + for (var i = 0, n = cm.display.viewFrom; i < index; i++)
3346 + n += view[i].size;
3347 + if (n != oldN) {
3348 + if (dir > 0) {
3349 + if (index == view.length - 1) return null;
3350 + diff = (n + view[index].size) - oldN;
3351 + index++;
3352 + } else {
3353 + diff = n - oldN;
3354 + }
3355 + oldN += diff; newN += diff;
3356 }
1525 -
1526 - if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
1527 - else cm.display.prevInput = text;
1528 - if (withOp) endOperation(cm);
1529 - cm.state.pasteIncoming = false;
1530 - return true;
1531 - }
1532 -
1533 - function resetInput(cm, user) {
1534 - var minimal, selected, doc = cm.doc;
1535 - if (!posEq(doc.sel.from, doc.sel.to)) {
1536 - cm.display.prevInput = "";
1537 - minimal = hasCopyEvent &&
1538 - (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1539 - var content = minimal ? "-" : selected || cm.getSelection();
1540 - cm.display.input.value = content;
1541 - if (cm.state.focused) selectInput(cm.display.input);
1542 - if (ie && !ie_lt9) cm.display.inputHasSelection = content;
1543 - } else if (user) {
1544 - cm.display.prevInput = cm.display.input.value = "";
1545 - if (ie && !ie_lt9) cm.display.inputHasSelection = null;
3357 + while (visualLineNo(cm.doc, newN) != newN) {
3358 + if (index == (dir < 0 ? 0 : view.length - 1)) return null;
3359 + newN += dir * view[index - (dir < 0 ? 1 : 0)].size;
3360 + index += dir;
3361 }
1547 - cm.display.inaccurateSelection = minimal;
3362 + return {index: index, lineN: newN};
3363 }
3364
1550 - function focusInput(cm) {
1551 - if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input))
1552 - cm.display.input.focus();
3365 + // Force the view to cover a given range, adding empty view element
3366 + // or clipping off existing ones as needed.
3367 + function adjustView(cm, from, to) {
3368 + var display = cm.display, view = display.view;
3369 + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
3370 + display.view = buildViewArray(cm, from, to);
3371 + display.viewFrom = from;
3372 + } else {
3373 + if (display.viewFrom > from)
3374 + display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view);
3375 + else if (display.viewFrom < from)
3376 + display.view = display.view.slice(findViewIndex(cm, from));
3377 + display.viewFrom = from;
3378 + if (display.viewTo < to)
3379 + display.view = display.view.concat(buildViewArray(cm, display.viewTo, to));
3380 + else if (display.viewTo > to)
3381 + display.view = display.view.slice(0, findViewIndex(cm, to));
3382 + }
3383 + display.viewTo = to;
3384 }
3385
1555 - function isReadOnly(cm) {
1556 - return cm.options.readOnly || cm.doc.cantEdit;
3386 + // Count the number of lines in the view whose DOM representation is
3387 + // out of date (or nonexistent).
3388 + function countDirtyView(cm) {
3389 + var view = cm.display.view, dirty = 0;
3390 + for (var i = 0; i < view.length; i++) {
3391 + var lineView = view[i];
3392 + if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty;
3393 + }
3394 + return dirty;
3395 }
3396
3397 // EVENT HANDLERS
3398
3399 + // Attach the necessary event handlers when initializing the editor
3400 function registerEventHandlers(cm) {
3401 var d = cm.display;
3402 on(d.scroller, "mousedown", operation(cm, onMouseDown));
1564 - if (ie)
3403 + // Older IE's will not fire a second mousedown for a double click
3404 + if (ie && ie_version < 11)
3405 on(d.scroller, "dblclick", operation(cm, function(e) {
3406 if (signalDOMEvent(cm, e)) return;
3407 var pos = posFromMouse(cm, e);
3408 if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
3409 e_preventDefault(e);
1570 - var word = findWordAt(getLine(cm.doc, pos.line).text, pos);
1571 - extendSelection(cm.doc, word.from, word.to);
3410 + var word = cm.findWordAt(pos);
3411 + extendSelection(cm.doc, word.anchor, word.head);
3412 }));
3413 else
3414 on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
1575 - on(d.lineSpace, "selectstart", function(e) {
1576 - if (!eventInWidget(d, e)) e_preventDefault(e);
1577 - });
1578 - // Gecko browsers fire contextmenu *after* opening the menu, at
3415 + // Some browsers fire contextmenu *after* opening the menu, at
3416 // which point we can't mess with it anymore. Context menu is
1580 - // handled in onMouseDown for Gecko.
1581 - if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
3417 + // handled in onMouseDown for these browsers.
3418 + if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
3419 +
3420 + // Used to suppress mouse event handling when a touch happens
3421 + var touchFinished, prevTouch = {end: 0};
3422 + function finishTouch() {
3423 + if (d.activeTouch) {
3424 + touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000);
3425 + prevTouch = d.activeTouch;
3426 + prevTouch.end = +new Date;
3427 + }
3428 + };
3429 + function isMouseLikeTouchEvent(e) {
3430 + if (e.touches.length != 1) return false;
3431 + var touch = e.touches[0];
3432 + return touch.radiusX <= 1 && touch.radiusY <= 1;
3433 + }
3434 + function farAway(touch, other) {
3435 + if (other.left == null) return true;
3436 + var dx = other.left - touch.left, dy = other.top - touch.top;
3437 + return dx * dx + dy * dy > 20 * 20;
3438 + }
3439 + on(d.scroller, "touchstart", function(e) {
3440 + if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) {
3441 + clearTimeout(touchFinished);
3442 + var now = +new Date;
3443 + d.activeTouch = {start: now, moved: false,
3444 + prev: now - prevTouch.end <= 300 ? prevTouch : null};
3445 + if (e.touches.length == 1) {
3446 + d.activeTouch.left = e.touches[0].pageX;
3447 + d.activeTouch.top = e.touches[0].pageY;
3448 + }
3449 + }
3450 + });
3451 + on(d.scroller, "touchmove", function() {
3452 + if (d.activeTouch) d.activeTouch.moved = true;
3453 + });
3454 + on(d.scroller, "touchend", function(e) {
3455 + var touch = d.activeTouch;
3456 + if (touch && !eventInWidget(d, e) && touch.left != null &&
3457 + !touch.moved && new Date - touch.start < 300) {
3458 + var pos = cm.coordsChar(d.activeTouch, "page"), range;
3459 + if (!touch.prev || farAway(touch, touch.prev)) // Single tap
3460 + range = new Range(pos, pos);
3461 + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap
3462 + range = cm.findWordAt(pos);
3463 + else // Triple tap
3464 + range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0)));
3465 + cm.setSelection(range.anchor, range.head);
3466 + cm.focus();
3467 + e_preventDefault(e);
3468 + }
3469 + finishTouch();
3470 + });
3471 + on(d.scroller, "touchcancel", finishTouch);
3472
3473 + // Sync scrolling between fake scrollbars and real scrollable
3474 + // area, ensure viewport is updated when scrolling.
3475 on(d.scroller, "scroll", function() {
3476 if (d.scroller.clientHeight) {
3477 setScrollTop(cm, d.scroller.scrollTop);
@@ -1587,132 +3479,100 @@ window.CodeMirror = (function() {
3479 signal(cm, "scroll", cm);
3480 }
3481 });
1590 - on(d.scrollbarV, "scroll", function() {
1591 - if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
1592 - });
1593 - on(d.scrollbarH, "scroll", function() {
1594 - if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
1595 - });
3482
3483 + // Listen to wheel events in order to try and update the viewport on time.
3484 on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
3485 on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
3486
1600 - function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
1601 - on(d.scrollbarH, "mousedown", reFocus);
1602 - on(d.scrollbarV, "mousedown", reFocus);
3487 // Prevent wrapper from ever scrolling
3488 on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
3489
1606 - var resizeTimer;
1607 - function onResize() {
1608 - if (resizeTimer == null) resizeTimer = setTimeout(function() {
1609 - resizeTimer = null;
1610 - // Might be a text scaling operation, clear size caches.
1611 - d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null;
1612 - clearCaches(cm);
1613 - runInOp(cm, bind(regChange, cm));
1614 - }, 100);
1615 - }
1616 - on(window, "resize", onResize);
1617 - // Above handler holds on to the editor and its data structures.
1618 - // Here we poll to unregister it when the editor is no longer in
1619 - // the document, so that it can be garbage-collected.
1620 - function unregister() {
1621 - for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
1622 - if (p) setTimeout(unregister, 5000);
1623 - else off(window, "resize", onResize);
1624 - }
1625 - setTimeout(unregister, 5000);
1626 -
1627 - on(d.input, "keyup", operation(cm, function(e) {
1628 - if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1629 - if (e.keyCode == 16) cm.doc.sel.shift = false;
1630 - }));
1631 - on(d.input, "input", function() {
1632 - if (ie && !ie_lt9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
1633 - fastPoll(cm);
1634 - });
1635 - on(d.input, "keydown", operation(cm, onKeyDown));
1636 - on(d.input, "keypress", operation(cm, onKeyPress));
1637 - on(d.input, "focus", bind(onFocus, cm));
1638 - on(d.input, "blur", bind(onBlur, cm));
1639 -
1640 - function drag_(e) {
1641 - if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1642 - e_stop(e);
1643 - }
1644 - if (cm.options.dragDrop) {
1645 - on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
1646 - on(d.scroller, "dragenter", drag_);
1647 - on(d.scroller, "dragover", drag_);
1648 - on(d.scroller, "drop", operation(cm, onDrop));
1649 - }
1650 - on(d.scroller, "paste", function(e) {
1651 - if (eventInWidget(d, e)) return;
1652 - focusInput(cm);
1653 - fastPoll(cm);
1654 - });
1655 - on(d.input, "paste", function() {
1656 - // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
1657 - // Add a char to the end of textarea before paste occur so that
1658 - // selection doesn't span to the end of textarea.
1659 - if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
1660 - var start = d.input.selectionStart, end = d.input.selectionEnd;
1661 - d.input.value += "$";
1662 - d.input.selectionStart = start;
1663 - d.input.selectionEnd = end;
1664 - cm.state.fakedLastChar = true;
1665 - }
1666 - cm.state.pasteIncoming = true;
1667 - fastPoll(cm);
1668 - });
3490 + d.dragFunctions = {
3491 + enter: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);},
3492 + over: function(e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }},
3493 + start: function(e){onDragStart(cm, e);},
3494 + drop: operation(cm, onDrop),
3495 + leave: function(e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }}
3496 + };
3497
1670 - function prepareCopy() {
1671 - if (d.inaccurateSelection) {
1672 - d.prevInput = "";
1673 - d.inaccurateSelection = false;
1674 - d.input.value = cm.getSelection();
1675 - selectInput(d.input);
1676 - }
3498 + var inp = d.input.getField();
3499 + on(inp, "keyup", function(e) { onKeyUp.call(cm, e); });
3500 + on(inp, "keydown", operation(cm, onKeyDown));
3501 + on(inp, "keypress", operation(cm, onKeyPress));
3502 + on(inp, "focus", bind(onFocus, cm));
3503 + on(inp, "blur", bind(onBlur, cm));
3504 + }
3505 +
3506 + function dragDropChanged(cm, value, old) {
3507 + var wasOn = old && old != CodeMirror.Init;
3508 + if (!value != !wasOn) {
3509 + var funcs = cm.display.dragFunctions;
3510 + var toggle = value ? on : off;
3511 + toggle(cm.display.scroller, "dragstart", funcs.start);
3512 + toggle(cm.display.scroller, "dragenter", funcs.enter);
3513 + toggle(cm.display.scroller, "dragover", funcs.over);
3514 + toggle(cm.display.scroller, "dragleave", funcs.leave);
3515 + toggle(cm.display.scroller, "drop", funcs.drop);
3516 }
1678 - on(d.input, "cut", prepareCopy);
1679 - on(d.input, "copy", prepareCopy);
3517 + }
3518
1681 - // Needed to handle Tab key in KHTML
1682 - if (khtml) on(d.sizer, "mouseup", function() {
1683 - if (document.activeElement == d.input) d.input.blur();
1684 - focusInput(cm);
1685 - });
3519 + // Called when the window resizes
3520 + function onResize(cm) {
3521 + var d = cm.display;
3522 + if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth)
3523 + return;
3524 + // Might be a text scaling operation, clear size caches.
3525 + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
3526 + d.scrollbarsClipped = false;
3527 + cm.setSize();
3528 }
3529
3530 + // MOUSE EVENTS
3531 +
3532 + // Return true when the given mouse event happened in a widget
3533 function eventInWidget(display, e) {
3534 for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
1690 - if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
3535 + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") ||
3536 + (n.parentNode == display.sizer && n != display.mover))
3537 + return true;
3538 }
3539 }
3540
1694 - function posFromMouse(cm, e, liberal) {
3541 + // Given a mouse event, find the corresponding position. If liberal
3542 + // is false, it checks whether a gutter or scrollbar was clicked,
3543 + // and returns null if it was. forRect is used by rectangular
3544 + // selections, and tries to estimate a character position even for
3545 + // coordinates beyond the right of the text.
3546 + function posFromMouse(cm, e, liberal, forRect) {
3547 var display = cm.display;
1696 - if (!liberal) {
1697 - var target = e_target(e);
1698 - if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
1699 - target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1700 - target == display.scrollbarFiller || target == display.gutterFiller) return null;
1701 - }
1702 - var x, y, space = getRect(display.lineSpace);
3548 + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null;
3549 +
3550 + var x, y, space = display.lineSpace.getBoundingClientRect();
3551 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1704 - try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1705 - return coordsChar(cm, x - space.left, y - space.top);
3552 + try { x = e.clientX - space.left; y = e.clientY - space.top; }
3553 + catch (e) { return null; }
3554 + var coords = coordsChar(cm, x, y), line;
3555 + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
3556 + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;
3557 + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));
3558 + }
3559 + return coords;
3560 }
3561
1708 - var lastClick, lastDoubleClick;
3562 + // A mouse down can be a single click, double click, triple click,
3563 + // start of selection drag, start of text drag, new cursor
3564 + // (ctrl-click), rectangle drag (alt-drag), or xwin
3565 + // middle-click-paste. Or it might be a click on something we should
3566 + // not interfere with, such as a scrollbar or widget.
3567 function onMouseDown(e) {
1710 - if (signalDOMEvent(this, e)) return;
1711 - var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
1712 - sel.shift = e.shiftKey;
3568 + var cm = this, display = cm.display;
3569 + if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) return;
3570 + display.shift = e.shiftKey;
3571
3572 if (eventInWidget(display, e)) {
3573 if (!webkit) {
3574 + // Briefly turn off draggability, to allow widgets to do
3575 + // normal dragging things.
3576 display.scroller.draggable = false;
3577 setTimeout(function(){display.scroller.draggable = true;}, 100);
3578 }
@@ -1720,89 +3580,185 @@ window.CodeMirror = (function() {
3580 }
3581 if (clickInGutter(cm, e)) return;
3582 var start = posFromMouse(cm, e);
3583 + window.focus();
3584
3585 switch (e_button(e)) {
1725 - case 3:
1726 - if (captureMiddleClick) onContextMenu.call(cm, cm, e);
1727 - return;
3586 + case 1:
3587 + // #3261: make sure, that we're not starting a second selection
3588 + if (cm.state.selectingText)
3589 + cm.state.selectingText(e);
3590 + else if (start)
3591 + leftButtonDown(cm, e, start);
3592 + else if (e_target(e) == display.scroller)
3593 + e_preventDefault(e);
3594 + break;
3595 case 2:
3596 if (webkit) cm.state.lastMiddleDown = +new Date;
3597 if (start) extendSelection(cm.doc, start);
1731 - setTimeout(bind(focusInput, cm), 20);
3598 + setTimeout(function() {display.input.focus();}, 20);
3599 e_preventDefault(e);
1733 - return;
3600 + break;
3601 + case 3:
3602 + if (captureRightClick) onContextMenu(cm, e);
3603 + else delayBlurEvent(cm);
3604 + break;
3605 }
1735 - // For button 1, if it was clicked inside the editor
1736 - // (posFromMouse returning non-null), we have to adjust the
1737 - // selection.
1738 - if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
3606 + }
3607
1740 - if (!cm.state.focused) onFocus(cm);
3608 + var lastClick, lastDoubleClick;
3609 + function leftButtonDown(cm, e, start) {
3610 + if (ie) setTimeout(bind(ensureFocus, cm), 0);
3611 + else cm.curOp.focus = activeElt();
3612
1742 - var now = +new Date, type = "single";
1743 - if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
3613 + var now = +new Date, type;
3614 + if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) {
3615 type = "triple";
1745 - e_preventDefault(e);
1746 - setTimeout(bind(focusInput, cm), 20);
1747 - selectLine(cm, start.line);
1748 - } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
3616 + } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) {
3617 type = "double";
3618 lastDoubleClick = {time: now, pos: start};
1751 - e_preventDefault(e);
1752 - var word = findWordAt(getLine(doc, start.line).text, start);
1753 - extendSelection(cm.doc, word.from, word.to);
1754 - } else { lastClick = {time: now, pos: start}; }
1755 -
1756 - var last = start;
1757 - if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&
1758 - !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
1759 - var dragEnd = operation(cm, function(e2) {
1760 - if (webkit) display.scroller.draggable = false;
1761 - cm.state.draggingText = false;
1762 - off(document, "mouseup", dragEnd);
1763 - off(display.scroller, "drop", dragEnd);
1764 - if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
1765 - e_preventDefault(e2);
1766 - extendSelection(cm.doc, start);
1767 - focusInput(cm);
1768 - }
1769 - });
1770 - // Let the drag handler handle this.
1771 - if (webkit) display.scroller.draggable = true;
1772 - cm.state.draggingText = dragEnd;
1773 - // IE's approach to draggable
1774 - if (display.scroller.dragDrop) display.scroller.dragDrop();
1775 - on(document, "mouseup", dragEnd);
1776 - on(display.scroller, "drop", dragEnd);
1777 - return;
3619 + } else {
3620 + type = "single";
3621 + lastClick = {time: now, pos: start};
3622 }
1779 - e_preventDefault(e);
1780 - if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
1781 -
1782 - var startstart = sel.from, startend = sel.to, lastPos = start;
3623
1784 - function doSelect(cur) {
1785 - if (posEq(lastPos, cur)) return;
1786 - lastPos = cur;
1787 -
1788 - if (type == "single") {
1789 - extendSelection(cm.doc, clipPos(doc, start), cur);
1790 - return;
3624 + var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained;
3625 + if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&
3626 + type == "single" && (contained = sel.contains(start)) > -1 &&
3627 + (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) &&
3628 + (cmp(contained.to(), start) > 0 || start.xRel < 0))
3629 + leftButtonStartDrag(cm, e, start, modifier);
3630 + else
3631 + leftButtonSelect(cm, e, start, type, modifier);
3632 + }
3633 +
3634 + // Start a text drag. When it ends, see if any dragging actually
3635 + // happen, and treat as a click if it didn't.
3636 + function leftButtonStartDrag(cm, e, start, modifier) {
3637 + var display = cm.display, startTime = +new Date;
3638 + var dragEnd = operation(cm, function(e2) {
3639 + if (webkit) display.scroller.draggable = false;
3640 + cm.state.draggingText = false;
3641 + off(document, "mouseup", dragEnd);
3642 + off(display.scroller, "drop", dragEnd);
3643 + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
3644 + e_preventDefault(e2);
3645 + if (!modifier && +new Date - 200 < startTime)
3646 + extendSelection(cm.doc, start);
3647 + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)
3648 + if (webkit || ie && ie_version == 9)
3649 + setTimeout(function() {document.body.focus(); display.input.focus();}, 20);
3650 + else
3651 + display.input.focus();
3652 }
3653 + });
3654 + // Let the drag handler handle this.
3655 + if (webkit) display.scroller.draggable = true;
3656 + cm.state.draggingText = dragEnd;
3657 + // IE's approach to draggable
3658 + if (display.scroller.dragDrop) display.scroller.dragDrop();
3659 + on(document, "mouseup", dragEnd);
3660 + on(display.scroller, "drop", dragEnd);
3661 + }
3662 +
3663 + // Normal selection, as opposed to text dragging.
3664 + function leftButtonSelect(cm, e, start, type, addNew) {
3665 + var display = cm.display, doc = cm.doc;
3666 + e_preventDefault(e);
3667
1793 - startstart = clipPos(doc, startstart);
1794 - startend = clipPos(doc, startend);
1795 - if (type == "double") {
1796 - var word = findWordAt(getLine(doc, cur.line).text, cur);
1797 - if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend);
1798 - else extendSelection(cm.doc, startstart, word.to);
1799 - } else if (type == "triple") {
1800 - if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0)));
1801 - else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0)));
3668 + var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges;
3669 + if (addNew && !e.shiftKey) {
3670 + ourIndex = doc.sel.contains(start);
3671 + if (ourIndex > -1)
3672 + ourRange = ranges[ourIndex];
3673 + else
3674 + ourRange = new Range(start, start);
3675 + } else {
3676 + ourRange = doc.sel.primary();
3677 + ourIndex = doc.sel.primIndex;
3678 + }
3679 +
3680 + if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) {
3681 + type = "rect";
3682 + if (!addNew) ourRange = new Range(start, start);
3683 + start = posFromMouse(cm, e, true, true);
3684 + ourIndex = -1;
3685 + } else if (type == "double") {
3686 + var word = cm.findWordAt(start);
3687 + if (cm.display.shift || doc.extend)
3688 + ourRange = extendRange(doc, ourRange, word.anchor, word.head);
3689 + else
3690 + ourRange = word;
3691 + } else if (type == "triple") {
3692 + var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0)));
3693 + if (cm.display.shift || doc.extend)
3694 + ourRange = extendRange(doc, ourRange, line.anchor, line.head);
3695 + else
3696 + ourRange = line;
3697 + } else {
3698 + ourRange = extendRange(doc, ourRange, start);
3699 + }
3700 +
3701 + if (!addNew) {
3702 + ourIndex = 0;
3703 + setSelection(doc, new Selection([ourRange], 0), sel_mouse);
3704 + startSel = doc.sel;
3705 + } else if (ourIndex == -1) {
3706 + ourIndex = ranges.length;
3707 + setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex),
3708 + {scroll: false, origin: "*mouse"});
3709 + } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) {
3710 + setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0),
3711 + {scroll: false, origin: "*mouse"});
3712 + startSel = doc.sel;
3713 + } else {
3714 + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
3715 + }
3716 +
3717 + var lastPos = start;
3718 + function extendTo(pos) {
3719 + if (cmp(lastPos, pos) == 0) return;
3720 + lastPos = pos;
3721 +
3722 + if (type == "rect") {
3723 + var ranges = [], tabSize = cm.options.tabSize;
3724 + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize);
3725 + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);
3726 + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol);
3727 + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));
3728 + line <= end; line++) {
3729 + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize);
3730 + if (left == right)
3731 + ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos)));
3732 + else if (text.length > leftPos)
3733 + ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize))));
3734 + }
3735 + if (!ranges.length) ranges.push(new Range(start, start));
3736 + setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex),
3737 + {origin: "*mouse", scroll: false});
3738 + cm.scrollIntoView(pos);
3739 + } else {
3740 + var oldRange = ourRange;
3741 + var anchor = oldRange.anchor, head = pos;
3742 + if (type != "single") {
3743 + if (type == "double")
3744 + var range = cm.findWordAt(pos);
3745 + else
3746 + var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0)));
3747 + if (cmp(range.anchor, anchor) > 0) {
3748 + head = range.head;
3749 + anchor = minPos(oldRange.from(), range.anchor);
3750 + } else {
3751 + head = range.anchor;
3752 + anchor = maxPos(oldRange.to(), range.head);
3753 + }
3754 + }
3755 + var ranges = startSel.ranges.slice(0);
3756 + ranges[ourIndex] = new Range(clipPos(doc, anchor), head);
3757 + setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse);
3758 }
3759 }
3760
1805 - var editorSize = getRect(display.wrapper);
3761 + var editorSize = display.wrapper.getBoundingClientRect();
3762 // Used to ensure timeout re-tries don't fire when another extend
3763 // happened in the meantime (clearTimeout isn't reliable -- at
3764 // least on Chrome, the timeouts still happen even when cleared,
@@ -1811,12 +3767,11 @@ window.CodeMirror = (function() {
3767
3768 function extend(e) {
3769 var curCount = ++counter;
1814 - var cur = posFromMouse(cm, e, true);
3770 + var cur = posFromMouse(cm, e, true, type == "rect");
3771 if (!cur) return;
1816 - if (!posEq(cur, last)) {
1817 - if (!cm.state.focused) onFocus(cm);
1818 - last = cur;
1819 - doSelect(cur);
3772 + if (cmp(cur, lastPos) != 0) {
3773 + cm.curOp.focus = activeElt();
3774 + extendTo(cur);
3775 var visible = visibleLines(display, doc);
3776 if (cur.line >= visible.to || cur.line < visible.from)
3777 setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
@@ -1831,52 +3786,52 @@ window.CodeMirror = (function() {
3786 }
3787
3788 function done(e) {
3789 + cm.state.selectingText = false;
3790 counter = Infinity;
3791 e_preventDefault(e);
1836 - focusInput(cm);
3792 + display.input.focus();
3793 off(document, "mousemove", move);
3794 off(document, "mouseup", up);
3795 + doc.history.lastSelOrigin = null;
3796 }
3797
3798 var move = operation(cm, function(e) {
1842 - if (!ie && !e_button(e)) done(e);
3799 + if (!e_button(e)) done(e);
3800 else extend(e);
3801 });
3802 var up = operation(cm, done);
3803 + cm.state.selectingText = up;
3804 on(document, "mousemove", move);
3805 on(document, "mouseup", up);
3806 }
3807
1850 - function gutterEvent(cm, e, type, prevent, signalfn) {
3808 + // Determines whether an event happened in the gutter, and fires the
3809 + // handlers for the corresponding event.
3810 + function gutterEvent(cm, e, type, prevent) {
3811 try { var mX = e.clientX, mY = e.clientY; }
3812 catch(e) { return false; }
1853 - if (mX >= Math.floor(getRect(cm.display.gutters).right)) return false;
3813 + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false;
3814 if (prevent) e_preventDefault(e);
3815
3816 var display = cm.display;
1857 - var lineBox = getRect(display.lineDiv);
3817 + var lineBox = display.lineDiv.getBoundingClientRect();
3818
3819 if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e);
3820 mY -= lineBox.top - display.viewOffset;
3821
3822 for (var i = 0; i < cm.options.gutters.length; ++i) {
3823 var g = display.gutters.childNodes[i];
1864 - if (g && getRect(g).right >= mX) {
3824 + if (g && g.getBoundingClientRect().right >= mX) {
3825 var line = lineAtHeight(cm.doc, mY);
3826 var gutter = cm.options.gutters[i];
1867 - signalfn(cm, type, cm, line, gutter, e);
3827 + signal(cm, type, cm, line, gutter, e);
3828 return e_defaultPrevented(e);
3829 }
3830 }
3831 }
3832
1873 - function contextMenuInGutter(cm, e) {
1874 - if (!hasHandler(cm, "gutterContextMenu")) return false;
1875 - return gutterEvent(cm, e, "gutterContextMenu", false, signal);
1876 - }
1877 -
3833 function clickInGutter(cm, e) {
1879 - return gutterEvent(cm, e, "gutterClick", true, signalLater);
3834 + return gutterEvent(cm, e, "gutterClick", true);
3835 }
3836
3837 // Kludge to work around strange IE behavior where it'll sometimes
@@ -1885,42 +3840,57 @@ window.CodeMirror = (function() {
3840
3841 function onDrop(e) {
3842 var cm = this;
1888 - if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
3843 + clearDragCursor(cm);
3844 + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
3845 return;
3846 e_preventDefault(e);
3847 if (ie) lastDrop = +new Date;
3848 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
1893 - if (!pos || isReadOnly(cm)) return;
3849 + if (!pos || cm.isReadOnly()) return;
3850 + // Might be a file drop, in which case we simply extract the text
3851 + // and insert it.
3852 if (files && files.length && window.FileReader && window.File) {
3853 var n = files.length, text = Array(n), read = 0;
3854 var loadFile = function(file, i) {
3855 + if (cm.options.allowDropFileTypes &&
3856 + indexOf(cm.options.allowDropFileTypes, file.type) == -1)
3857 + return;
3858 +
3859 var reader = new FileReader;
1898 - reader.onload = function() {
1899 - text[i] = reader.result;
3860 + reader.onload = operation(cm, function() {
3861 + var content = reader.result;
3862 + if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = "";
3863 + text[i] = content;
3864 if (++read == n) {
3865 pos = clipPos(cm.doc, pos);
1902 - makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around");
3866 + var change = {from: pos, to: pos,
3867 + text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())),
3868 + origin: "paste"};
3869 + makeChange(cm.doc, change);
3870 + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));
3871 }
1904 - };
3872 + });
3873 reader.readAsText(file);
3874 };
3875 for (var i = 0; i < n; ++i) loadFile(files[i], i);
1908 - } else {
3876 + } else { // Normal drop
3877 // Don't do a replace if the drop happened inside of the selected text.
1910 - if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) {
3878 + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
3879 cm.state.draggingText(e);
3880 // Ensure the editor is re-focused
1913 - setTimeout(bind(focusInput, cm), 20);
3881 + setTimeout(function() {cm.display.input.focus();}, 20);
3882 return;
3883 }
3884 try {
3885 var text = e.dataTransfer.getData("Text");
3886 if (text) {
1919 - var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to;
1920 - setSelection(cm.doc, pos, pos);
1921 - if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste");
1922 - cm.replaceSelection(text, null, "paste");
1923 - focusInput(cm);
3887 + if (cm.state.draggingText && !(mac ? e.altKey : e.ctrlKey))
3888 + var selected = cm.listSelections();
3889 + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
3890 + if (selected) for (var i = 0; i < selected.length; ++i)
3891 + replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag");
3892 + cm.replaceSelection(text, "around", "paste");
3893 + cm.display.input.focus();
3894 }
3895 }
3896 catch(e){}
@@ -1931,41 +3901,66 @@ window.CodeMirror = (function() {
3901 if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
3902 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
3903
1934 - var txt = cm.getSelection();
1935 - e.dataTransfer.setData("Text", txt);
3904 + e.dataTransfer.setData("Text", cm.getSelection());
3905 + e.dataTransfer.effectAllowed = "copyMove"
3906
3907 // Use dummy image instead of default browsers image.
3908 // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
3909 if (e.dataTransfer.setDragImage && !safari) {
3910 var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
3911 img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
1942 - if (opera) {
3912 + if (presto) {
3913 img.width = img.height = 1;
3914 cm.display.wrapper.appendChild(img);
3915 // Force a relayout, or Opera won't use our image for some obscure reason
3916 img._top = img.offsetTop;
3917 }
3918 e.dataTransfer.setDragImage(img, 0, 0);
1949 - if (opera) img.parentNode.removeChild(img);
3919 + if (presto) img.parentNode.removeChild(img);
3920 + }
3921 + }
3922 +
3923 + function onDragOver(cm, e) {
3924 + var pos = posFromMouse(cm, e);
3925 + if (!pos) return;
3926 + var frag = document.createDocumentFragment();
3927 + drawSelectionCursor(cm, pos, frag);
3928 + if (!cm.display.dragCursor) {
3929 + cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors");
3930 + cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv);
3931 }
3932 + removeChildrenAndAdd(cm.display.dragCursor, frag);
3933 }
3934
3935 + function clearDragCursor(cm) {
3936 + if (cm.display.dragCursor) {
3937 + cm.display.lineSpace.removeChild(cm.display.dragCursor);
3938 + cm.display.dragCursor = null;
3939 + }
3940 + }
3941 +
3942 + // SCROLL EVENTS
3943 +
3944 + // Sync the scrollable area and scrollbars, ensure the viewport
3945 + // covers the visible area.
3946 function setScrollTop(cm, val) {
3947 if (Math.abs(cm.doc.scrollTop - val) < 2) return;
3948 cm.doc.scrollTop = val;
1956 - if (!gecko) updateDisplay(cm, [], val);
3949 + if (!gecko) updateDisplaySimple(cm, {top: val});
3950 if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
1958 - if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
1959 - if (gecko) updateDisplay(cm, []);
3951 + cm.display.scrollbars.setScrollTop(val);
3952 + if (gecko) updateDisplaySimple(cm);
3953 startWorker(cm, 100);
3954 }
3955 + // Sync scroller and scrollbar, ensure the gutter elements are
3956 + // aligned.
3957 function setScrollLeft(cm, val, isScroller) {
3958 if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
3959 val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
3960 cm.doc.scrollLeft = val;
3961 alignHorizontally(cm);
3962 if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
1968 - if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
3963 + cm.display.scrollbars.setScrollLeft(val);
3964 }
3965
3966 // Since the delta values reported on mouse wheel events are
@@ -1989,26 +3984,40 @@ window.CodeMirror = (function() {
3984 else if (chrome) wheelPixelsPerUnit = -.7;
3985 else if (safari) wheelPixelsPerUnit = -1/3;
3986
1992 - function onScrollWheel(cm, e) {
3987 + var wheelEventDelta = function(e) {
3988 var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
3989 if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
3990 if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
3991 else if (dy == null) dy = e.wheelDelta;
3992 + return {x: dx, y: dy};
3993 + };
3994 + CodeMirror.wheelEventPixels = function(e) {
3995 + var delta = wheelEventDelta(e);
3996 + delta.x *= wheelPixelsPerUnit;
3997 + delta.y *= wheelPixelsPerUnit;
3998 + return delta;

This file is too large to show in full.

docs/js/javascript.js
+460 -139
@@ -1,9 +1,28 @@
1 -// TODO actually recognize syntax of TypeScript constructs
1 +// CodeMirror, copyright (c) by Marijn Haverbeke and others
2 +// Distributed under an MIT license: http://codemirror.net/LICENSE
3 +
4 +(function(mod) {
5 + if (typeof exports == "object" && typeof module == "object") // CommonJS
6 + mod(require("../../lib/codemirror"));
7 + else if (typeof define == "function" && define.amd) // AMD
8 + define(["../../lib/codemirror"], mod);
9 + else // Plain browser env
10 + mod(CodeMirror);
11 +})(function(CodeMirror) {
12 +"use strict";
13 +
14 +function expressionAllowed(stream, state, backUp) {
15 + return /^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
16 + (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
17 +}
18
19 CodeMirror.defineMode("javascript", function(config, parserConfig) {
20 var indentUnit = config.indentUnit;
5 - var jsonMode = parserConfig.json;
21 + var statementIndent = parserConfig.statementIndent;
22 + var jsonldMode = parserConfig.jsonld;
23 + var jsonMode = parserConfig.json || jsonldMode;
24 var isTS = parserConfig.typescript;
25 + var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
26
27 // Tokenizer
28
@@ -11,15 +30,18 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
30 function kw(type) {return {type: type, style: "keyword"};}
31 var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
32 var operator = kw("operator"), atom = {type: "atom", style: "atom"};
14 -
33 +
34 var jsKeywords = {
16 - "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
17 - "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
35 + "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
36 + "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C,
37 "var": kw("var"), "const": kw("var"), "let": kw("var"),
38 "function": kw("function"), "catch": kw("catch"),
39 "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
40 "in": operator, "typeof": operator, "instanceof": operator,
22 - "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
41 + "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
42 + "this": kw("this"), "class": kw("class"), "super": kw("atom"),
43 + "yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
44 + "await": C, "async": kw("async")
45 };
46
47 // Extend the 'normal' keywords with the TypeScript language extensions
@@ -27,21 +49,23 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
49 var type = {type: "variable", style: "variable-3"};
50 var tsKeywords = {
51 // object-like things
30 - "interface": kw("interface"),
31 - "class": kw("class"),
32 - "extends": kw("extends"),
33 - "constructor": kw("constructor"),
52 + "interface": kw("class"),
53 + "implements": C,
54 + "namespace": C,
55 + "module": kw("module"),
56 + "enum": kw("module"),
57
58 // scope modifiers
36 - "public": kw("public"),
37 - "private": kw("private"),
38 - "protected": kw("protected"),
39 - "static": kw("static"),
59 + "public": kw("modifier"),
60 + "private": kw("modifier"),
61 + "protected": kw("modifier"),
62 + "abstract": kw("modifier"),
63
41 - "super": kw("super"),
64 + // operators
65 + "as": operator,
66
67 // types
44 - "string": type, "number": type, "bool": type, "any": type
68 + "string": type, "number": type, "boolean": type, "any": type
69 };
70
71 for (var attr in tsKeywords) {
@@ -52,21 +76,19 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
76 return jsKeywords;
77 }();
78
55 - var isOperatorChar = /[+\-*&%=<>!?|]/;
56 -
57 - function chain(stream, state, f) {
58 - state.tokenize = f;
59 - return f(stream, state);
60 - }
79 + var isOperatorChar = /[+\-*&%=<>!?|~^]/;
80 + var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
81
62 - function nextUntilUnescaped(stream, end) {
63 - var escaped = false, next;
82 + function readRegexp(stream) {
83 + var escaped = false, next, inSet = false;
84 while ((next = stream.next()) != null) {
65 - if (next == end && !escaped)
66 - return false;
85 + if (!escaped) {
86 + if (next == "/" && !inSet) return;
87 + if (next == "[") inSet = true;
88 + else if (inSet && next == "]") inSet = false;
89 + }
90 escaped = !escaped && next == "\\";
91 }
69 - return escaped;
92 }
93
94 // Used as scratch variables to communicate multiple values without
@@ -76,69 +98,84 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
98 type = tp; content = cont;
99 return style;
100 }
79 -
80 - function jsTokenBase(stream, state) {
101 + function tokenBase(stream, state) {
102 var ch = stream.next();
82 - if (ch == '"' || ch == "'")
83 - return chain(stream, state, jsTokenString(ch));
84 - else if (/[\[\]{}\(\),;\:\.]/.test(ch))
103 + if (ch == '"' || ch == "'") {
104 + state.tokenize = tokenString(ch);
105 + return state.tokenize(stream, state);
106 + } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
107 + return ret("number", "number");
108 + } else if (ch == "." && stream.match("..")) {
109 + return ret("spread", "meta");
110 + } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
111 return ret(ch);
86 - else if (ch == "0" && stream.eat(/x/i)) {
112 + } else if (ch == "=" && stream.eat(">")) {
113 + return ret("=>", "operator");
114 + } else if (ch == "0" && stream.eat(/x/i)) {
115 stream.eatWhile(/[\da-f]/i);
116 return ret("number", "number");
89 - }
90 - else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
117 + } else if (ch == "0" && stream.eat(/o/i)) {
118 + stream.eatWhile(/[0-7]/i);
119 + return ret("number", "number");
120 + } else if (ch == "0" && stream.eat(/b/i)) {
121 + stream.eatWhile(/[01]/i);
122 + return ret("number", "number");
123 + } else if (/\d/.test(ch)) {
124 stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
125 return ret("number", "number");
93 - }
94 - else if (ch == "/") {
126 + } else if (ch == "/") {
127 if (stream.eat("*")) {
96 - return chain(stream, state, jsTokenComment);
97 - }
98 - else if (stream.eat("/")) {
128 + state.tokenize = tokenComment;
129 + return tokenComment(stream, state);
130 + } else if (stream.eat("/")) {
131 stream.skipToEnd();
132 return ret("comment", "comment");
101 - }
102 - else if (state.lastType == "operator" || state.lastType == "keyword c" ||
103 - /^[\[{}\(,;:]$/.test(state.lastType)) {
104 - nextUntilUnescaped(stream, "/");
105 - stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
133 + } else if (expressionAllowed(stream, state, 1)) {
134 + readRegexp(stream);
135 + stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
136 return ret("regexp", "string-2");
107 - }
108 - else {
137 + } else {
138 stream.eatWhile(isOperatorChar);
110 - return ret("operator", null, stream.current());
139 + return ret("operator", "operator", stream.current());
140 }
112 - }
113 - else if (ch == "#") {
141 + } else if (ch == "`") {
142 + state.tokenize = tokenQuasi;
143 + return tokenQuasi(stream, state);
144 + } else if (ch == "#") {
145 stream.skipToEnd();
146 return ret("error", "error");
116 - }
117 - else if (isOperatorChar.test(ch)) {
147 + } else if (isOperatorChar.test(ch)) {
148 stream.eatWhile(isOperatorChar);
119 - return ret("operator", null, stream.current());
120 - }
121 - else {
122 - stream.eatWhile(/[\w\$_]/);
149 + return ret("operator", "operator", stream.current());
150 + } else if (wordRE.test(ch)) {
151 + stream.eatWhile(wordRE);
152 var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
153 return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
154 ret("variable", "variable", word);
155 }
156 }
157
129 - function jsTokenString(quote) {
158 + function tokenString(quote) {
159 return function(stream, state) {
131 - if (!nextUntilUnescaped(stream, quote))
132 - state.tokenize = jsTokenBase;
160 + var escaped = false, next;
161 + if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
162 + state.tokenize = tokenBase;
163 + return ret("jsonld-keyword", "meta");
164 + }
165 + while ((next = stream.next()) != null) {
166 + if (next == quote && !escaped) break;
167 + escaped = !escaped && next == "\\";
168 + }
169 + if (!escaped) state.tokenize = tokenBase;
170 return ret("string", "string");
171 };
172 }
173
137 - function jsTokenComment(stream, state) {
174 + function tokenComment(stream, state) {
175 var maybeEnd = false, ch;
176 while (ch = stream.next()) {
177 if (ch == "/" && maybeEnd) {
141 - state.tokenize = jsTokenBase;
178 + state.tokenize = tokenBase;
179 break;
180 }
181 maybeEnd = (ch == "*");
@@ -146,9 +183,55 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
183 return ret("comment", "comment");
184 }
185
186 + function tokenQuasi(stream, state) {
187 + var escaped = false, next;
188 + while ((next = stream.next()) != null) {
189 + if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
190 + state.tokenize = tokenBase;
191 + break;
192 + }
193 + escaped = !escaped && next == "\\";
194 + }
195 + return ret("quasi", "string-2", stream.current());
196 + }
197 +
198 + var brackets = "([{}])";
199 + // This is a crude lookahead trick to try and notice that we're
200 + // parsing the argument patterns for a fat-arrow function before we
201 + // actually hit the arrow token. It only works if the arrow is on
202 + // the same line as the arguments and there's no strange noise
203 + // (comments) in between. Fallback is to only notice when we hit the
204 + // arrow, and not declare the arguments as locals for the arrow
205 + // body.
206 + function findFatArrow(stream, state) {
207 + if (state.fatArrowAt) state.fatArrowAt = null;
208 + var arrow = stream.string.indexOf("=>", stream.start);
209 + if (arrow < 0) return;
210 +
211 + var depth = 0, sawSomething = false;
212 + for (var pos = arrow - 1; pos >= 0; --pos) {
213 + var ch = stream.string.charAt(pos);
214 + var bracket = brackets.indexOf(ch);
215 + if (bracket >= 0 && bracket < 3) {
216 + if (!depth) { ++pos; break; }
217 + if (--depth == 0) break;
218 + } else if (bracket >= 3 && bracket < 6) {
219 + ++depth;
220 + } else if (wordRE.test(ch)) {
221 + sawSomething = true;
222 + } else if (/["'\/]/.test(ch)) {
223 + return;
224 + } else if (sawSomething && !depth) {
225 + ++pos;
226 + break;
227 + }
228 + }
229 + if (sawSomething && !depth) state.fatArrowAt = pos;
230 + }
231 +
232 // Parser
233
151 - var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
234 + var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
235
236 function JSLexical(indented, column, type, align, prev, info) {
237 this.indented = indented;
@@ -162,14 +245,18 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
245 function inScope(state, varname) {
246 for (var v = state.localVars; v; v = v.next)
247 if (v.name == varname) return true;
248 + for (var cx = state.context; cx; cx = cx.prev) {
249 + for (var v = cx.vars; v; v = v.next)
250 + if (v.name == varname) return true;
251 + }
252 }
253
254 function parseJS(state, style, type, content, stream) {
255 var cc = state.cc;
256 // Communicate our context to the combinators.
257 // (Less wasteful than consing up a hundred closures on every call.)
171 - cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
172 -
258 + cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
259 +
260 if (!state.lexical.hasOwnProperty("align"))
261 state.lexical.align = true;
262
@@ -202,13 +289,14 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
289 return false;
290 }
291 var state = cx.state;
292 + cx.marked = "def";
293 if (state.context) {
206 - cx.marked = "def";
294 if (inList(state.localVars)) return;
295 state.localVars = {name: varname, next: state.localVars};
296 } else {
297 if (inList(state.globalVars)) return;
211 - state.globalVars = {name: varname, next: state.globalVars};
298 + if (parserConfig.globalVars)
299 + state.globalVars = {name: varname, next: state.globalVars};
300 }
301 }
302
@@ -225,8 +313,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
313 }
314 function pushlex(type, info) {
315 var result = function() {
228 - var state = cx.state;
229 - state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info);
316 + var state = cx.state, indent = state.indented;
317 + if (state.lexical.type == "stat") indent = state.lexical.indented;
318 + else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
319 + indent = outer.indented;
320 + state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
321 };
322 result.lex = true;
323 return result;
@@ -242,142 +333,342 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
333 poplex.lex = true;
334
335 function expect(wanted) {
245 - return function(type) {
336 + function exp(type) {
337 if (type == wanted) return cont();
338 else if (wanted == ";") return pass();
248 - else return cont(arguments.callee);
339 + else return cont(exp);
340 };
341 + return exp;
342 }
343
252 - function statement(type) {
253 - if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
344 + function statement(type, value) {
345 + if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
346 if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
347 if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
348 if (type == "{") return cont(pushlex("}"), block, poplex);
349 if (type == ";") return cont();
350 + if (type == "if") {
351 + if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
352 + cx.state.cc.pop()();
353 + return cont(pushlex("form"), expression, statement, poplex, maybeelse);
354 + }
355 if (type == "function") return cont(functiondef);
259 - if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
260 - poplex, statement, poplex);
356 + if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
357 if (type == "variable") return cont(pushlex("stat"), maybelabel);
358 if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
263 - block, poplex, poplex);
359 + block, poplex, poplex);
360 if (type == "case") return cont(expression, expect(":"));
361 if (type == "default") return cont(expect(":"));
362 if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
267 - statement, poplex, popcontext);
363 + statement, poplex, popcontext);
364 + if (type == "class") return cont(pushlex("form"), className, poplex);
365 + if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
366 + if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
367 + if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex)
368 + if (type == "async") return cont(statement)
369 return pass(pushlex("stat"), expression, expect(";"), poplex);
370 }
371 function expression(type) {
271 - if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
272 - if (type == "function") return cont(functiondef);
273 - if (type == "keyword c") return cont(maybeexpression);
274 - if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
275 - if (type == "operator") return cont(expression);
276 - if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
277 - if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
372 + return expressionInner(type, false);
373 + }
374 + function expressionNoComma(type) {
375 + return expressionInner(type, true);
376 + }
377 + function expressionInner(type, noComma) {
378 + if (cx.state.fatArrowAt == cx.stream.start) {
379 + var body = noComma ? arrowBodyNoComma : arrowBody;
380 + if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
381 + else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
382 + }
383 +
384 + var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
385 + if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
386 + if (type == "function") return cont(functiondef, maybeop);
387 + if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
388 + if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
389 + if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
390 + if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
391 + if (type == "{") return contCommasep(objprop, "}", null, maybeop);
392 + if (type == "quasi") return pass(quasi, maybeop);
393 + if (type == "new") return cont(maybeTarget(noComma));
394 return cont();
395 }
396 function maybeexpression(type) {
397 if (type.match(/[;\}\)\],]/)) return pass();
398 return pass(expression);
399 }
284 -
285 - function maybeoperator(type, value) {
400 + function maybeexpressionNoComma(type) {
401 + if (type.match(/[;\}\)\],]/)) return pass();
402 + return pass(expressionNoComma);
403 + }
404 +
405 + function maybeoperatorComma(type, value) {
406 + if (type == ",") return cont(expression);
407 + return maybeoperatorNoComma(type, value, false);
408 + }
409 + function maybeoperatorNoComma(type, value, noComma) {
410 + var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
411 + var expr = noComma == false ? expression : expressionNoComma;
412 + if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
413 if (type == "operator") {
287 - if (/\+\+|--/.test(value)) return cont(maybeoperator);
288 - if (value == "?") return cont(expression, expect(":"), expression);
289 - return cont(expression);
414 + if (/\+\+|--/.test(value)) return cont(me);
415 + if (value == "?") return cont(expression, expect(":"), expr);
416 + return cont(expr);
417 }
418 + if (type == "quasi") { return pass(quasi, me); }
419 if (type == ";") return;
292 - if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
293 - if (type == ".") return cont(property, maybeoperator);
294 - if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
420 + if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
421 + if (type == ".") return cont(property, me);
422 + if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
423 + }
424 + function quasi(type, value) {
425 + if (type != "quasi") return pass();
426 + if (value.slice(value.length - 2) != "${") return cont(quasi);
427 + return cont(expression, continueQuasi);
428 + }
429 + function continueQuasi(type) {
430 + if (type == "}") {
431 + cx.marked = "string-2";
432 + cx.state.tokenize = tokenQuasi;
433 + return cont(quasi);
434 + }
435 + }
436 + function arrowBody(type) {
437 + findFatArrow(cx.stream, cx.state);
438 + return pass(type == "{" ? statement : expression);
439 + }
440 + function arrowBodyNoComma(type) {
441 + findFatArrow(cx.stream, cx.state);
442 + return pass(type == "{" ? statement : expressionNoComma);
443 + }
444 + function maybeTarget(noComma) {
445 + return function(type) {
446 + if (type == ".") return cont(noComma ? targetNoComma : target);
447 + else return pass(noComma ? expressionNoComma : expression);
448 + };
449 + }
450 + function target(_, value) {
451 + if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
452 + }
453 + function targetNoComma(_, value) {
454 + if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
455 }
456 function maybelabel(type) {
457 if (type == ":") return cont(poplex, statement);
298 - return pass(maybeoperator, expect(";"), poplex);
458 + return pass(maybeoperatorComma, expect(";"), poplex);
459 }
460 function property(type) {
461 if (type == "variable") {cx.marked = "property"; return cont();}
462 }
303 - function objprop(type) {
304 - if (type == "variable") cx.marked = "property";
305 - if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
463 + function objprop(type, value) {
464 + if (type == "variable" || cx.style == "keyword") {
465 + cx.marked = "property";
466 + if (value == "get" || value == "set") return cont(getterSetter);
467 + return cont(afterprop);
468 + } else if (type == "number" || type == "string") {
469 + cx.marked = jsonldMode ? "property" : (cx.style + " property");
470 + return cont(afterprop);
471 + } else if (type == "jsonld-keyword") {
472 + return cont(afterprop);
473 + } else if (type == "modifier") {
474 + return cont(objprop)
475 + } else if (type == "[") {
476 + return cont(expression, expect("]"), afterprop);
477 + } else if (type == "spread") {
478 + return cont(expression);
479 + }
480 + }
481 + function getterSetter(type) {
482 + if (type != "variable") return pass(afterprop);
483 + cx.marked = "property";
484 + return cont(functiondef);
485 + }
486 + function afterprop(type) {
487 + if (type == ":") return cont(expressionNoComma);
488 + if (type == "(") return pass(functiondef);
489 }
490 function commasep(what, end) {
308 - function proceed(type) {
309 - if (type == ",") return cont(what, proceed);
310 - if (type == end) return cont();
491 + function proceed(type, value) {
492 + if (type == ",") {
493 + var lex = cx.state.lexical;
494 + if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
495 + return cont(what, proceed);
496 + }
497 + if (type == end || value == end) return cont();
498 return cont(expect(end));
499 }
313 - return function(type) {
314 - if (type == end) return cont();
315 - else return pass(what, proceed);
500 + return function(type, value) {
501 + if (type == end || value == end) return cont();
502 + return pass(what, proceed);
503 };
504 }
505 + function contCommasep(what, end, info) {
506 + for (var i = 3; i < arguments.length; i++)
507 + cx.cc.push(arguments[i]);
508 + return cont(pushlex(end, info), commasep(what, end), poplex);
509 + }
510 function block(type) {
511 if (type == "}") return cont();
512 return pass(statement, block);
513 }
514 function maybetype(type) {
323 - if (type == ":") return cont(typedef);
324 - return pass();
515 + if (isTS && type == ":") return cont(typeexpr);
516 + }
517 + function maybedefault(_, value) {
518 + if (value == "=") return cont(expressionNoComma);
519 + }
520 + function typeexpr(type) {
521 + if (type == "variable") {cx.marked = "variable-3"; return cont(afterType);}
522 }
326 - function typedef(type) {
327 - if (type == "variable"){cx.marked = "variable-3"; return cont();}
328 - return pass();
523 + function afterType(type, value) {
524 + if (value == "<") return cont(commasep(typeexpr, ">"), afterType)
525 + if (type == "[") return cont(expect("]"), afterType)
526 }
330 - function vardef1(type, value) {
331 - if (type == "variable") {
527 + function vardef() {
528 + return pass(pattern, maybetype, maybeAssign, vardefCont);
529 + }
530 + function pattern(type, value) {
531 + if (type == "modifier") return cont(pattern)
532 + if (type == "variable") { register(value); return cont(); }
533 + if (type == "spread") return cont(pattern);
534 + if (type == "[") return contCommasep(pattern, "]");
535 + if (type == "{") return contCommasep(proppattern, "}");
536 + }
537 + function proppattern(type, value) {
538 + if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
539 register(value);
333 - return isTS ? cont(maybetype, vardef2) : cont(vardef2);
540 + return cont(maybeAssign);
541 }
335 - return pass();
542 + if (type == "variable") cx.marked = "property";
543 + if (type == "spread") return cont(pattern);
544 + if (type == "}") return pass();
545 + return cont(expect(":"), pattern, maybeAssign);
546 }
337 - function vardef2(type, value) {
338 - if (value == "=") return cont(expression, vardef2);
339 - if (type == ",") return cont(vardef1);
547 + function maybeAssign(_type, value) {
548 + if (value == "=") return cont(expressionNoComma);
549 + }
550 + function vardefCont(type) {
551 + if (type == ",") return cont(vardef);
552 + }
553 + function maybeelse(type, value) {
554 + if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
555 + }
556 + function forspec(type) {
557 + if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
558 }
559 function forspec1(type) {
342 - if (type == "var") return cont(vardef1, expect(";"), forspec2);
560 + if (type == "var") return cont(vardef, expect(";"), forspec2);
561 if (type == ";") return cont(forspec2);
344 - if (type == "variable") return cont(formaybein);
345 - return cont(forspec2);
562 + if (type == "variable") return cont(formaybeinof);
563 + return pass(expression, expect(";"), forspec2);
564 }
347 - function formaybein(_type, value) {
348 - if (value == "in") return cont(expression);
349 - return cont(maybeoperator, forspec2);
565 + function formaybeinof(_type, value) {
566 + if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
567 + return cont(maybeoperatorComma, forspec2);
568 }
569 function forspec2(type, value) {
570 if (type == ";") return cont(forspec3);
353 - if (value == "in") return cont(expression);
354 - return cont(expression, expect(";"), forspec3);
571 + if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
572 + return pass(expression, expect(";"), forspec3);
573 }
574 function forspec3(type) {
575 if (type != ")") cont(expression);
576 }
577 function functiondef(type, value) {
578 + if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
579 if (type == "variable") {register(value); return cont(functiondef);}
361 - if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
580 + if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, maybetype, statement, popcontext);
581 }
363 - function funarg(type, value) {
364 - if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();}
582 + function funarg(type) {
583 + if (type == "spread") return cont(funarg);
584 + return pass(pattern, maybetype, maybedefault);
585 + }
586 + function className(type, value) {
587 + if (type == "variable") {register(value); return cont(classNameAfter);}
588 + }
589 + function classNameAfter(type, value) {
590 + if (value == "extends") return cont(expression, classNameAfter);
591 + if (type == "{") return cont(pushlex("}"), classBody, poplex);
592 + }
593 + function classBody(type, value) {
594 + if (type == "variable" || cx.style == "keyword") {
595 + if (value == "static") {
596 + cx.marked = "keyword";
597 + return cont(classBody);
598 + }
599 + cx.marked = "property";
600 + if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody);
601 + return cont(functiondef, classBody);
602 + }
603 + if (value == "*") {
604 + cx.marked = "keyword";
605 + return cont(classBody);
606 + }
607 + if (type == ";") return cont(classBody);
608 + if (type == "}") return cont();
609 + }
610 + function classGetterSetter(type) {
611 + if (type != "variable") return pass();
612 + cx.marked = "property";
613 + return cont();
614 + }
615 + function afterExport(_type, value) {
616 + if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
617 + if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
618 + return pass(statement);
619 + }
620 + function afterImport(type) {
621 + if (type == "string") return cont();
622 + return pass(importSpec, maybeFrom);
623 + }
624 + function importSpec(type, value) {
625 + if (type == "{") return contCommasep(importSpec, "}");
626 + if (type == "variable") register(value);
627 + if (value == "*") cx.marked = "keyword";
628 + return cont(maybeAs);
629 + }
630 + function maybeAs(_type, value) {
631 + if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
632 + }
633 + function maybeFrom(_type, value) {
634 + if (value == "from") { cx.marked = "keyword"; return cont(expression); }
635 + }
636 + function arrayLiteral(type) {
637 + if (type == "]") return cont();
638 + return pass(expressionNoComma, maybeArrayComprehension);
639 + }
640 + function maybeArrayComprehension(type) {
641 + if (type == "for") return pass(comprehension, expect("]"));
642 + if (type == ",") return cont(commasep(maybeexpressionNoComma, "]"));
643 + return pass(commasep(expressionNoComma, "]"));
644 + }
645 + function comprehension(type) {
646 + if (type == "for") return cont(forspec, comprehension);
647 + if (type == "if") return cont(expression, comprehension);
648 + }
649 +
650 + function isContinuedStatement(state, textAfter) {
651 + return state.lastType == "operator" || state.lastType == "," ||
652 + isOperatorChar.test(textAfter.charAt(0)) ||
653 + /[,.]/.test(textAfter.charAt(0));
654 }
655
656 // Interface
657
658 return {
659 startState: function(basecolumn) {
371 - return {
372 - tokenize: jsTokenBase,
373 - lastType: null,
660 + var state = {
661 + tokenize: tokenBase,
662 + lastType: "sof",
663 cc: [],
664 lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
665 localVars: parserConfig.localVars,
377 - globalVars: parserConfig.globalVars,
666 context: parserConfig.localVars && {vars: parserConfig.localVars},
379 - indented: 0
667 + indented: basecolumn || 0
668 };
669 + if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
670 + state.globalVars = parserConfig.globalVars;
671 + return state;
672 },
673
674 token: function(stream, state) {
@@ -385,41 +676,71 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
676 if (!state.lexical.hasOwnProperty("align"))
677 state.lexical.align = false;
678 state.indented = stream.indentation();
679 + findFatArrow(stream, state);
680 }
389 - if (stream.eatSpace()) return null;
681 + if (state.tokenize != tokenComment && stream.eatSpace()) return null;
682 var style = state.tokenize(stream, state);
683 if (type == "comment") return style;
392 - state.lastType = type;
684 + state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
685 return parseJS(state, style, type, content, stream);
686 },
687
688 indent: function(state, textAfter) {
397 - if (state.tokenize == jsTokenComment) return CodeMirror.Pass;
398 - if (state.tokenize != jsTokenBase) return 0;
689 + if (state.tokenize == tokenComment) return CodeMirror.Pass;
690 + if (state.tokenize != tokenBase) return 0;
691 var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
692 + // Kludge to prevent 'maybelse' from blocking lexical scope pops
693 + if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
694 + var c = state.cc[i];
695 + if (c == poplex) lexical = lexical.prev;
696 + else if (c != maybeelse) break;
697 + }
698 if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
699 + if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
700 + lexical = lexical.prev;
701 var type = lexical.type, closing = firstChar == type;
402 - if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0);
702 +
703 + if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
704 else if (type == "form" && firstChar == "{") return lexical.indented;
705 else if (type == "form") return lexical.indented + indentUnit;
706 else if (type == "stat")
406 - return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? indentUnit : 0);
407 - else if (lexical.info == "switch" && !closing)
707 + return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
708 + else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
709 return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
710 else if (lexical.align) return lexical.column + (closing ? 0 : 1);
711 else return lexical.indented + (closing ? 0 : indentUnit);
712 },
713
413 - electricChars: ":{}",
414 -
415 - jsonMode: jsonMode
714 + electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
715 + blockCommentStart: jsonMode ? null : "/*",
716 + blockCommentEnd: jsonMode ? null : "*/",
717 + lineComment: jsonMode ? null : "//",
718 + fold: "brace",
719 + closeBrackets: "()[]{}''\"\"``",
720 +
721 + helperType: jsonMode ? "json" : "javascript",
722 + jsonldMode: jsonldMode,
723 + jsonMode: jsonMode,
724 +
725 + expressionAllowed: expressionAllowed,
726 + skipExpression: function(state) {
727 + var top = state.cc[state.cc.length - 1]
728 + if (top == expression || top == expressionNoComma) state.cc.pop()
729 + }
730 };
731 });
732
733 +CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
734 +
735 CodeMirror.defineMIME("text/javascript", "javascript");
736 CodeMirror.defineMIME("text/ecmascript", "javascript");
737 CodeMirror.defineMIME("application/javascript", "javascript");
738 +CodeMirror.defineMIME("application/x-javascript", "javascript");
739 CodeMirror.defineMIME("application/ecmascript", "javascript");
740 CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
741 +CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
742 +CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
743 CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
744 CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
745 +
746 +});
docs/js/jsx.js new
+147
@@ -0,0 +1,147 @@
1 +// CodeMirror, copyright (c) by Marijn Haverbeke and others
2 +// Distributed under an MIT license: http://codemirror.net/LICENSE
3 +
4 +(function(mod) {
5 + if (typeof exports == "object" && typeof module == "object") // CommonJS
6 + mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"))
7 + else if (typeof define == "function" && define.amd) // AMD
8 + define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript"], mod)
9 + else // Plain browser env
10 + mod(CodeMirror)
11 +})(function(CodeMirror) {
12 + "use strict"
13 +
14 + // Depth means the amount of open braces in JS context, in XML
15 + // context 0 means not in tag, 1 means in tag, and 2 means in tag
16 + // and js block comment.
17 + function Context(state, mode, depth, prev) {
18 + this.state = state; this.mode = mode; this.depth = depth; this.prev = prev
19 + }
20 +
21 + function copyContext(context) {
22 + return new Context(CodeMirror.copyState(context.mode, context.state),
23 + context.mode,
24 + context.depth,
25 + context.prev && copyContext(context.prev))
26 + }
27 +
28 + CodeMirror.defineMode("jsx", function(config, modeConfig) {
29 + var xmlMode = CodeMirror.getMode(config, {name: "xml", allowMissing: true, multilineTagIndentPastTag: false})
30 + var jsMode = CodeMirror.getMode(config, modeConfig && modeConfig.base || "javascript")
31 +
32 + function flatXMLIndent(state) {
33 + var tagName = state.tagName
34 + state.tagName = null
35 + var result = xmlMode.indent(state, "")
36 + state.tagName = tagName
37 + return result
38 + }
39 +
40 + function token(stream, state) {
41 + if (state.context.mode == xmlMode)
42 + return xmlToken(stream, state, state.context)
43 + else
44 + return jsToken(stream, state, state.context)
45 + }
46 +
47 + function xmlToken(stream, state, cx) {
48 + if (cx.depth == 2) { // Inside a JS /* */ comment
49 + if (stream.match(/^.*?\*\//)) cx.depth = 1
50 + else stream.skipToEnd()
51 + return "comment"
52 + }
53 +
54 + if (stream.peek() == "{") {
55 + xmlMode.skipAttribute(cx.state)
56 +
57 + var indent = flatXMLIndent(cx.state), xmlContext = cx.state.context
58 + // If JS starts on same line as tag
59 + if (xmlContext && stream.match(/^[^>]*>\s*$/, false)) {
60 + while (xmlContext.prev && !xmlContext.startOfLine)
61 + xmlContext = xmlContext.prev
62 + // If tag starts the line, use XML indentation level
63 + if (xmlContext.startOfLine) indent -= config.indentUnit
64 + // Else use JS indentation level
65 + else if (cx.prev.state.lexical) indent = cx.prev.state.lexical.indented
66 + // Else if inside of tag
67 + } else if (cx.depth == 1) {
68 + indent += config.indentUnit
69 + }
70 +
71 + state.context = new Context(CodeMirror.startState(jsMode, indent),
72 + jsMode, 0, state.context)
73 + return null
74 + }
75 +
76 + if (cx.depth == 1) { // Inside of tag
77 + if (stream.peek() == "<") { // Tag inside of tag
78 + xmlMode.skipAttribute(cx.state)
79 + state.context = new Context(CodeMirror.startState(xmlMode, flatXMLIndent(cx.state)),
80 + xmlMode, 0, state.context)
81 + return null
82 + } else if (stream.match("//")) {
83 + stream.skipToEnd()
84 + return "comment"
85 + } else if (stream.match("/*")) {
86 + cx.depth = 2
87 + return token(stream, state)
88 + }
89 + }
90 +
91 + var style = xmlMode.token(stream, cx.state), cur = stream.current(), stop
92 + if (/\btag\b/.test(style)) {
93 + if (/>$/.test(cur)) {
94 + if (cx.state.context) cx.depth = 0
95 + else state.context = state.context.prev
96 + } else if (/^</.test(cur)) {
97 + cx.depth = 1
98 + }
99 + } else if (!style && (stop = cur.indexOf("{")) > -1) {
100 + stream.backUp(cur.length - stop)
101 + }
102 + return style
103 + }
104 +
105 + function jsToken(stream, state, cx) {
106 + if (stream.peek() == "<" && jsMode.expressionAllowed(stream, cx.state)) {
107 + jsMode.skipExpression(cx.state)
108 + state.context = new Context(CodeMirror.startState(xmlMode, jsMode.indent(cx.state, "")),
109 + xmlMode, 0, state.context)
110 + return null
111 + }
112 +
113 + var style = jsMode.token(stream, cx.state)
114 + if (!style && cx.depth != null) {
115 + var cur = stream.current()
116 + if (cur == "{") {
117 + cx.depth++
118 + } else if (cur == "}") {
119 + if (--cx.depth == 0) state.context = state.context.prev
120 + }
121 + }
122 + return style
123 + }
124 +
125 + return {
126 + startState: function() {
127 + return {context: new Context(CodeMirror.startState(jsMode), jsMode)}
128 + },
129 +
130 + copyState: function(state) {
131 + return {context: copyContext(state.context)}
132 + },
133 +
134 + token: token,
135 +
136 + indent: function(state, textAfter, fullLine) {
137 + return state.context.mode.indent(state.context.state, textAfter, fullLine)
138 + },
139 +
140 + innerMode: function(state) {
141 + return state.context
142 + }
143 + }
144 + }, "xml", "javascript")
145 +
146 + CodeMirror.defineMIME("text/jsx", "jsx")
147 +});
docs/js/xml.js new
+394
@@ -0,0 +1,394 @@
1 +// CodeMirror, copyright (c) by Marijn Haverbeke and others
2 +// Distributed under an MIT license: http://codemirror.net/LICENSE
3 +
4 +(function(mod) {
5 + if (typeof exports == "object" && typeof module == "object") // CommonJS
6 + mod(require("../../lib/codemirror"));
7 + else if (typeof define == "function" && define.amd) // AMD
8 + define(["../../lib/codemirror"], mod);
9 + else // Plain browser env
10 + mod(CodeMirror);
11 +})(function(CodeMirror) {
12 +"use strict";
13 +
14 +var htmlConfig = {
15 + autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
16 + 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
17 + 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
18 + 'track': true, 'wbr': true, 'menuitem': true},
19 + implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
20 + 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
21 + 'th': true, 'tr': true},
22 + contextGrabbers: {
23 + 'dd': {'dd': true, 'dt': true},
24 + 'dt': {'dd': true, 'dt': true},
25 + 'li': {'li': true},
26 + 'option': {'option': true, 'optgroup': true},
27 + 'optgroup': {'optgroup': true},
28 + 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
29 + 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
30 + 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
31 + 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
32 + 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
33 + 'rp': {'rp': true, 'rt': true},
34 + 'rt': {'rp': true, 'rt': true},
35 + 'tbody': {'tbody': true, 'tfoot': true},
36 + 'td': {'td': true, 'th': true},
37 + 'tfoot': {'tbody': true},
38 + 'th': {'td': true, 'th': true},
39 + 'thead': {'tbody': true, 'tfoot': true},
40 + 'tr': {'tr': true}
41 + },
42 + doNotIndent: {"pre": true},
43 + allowUnquoted: true,
44 + allowMissing: true,
45 + caseFold: true
46 +}
47 +
48 +var xmlConfig = {
49 + autoSelfClosers: {},
50 + implicitlyClosed: {},
51 + contextGrabbers: {},
52 + doNotIndent: {},
53 + allowUnquoted: false,
54 + allowMissing: false,
55 + caseFold: false
56 +}
57 +
58 +CodeMirror.defineMode("xml", function(editorConf, config_) {
59 + var indentUnit = editorConf.indentUnit
60 + var config = {}
61 + var defaults = config_.htmlMode ? htmlConfig : xmlConfig
62 + for (var prop in defaults) config[prop] = defaults[prop]
63 + for (var prop in config_) config[prop] = config_[prop]
64 +
65 + // Return variables for tokenizers
66 + var type, setStyle;
67 +
68 + function inText(stream, state) {
69 + function chain(parser) {
70 + state.tokenize = parser;
71 + return parser(stream, state);
72 + }
73 +
74 + var ch = stream.next();
75 + if (ch == "<") {
76 + if (stream.eat("!")) {
77 + if (stream.eat("[")) {
78 + if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
79 + else return null;
80 + } else if (stream.match("--")) {
81 + return chain(inBlock("comment", "-->"));
82 + } else if (stream.match("DOCTYPE", true, true)) {
83 + stream.eatWhile(/[\w\._\-]/);
84 + return chain(doctype(1));
85 + } else {
86 + return null;
87 + }
88 + } else if (stream.eat("?")) {
89 + stream.eatWhile(/[\w\._\-]/);
90 + state.tokenize = inBlock("meta", "?>");
91 + return "meta";
92 + } else {
93 + type = stream.eat("/") ? "closeTag" : "openTag";
94 + state.tokenize = inTag;
95 + return "tag bracket";
96 + }
97 + } else if (ch == "&") {
98 + var ok;
99 + if (stream.eat("#")) {
100 + if (stream.eat("x")) {
101 + ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
102 + } else {
103 + ok = stream.eatWhile(/[\d]/) && stream.eat(";");
104 + }
105 + } else {
106 + ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
107 + }
108 + return ok ? "atom" : "error";
109 + } else {
110 + stream.eatWhile(/[^&<]/);
111 + return null;
112 + }
113 + }
114 + inText.isInText = true;
115 +
116 + function inTag(stream, state) {
117 + var ch = stream.next();
118 + if (ch == ">" || (ch == "/" && stream.eat(">"))) {
119 + state.tokenize = inText;
120 + type = ch == ">" ? "endTag" : "selfcloseTag";
121 + return "tag bracket";
122 + } else if (ch == "=") {
123 + type = "equals";
124 + return null;
125 + } else if (ch == "<") {
126 + state.tokenize = inText;
127 + state.state = baseState;
128 + state.tagName = state.tagStart = null;
129 + var next = state.tokenize(stream, state);
130 + return next ? next + " tag error" : "tag error";
131 + } else if (/[\'\"]/.test(ch)) {
132 + state.tokenize = inAttribute(ch);
133 + state.stringStartCol = stream.column();
134 + return state.tokenize(stream, state);
135 + } else {
136 + stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
137 + return "word";
138 + }
139 + }
140 +
141 + function inAttribute(quote) {
142 + var closure = function(stream, state) {
143 + while (!stream.eol()) {
144 + if (stream.next() == quote) {
145 + state.tokenize = inTag;
146 + break;
147 + }
148 + }
149 + return "string";
150 + };
151 + closure.isInAttribute = true;
152 + return closure;
153 + }
154 +
155 + function inBlock(style, terminator) {
156 + return function(stream, state) {
157 + while (!stream.eol()) {
158 + if (stream.match(terminator)) {
159 + state.tokenize = inText;
160 + break;
161 + }
162 + stream.next();
163 + }
164 + return style;
165 + };
166 + }
167 + function doctype(depth) {
168 + return function(stream, state) {
169 + var ch;
170 + while ((ch = stream.next()) != null) {
171 + if (ch == "<") {
172 + state.tokenize = doctype(depth + 1);
173 + return state.tokenize(stream, state);
174 + } else if (ch == ">") {
175 + if (depth == 1) {
176 + state.tokenize = inText;
177 + break;
178 + } else {
179 + state.tokenize = doctype(depth - 1);
180 + return state.tokenize(stream, state);
181 + }
182 + }
183 + }
184 + return "meta";
185 + };
186 + }
187 +
188 + function Context(state, tagName, startOfLine) {
189 + this.prev = state.context;
190 + this.tagName = tagName;
191 + this.indent = state.indented;
192 + this.startOfLine = startOfLine;
193 + if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
194 + this.noIndent = true;
195 + }
196 + function popContext(state) {
197 + if (state.context) state.context = state.context.prev;
198 + }
199 + function maybePopContext(state, nextTagName) {
200 + var parentTagName;
201 + while (true) {
202 + if (!state.context) {
203 + return;
204 + }
205 + parentTagName = state.context.tagName;
206 + if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||
207 + !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
208 + return;
209 + }
210 + popContext(state);
211 + }
212 + }
213 +
214 + function baseState(type, stream, state) {
215 + if (type == "openTag") {
216 + state.tagStart = stream.column();
217 + return tagNameState;
218 + } else if (type == "closeTag") {
219 + return closeTagNameState;
220 + } else {
221 + return baseState;
222 + }
223 + }
224 + function tagNameState(type, stream, state) {
225 + if (type == "word") {
226 + state.tagName = stream.current();
227 + setStyle = "tag";
228 + return attrState;
229 + } else {
230 + setStyle = "error";
231 + return tagNameState;
232 + }
233 + }
234 + function closeTagNameState(type, stream, state) {
235 + if (type == "word") {
236 + var tagName = stream.current();
237 + if (state.context && state.context.tagName != tagName &&
238 + config.implicitlyClosed.hasOwnProperty(state.context.tagName))
239 + popContext(state);
240 + if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
241 + setStyle = "tag";
242 + return closeState;
243 + } else {
244 + setStyle = "tag error";
245 + return closeStateErr;
246 + }
247 + } else {
248 + setStyle = "error";
249 + return closeStateErr;
250 + }
251 + }
252 +
253 + function closeState(type, _stream, state) {
254 + if (type != "endTag") {
255 + setStyle = "error";
256 + return closeState;
257 + }
258 + popContext(state);
259 + return baseState;
260 + }
261 + function closeStateErr(type, stream, state) {
262 + setStyle = "error";
263 + return closeState(type, stream, state);
264 + }
265 +
266 + function attrState(type, _stream, state) {
267 + if (type == "word") {
268 + setStyle = "attribute";
269 + return attrEqState;
270 + } else if (type == "endTag" || type == "selfcloseTag") {
271 + var tagName = state.tagName, tagStart = state.tagStart;
272 + state.tagName = state.tagStart = null;
273 + if (type == "selfcloseTag" ||
274 + config.autoSelfClosers.hasOwnProperty(tagName)) {
275 + maybePopContext(state, tagName);
276 + } else {
277 + maybePopContext(state, tagName);
278 + state.context = new Context(state, tagName, tagStart == state.indented);
279 + }
280 + return baseState;
281 + }
282 + setStyle = "error";
283 + return attrState;
284 + }
285 + function attrEqState(type, stream, state) {
286 + if (type == "equals") return attrValueState;
287 + if (!config.allowMissing) setStyle = "error";
288 + return attrState(type, stream, state);
289 + }
290 + function attrValueState(type, stream, state) {
291 + if (type == "string") return attrContinuedState;
292 + if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;}
293 + setStyle = "error";
294 + return attrState(type, stream, state);
295 + }
296 + function attrContinuedState(type, stream, state) {
297 + if (type == "string") return attrContinuedState;
298 + return attrState(type, stream, state);
299 + }
300 +
301 + return {
302 + startState: function(baseIndent) {
303 + var state = {tokenize: inText,
304 + state: baseState,
305 + indented: baseIndent || 0,
306 + tagName: null, tagStart: null,
307 + context: null}
308 + if (baseIndent != null) state.baseIndent = baseIndent
309 + return state
310 + },
311 +
312 + token: function(stream, state) {
313 + if (!state.tagName && stream.sol())
314 + state.indented = stream.indentation();
315 +
316 + if (stream.eatSpace()) return null;
317 + type = null;
318 + var style = state.tokenize(stream, state);
319 + if ((style || type) && style != "comment") {
320 + setStyle = null;
321 + state.state = state.state(type || style, stream, state);
322 + if (setStyle)
323 + style = setStyle == "error" ? style + " error" : setStyle;
324 + }
325 + return style;
326 + },
327 +
328 + indent: function(state, textAfter, fullLine) {
329 + var context = state.context;
330 + // Indent multi-line strings (e.g. css).
331 + if (state.tokenize.isInAttribute) {
332 + if (state.tagStart == state.indented)
333 + return state.stringStartCol + 1;
334 + else
335 + return state.indented + indentUnit;
336 + }
337 + if (context && context.noIndent) return CodeMirror.Pass;
338 + if (state.tokenize != inTag && state.tokenize != inText)
339 + return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
340 + // Indent the starts of attribute names.
341 + if (state.tagName) {
342 + if (config.multilineTagIndentPastTag !== false)
343 + return state.tagStart + state.tagName.length + 2;
344 + else
345 + return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
346 + }
347 + if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
348 + var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
349 + if (tagAfter && tagAfter[1]) { // Closing tag spotted
350 + while (context) {
351 + if (context.tagName == tagAfter[2]) {
352 + context = context.prev;
353 + break;
354 + } else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {
355 + context = context.prev;
356 + } else {
357 + break;
358 + }
359 + }
360 + } else if (tagAfter) { // Opening tag spotted
361 + while (context) {
362 + var grabbers = config.contextGrabbers[context.tagName];
363 + if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
364 + context = context.prev;
365 + else
366 + break;
367 + }
368 + }
369 + while (context && context.prev && !context.startOfLine)
370 + context = context.prev;
371 + if (context) return context.indent + indentUnit;
372 + else return state.baseIndent || 0;
373 + },
374 +
375 + electricInput: /<\/[\s\w:]+>$/,
376 + blockCommentStart: "<!--",
377 + blockCommentEnd: "-->",
378 +
379 + configuration: config.htmlMode ? "html" : "xml",
380 + helperType: config.htmlMode ? "html" : "xml",
381 +
382 + skipAttribute: function(state) {
383 + if (state.state == attrValueState)
384 + state.state = attrState
385 + }
386 + };
387 +});
388 +
389 +CodeMirror.defineMIME("text/xml", "xml");
390 +CodeMirror.defineMIME("application/xml", "xml");
391 +if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
392 + CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
393 +
394 +});