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
+});