Show More
@@ -1,174 +1,246 b'' | |||
|
1 | /* BASICS */ | |
|
2 | ||
|
1 | 3 | .CodeMirror { |
|
2 | line-height: 1em; | |
|
4 | /* Set height, width, borders, and global font properties here */ | |
|
3 | 5 | font-family: monospace; |
|
6 | height: 300px; | |
|
7 | } | |
|
8 | .CodeMirror-scroll { | |
|
9 | /* Set scrolling behaviour here */ | |
|
10 | overflow: auto; | |
|
11 | } | |
|
12 | ||
|
13 | /* PADDING */ | |
|
14 | ||
|
15 | .CodeMirror-lines { | |
|
16 | padding: 4px 0; /* Vertical padding around content */ | |
|
17 | } | |
|
18 | .CodeMirror pre { | |
|
19 | padding: 0 4px; /* Horizontal padding of content */ | |
|
20 | } | |
|
21 | ||
|
22 | .CodeMirror-scrollbar-filler { | |
|
23 | background-color: white; /* The little square between H and V scrollbars */ | |
|
24 | } | |
|
25 | ||
|
26 | /* GUTTER */ | |
|
27 | ||
|
28 | .CodeMirror-gutters { | |
|
29 | border-right: 1px solid #ddd; | |
|
30 | background-color: #f7f7f7; | |
|
31 | } | |
|
32 | .CodeMirror-linenumbers {} | |
|
33 | .CodeMirror-linenumber { | |
|
34 | padding: 0 3px 0 5px; | |
|
35 | min-width: 20px; | |
|
36 | text-align: right; | |
|
37 | color: #999; | |
|
38 | } | |
|
39 | ||
|
40 | /* CURSOR */ | |
|
4 | 41 | |
|
5 | /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */ | |
|
42 | .CodeMirror div.CodeMirror-cursor { | |
|
43 | border-left: 1px solid black; | |
|
44 | z-index: 3; | |
|
45 | } | |
|
46 | /* Shown when moving in bi-directional text */ | |
|
47 | .CodeMirror div.CodeMirror-secondarycursor { | |
|
48 | border-left: 1px solid silver; | |
|
49 | } | |
|
50 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { | |
|
51 | width: auto; | |
|
52 | border: 0; | |
|
53 | background: #7e7; | |
|
54 | z-index: 1; | |
|
55 | } | |
|
56 | /* Can style cursor different in overwrite (non-insert) mode */ | |
|
57 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} | |
|
58 | ||
|
59 | .cm-tab { display: inline-block; } | |
|
60 | ||
|
61 | /* DEFAULT THEME */ | |
|
62 | ||
|
63 | .cm-s-default .cm-keyword {color: #708;} | |
|
64 | .cm-s-default .cm-atom {color: #219;} | |
|
65 | .cm-s-default .cm-number {color: #164;} | |
|
66 | .cm-s-default .cm-def {color: #00f;} | |
|
67 | .cm-s-default .cm-variable {color: black;} | |
|
68 | .cm-s-default .cm-variable-2 {color: #05a;} | |
|
69 | .cm-s-default .cm-variable-3 {color: #085;} | |
|
70 | .cm-s-default .cm-property {color: black;} | |
|
71 | .cm-s-default .cm-operator {color: black;} | |
|
72 | .cm-s-default .cm-comment {color: #a50;} | |
|
73 | .cm-s-default .cm-string {color: #a11;} | |
|
74 | .cm-s-default .cm-string-2 {color: #f50;} | |
|
75 | .cm-s-default .cm-meta {color: #555;} | |
|
76 | .cm-s-default .cm-error {color: #f00;} | |
|
77 | .cm-s-default .cm-qualifier {color: #555;} | |
|
78 | .cm-s-default .cm-builtin {color: #30a;} | |
|
79 | .cm-s-default .cm-bracket {color: #997;} | |
|
80 | .cm-s-default .cm-tag {color: #170;} | |
|
81 | .cm-s-default .cm-attribute {color: #00c;} | |
|
82 | .cm-s-default .cm-header {color: blue;} | |
|
83 | .cm-s-default .cm-quote {color: #090;} | |
|
84 | .cm-s-default .cm-hr {color: #999;} | |
|
85 | .cm-s-default .cm-link {color: #00c;} | |
|
86 | ||
|
87 | .cm-negative {color: #d44;} | |
|
88 | .cm-positive {color: #292;} | |
|
89 | .cm-header, .cm-strong {font-weight: bold;} | |
|
90 | .cm-em {font-style: italic;} | |
|
91 | .cm-link {text-decoration: underline;} | |
|
92 | ||
|
93 | .cm-invalidchar {color: #f00;} | |
|
94 | ||
|
95 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} | |
|
96 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} | |
|
97 | ||
|
98 | /* STOP */ | |
|
99 | ||
|
100 | /* The rest of this file contains styles related to the mechanics of | |
|
101 | the editor. You probably shouldn't touch them. */ | |
|
102 | ||
|
103 | .CodeMirror { | |
|
104 | line-height: 1; | |
|
6 | 105 | position: relative; |
|
7 | /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */ | |
|
8 | 106 | overflow: hidden; |
|
107 | background: white; | |
|
108 | color: black; | |
|
9 | 109 | } |
|
10 | 110 | |
|
11 | 111 | .CodeMirror-scroll { |
|
12 | overflow: auto; | |
|
13 | height: 300px; | |
|
14 | /* This is needed to prevent an IE[67] bug where the scrolled content | |
|
15 | is visible outside of the scrolling box. */ | |
|
112 | /* 30px is the magic margin used to hide the element's real scrollbars */ | |
|
113 | /* See overflow: hidden in .CodeMirror */ | |
|
114 | margin-bottom: -30px; margin-right: -30px; | |
|
115 | padding-bottom: 30px; padding-right: 30px; | |
|
116 | height: 100%; | |
|
117 | outline: none; /* Prevent dragging from highlighting the element */ | |
|
16 | 118 | position: relative; |
|
17 | outline: none; | |
|
119 | } | |
|
120 | .CodeMirror-sizer { | |
|
121 | position: relative; | |
|
18 | 122 | } |
|
19 | 123 | |
|
20 | /* Vertical scrollbar */ | |
|
21 | .CodeMirror-scrollbar { | |
|
124 | /* The fake, visible scrollbars. Used to force redraw during scrolling | |
|
125 | before actuall scrolling happens, thus preventing shaking and | |
|
126 | flickering artifacts. */ | |
|
127 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler { | |
|
22 | 128 | position: absolute; |
|
129 | z-index: 6; | |
|
130 | display: none; | |
|
131 | } | |
|
132 | .CodeMirror-vscrollbar { | |
|
23 | 133 | right: 0; top: 0; |
|
24 | 134 | overflow-x: hidden; |
|
25 | 135 | overflow-y: scroll; |
|
26 | z-index: 5; | |
|
27 | } | |
|
28 | .CodeMirror-scrollbar-inner { | |
|
29 | /* This needs to have a nonzero width in order for the scrollbar to appear | |
|
30 | in Firefox and IE9. */ | |
|
31 | width: 1px; | |
|
32 | 136 | } |
|
33 |
.CodeMirror-scrollbar |
|
|
34 | /* Ensure that the scrollbar appears in Lion, and that it overlaps the content | |
|
35 | rather than sitting to the right of it. */ | |
|
36 | position: absolute; | |
|
37 | z-index: 1; | |
|
38 | float: none; | |
|
39 | right: 0; | |
|
40 | min-width: 12px; | |
|
137 | .CodeMirror-hscrollbar { | |
|
138 | bottom: 0; left: 0; | |
|
139 | overflow-y: hidden; | |
|
140 | overflow-x: scroll; | |
|
41 | 141 | } |
|
42 |
.CodeMirror-scrollbar |
|
|
43 | min-width: 12px; | |
|
44 | } | |
|
45 | .CodeMirror-scrollbar.cm-sb-ie7 { | |
|
46 | min-width: 18px; | |
|
142 | .CodeMirror-scrollbar-filler { | |
|
143 | right: 0; bottom: 0; | |
|
144 | z-index: 6; | |
|
47 | 145 | } |
|
48 | 146 | |
|
49 | .CodeMirror-gutter { | |
|
147 | .CodeMirror-gutters { | |
|
50 | 148 | position: absolute; left: 0; top: 0; |
|
51 | z-index: 10; | |
|
52 | background-color: #f7f7f7; | |
|
53 | border-right: 1px solid #eee; | |
|
54 | min-width: 2em; | |
|
55 | 149 | height: 100%; |
|
150 | padding-bottom: 30px; | |
|
151 | z-index: 3; | |
|
56 | 152 | } |
|
57 |
.CodeMirror-gutter |
|
|
58 | color: #aaa; | |
|
59 | text-align: right; | |
|
60 | padding: .4em .2em .4em .4em; | |
|
61 | white-space: pre !important; | |
|
153 | .CodeMirror-gutter { | |
|
154 | height: 100%; | |
|
155 | padding-bottom: 30px; | |
|
156 | margin-bottom: -32px; | |
|
157 | display: inline-block; | |
|
158 | /* Hack to make IE7 behave */ | |
|
159 | *zoom:1; | |
|
160 | *display:inline; | |
|
161 | } | |
|
162 | .CodeMirror-gutter-elt { | |
|
163 | position: absolute; | |
|
62 | 164 | cursor: default; |
|
165 | z-index: 4; | |
|
63 | 166 | } |
|
167 | ||
|
64 | 168 | .CodeMirror-lines { |
|
65 | padding: .4em; | |
|
66 | white-space: pre; | |
|
67 | 169 | cursor: text; |
|
68 | 170 | } |
|
69 | ||
|
70 | 171 | .CodeMirror pre { |
|
71 | -moz-border-radius: 0; | |
|
72 | -webkit-border-radius: 0; | |
|
73 |
|
|
|
74 | border-radius: 0; | |
|
75 | border-width: 0; margin: 0; padding: 0; background: transparent; | |
|
172 | /* Reset some styles that the rest of the page might have set */ | |
|
173 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; | |
|
174 | border-width: 0; | |
|
175 | background: transparent; | |
|
76 | 176 | font-family: inherit; |
|
77 | 177 | font-size: inherit; |
|
78 |
|
|
|
178 | margin: 0; | |
|
79 | 179 | white-space: pre; |
|
80 | 180 | word-wrap: normal; |
|
81 | 181 | line-height: inherit; |
|
82 | 182 | color: inherit; |
|
183 | z-index: 2; | |
|
184 | position: relative; | |
|
83 | 185 | overflow: visible; |
|
84 | 186 | } |
|
85 | ||
|
86 | 187 | .CodeMirror-wrap pre { |
|
87 | 188 | word-wrap: break-word; |
|
88 | 189 | white-space: pre-wrap; |
|
89 | 190 | word-break: normal; |
|
90 | 191 | } |
|
192 | .CodeMirror-linebackground { | |
|
193 | position: absolute; | |
|
194 | left: 0; right: 0; top: 0; bottom: 0; | |
|
195 | z-index: 0; | |
|
196 | } | |
|
197 | ||
|
198 | .CodeMirror-linewidget { | |
|
199 | position: relative; | |
|
200 | z-index: 2; | |
|
201 | overflow: auto; | |
|
202 | } | |
|
203 | ||
|
204 | .CodeMirror-widget { | |
|
205 | display: inline-block; | |
|
206 | } | |
|
207 | ||
|
91 | 208 | .CodeMirror-wrap .CodeMirror-scroll { |
|
92 | 209 | overflow-x: hidden; |
|
93 | 210 | } |
|
94 | 211 | |
|
95 |
.CodeMirror |
|
|
96 | outline: none !important; | |
|
212 | .CodeMirror-measure { | |
|
213 | position: absolute; | |
|
214 | width: 100%; height: 0px; | |
|
215 | overflow: hidden; | |
|
216 | visibility: hidden; | |
|
97 | 217 | } |
|
218 | .CodeMirror-measure pre { position: static; } | |
|
98 | 219 | |
|
99 |
.CodeMirror |
|
|
100 | z-index: 10; | |
|
220 | .CodeMirror div.CodeMirror-cursor { | |
|
101 | 221 | position: absolute; |
|
102 | 222 | visibility: hidden; |
|
103 | border-left: 1px solid black; | |
|
104 | 223 | border-right: none; |
|
105 | 224 | width: 0; |
|
106 | 225 | } |
|
107 |
. |
|
|
108 | width: auto; | |
|
109 | border: 0; | |
|
110 | background: transparent; | |
|
111 | background: rgba(0, 200, 0, .4); | |
|
112 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); | |
|
113 | } | |
|
114 | /* Kludge to turn off filter in ie9+, which also accepts rgba */ | |
|
115 | .cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) { | |
|
116 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); | |
|
117 | } | |
|
118 | .CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} | |
|
119 | .CodeMirror-focused pre.CodeMirror-cursor { | |
|
226 | .CodeMirror-focused div.CodeMirror-cursor { | |
|
120 | 227 | visibility: visible; |
|
121 | 228 | } |
|
122 | 229 | |
|
123 |
|
|
|
124 |
.CodeMirror-focused |
|
|
230 | .CodeMirror-selected { background: #d9d9d9; } | |
|
231 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } | |
|
125 | 232 | |
|
126 |
. |
|
|
233 | .cm-searching { | |
|
127 | 234 | background: #ffa; |
|
128 | 235 | background: rgba(255, 255, 0, .4); |
|
129 | 236 | } |
|
130 | 237 | |
|
131 | /* Default theme */ | |
|
132 | ||
|
133 | .cm-s-default span.cm-keyword {color: #708;} | |
|
134 | .cm-s-default span.cm-atom {color: #219;} | |
|
135 | .cm-s-default span.cm-number {color: #164;} | |
|
136 | .cm-s-default span.cm-def {color: #00f;} | |
|
137 | .cm-s-default span.cm-variable {color: black;} | |
|
138 | .cm-s-default span.cm-variable-2 {color: #05a;} | |
|
139 | .cm-s-default span.cm-variable-3 {color: #085;} | |
|
140 | .cm-s-default span.cm-property {color: black;} | |
|
141 | .cm-s-default span.cm-operator {color: black;} | |
|
142 | .cm-s-default span.cm-comment {color: #a50;} | |
|
143 | .cm-s-default span.cm-string {color: #a11;} | |
|
144 | .cm-s-default span.cm-string-2 {color: #f50;} | |
|
145 | .cm-s-default span.cm-meta {color: #555;} | |
|
146 | .cm-s-default span.cm-error {color: #f00;} | |
|
147 | .cm-s-default span.cm-qualifier {color: #555;} | |
|
148 | .cm-s-default span.cm-builtin {color: #30a;} | |
|
149 | .cm-s-default span.cm-bracket {color: #997;} | |
|
150 | .cm-s-default span.cm-tag {color: #170;} | |
|
151 | .cm-s-default span.cm-attribute {color: #00c;} | |
|
152 | .cm-s-default span.cm-header {color: blue;} | |
|
153 | .cm-s-default span.cm-quote {color: #090;} | |
|
154 | .cm-s-default span.cm-hr {color: #999;} | |
|
155 | .cm-s-default span.cm-link {color: #00c;} | |
|
156 | ||
|
157 | span.cm-header, span.cm-strong {font-weight: bold;} | |
|
158 | span.cm-em {font-style: italic;} | |
|
159 | span.cm-emstrong {font-style: italic; font-weight: bold;} | |
|
160 | span.cm-link {text-decoration: underline;} | |
|
161 | ||
|
162 | span.cm-invalidchar {color: #f00;} | |
|
163 | ||
|
164 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} | |
|
165 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} | |
|
238 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ | |
|
239 | .CodeMirror span { *vertical-align: text-bottom; } | |
|
166 | 240 | |
|
167 | 241 | @media print { |
|
168 | ||
|
169 | 242 | /* Hide the cursor when printing */ |
|
170 |
.CodeMirror |
|
|
243 | .CodeMirror div.CodeMirror-cursor { | |
|
171 | 244 | visibility: hidden; |
|
172 | 245 | } |
|
173 | ||
|
174 | 246 | } |
This diff has been collapsed as it changes many lines, (7552 lines changed) Show them Hide them | |||
@@ -1,3169 +1,5583 b'' | |||
|
1 | // All functions that need access to the editor's state live inside | |
|
2 | // the CodeMirror function. Below that, at the bottom of the file, | |
|
3 | // some utilities are defined. | |
|
4 | ||
|
5 | 1 | // CodeMirror is the only global var we claim |
|
6 | 2 |
|
|
7 | 3 | "use strict"; |
|
8 | // This is the function that produces an editor instance. Its | |
|
9 | // closure is used to store the editor state. | |
|
10 | function CodeMirror(place, givenOptions) { | |
|
4 | ||
|
5 | // BROWSER SNIFFING | |
|
6 | ||
|
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 | var ie = /MSIE \d/.test(navigator.userAgent); | |
|
11 | var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8); | |
|
12 | var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); | |
|
13 | var webkit = /WebKit\//.test(navigator.userAgent); | |
|
14 | var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); | |
|
15 | var chrome = /Chrome\//.test(navigator.userAgent); | |
|
16 | var opera = /Opera\//.test(navigator.userAgent); | |
|
17 | var safari = /Apple Computer/.test(navigator.vendor); | |
|
18 | var khtml = /KHTML\//.test(navigator.userAgent); | |
|
19 | var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent); | |
|
20 | var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); | |
|
21 | var phantom = /PhantomJS/.test(navigator.userAgent); | |
|
22 | ||
|
23 | var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); | |
|
24 | // This is woefully incomplete. Suggestions for alternative methods welcome. | |
|
25 | var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); | |
|
26 | var mac = ios || /Mac/.test(navigator.platform); | |
|
27 | var windows = /windows/i.test(navigator.platform); | |
|
28 | ||
|
29 | var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/); | |
|
30 | if (opera_version) opera_version = Number(opera_version[1]); | |
|
31 | // Some browsers use the wrong event properties to signal cmd/ctrl on OS X | |
|
32 | var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11)); | |
|
33 | var captureMiddleClick = gecko || (ie && !ie_lt9); | |
|
34 | ||
|
35 | // Optimize some code when these features are not used | |
|
36 | var sawReadOnlySpans = false, sawCollapsedSpans = false; | |
|
37 | ||
|
38 | // CONSTRUCTOR | |
|
39 | ||
|
40 | function CodeMirror(place, options) { | |
|
41 | if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); | |
|
42 | ||
|
43 | this.options = options = options || {}; | |
|
11 | 44 | // Determine effective options based on given values and defaults. |
|
12 | var options = {}, defaults = CodeMirror.defaults; | |
|
13 | for (var opt in defaults) | |
|
14 | if (defaults.hasOwnProperty(opt)) | |
|
15 | options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; | |
|
16 | ||
|
17 | var input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em"); | |
|
18 | input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); | |
|
45 | for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt)) | |
|
46 | options[opt] = defaults[opt]; | |
|
47 | setGuttersForLineNumbers(options); | |
|
48 | ||
|
49 | var docStart = typeof options.value == "string" ? 0 : options.value.first; | |
|
50 | var display = this.display = makeDisplay(place, docStart); | |
|
51 | display.wrapper.CodeMirror = this; | |
|
52 | updateGutters(this); | |
|
53 | if (options.autofocus && !mobile) focusInput(this); | |
|
54 | ||
|
55 | this.state = {keyMaps: [], | |
|
56 | overlays: [], | |
|
57 | modeGen: 0, | |
|
58 | overwrite: false, focused: false, | |
|
59 | suppressEdits: false, pasteIncoming: false, | |
|
60 | draggingText: false, | |
|
61 | highlight: new Delayed()}; | |
|
62 | ||
|
63 | themeChanged(this); | |
|
64 | if (options.lineWrapping) | |
|
65 | this.display.wrapper.className += " CodeMirror-wrap"; | |
|
66 | ||
|
67 | var doc = options.value; | |
|
68 | if (typeof doc == "string") doc = new Doc(options.value, options.mode); | |
|
69 | operation(this, attachDoc)(this, doc); | |
|
70 | ||
|
71 | // Override magic textarea content restore that IE sometimes does | |
|
72 | // on our hidden textarea on reload | |
|
73 | if (ie) setTimeout(bind(resetInput, this, true), 20); | |
|
74 | ||
|
75 | registerEventHandlers(this); | |
|
76 | // IE throws unspecified error in certain cases, when | |
|
77 | // trying to access activeElement before onload | |
|
78 | var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { } | |
|
79 | if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20); | |
|
80 | else onBlur(this); | |
|
81 | ||
|
82 | operation(this, function() { | |
|
83 | for (var opt in optionHandlers) | |
|
84 | if (optionHandlers.propertyIsEnumerable(opt)) | |
|
85 | optionHandlers[opt](this, options[opt], Init); | |
|
86 | for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); | |
|
87 | })(); | |
|
88 | } | |
|
89 | ||
|
90 | // DISPLAY CONSTRUCTOR | |
|
91 | ||
|
92 | function makeDisplay(place, docStart) { | |
|
93 | var d = {}; | |
|
94 | ||
|
95 | var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;"); | |
|
96 | if (webkit) input.style.width = "1000px"; | |
|
97 | else input.setAttribute("wrap", "off"); | |
|
98 | // if border: 0; -- iOS fails to open keyboard (issue #1287) | |
|
99 | if (ios) input.style.border = "1px solid black"; | |
|
100 | input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); | |
|
101 | ||
|
19 | 102 | // Wraps and hides input textarea |
|
20 |
|
|
|
21 | // The empty scrollbar content, used solely for managing the scrollbar thumb. | |
|
22 |
|
|
|
23 | // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself. | |
|
24 |
|
|
|
103 | d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); | |
|
104 | // The actual fake scrollbars. | |
|
105 | d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar"); | |
|
106 | d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar"); | |
|
107 | d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); | |
|
25 | 108 | // DIVs containing the selection and the actual code |
|
26 | var lineDiv = elt("div"), selectionDiv = elt("div", null, null, "position: relative; z-index: -1"); | |
|
109 | d.lineDiv = elt("div"); | |
|
110 | d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); | |
|
27 | 111 | // Blinky cursor, and element used to ensure cursor fits at the end of a line |
|
28 | var cursor = elt("pre", "\u00a0", "CodeMirror-cursor"), widthForcer = elt("pre", "\u00a0", "CodeMirror-cursor", "visibility: hidden"); | |
|
112 | d.cursor = elt("div", "\u00a0", "CodeMirror-cursor"); | |
|
113 | // Secondary cursor, shown when on a 'jump' in bi-directional text | |
|
114 | d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"); | |
|
29 | 115 | // Used to measure text size |
|
30 | var measure = elt("div", null, null, "position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;"); | |
|
31 | var lineSpace = elt("div", [measure, cursor, widthForcer, selectionDiv, lineDiv], null, "position: relative; z-index: 0"); | |
|
32 | var gutterText = elt("div", null, "CodeMirror-gutter-text"), gutter = elt("div", [gutterText], "CodeMirror-gutter"); | |
|
116 | d.measure = elt("div", null, "CodeMirror-measure"); | |
|
117 | // Wraps everything that needs to exist inside the vertically-padded coordinate system | |
|
118 | d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor], | |
|
119 | null, "position: relative; outline: none"); | |
|
33 | 120 | // Moved around its parent to cover visible view |
|
34 |
|
|
|
121 | d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); | |
|
35 | 122 | // Set to the height of the text, causes scrolling |
|
36 |
|
|
|
123 | d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); | |
|
124 | // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers | |
|
125 | d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;"); | |
|
126 | // Will contain the gutters, if any | |
|
127 | d.gutters = elt("div", null, "CodeMirror-gutters"); | |
|
128 | d.lineGutter = null; | |
|
129 | // Helper element to properly size the gutter backgrounds | |
|
130 | var scrollerInner = elt("div", [d.sizer, d.heightForcer, d.gutters], null, "position: relative; min-height: 100%"); | |
|
37 | 131 | // Provides scrolling |
|
38 |
|
|
|
39 | scroller.setAttribute("tabIndex", "-1"); | |
|
132 | d.scroller = elt("div", [scrollerInner], "CodeMirror-scroll"); | |
|
133 | d.scroller.setAttribute("tabIndex", "-1"); | |
|
40 | 134 | // The element in which the editor lives. |
|
41 | var wrapper = elt("div", [inputDiv, scrollbar, scroller], "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "")); | |
|
42 | if (place.appendChild) place.appendChild(wrapper); else place(wrapper); | |
|
43 | ||
|
44 | themeChanged(); keyMapChanged(); | |
|
135 | d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV, | |
|
136 | d.scrollbarFiller, d.scroller], "CodeMirror"); | |
|
137 | // Work around IE7 z-index bug | |
|
138 | if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } | |
|
139 | if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper); | |
|
140 | ||
|
45 | 141 | // Needed to hide big blue blinking cursor on Mobile Safari |
|
46 | 142 | if (ios) input.style.width = "0px"; |
|
47 | if (!webkit) scroller.draggable = true; | |
|
48 | lineSpace.style.outline = "none"; | |
|
49 | if (options.tabindex != null) input.tabIndex = options.tabindex; | |
|
50 | if (options.autofocus) focusInput(); | |
|
51 | if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; | |
|
143 | if (!webkit) d.scroller.draggable = true; | |
|
52 | 144 | // Needed to handle Tab key in KHTML |
|
53 |
if (khtml) inputDiv.style.height = "1px" |
|
|
54 | ||
|
55 | // Check for OS X >= 10.7. This has transparent scrollbars, so the | |
|
56 | // overlaying of one scrollbar with another won't work. This is a | |
|
57 | // temporary hack to simply turn off the overlay scrollbar. See | |
|
58 | // issue #727. | |
|
59 | if (mac_geLion) { scrollbar.style.zIndex = -2; scrollbar.style.visibility = "hidden"; } | |
|
145 | if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; } | |
|
60 | 146 | // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). |
|
61 | else if (ie_lt8) scrollbar.style.minWidth = "18px"; | |
|
62 | ||
|
63 | // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. | |
|
64 | var poll = new Delayed(), highlight = new Delayed(), blinker; | |
|
65 | ||
|
66 | // mode holds a mode API object. doc is the tree of Line objects, | |
|
67 | // frontier is the point up to which the content has been parsed, | |
|
68 | // and history the undo history (instance of History constructor). | |
|
69 | var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), frontier = 0, focused; | |
|
70 | loadMode(); | |
|
71 | // The selection. These are always maintained to point at valid | |
|
72 | // positions. Inverted is used to remember that the user is | |
|
73 | // selecting bottom-to-top. | |
|
74 | var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; | |
|
75 | // Selection-related flags. shiftSelecting obviously tracks | |
|
76 | // whether the user is holding shift. | |
|
77 | var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, draggingText, | |
|
78 | overwrite = false, suppressEdits = false, pasteIncoming = false; | |
|
79 | // Variables used by startOperation/endOperation to track what | |
|
80 | // happened during the operation. | |
|
81 | var updateInput, userSelChange, changes, textChanged, selectionChanged, | |
|
82 | gutterDirty, callbacks; | |
|
147 | else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px"; | |
|
148 | ||
|
83 | 149 | // Current visible range (may be bigger than the view window). |
|
84 | var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; | |
|
85 | // bracketHighlighted is used to remember that a bracket has been | |
|
86 | // marked. | |
|
87 | var bracketHighlighted; | |
|
150 | d.viewOffset = d.lastSizeC = 0; | |
|
151 | d.showingFrom = d.showingTo = docStart; | |
|
152 | ||
|
153 | // Used to only resize the line number gutter when necessary (when | |
|
154 | // the amount of lines crosses a boundary that makes its width change) | |
|
155 | d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; | |
|
156 | // See readInput and resetInput | |
|
157 | d.prevInput = ""; | |
|
158 | // Set to true when a non-horizontal-scrolling widget is added. As | |
|
159 | // an optimization, widget aligning is skipped when d is false. | |
|
160 | d.alignWidgets = false; | |
|
161 | // Flag that indicates whether we currently expect input to appear | |
|
162 | // (after some event like 'keypress' or 'input') and are polling | |
|
163 | // intensively. | |
|
164 | d.pollingFast = false; | |
|
165 | // Self-resetting timeout for the poller | |
|
166 | d.poll = new Delayed(); | |
|
167 | ||
|
168 | d.cachedCharWidth = d.cachedTextHeight = null; | |
|
169 | d.measureLineCache = []; | |
|
170 | d.measureLineCachePos = 0; | |
|
171 | ||
|
172 | // Tracks when resetInput has punted to just putting a short | |
|
173 | // string instead of the (large) selection. | |
|
174 | d.inaccurateSelection = false; | |
|
175 | ||
|
88 | 176 | // Tracks the maximum line length so that the horizontal scrollbar |
|
89 | 177 | // can be kept static when scrolling. |
|
90 | var maxLine = getLine(0), updateMaxLine = false, maxLineChanged = true; | |
|
91 | var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll | |
|
92 | var goalColumn = null; | |
|
93 | ||
|
94 | // Initialize the content. | |
|
95 | operation(function(){setValue(options.value || ""); updateInput = false;})(); | |
|
96 | var history = new History(); | |
|
97 | ||
|
98 | // Register our event handlers. | |
|
99 | connect(scroller, "mousedown", operation(onMouseDown)); | |
|
100 | connect(scroller, "dblclick", operation(onDoubleClick)); | |
|
101 | connect(lineSpace, "selectstart", e_preventDefault); | |
|
178 | d.maxLine = null; | |
|
179 | d.maxLineLength = 0; | |
|
180 | d.maxLineChanged = false; | |
|
181 | ||
|
182 | // Used for measuring wheel scrolling granularity | |
|
183 | d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; | |
|
184 | ||
|
185 | return d; | |
|
186 | } | |
|
187 | ||
|
188 | // STATE UPDATES | |
|
189 | ||
|
190 | // Used to get the editor into a consistent state again when options change. | |
|
191 | ||
|
192 | function loadMode(cm) { | |
|
193 | cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); | |
|
194 | cm.doc.iter(function(line) { | |
|
195 | if (line.stateAfter) line.stateAfter = null; | |
|
196 | if (line.styles) line.styles = null; | |
|
197 | }); | |
|
198 | cm.doc.frontier = cm.doc.first; | |
|
199 | startWorker(cm, 100); | |
|
200 | cm.state.modeGen++; | |
|
201 | if (cm.curOp) regChange(cm); | |
|
202 | } | |
|
203 | ||
|
204 | function wrappingChanged(cm) { | |
|
205 | if (cm.options.lineWrapping) { | |
|
206 | cm.display.wrapper.className += " CodeMirror-wrap"; | |
|
207 | cm.display.sizer.style.minWidth = ""; | |
|
208 | } else { | |
|
209 | cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", ""); | |
|
210 | computeMaxLength(cm); | |
|
211 | } | |
|
212 | estimateLineHeights(cm); | |
|
213 | regChange(cm); | |
|
214 | clearCaches(cm); | |
|
215 | setTimeout(function(){updateScrollbars(cm.display, cm.doc.height);}, 100); | |
|
216 | } | |
|
217 | ||
|
218 | function estimateHeight(cm) { | |
|
219 | var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; | |
|
220 | var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); | |
|
221 | return function(line) { | |
|
222 | if (lineIsHidden(cm.doc, line)) | |
|
223 | return 0; | |
|
224 | else if (wrapping) | |
|
225 | return (Math.ceil(line.text.length / perLine) || 1) * th; | |
|
226 | else | |
|
227 | return th; | |
|
228 | }; | |
|
229 | } | |
|
230 | ||
|
231 | function estimateLineHeights(cm) { | |
|
232 | var doc = cm.doc, est = estimateHeight(cm); | |
|
233 | doc.iter(function(line) { | |
|
234 | var estHeight = est(line); | |
|
235 | if (estHeight != line.height) updateLineHeight(line, estHeight); | |
|
236 | }); | |
|
237 | } | |
|
238 | ||
|
239 | function keyMapChanged(cm) { | |
|
240 | var style = keyMap[cm.options.keyMap].style; | |
|
241 | cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + | |
|
242 | (style ? " cm-keymap-" + style : ""); | |
|
243 | } | |
|
244 | ||
|
245 | function themeChanged(cm) { | |
|
246 | cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + | |
|
247 | cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); | |
|
248 | clearCaches(cm); | |
|
249 | } | |
|
250 | ||
|
251 | function guttersChanged(cm) { | |
|
252 | updateGutters(cm); | |
|
253 | regChange(cm); | |
|
254 | } | |
|
255 | ||
|
256 | function updateGutters(cm) { | |
|
257 | var gutters = cm.display.gutters, specs = cm.options.gutters; | |
|
258 | removeChildren(gutters); | |
|
259 | for (var i = 0; i < specs.length; ++i) { | |
|
260 | var gutterClass = specs[i]; | |
|
261 | var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); | |
|
262 | if (gutterClass == "CodeMirror-linenumbers") { | |
|
263 | cm.display.lineGutter = gElt; | |
|
264 | gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; | |
|
265 | } | |
|
266 | } | |
|
267 | gutters.style.display = i ? "" : "none"; | |
|
268 | } | |
|
269 | ||
|
270 | function lineLength(doc, line) { | |
|
271 | if (line.height == 0) return 0; | |
|
272 | var len = line.text.length, merged, cur = line; | |
|
273 | while (merged = collapsedSpanAtStart(cur)) { | |
|
274 | var found = merged.find(); | |
|
275 | cur = getLine(doc, found.from.line); | |
|
276 | len += found.from.ch - found.to.ch; | |
|
277 | } | |
|
278 | cur = line; | |
|
279 | while (merged = collapsedSpanAtEnd(cur)) { | |
|
280 | var found = merged.find(); | |
|
281 | len -= cur.text.length - found.from.ch; | |
|
282 | cur = getLine(doc, found.to.line); | |
|
283 | len += cur.text.length - found.to.ch; | |
|
284 | } | |
|
285 | return len; | |
|
286 | } | |
|
287 | ||
|
288 | function computeMaxLength(cm) { | |
|
289 | var d = cm.display, doc = cm.doc; | |
|
290 | d.maxLine = getLine(doc, doc.first); | |
|
291 | d.maxLineLength = lineLength(doc, d.maxLine); | |
|
292 | d.maxLineChanged = true; | |
|
293 | doc.iter(function(line) { | |
|
294 | var len = lineLength(doc, line); | |
|
295 | if (len > d.maxLineLength) { | |
|
296 | d.maxLineLength = len; | |
|
297 | d.maxLine = line; | |
|
298 | } | |
|
299 | }); | |
|
300 | } | |
|
301 | ||
|
302 | // Make sure the gutters options contains the element | |
|
303 | // "CodeMirror-linenumbers" when the lineNumbers option is true. | |
|
304 | function setGuttersForLineNumbers(options) { | |
|
305 | var found = false; | |
|
306 | for (var i = 0; i < options.gutters.length; ++i) { | |
|
307 | if (options.gutters[i] == "CodeMirror-linenumbers") { | |
|
308 | if (options.lineNumbers) found = true; | |
|
309 | else options.gutters.splice(i--, 1); | |
|
310 | } | |
|
311 | } | |
|
312 | if (!found && options.lineNumbers) | |
|
313 | options.gutters.push("CodeMirror-linenumbers"); | |
|
314 | } | |
|
315 | ||
|
316 | // SCROLLBARS | |
|
317 | ||
|
318 | // Re-synchronize the fake scrollbars with the actual size of the | |
|
319 | // content. Optionally force a scrollTop. | |
|
320 | function updateScrollbars(d /* display */, docHeight) { | |
|
321 | var totalHeight = docHeight + paddingVert(d); | |
|
322 | d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px"; | |
|
323 | var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight); | |
|
324 | var needsH = d.scroller.scrollWidth > d.scroller.clientWidth; | |
|
325 | var needsV = scrollHeight > d.scroller.clientHeight; | |
|
326 | if (needsV) { | |
|
327 | d.scrollbarV.style.display = "block"; | |
|
328 | d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; | |
|
329 | d.scrollbarV.firstChild.style.height = | |
|
330 | (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px"; | |
|
331 | } else d.scrollbarV.style.display = ""; | |
|
332 | if (needsH) { | |
|
333 | d.scrollbarH.style.display = "block"; | |
|
334 | d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0"; | |
|
335 | d.scrollbarH.firstChild.style.width = | |
|
336 | (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px"; | |
|
337 | } else d.scrollbarH.style.display = ""; | |
|
338 | if (needsH && needsV) { | |
|
339 | d.scrollbarFiller.style.display = "block"; | |
|
340 | d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px"; | |
|
341 | } else d.scrollbarFiller.style.display = ""; | |
|
342 | ||
|
343 | if (mac_geLion && scrollbarWidth(d.measure) === 0) | |
|
344 | d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; | |
|
345 | } | |
|
346 | ||
|
347 | function visibleLines(display, doc, viewPort) { | |
|
348 | var top = display.scroller.scrollTop, height = display.wrapper.clientHeight; | |
|
349 | if (typeof viewPort == "number") top = viewPort; | |
|
350 | else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;} | |
|
351 | top = Math.floor(top - paddingTop(display)); | |
|
352 | var bottom = Math.ceil(top + height); | |
|
353 | return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)}; | |
|
354 | } | |
|
355 | ||
|
356 | // LINE NUMBERS | |
|
357 | ||
|
358 | function alignHorizontally(cm) { | |
|
359 | var display = cm.display; | |
|
360 | if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; | |
|
361 | var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; | |
|
362 | var gutterW = display.gutters.offsetWidth, l = comp + "px"; | |
|
363 | for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) { | |
|
364 | for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l; | |
|
365 | } | |
|
366 | if (cm.options.fixedGutter) | |
|
367 | display.gutters.style.left = (comp + gutterW) + "px"; | |
|
368 | } | |
|
369 | ||
|
370 | function maybeUpdateLineNumberWidth(cm) { | |
|
371 | if (!cm.options.lineNumbers) return false; | |
|
372 | var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; | |
|
373 | if (last.length != display.lineNumChars) { | |
|
374 | var test = display.measure.appendChild(elt("div", [elt("div", last)], | |
|
375 | "CodeMirror-linenumber CodeMirror-gutter-elt")); | |
|
376 | var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; | |
|
377 | display.lineGutter.style.width = ""; | |
|
378 | display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding); | |
|
379 | display.lineNumWidth = display.lineNumInnerWidth + padding; | |
|
380 | display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; | |
|
381 | display.lineGutter.style.width = display.lineNumWidth + "px"; | |
|
382 | return true; | |
|
383 | } | |
|
384 | return false; | |
|
385 | } | |
|
386 | ||
|
387 | function lineNumberFor(options, i) { | |
|
388 | return String(options.lineNumberFormatter(i + options.firstLineNumber)); | |
|
389 | } | |
|
390 | function compensateForHScroll(display) { | |
|
391 | return getRect(display.scroller).left - getRect(display.sizer).left; | |
|
392 | } | |
|
393 | ||
|
394 | // DISPLAY DRAWING | |
|
395 | ||
|
396 | function updateDisplay(cm, changes, viewPort) { | |
|
397 | var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated; | |
|
398 | var visible = visibleLines(cm.display, cm.doc, viewPort); | |
|
399 | for (;;) { | |
|
400 | if (updateDisplayInner(cm, changes, visible)) { | |
|
401 | updated = true; | |
|
402 | signalLater(cm, "update", cm); | |
|
403 | if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo) | |
|
404 | signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo); | |
|
405 | } else break; | |
|
406 | updateSelection(cm); | |
|
407 | updateScrollbars(cm.display, cm.doc.height); | |
|
408 | ||
|
409 | // Clip forced viewport to actual scrollable area | |
|
410 | if (viewPort) | |
|
411 | viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, | |
|
412 | typeof viewPort == "number" ? viewPort : viewPort.top); | |
|
413 | visible = visibleLines(cm.display, cm.doc, viewPort); | |
|
414 | if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo) | |
|
415 | break; | |
|
416 | changes = []; | |
|
417 | } | |
|
418 | ||
|
419 | return updated; | |
|
420 | } | |
|
421 | ||
|
422 | // Uses a set of changes plus the current scroll position to | |
|
423 | // determine which DOM updates have to be made, and makes the | |
|
424 | // updates. | |
|
425 | function updateDisplayInner(cm, changes, visible) { | |
|
426 | var display = cm.display, doc = cm.doc; | |
|
427 | if (!display.wrapper.clientWidth) { | |
|
428 | display.showingFrom = display.showingTo = doc.first; | |
|
429 | display.viewOffset = 0; | |
|
430 | return; | |
|
431 | } | |
|
432 | ||
|
433 | // Bail out if the visible area is already rendered and nothing changed. | |
|
434 | if (changes.length == 0 && | |
|
435 | visible.from > display.showingFrom && visible.to < display.showingTo) | |
|
436 | return; | |
|
437 | ||
|
438 | if (maybeUpdateLineNumberWidth(cm)) | |
|
439 | changes = [{from: doc.first, to: doc.first + doc.size}]; | |
|
440 | var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px"; | |
|
441 | display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0"; | |
|
442 | ||
|
443 | // Used to determine which lines need their line numbers updated | |
|
444 | var positionsChangedFrom = Infinity; | |
|
445 | if (cm.options.lineNumbers) | |
|
446 | for (var i = 0; i < changes.length; ++i) | |
|
447 | if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; } | |
|
448 | ||
|
449 | var end = doc.first + doc.size; | |
|
450 | var from = Math.max(visible.from - cm.options.viewportMargin, doc.first); | |
|
451 | var to = Math.min(end, visible.to + cm.options.viewportMargin); | |
|
452 | if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom); | |
|
453 | if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo); | |
|
454 | if (sawCollapsedSpans) { | |
|
455 | from = lineNo(visualLine(doc, getLine(doc, from))); | |
|
456 | while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to; | |
|
457 | } | |
|
458 | ||
|
459 | // Create a range of theoretically intact lines, and punch holes | |
|
460 | // in that using the change info. | |
|
461 | var intact = [{from: Math.max(display.showingFrom, doc.first), | |
|
462 | to: Math.min(display.showingTo, end)}]; | |
|
463 | if (intact[0].from >= intact[0].to) intact = []; | |
|
464 | else intact = computeIntact(intact, changes); | |
|
465 | // When merged lines are present, we might have to reduce the | |
|
466 | // intact ranges because changes in continued fragments of the | |
|
467 | // intact lines do require the lines to be redrawn. | |
|
468 | if (sawCollapsedSpans) | |
|
469 | for (var i = 0; i < intact.length; ++i) { | |
|
470 | var range = intact[i], merged; | |
|
471 | while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) { | |
|
472 | var newTo = merged.find().from.line; | |
|
473 | if (newTo > range.from) range.to = newTo; | |
|
474 | else { intact.splice(i--, 1); break; } | |
|
475 | } | |
|
476 | } | |
|
477 | ||
|
478 | // Clip off the parts that won't be visible | |
|
479 | var intactLines = 0; | |
|
480 | for (var i = 0; i < intact.length; ++i) { | |
|
481 | var range = intact[i]; | |
|
482 | if (range.from < from) range.from = from; | |
|
483 | if (range.to > to) range.to = to; | |
|
484 | if (range.from >= range.to) intact.splice(i--, 1); | |
|
485 | else intactLines += range.to - range.from; | |
|
486 | } | |
|
487 | if (intactLines == to - from && from == display.showingFrom && to == display.showingTo) { | |
|
488 | updateViewOffset(cm); | |
|
489 | return; | |
|
490 | } | |
|
491 | intact.sort(function(a, b) {return a.from - b.from;}); | |
|
492 | ||
|
493 | // Avoid crashing on IE's "unspecified error" when in iframes | |
|
494 | try { | |
|
495 | var focused = document.activeElement; | |
|
496 | } catch(e) {} | |
|
497 | if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none"; | |
|
498 | patchDisplay(cm, from, to, intact, positionsChangedFrom); | |
|
499 | display.lineDiv.style.display = ""; | |
|
500 | if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus(); | |
|
501 | ||
|
502 | var different = from != display.showingFrom || to != display.showingTo || | |
|
503 | display.lastSizeC != display.wrapper.clientHeight; | |
|
504 | // This is just a bogus formula that detects when the editor is | |
|
505 | // resized or the font size changes. | |
|
506 | if (different) display.lastSizeC = display.wrapper.clientHeight; | |
|
507 | display.showingFrom = from; display.showingTo = to; | |
|
508 | startWorker(cm, 100); | |
|
509 | ||
|
510 | var prevBottom = display.lineDiv.offsetTop; | |
|
511 | for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) { | |
|
512 | if (ie_lt8) { | |
|
513 | var bot = node.offsetTop + node.offsetHeight; | |
|
514 | height = bot - prevBottom; | |
|
515 | prevBottom = bot; | |
|
516 | } else { | |
|
517 | var box = getRect(node); | |
|
518 | height = box.bottom - box.top; | |
|
519 | } | |
|
520 | var diff = node.lineObj.height - height; | |
|
521 | if (height < 2) height = textHeight(display); | |
|
522 | if (diff > .001 || diff < -.001) { | |
|
523 | updateLineHeight(node.lineObj, height); | |
|
524 | var widgets = node.lineObj.widgets; | |
|
525 | if (widgets) for (var i = 0; i < widgets.length; ++i) | |
|
526 | widgets[i].height = widgets[i].node.offsetHeight; | |
|
527 | } | |
|
528 | } | |
|
529 | updateViewOffset(cm); | |
|
530 | ||
|
531 | return true; | |
|
532 | } | |
|
533 | ||
|
534 | function updateViewOffset(cm) { | |
|
535 | var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom)); | |
|
536 | // Position the mover div to align with the current virtual scroll position | |
|
537 | cm.display.mover.style.top = off + "px"; | |
|
538 | } | |
|
539 | ||
|
540 | function computeIntact(intact, changes) { | |
|
541 | for (var i = 0, l = changes.length || 0; i < l; ++i) { | |
|
542 | var change = changes[i], intact2 = [], diff = change.diff || 0; | |
|
543 | for (var j = 0, l2 = intact.length; j < l2; ++j) { | |
|
544 | var range = intact[j]; | |
|
545 | if (change.to <= range.from && change.diff) { | |
|
546 | intact2.push({from: range.from + diff, to: range.to + diff}); | |
|
547 | } else if (change.to <= range.from || change.from >= range.to) { | |
|
548 | intact2.push(range); | |
|
549 | } else { | |
|
550 | if (change.from > range.from) | |
|
551 | intact2.push({from: range.from, to: change.from}); | |
|
552 | if (change.to < range.to) | |
|
553 | intact2.push({from: change.to + diff, to: range.to + diff}); | |
|
554 | } | |
|
555 | } | |
|
556 | intact = intact2; | |
|
557 | } | |
|
558 | return intact; | |
|
559 | } | |
|
560 | ||
|
561 | function getDimensions(cm) { | |
|
562 | var d = cm.display, left = {}, width = {}; | |
|
563 | for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { | |
|
564 | left[cm.options.gutters[i]] = n.offsetLeft; | |
|
565 | width[cm.options.gutters[i]] = n.offsetWidth; | |
|
566 | } | |
|
567 | return {fixedPos: compensateForHScroll(d), | |
|
568 | gutterTotalWidth: d.gutters.offsetWidth, | |
|
569 | gutterLeft: left, | |
|
570 | gutterWidth: width, | |
|
571 | wrapperWidth: d.wrapper.clientWidth}; | |
|
572 | } | |
|
573 | ||
|
574 | function patchDisplay(cm, from, to, intact, updateNumbersFrom) { | |
|
575 | var dims = getDimensions(cm); | |
|
576 | var display = cm.display, lineNumbers = cm.options.lineNumbers; | |
|
577 | if (!intact.length && (!webkit || !cm.display.currentWheelTarget)) | |
|
578 | removeChildren(display.lineDiv); | |
|
579 | var container = display.lineDiv, cur = container.firstChild; | |
|
580 | ||
|
581 | function rm(node) { | |
|
582 | var next = node.nextSibling; | |
|
583 | if (webkit && mac && cm.display.currentWheelTarget == node) { | |
|
584 | node.style.display = "none"; | |
|
585 | node.lineObj = null; | |
|
586 | } else { | |
|
587 | node.parentNode.removeChild(node); | |
|
588 | } | |
|
589 | return next; | |
|
590 | } | |
|
591 | ||
|
592 | var nextIntact = intact.shift(), lineN = from; | |
|
593 | cm.doc.iter(from, to, function(line) { | |
|
594 | if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift(); | |
|
595 | if (lineIsHidden(cm.doc, line)) { | |
|
596 | if (line.height != 0) updateLineHeight(line, 0); | |
|
597 | if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) | |
|
598 | if (line.widgets[i].showIfHidden) { | |
|
599 | var prev = cur.previousSibling; | |
|
600 | if (/pre/i.test(prev.nodeName)) { | |
|
601 | var wrap = elt("div", null, null, "position: relative"); | |
|
602 | prev.parentNode.replaceChild(wrap, prev); | |
|
603 | wrap.appendChild(prev); | |
|
604 | prev = wrap; | |
|
605 | } | |
|
606 | var wnode = prev.appendChild(elt("div", [line.widgets[i].node], "CodeMirror-linewidget")); | |
|
607 | positionLineWidget(line.widgets[i], wnode, prev, dims); | |
|
608 | } | |
|
609 | } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) { | |
|
610 | // This line is intact. Skip to the actual node. Update its | |
|
611 | // line number if needed. | |
|
612 | while (cur.lineObj != line) cur = rm(cur); | |
|
613 | if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber) | |
|
614 | setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN)); | |
|
615 | cur = cur.nextSibling; | |
|
616 | } else { | |
|
617 | // For lines with widgets, make an attempt to find and reuse | |
|
618 | // the existing element, so that widgets aren't needlessly | |
|
619 | // removed and re-inserted into the dom | |
|
620 | if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling) | |
|
621 | if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; } | |
|
622 | // This line needs to be generated. | |
|
623 | var lineNode = buildLineElement(cm, line, lineN, dims, reuse); | |
|
624 | if (lineNode != reuse) { | |
|
625 | container.insertBefore(lineNode, cur); | |
|
626 | } else { | |
|
627 | while (cur != reuse) cur = rm(cur); | |
|
628 | cur = cur.nextSibling; | |
|
629 | } | |
|
630 | ||
|
631 | lineNode.lineObj = line; | |
|
632 | } | |
|
633 | ++lineN; | |
|
634 | }); | |
|
635 | while (cur) cur = rm(cur); | |
|
636 | } | |
|
637 | ||
|
638 | function buildLineElement(cm, line, lineNo, dims, reuse) { | |
|
639 | var lineElement = lineContent(cm, line); | |
|
640 | var markers = line.gutterMarkers, display = cm.display, wrap; | |
|
641 | ||
|
642 | if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets) | |
|
643 | return lineElement; | |
|
644 | ||
|
645 | // Lines with gutter elements, widgets or a background class need | |
|
646 | // to be wrapped again, and have the extra elements added to the | |
|
647 | // wrapper div | |
|
648 | ||
|
649 | if (reuse) { | |
|
650 | reuse.alignable = null; | |
|
651 | var isOk = true, widgetsSeen = 0; | |
|
652 | for (var n = reuse.firstChild, next; n; n = next) { | |
|
653 | next = n.nextSibling; | |
|
654 | if (!/\bCodeMirror-linewidget\b/.test(n.className)) { | |
|
655 | reuse.removeChild(n); | |
|
656 | } else { | |
|
657 | for (var i = 0, first = true; i < line.widgets.length; ++i) { | |
|
658 | var widget = line.widgets[i], isFirst = false; | |
|
659 | if (!widget.above) { isFirst = first; first = false; } | |
|
660 | if (widget.node == n.firstChild) { | |
|
661 | positionLineWidget(widget, n, reuse, dims); | |
|
662 | ++widgetsSeen; | |
|
663 | if (isFirst) reuse.insertBefore(lineElement, n); | |
|
664 | break; | |
|
665 | } | |
|
666 | } | |
|
667 | if (i == line.widgets.length) { isOk = false; break; } | |
|
668 | } | |
|
669 | } | |
|
670 | if (isOk && widgetsSeen == line.widgets.length) { | |
|
671 | wrap = reuse; | |
|
672 | reuse.className = line.wrapClass || ""; | |
|
673 | } | |
|
674 | } | |
|
675 | if (!wrap) { | |
|
676 | wrap = elt("div", null, line.wrapClass, "position: relative"); | |
|
677 | wrap.appendChild(lineElement); | |
|
678 | } | |
|
679 | // Kludge to make sure the styled element lies behind the selection (by z-index) | |
|
680 | if (line.bgClass) | |
|
681 | wrap.insertBefore(elt("div", null, line.bgClass + " CodeMirror-linebackground"), wrap.firstChild); | |
|
682 | if (cm.options.lineNumbers || markers) { | |
|
683 | var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " + | |
|
684 | (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), | |
|
685 | wrap.firstChild); | |
|
686 | if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap); | |
|
687 | if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) | |
|
688 | wrap.lineNumber = gutterWrap.appendChild( | |
|
689 | elt("div", lineNumberFor(cm.options, lineNo), | |
|
690 | "CodeMirror-linenumber CodeMirror-gutter-elt", | |
|
691 | "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " | |
|
692 | + display.lineNumInnerWidth + "px")); | |
|
693 | if (markers) | |
|
694 | for (var k = 0; k < cm.options.gutters.length; ++k) { | |
|
695 | var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; | |
|
696 | if (found) | |
|
697 | gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + | |
|
698 | dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); | |
|
699 | } | |
|
700 | } | |
|
701 | if (ie_lt8) wrap.style.zIndex = 2; | |
|
702 | if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) { | |
|
703 | var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); | |
|
704 | positionLineWidget(widget, node, wrap, dims); | |
|
705 | if (widget.above) | |
|
706 | wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); | |
|
707 | else | |
|
708 | wrap.appendChild(node); | |
|
709 | signalLater(widget, "redraw"); | |
|
710 | } | |
|
711 | return wrap; | |
|
712 | } | |
|
713 | ||
|
714 | function positionLineWidget(widget, node, wrap, dims) { | |
|
715 | if (widget.noHScroll) { | |
|
716 | (wrap.alignable || (wrap.alignable = [])).push(node); | |
|
717 | var width = dims.wrapperWidth; | |
|
718 | node.style.left = dims.fixedPos + "px"; | |
|
719 | if (!widget.coverGutter) { | |
|
720 | width -= dims.gutterTotalWidth; | |
|
721 | node.style.paddingLeft = dims.gutterTotalWidth + "px"; | |
|
722 | } | |
|
723 | node.style.width = width + "px"; | |
|
724 | } | |
|
725 | if (widget.coverGutter) { | |
|
726 | node.style.zIndex = 5; | |
|
727 | node.style.position = "relative"; | |
|
728 | if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; | |
|
729 | } | |
|
730 | } | |
|
731 | ||
|
732 | // SELECTION / CURSOR | |
|
733 | ||
|
734 | function updateSelection(cm) { | |
|
735 | var display = cm.display; | |
|
736 | var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to); | |
|
737 | if (collapsed || cm.options.showCursorWhenSelecting) | |
|
738 | updateSelectionCursor(cm); | |
|
739 | else | |
|
740 | display.cursor.style.display = display.otherCursor.style.display = "none"; | |
|
741 | if (!collapsed) | |
|
742 | updateSelectionRange(cm); | |
|
743 | else | |
|
744 | display.selectionDiv.style.display = "none"; | |
|
745 | ||
|
746 | // Move the hidden textarea near the cursor to prevent scrolling artifacts | |
|
747 | if (cm.options.moveInputWithCursor) { | |
|
748 | var headPos = cursorCoords(cm, cm.doc.sel.head, "div"); | |
|
749 | var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv); | |
|
750 | display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, | |
|
751 | headPos.top + lineOff.top - wrapOff.top)) + "px"; | |
|
752 | display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, | |
|
753 | headPos.left + lineOff.left - wrapOff.left)) + "px"; | |
|
754 | } | |
|
755 | } | |
|
756 | ||
|
757 | // No selection, plain cursor | |
|
758 | function updateSelectionCursor(cm) { | |
|
759 | var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div"); | |
|
760 | display.cursor.style.left = pos.left + "px"; | |
|
761 | display.cursor.style.top = pos.top + "px"; | |
|
762 | display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; | |
|
763 | display.cursor.style.display = ""; | |
|
764 | ||
|
765 | if (pos.other) { | |
|
766 | display.otherCursor.style.display = ""; | |
|
767 | display.otherCursor.style.left = pos.other.left + "px"; | |
|
768 | display.otherCursor.style.top = pos.other.top + "px"; | |
|
769 | display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; | |
|
770 | } else { display.otherCursor.style.display = "none"; } | |
|
771 | } | |
|
772 | ||
|
773 | // Highlight selection | |
|
774 | function updateSelectionRange(cm) { | |
|
775 | var display = cm.display, doc = cm.doc, sel = cm.doc.sel; | |
|
776 | var fragment = document.createDocumentFragment(); | |
|
777 | var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display); | |
|
778 | ||
|
779 | function add(left, top, width, bottom) { | |
|
780 | if (top < 0) top = 0; | |
|
781 | fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + | |
|
782 | "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) + | |
|
783 | "px; height: " + (bottom - top) + "px")); | |
|
784 | } | |
|
785 | ||
|
786 | function drawForLine(line, fromArg, toArg, retTop) { | |
|
787 | var lineObj = getLine(doc, line); | |
|
788 | var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity; | |
|
789 | function coords(ch) { | |
|
790 | return charCoords(cm, Pos(line, ch), "div", lineObj); | |
|
791 | } | |
|
792 | ||
|
793 | iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { | |
|
794 | var leftPos = coords(from), rightPos, left, right; | |
|
795 | if (from == to) { | |
|
796 | rightPos = leftPos; | |
|
797 | left = right = leftPos.left; | |
|
798 | } else { | |
|
799 | rightPos = coords(to - 1); | |
|
800 | if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } | |
|
801 | left = leftPos.left; | |
|
802 | right = rightPos.right; | |
|
803 | } | |
|
804 | if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part | |
|
805 | add(left, leftPos.top, null, leftPos.bottom); | |
|
806 | left = pl; | |
|
807 | if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); | |
|
808 | } | |
|
809 | if (toArg == null && to == lineLen) right = clientWidth; | |
|
810 | if (fromArg == null && from == 0) left = pl; | |
|
811 | rVal = retTop ? Math.min(rightPos.top, rVal) : Math.max(rightPos.bottom, rVal); | |
|
812 | if (left < pl + 1) left = pl; | |
|
813 | add(left, rightPos.top, right - left, rightPos.bottom); | |
|
814 | }); | |
|
815 | return rVal; | |
|
816 | } | |
|
817 | ||
|
818 | if (sel.from.line == sel.to.line) { | |
|
819 | drawForLine(sel.from.line, sel.from.ch, sel.to.ch); | |
|
820 | } else { | |
|
821 | var fromObj = getLine(doc, sel.from.line); | |
|
822 | var cur = fromObj, merged, path = [sel.from.line, sel.from.ch], singleLine; | |
|
823 | while (merged = collapsedSpanAtEnd(cur)) { | |
|
824 | var found = merged.find(); | |
|
825 | path.push(found.from.ch, found.to.line, found.to.ch); | |
|
826 | if (found.to.line == sel.to.line) { | |
|
827 | path.push(sel.to.ch); | |
|
828 | singleLine = true; | |
|
829 | break; | |
|
830 | } | |
|
831 | cur = getLine(doc, found.to.line); | |
|
832 | } | |
|
833 | ||
|
834 | // This is a single, merged line | |
|
835 | if (singleLine) { | |
|
836 | for (var i = 0; i < path.length; i += 3) | |
|
837 | drawForLine(path[i], path[i+1], path[i+2]); | |
|
838 | } else { | |
|
839 | var middleTop, middleBot, toObj = getLine(doc, sel.to.line); | |
|
840 | if (sel.from.ch) | |
|
841 | // Draw the first line of selection. | |
|
842 | middleTop = drawForLine(sel.from.line, sel.from.ch, null, false); | |
|
843 | else | |
|
844 | // Simply include it in the middle block. | |
|
845 | middleTop = heightAtLine(cm, fromObj) - display.viewOffset; | |
|
846 | ||
|
847 | if (!sel.to.ch) | |
|
848 | middleBot = heightAtLine(cm, toObj) - display.viewOffset; | |
|
849 | else | |
|
850 | middleBot = drawForLine(sel.to.line, collapsedSpanAtStart(toObj) ? null : 0, sel.to.ch, true); | |
|
851 | ||
|
852 | if (middleTop < middleBot) add(pl, middleTop, null, middleBot); | |
|
853 | } | |
|
854 | } | |
|
855 | ||
|
856 | removeChildrenAndAdd(display.selectionDiv, fragment); | |
|
857 | display.selectionDiv.style.display = ""; | |
|
858 | } | |
|
859 | ||
|
860 | // Cursor-blinking | |
|
861 | function restartBlink(cm) { | |
|
862 | if (!cm.state.focused) return; | |
|
863 | var display = cm.display; | |
|
864 | clearInterval(display.blinker); | |
|
865 | var on = true; | |
|
866 | display.cursor.style.visibility = display.otherCursor.style.visibility = ""; | |
|
867 | display.blinker = setInterval(function() { | |
|
868 | display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; | |
|
869 | }, cm.options.cursorBlinkRate); | |
|
870 | } | |
|
871 | ||
|
872 | // HIGHLIGHT WORKER | |
|
873 | ||
|
874 | function startWorker(cm, time) { | |
|
875 | if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo) | |
|
876 | cm.state.highlight.set(time, bind(highlightWorker, cm)); | |
|
877 | } | |
|
878 | ||
|
879 | function highlightWorker(cm) { | |
|
880 | var doc = cm.doc; | |
|
881 | if (doc.frontier < doc.first) doc.frontier = doc.first; | |
|
882 | if (doc.frontier >= cm.display.showingTo) return; | |
|
883 | var end = +new Date + cm.options.workTime; | |
|
884 | var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); | |
|
885 | var changed = [], prevChange; | |
|
886 | doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) { | |
|
887 | if (doc.frontier >= cm.display.showingFrom) { // Visible | |
|
888 | var oldStyles = line.styles; | |
|
889 | line.styles = highlightLine(cm, line, state); | |
|
890 | var ischange = !oldStyles || oldStyles.length != line.styles.length; | |
|
891 | for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; | |
|
892 | if (ischange) { | |
|
893 | if (prevChange && prevChange.end == doc.frontier) prevChange.end++; | |
|
894 | else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1}); | |
|
895 | } | |
|
896 | line.stateAfter = copyState(doc.mode, state); | |
|
897 | } else { | |
|
898 | processLine(cm, line, state); | |
|
899 | line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; | |
|
900 | } | |
|
901 | ++doc.frontier; | |
|
902 | if (+new Date > end) { | |
|
903 | startWorker(cm, cm.options.workDelay); | |
|
904 | return true; | |
|
905 | } | |
|
906 | }); | |
|
907 | if (changed.length) | |
|
908 | operation(cm, function() { | |
|
909 | for (var i = 0; i < changed.length; ++i) | |
|
910 | regChange(this, changed[i].start, changed[i].end); | |
|
911 | })(); | |
|
912 | } | |
|
913 | ||
|
914 | // Finds the line to start with when starting a parse. Tries to | |
|
915 | // find a line with a stateAfter, so that it can start with a | |
|
916 | // valid state. If that fails, it returns the line with the | |
|
917 | // smallest indentation, which tends to need the least context to | |
|
918 | // parse correctly. | |
|
919 | function findStartLine(cm, n) { | |
|
920 | var minindent, minline, doc = cm.doc; | |
|
921 | for (var search = n, lim = n - 100; search > lim; --search) { | |
|
922 | if (search <= doc.first) return doc.first; | |
|
923 | var line = getLine(doc, search - 1); | |
|
924 | if (line.stateAfter) return search; | |
|
925 | var indented = countColumn(line.text, null, cm.options.tabSize); | |
|
926 | if (minline == null || minindent > indented) { | |
|
927 | minline = search - 1; | |
|
928 | minindent = indented; | |
|
929 | } | |
|
930 | } | |
|
931 | return minline; | |
|
932 | } | |
|
933 | ||
|
934 | function getStateBefore(cm, n) { | |
|
935 | var doc = cm.doc, display = cm.display; | |
|
936 | if (!doc.mode.startState) return true; | |
|
937 | var pos = findStartLine(cm, n), state = pos > doc.first && getLine(doc, pos-1).stateAfter; | |
|
938 | if (!state) state = startState(doc.mode); | |
|
939 | else state = copyState(doc.mode, state); | |
|
940 | doc.iter(pos, n, function(line) { | |
|
941 | processLine(cm, line, state); | |
|
942 | var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo; | |
|
943 | line.stateAfter = save ? copyState(doc.mode, state) : null; | |
|
944 | ++pos; | |
|
945 | }); | |
|
946 | return state; | |
|
947 | } | |
|
948 | ||
|
949 | // POSITION MEASUREMENT | |
|
950 | ||
|
951 | function paddingTop(display) {return display.lineSpace.offsetTop;} | |
|
952 | function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} | |
|
953 | function paddingLeft(display) { | |
|
954 | var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x")); | |
|
955 | return e.offsetLeft; | |
|
956 | } | |
|
957 | ||
|
958 | function measureChar(cm, line, ch, data) { | |
|
959 | var dir = -1; | |
|
960 | data = data || measureLine(cm, line); | |
|
961 | ||
|
962 | for (var pos = ch;; pos += dir) { | |
|
963 | var r = data[pos]; | |
|
964 | if (r) break; | |
|
965 | if (dir < 0 && pos == 0) dir = 1; | |
|
966 | } | |
|
967 | return {left: pos < ch ? r.right : r.left, | |
|
968 | right: pos > ch ? r.left : r.right, | |
|
969 | top: r.top, bottom: r.bottom}; | |
|
970 | } | |
|
971 | ||
|
972 | function findCachedMeasurement(cm, line) { | |
|
973 | var cache = cm.display.measureLineCache; | |
|
974 | for (var i = 0; i < cache.length; ++i) { | |
|
975 | var memo = cache[i]; | |
|
976 | if (memo.text == line.text && memo.markedSpans == line.markedSpans && | |
|
977 | cm.display.scroller.clientWidth == memo.width && | |
|
978 | memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass) | |
|
979 | return memo.measure; | |
|
980 | } | |
|
981 | } | |
|
982 | ||
|
983 | function measureLine(cm, line) { | |
|
984 | // First look in the cache | |
|
985 | var measure = findCachedMeasurement(cm, line); | |
|
986 | if (!measure) { | |
|
987 | // Failing that, recompute and store result in cache | |
|
988 | measure = measureLineInner(cm, line); | |
|
989 | var cache = cm.display.measureLineCache; | |
|
990 | var memo = {text: line.text, width: cm.display.scroller.clientWidth, | |
|
991 | markedSpans: line.markedSpans, measure: measure, | |
|
992 | classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass}; | |
|
993 | if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo; | |
|
994 | else cache.push(memo); | |
|
995 | } | |
|
996 | return measure; | |
|
997 | } | |
|
998 | ||
|
999 | function measureLineInner(cm, line) { | |
|
1000 | var display = cm.display, measure = emptyArray(line.text.length); | |
|
1001 | var pre = lineContent(cm, line, measure); | |
|
1002 | ||
|
1003 | // IE does not cache element positions of inline elements between | |
|
1004 | // calls to getBoundingClientRect. This makes the loop below, | |
|
1005 | // which gathers the positions of all the characters on the line, | |
|
1006 | // do an amount of layout work quadratic to the number of | |
|
1007 | // characters. When line wrapping is off, we try to improve things | |
|
1008 | // by first subdividing the line into a bunch of inline blocks, so | |
|
1009 | // that IE can reuse most of the layout information from caches | |
|
1010 | // for those blocks. This does interfere with line wrapping, so it | |
|
1011 | // doesn't work when wrapping is on, but in that case the | |
|
1012 | // situation is slightly better, since IE does cache line-wrapping | |
|
1013 | // information and only recomputes per-line. | |
|
1014 | if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) { | |
|
1015 | var fragment = document.createDocumentFragment(); | |
|
1016 | var chunk = 10, n = pre.childNodes.length; | |
|
1017 | for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) { | |
|
1018 | var wrap = elt("div", null, null, "display: inline-block"); | |
|
1019 | for (var j = 0; j < chunk && n; ++j) { | |
|
1020 | wrap.appendChild(pre.firstChild); | |
|
1021 | --n; | |
|
1022 | } | |
|
1023 | fragment.appendChild(wrap); | |
|
1024 | } | |
|
1025 | pre.appendChild(fragment); | |
|
1026 | } | |
|
1027 | ||
|
1028 | removeChildrenAndAdd(display.measure, pre); | |
|
1029 | ||
|
1030 | var outer = getRect(display.lineDiv); | |
|
1031 | var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight; | |
|
1032 | // Work around an IE7/8 bug where it will sometimes have randomly | |
|
1033 | // replaced our pre with a clone at this point. | |
|
1034 | if (ie_lt9 && display.measure.first != pre) | |
|
1035 | removeChildrenAndAdd(display.measure, pre); | |
|
1036 | ||
|
1037 | for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { | |
|
1038 | var size = getRect(cur); | |
|
1039 | var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot); | |
|
1040 | for (var j = 0; j < vranges.length; j += 2) { | |
|
1041 | var rtop = vranges[j], rbot = vranges[j+1]; | |
|
1042 | if (rtop > bot || rbot < top) continue; | |
|
1043 | if (rtop <= top && rbot >= bot || | |
|
1044 | top <= rtop && bot >= rbot || | |
|
1045 | Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) { | |
|
1046 | vranges[j] = Math.min(top, rtop); | |
|
1047 | vranges[j+1] = Math.max(bot, rbot); | |
|
1048 | break; | |
|
1049 | } | |
|
1050 | } | |
|
1051 | if (j == vranges.length) vranges.push(top, bot); | |
|
1052 | var right = size.right; | |
|
1053 | if (cur.measureRight) right = getRect(cur.measureRight).left; | |
|
1054 | data[i] = {left: size.left - outer.left, right: right - outer.left, top: j}; | |
|
1055 | } | |
|
1056 | for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { | |
|
1057 | var vr = cur.top; | |
|
1058 | cur.top = vranges[vr]; cur.bottom = vranges[vr+1]; | |
|
1059 | } | |
|
1060 | ||
|
1061 | return data; | |
|
1062 | } | |
|
1063 | ||
|
1064 | function measureLineWidth(cm, line) { | |
|
1065 | var hasBadSpan = false; | |
|
1066 | if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) { | |
|
1067 | var sp = line.markedSpans[i]; | |
|
1068 | if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true; | |
|
1069 | } | |
|
1070 | var cached = !hasBadSpan && findCachedMeasurement(cm, line); | |
|
1071 | if (cached) return measureChar(cm, line, line.text.length, cached).right; | |
|
1072 | ||
|
1073 | var pre = lineContent(cm, line); | |
|
1074 | var end = pre.appendChild(zeroWidthElement(cm.display.measure)); | |
|
1075 | removeChildrenAndAdd(cm.display.measure, pre); | |
|
1076 | return getRect(end).right - getRect(cm.display.lineDiv).left; | |
|
1077 | } | |
|
1078 | ||
|
1079 | function clearCaches(cm) { | |
|
1080 | cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; | |
|
1081 | cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; | |
|
1082 | if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; | |
|
1083 | cm.display.lineNumChars = null; | |
|
1084 | } | |
|
1085 | ||
|
1086 | // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" | |
|
1087 | function intoCoordSystem(cm, lineObj, rect, context) { | |
|
1088 | if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { | |
|
1089 | var size = widgetHeight(lineObj.widgets[i]); | |
|
1090 | rect.top += size; rect.bottom += size; | |
|
1091 | } | |
|
1092 | if (context == "line") return rect; | |
|
1093 | if (!context) context = "local"; | |
|
1094 | var yOff = heightAtLine(cm, lineObj); | |
|
1095 | if (context != "local") yOff -= cm.display.viewOffset; | |
|
1096 | if (context == "page") { | |
|
1097 | var lOff = getRect(cm.display.lineSpace); | |
|
1098 | yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop); | |
|
1099 | var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft); | |
|
1100 | rect.left += xOff; rect.right += xOff; | |
|
1101 | } | |
|
1102 | rect.top += yOff; rect.bottom += yOff; | |
|
1103 | return rect; | |
|
1104 | } | |
|
1105 | ||
|
1106 | // Context may be "window", "page", "div", or "local"/null | |
|
1107 | // Result is in "div" coords | |
|
1108 | function fromCoordSystem(cm, coords, context) { | |
|
1109 | if (context == "div") return coords; | |
|
1110 | var left = coords.left, top = coords.top; | |
|
1111 | if (context == "page") { | |
|
1112 | left -= window.pageXOffset || (document.documentElement || document.body).scrollLeft; | |
|
1113 | top -= window.pageYOffset || (document.documentElement || document.body).scrollTop; | |
|
1114 | } | |
|
1115 | var lineSpaceBox = getRect(cm.display.lineSpace); | |
|
1116 | left -= lineSpaceBox.left; | |
|
1117 | top -= lineSpaceBox.top; | |
|
1118 | if (context == "local" || !context) { | |
|
1119 | var editorBox = getRect(cm.display.wrapper); | |
|
1120 | left += editorBox.left; | |
|
1121 | top += editorBox.top; | |
|
1122 | } | |
|
1123 | return {left: left, top: top}; | |
|
1124 | } | |
|
1125 | ||
|
1126 | function charCoords(cm, pos, context, lineObj) { | |
|
1127 | if (!lineObj) lineObj = getLine(cm.doc, pos.line); | |
|
1128 | return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context); | |
|
1129 | } | |
|
1130 | ||
|
1131 | function cursorCoords(cm, pos, context, lineObj, measurement) { | |
|
1132 | lineObj = lineObj || getLine(cm.doc, pos.line); | |
|
1133 | if (!measurement) measurement = measureLine(cm, lineObj); | |
|
1134 | function get(ch, right) { | |
|
1135 | var m = measureChar(cm, lineObj, ch, measurement); | |
|
1136 | if (right) m.left = m.right; else m.right = m.left; | |
|
1137 | return intoCoordSystem(cm, lineObj, m, context); | |
|
1138 | } | |
|
1139 | var order = getOrder(lineObj), ch = pos.ch; | |
|
1140 | if (!order) return get(ch); | |
|
1141 | var main, other, linedir = order[0].level; | |
|
1142 | for (var i = 0; i < order.length; ++i) { | |
|
1143 | var part = order[i], rtl = part.level % 2, nb, here; | |
|
1144 | if (part.from < ch && part.to > ch) return get(ch, rtl); | |
|
1145 | var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to; | |
|
1146 | if (left == ch) { | |
|
1147 | // IE returns bogus offsets and widths for edges where the | |
|
1148 | // direction flips, but only for the side with the lower | |
|
1149 | // level. So we try to use the side with the higher level. | |
|
1150 | if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true); | |
|
1151 | else here = get(rtl && part.from != part.to ? ch - 1 : ch); | |
|
1152 | if (rtl == linedir) main = here; else other = here; | |
|
1153 | } else if (right == ch) { | |
|
1154 | var nb = i < order.length - 1 && order[i+1]; | |
|
1155 | if (!rtl && nb && nb.from == nb.to) continue; | |
|
1156 | if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from); | |
|
1157 | else here = get(rtl ? ch : ch - 1, true); | |
|
1158 | if (rtl == linedir) main = here; else other = here; | |
|
1159 | } | |
|
1160 | } | |
|
1161 | if (linedir && !ch) other = get(order[0].to - 1); | |
|
1162 | if (!main) return other; | |
|
1163 | if (other) main.other = other; | |
|
1164 | return main; | |
|
1165 | } | |
|
1166 | ||
|
1167 | function PosMaybeOutside(line, ch, outside) { | |
|
1168 | var pos = new Pos(line, ch); | |
|
1169 | if (outside) pos.outside = true; | |
|
1170 | return pos; | |
|
1171 | } | |
|
1172 | ||
|
1173 | // Coords must be lineSpace-local | |
|
1174 | function coordsChar(cm, x, y) { | |
|
1175 | var doc = cm.doc; | |
|
1176 | y += cm.display.viewOffset; | |
|
1177 | if (y < 0) return PosMaybeOutside(doc.first, 0, true); | |
|
1178 | var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1; | |
|
1179 | if (lineNo > last) | |
|
1180 | return PosMaybeOutside(doc.first + doc.size - 1, getLine(doc, last).text.length, true); | |
|
1181 | if (x < 0) x = 0; | |
|
1182 | ||
|
1183 | for (;;) { | |
|
1184 | var lineObj = getLine(doc, lineNo); | |
|
1185 | var found = coordsCharInner(cm, lineObj, lineNo, x, y); | |
|
1186 | var merged = collapsedSpanAtEnd(lineObj); | |
|
1187 | var mergedPos = merged && merged.find(); | |
|
1188 | if (merged && found.ch >= mergedPos.from.ch) | |
|
1189 | lineNo = mergedPos.to.line; | |
|
1190 | else | |
|
1191 | return found; | |
|
1192 | } | |
|
1193 | } | |
|
1194 | ||
|
1195 | function coordsCharInner(cm, lineObj, lineNo, x, y) { | |
|
1196 | var innerOff = y - heightAtLine(cm, lineObj); | |
|
1197 | var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; | |
|
1198 | var measurement = measureLine(cm, lineObj); | |
|
1199 | ||
|
1200 | function getX(ch) { | |
|
1201 | var sp = cursorCoords(cm, Pos(lineNo, ch), "line", | |
|
1202 | lineObj, measurement); | |
|
1203 | wrongLine = true; | |
|
1204 | if (innerOff > sp.bottom) return sp.left - adjust; | |
|
1205 | else if (innerOff < sp.top) return sp.left + adjust; | |
|
1206 | else wrongLine = false; | |
|
1207 | return sp.left; | |
|
1208 | } | |
|
1209 | ||
|
1210 | var bidi = getOrder(lineObj), dist = lineObj.text.length; | |
|
1211 | var from = lineLeft(lineObj), to = lineRight(lineObj); | |
|
1212 | var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; | |
|
1213 | ||
|
1214 | if (x > toX) return PosMaybeOutside(lineNo, to, toOutside); | |
|
1215 | // Do a binary search between these bounds. | |
|
1216 | for (;;) { | |
|
1217 | if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { | |
|
1218 | var after = x - fromX < toX - x, ch = after ? from : to; | |
|
1219 | while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; | |
|
1220 | var pos = PosMaybeOutside(lineNo, ch, after ? fromOutside : toOutside); | |
|
1221 | pos.after = after; | |
|
1222 | return pos; | |
|
1223 | } | |
|
1224 | var step = Math.ceil(dist / 2), middle = from + step; | |
|
1225 | if (bidi) { | |
|
1226 | middle = from; | |
|
1227 | for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); | |
|
1228 | } | |
|
1229 | var middleX = getX(middle); | |
|
1230 | if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} | |
|
1231 | else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} | |
|
1232 | } | |
|
1233 | } | |
|
1234 | ||
|
1235 | var measureText; | |
|
1236 | function textHeight(display) { | |
|
1237 | if (display.cachedTextHeight != null) return display.cachedTextHeight; | |
|
1238 | if (measureText == null) { | |
|
1239 | measureText = elt("pre"); | |
|
1240 | // Measure a bunch of lines, for browsers that compute | |
|
1241 | // fractional heights. | |
|
1242 | for (var i = 0; i < 49; ++i) { | |
|
1243 | measureText.appendChild(document.createTextNode("x")); | |
|
1244 | measureText.appendChild(elt("br")); | |
|
1245 | } | |
|
1246 | measureText.appendChild(document.createTextNode("x")); | |
|
1247 | } | |
|
1248 | removeChildrenAndAdd(display.measure, measureText); | |
|
1249 | var height = measureText.offsetHeight / 50; | |
|
1250 | if (height > 3) display.cachedTextHeight = height; | |
|
1251 | removeChildren(display.measure); | |
|
1252 | return height || 1; | |
|
1253 | } | |
|
1254 | ||
|
1255 | function charWidth(display) { | |
|
1256 | if (display.cachedCharWidth != null) return display.cachedCharWidth; | |
|
1257 | var anchor = elt("span", "x"); | |
|
1258 | var pre = elt("pre", [anchor]); | |
|
1259 | removeChildrenAndAdd(display.measure, pre); | |
|
1260 | var width = anchor.offsetWidth; | |
|
1261 | if (width > 2) display.cachedCharWidth = width; | |
|
1262 | return width || 10; | |
|
1263 | } | |
|
1264 | ||
|
1265 | // OPERATIONS | |
|
1266 | ||
|
1267 | // Operations are used to wrap changes in such a way that each | |
|
1268 | // change won't have to update the cursor and display (which would | |
|
1269 | // be awkward, slow, and error-prone), but instead updates are | |
|
1270 | // batched and then all combined and executed at once. | |
|
1271 | ||
|
1272 | var nextOpId = 0; | |
|
1273 | function startOperation(cm) { | |
|
1274 | cm.curOp = { | |
|
1275 | // An array of ranges of lines that have to be updated. See | |
|
1276 | // updateDisplay. | |
|
1277 | changes: [], | |
|
1278 | updateInput: null, | |
|
1279 | userSelChange: null, | |
|
1280 | textChanged: null, | |
|
1281 | selectionChanged: false, | |
|
1282 | cursorActivity: false, | |
|
1283 | updateMaxLine: false, | |
|
1284 | updateScrollPos: false, | |
|
1285 | id: ++nextOpId | |
|
1286 | }; | |
|
1287 | if (!delayedCallbackDepth++) delayedCallbacks = []; | |
|
1288 | } | |
|
1289 | ||
|
1290 | function endOperation(cm) { | |
|
1291 | var op = cm.curOp, doc = cm.doc, display = cm.display; | |
|
1292 | cm.curOp = null; | |
|
1293 | ||
|
1294 | if (op.updateMaxLine) computeMaxLength(cm); | |
|
1295 | if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) { | |
|
1296 | var width = measureLineWidth(cm, display.maxLine); | |
|
1297 | display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px"; | |
|
1298 | display.maxLineChanged = false; | |
|
1299 | var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth); | |
|
1300 | if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos) | |
|
1301 | setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true); | |
|
1302 | } | |
|
1303 | var newScrollPos, updated; | |
|
1304 | if (op.updateScrollPos) { | |
|
1305 | newScrollPos = op.updateScrollPos; | |
|
1306 | } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible | |
|
1307 | var coords = cursorCoords(cm, doc.sel.head); | |
|
1308 | newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); | |
|
1309 | } | |
|
1310 | if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null) { | |
|
1311 | updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop); | |
|
1312 | if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop; | |
|
1313 | } | |
|
1314 | if (!updated && op.selectionChanged) updateSelection(cm); | |
|
1315 | if (op.updateScrollPos) { | |
|
1316 | display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop; | |
|
1317 | display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft; | |
|
1318 | alignHorizontally(cm); | |
|
1319 | if (op.scrollToPos) | |
|
1320 | scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin); | |
|
1321 | } else if (newScrollPos) { | |
|
1322 | scrollCursorIntoView(cm); | |
|
1323 | } | |
|
1324 | if (op.selectionChanged) restartBlink(cm); | |
|
1325 | ||
|
1326 | if (cm.state.focused && op.updateInput) | |
|
1327 | resetInput(cm, op.userSelChange); | |
|
1328 | ||
|
1329 | var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; | |
|
1330 | if (hidden) for (var i = 0; i < hidden.length; ++i) | |
|
1331 | if (!hidden[i].lines.length) signal(hidden[i], "hide"); | |
|
1332 | if (unhidden) for (var i = 0; i < unhidden.length; ++i) | |
|
1333 | if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); | |
|
1334 | ||
|
1335 | var delayed; | |
|
1336 | if (!--delayedCallbackDepth) { | |
|
1337 | delayed = delayedCallbacks; | |
|
1338 | delayedCallbacks = null; | |
|
1339 | } | |
|
1340 | if (op.textChanged) | |
|
1341 | signal(cm, "change", cm, op.textChanged); | |
|
1342 | if (op.cursorActivity) signal(cm, "cursorActivity", cm); | |
|
1343 | if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](); | |
|
1344 | } | |
|
1345 | ||
|
1346 | // Wraps a function in an operation. Returns the wrapped function. | |
|
1347 | function operation(cm1, f) { | |
|
1348 | return function() { | |
|
1349 | var cm = cm1 || this, withOp = !cm.curOp; | |
|
1350 | if (withOp) startOperation(cm); | |
|
1351 | try { var result = f.apply(cm, arguments); } | |
|
1352 | finally { if (withOp) endOperation(cm); } | |
|
1353 | return result; | |
|
1354 | }; | |
|
1355 | } | |
|
1356 | function docOperation(f) { | |
|
1357 | return function() { | |
|
1358 | var withOp = this.cm && !this.cm.curOp, result; | |
|
1359 | if (withOp) startOperation(this.cm); | |
|
1360 | try { result = f.apply(this, arguments); } | |
|
1361 | finally { if (withOp) endOperation(this.cm); } | |
|
1362 | return result; | |
|
1363 | }; | |
|
1364 | } | |
|
1365 | function runInOp(cm, f) { | |
|
1366 | var withOp = !cm.curOp, result; | |
|
1367 | if (withOp) startOperation(cm); | |
|
1368 | try { result = f(); } | |
|
1369 | finally { if (withOp) endOperation(cm); } | |
|
1370 | return result; | |
|
1371 | } | |
|
1372 | ||
|
1373 | function regChange(cm, from, to, lendiff) { | |
|
1374 | if (from == null) from = cm.doc.first; | |
|
1375 | if (to == null) to = cm.doc.first + cm.doc.size; | |
|
1376 | cm.curOp.changes.push({from: from, to: to, diff: lendiff}); | |
|
1377 | } | |
|
1378 | ||
|
1379 | // INPUT HANDLING | |
|
1380 | ||
|
1381 | function slowPoll(cm) { | |
|
1382 | if (cm.display.pollingFast) return; | |
|
1383 | cm.display.poll.set(cm.options.pollInterval, function() { | |
|
1384 | readInput(cm); | |
|
1385 | if (cm.state.focused) slowPoll(cm); | |
|
1386 | }); | |
|
1387 | } | |
|
1388 | ||
|
1389 | function fastPoll(cm) { | |
|
1390 | var missed = false; | |
|
1391 | cm.display.pollingFast = true; | |
|
1392 | function p() { | |
|
1393 | var changed = readInput(cm); | |
|
1394 | if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);} | |
|
1395 | else {cm.display.pollingFast = false; slowPoll(cm);} | |
|
1396 | } | |
|
1397 | cm.display.poll.set(20, p); | |
|
1398 | } | |
|
1399 | ||
|
1400 | // prevInput is a hack to work with IME. If we reset the textarea | |
|
1401 | // on every change, that breaks IME. So we look for changes | |
|
1402 | // compared to the previous content instead. (Modern browsers have | |
|
1403 | // events that indicate IME taking place, but these are not widely | |
|
1404 | // supported or compatible enough yet to rely on.) | |
|
1405 | function readInput(cm) { | |
|
1406 | var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel; | |
|
1407 | if (!cm.state.focused || hasSelection(input) || isReadOnly(cm)) return false; | |
|
1408 | var text = input.value; | |
|
1409 | if (text == prevInput && posEq(sel.from, sel.to)) return false; | |
|
1410 | if (ie && !ie_lt9 && cm.display.inputHasSelection === text) { | |
|
1411 | resetInput(cm, true); | |
|
1412 | return false; | |
|
1413 | } | |
|
1414 | ||
|
1415 | var withOp = !cm.curOp; | |
|
1416 | if (withOp) startOperation(cm); | |
|
1417 | sel.shift = false; | |
|
1418 | var same = 0, l = Math.min(prevInput.length, text.length); | |
|
1419 | while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; | |
|
1420 | var from = sel.from, to = sel.to; | |
|
1421 | if (same < prevInput.length) | |
|
1422 | from = Pos(from.line, from.ch - (prevInput.length - same)); | |
|
1423 | else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming) | |
|
1424 | to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same))); | |
|
1425 | var updateInput = cm.curOp.updateInput; | |
|
1426 | makeChange(cm.doc, {from: from, to: to, text: splitLines(text.slice(same)), | |
|
1427 | origin: cm.state.pasteIncoming ? "paste" : "+input"}, "end"); | |
|
1428 | ||
|
1429 | cm.curOp.updateInput = updateInput; | |
|
1430 | if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = ""; | |
|
1431 | else cm.display.prevInput = text; | |
|
1432 | if (withOp) endOperation(cm); | |
|
1433 | cm.state.pasteIncoming = false; | |
|
1434 | return true; | |
|
1435 | } | |
|
1436 | ||
|
1437 | function resetInput(cm, user) { | |
|
1438 | var minimal, selected, doc = cm.doc; | |
|
1439 | if (!posEq(doc.sel.from, doc.sel.to)) { | |
|
1440 | cm.display.prevInput = ""; | |
|
1441 | minimal = hasCopyEvent && | |
|
1442 | (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000); | |
|
1443 | var content = minimal ? "-" : selected || cm.getSelection(); | |
|
1444 | cm.display.input.value = content; | |
|
1445 | if (cm.state.focused) selectInput(cm.display.input); | |
|
1446 | if (ie && !ie_lt9) cm.display.inputHasSelection = content; | |
|
1447 | } else if (user) { | |
|
1448 | cm.display.prevInput = cm.display.input.value = ""; | |
|
1449 | if (ie && !ie_lt9) cm.display.inputHasSelection = null; | |
|
1450 | } | |
|
1451 | cm.display.inaccurateSelection = minimal; | |
|
1452 | } | |
|
1453 | ||
|
1454 | function focusInput(cm) { | |
|
1455 | if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input)) | |
|
1456 | cm.display.input.focus(); | |
|
1457 | } | |
|
1458 | ||
|
1459 | function isReadOnly(cm) { | |
|
1460 | return cm.options.readOnly || cm.doc.cantEdit; | |
|
1461 | } | |
|
1462 | ||
|
1463 | // EVENT HANDLERS | |
|
1464 | ||
|
1465 | function registerEventHandlers(cm) { | |
|
1466 | var d = cm.display; | |
|
1467 | on(d.scroller, "mousedown", operation(cm, onMouseDown)); | |
|
1468 | if (ie) | |
|
1469 | on(d.scroller, "dblclick", operation(cm, function(e) { | |
|
1470 | var pos = posFromMouse(cm, e); | |
|
1471 | if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; | |
|
1472 | e_preventDefault(e); | |
|
1473 | var word = findWordAt(getLine(cm.doc, pos.line).text, pos); | |
|
1474 | extendSelection(cm.doc, word.from, word.to); | |
|
1475 | })); | |
|
1476 | else | |
|
1477 | on(d.scroller, "dblclick", e_preventDefault); | |
|
1478 | on(d.lineSpace, "selectstart", function(e) { | |
|
1479 | if (!eventInWidget(d, e)) e_preventDefault(e); | |
|
1480 | }); | |
|
102 | 1481 | // Gecko browsers fire contextmenu *after* opening the menu, at |
|
103 | 1482 | // which point we can't mess with it anymore. Context menu is |
|
104 | 1483 | // handled in onMouseDown for Gecko. |
|
105 |
if (! |
|
|
106 | connect(scroller, "scroll", onScrollMain); | |
|
107 |
|
|
|
108 | connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);}); | |
|
109 | var resizeHandler = connect(window, "resize", function() { | |
|
110 | if (wrapper.parentNode) updateDisplay(true); | |
|
111 | else resizeHandler(); | |
|
112 | }, true); | |
|
113 | connect(input, "keyup", operation(onKeyUp)); | |
|
114 | connect(input, "input", fastPoll); | |
|
115 | connect(input, "keydown", operation(onKeyDown)); | |
|
116 | connect(input, "keypress", operation(onKeyPress)); | |
|
117 | connect(input, "focus", onFocus); | |
|
118 | connect(input, "blur", onBlur); | |
|
1484 | if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); | |
|
1485 | ||
|
1486 | on(d.scroller, "scroll", function() { | |
|
1487 | if (d.scroller.clientHeight) { | |
|
1488 | setScrollTop(cm, d.scroller.scrollTop); | |
|
1489 | setScrollLeft(cm, d.scroller.scrollLeft, true); | |
|
1490 | signal(cm, "scroll", cm); | |
|
1491 | } | |
|
1492 | }); | |
|
1493 | on(d.scrollbarV, "scroll", function() { | |
|
1494 | if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop); | |
|
1495 | }); | |
|
1496 | on(d.scrollbarH, "scroll", function() { | |
|
1497 | if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft); | |
|
1498 | }); | |
|
1499 | ||
|
1500 | on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); | |
|
1501 | on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); | |
|
1502 | ||
|
1503 | function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); } | |
|
1504 | on(d.scrollbarH, "mousedown", reFocus); | |
|
1505 | on(d.scrollbarV, "mousedown", reFocus); | |
|
1506 | // Prevent wrapper from ever scrolling | |
|
1507 | on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); | |
|
1508 | ||
|
1509 | function onResize() { | |
|
1510 | // Might be a text scaling operation, clear size caches. | |
|
1511 | d.cachedCharWidth = d.cachedTextHeight = null; | |
|
1512 | clearCaches(cm); | |
|
1513 | runInOp(cm, bind(regChange, cm)); | |
|
1514 | } | |
|
1515 | on(window, "resize", onResize); | |
|
1516 | // Above handler holds on to the editor and its data structures. | |
|
1517 | // Here we poll to unregister it when the editor is no longer in | |
|
1518 | // the document, so that it can be garbage-collected. | |
|
1519 | function unregister() { | |
|
1520 | for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {} | |
|
1521 | if (p) setTimeout(unregister, 5000); | |
|
1522 | else off(window, "resize", onResize); | |
|
1523 | } | |
|
1524 | setTimeout(unregister, 5000); | |
|
1525 | ||
|
1526 | on(d.input, "keyup", operation(cm, function(e) { | |
|
1527 | if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; | |
|
1528 | if (e.keyCode == 16) cm.doc.sel.shift = false; | |
|
1529 | })); | |
|
1530 | on(d.input, "input", bind(fastPoll, cm)); | |
|
1531 | on(d.input, "keydown", operation(cm, onKeyDown)); | |
|
1532 | on(d.input, "keypress", operation(cm, onKeyPress)); | |
|
1533 | on(d.input, "focus", bind(onFocus, cm)); | |
|
1534 | on(d.input, "blur", bind(onBlur, cm)); | |
|
119 | 1535 | |
|
120 | 1536 | function drag_(e) { |
|
121 |
if (options.onDragEvent && options.onDragEvent( |
|
|
1537 | if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; | |
|
122 | 1538 | e_stop(e); |
|
123 | 1539 | } |
|
124 | if (options.dragDrop) { | |
|
125 |
|
|
|
126 |
|
|
|
127 |
|
|
|
128 |
|
|
|
129 | } | |
|
130 |
|
|
|
131 | connect(input, "paste", function(){pasteIncoming = true; fastPoll();}); | |
|
132 | connect(input, "cut", operation(function(){ | |
|
133 | if (!options.readOnly) replaceSelection(""); | |
|
134 |
}) |
|
|
1540 | if (cm.options.dragDrop) { | |
|
1541 | on(d.scroller, "dragstart", function(e){onDragStart(cm, e);}); | |
|
1542 | on(d.scroller, "dragenter", drag_); | |
|
1543 | on(d.scroller, "dragover", drag_); | |
|
1544 | on(d.scroller, "drop", operation(cm, onDrop)); | |
|
1545 | } | |
|
1546 | on(d.scroller, "paste", function(e){ | |
|
1547 | if (eventInWidget(d, e)) return; | |
|
1548 | focusInput(cm); | |
|
1549 | fastPoll(cm); | |
|
1550 | }); | |
|
1551 | on(d.input, "paste", function() { | |
|
1552 | cm.state.pasteIncoming = true; | |
|
1553 | fastPoll(cm); | |
|
1554 | }); | |
|
1555 | ||
|
1556 | function prepareCopy() { | |
|
1557 | if (d.inaccurateSelection) { | |
|
1558 | d.prevInput = ""; | |
|
1559 | d.inaccurateSelection = false; | |
|
1560 | d.input.value = cm.getSelection(); | |
|
1561 | selectInput(d.input); | |
|
1562 | } | |
|
1563 | } | |
|
1564 | on(d.input, "cut", prepareCopy); | |
|
1565 | on(d.input, "copy", prepareCopy); | |
|
135 | 1566 | |
|
136 | 1567 | // Needed to handle Tab key in KHTML |
|
137 |
if (khtml) |
|
|
138 | if (document.activeElement == input) input.blur(); | |
|
139 | focusInput(); | |
|
1568 | if (khtml) on(d.sizer, "mouseup", function() { | |
|
1569 | if (document.activeElement == d.input) d.input.blur(); | |
|
1570 | focusInput(cm); | |
|
140 | 1571 | }); |
|
141 | ||
|
142 | // IE throws unspecified error in certain cases, when | |
|
143 | // trying to access activeElement before onload | |
|
144 | var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { } | |
|
145 | if (hasFocus || options.autofocus) setTimeout(onFocus, 20); | |
|
146 | else onBlur(); | |
|
147 | ||
|
148 | function isLine(l) {return l >= 0 && l < doc.size;} | |
|
149 | // The instance object that we'll return. Mostly calls out to | |
|
150 | // local functions in the CodeMirror function. Some do some extra | |
|
151 | // range checking and/or clipping. operation is used to wrap the | |
|
152 | // call so that changes it makes are tracked, and the display is | |
|
153 | // updated afterwards. | |
|
154 | var instance = wrapper.CodeMirror = { | |
|
155 | getValue: getValue, | |
|
156 | setValue: operation(setValue), | |
|
157 | getSelection: getSelection, | |
|
158 | replaceSelection: operation(replaceSelection), | |
|
159 | focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();}, | |
|
160 | setOption: function(option, value) { | |
|
161 | var oldVal = options[option]; | |
|
162 | options[option] = value; | |
|
163 | if (option == "mode" || option == "indentUnit") loadMode(); | |
|
164 | else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();} | |
|
165 | else if (option == "readOnly" && !value) {resetInput(true);} | |
|
166 | else if (option == "theme") themeChanged(); | |
|
167 | else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); | |
|
168 | else if (option == "tabSize") updateDisplay(true); | |
|
169 | else if (option == "keyMap") keyMapChanged(); | |
|
170 | else if (option == "tabindex") input.tabIndex = value; | |
|
171 | if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || | |
|
172 | option == "theme" || option == "lineNumberFormatter") { | |
|
173 | gutterChanged(); | |
|
174 | updateDisplay(true); | |
|
175 | } | |
|
176 |
|
|
|
177 | getOption: function(option) {return options[option];}, | |
|
178 | getMode: function() {return mode;}, | |
|
179 | undo: operation(undo), | |
|
180 | redo: operation(redo), | |
|
181 | indentLine: operation(function(n, dir) { | |
|
182 | if (typeof dir != "string") { | |
|
183 | if (dir == null) dir = options.smartIndent ? "smart" : "prev"; | |
|
184 | else dir = dir ? "add" : "subtract"; | |
|
185 | } | |
|
186 | if (isLine(n)) indentLine(n, dir); | |
|
187 | }), | |
|
188 | indentSelection: operation(indentSelected), | |
|
189 | historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, | |
|
190 | clearHistory: function() {history = new History();}, | |
|
191 | setHistory: function(histData) { | |
|
192 | history = new History(); | |
|
193 | history.done = histData.done; | |
|
194 | history.undone = histData.undone; | |
|
195 | }, | |
|
196 | getHistory: function() { | |
|
197 | function cp(arr) { | |
|
198 | for (var i = 0, nw = [], nwelt; i < arr.length; ++i) { | |
|
199 | nw.push(nwelt = []); | |
|
200 | for (var j = 0, elt = arr[i]; j < elt.length; ++j) { | |
|
201 | var old = [], cur = elt[j]; | |
|
202 | nwelt.push({start: cur.start, added: cur.added, old: old}); | |
|
203 | for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k])); | |
|
204 | } | |
|
205 | } | |
|
206 | return nw; | |
|
207 | } | |
|
208 | return {done: cp(history.done), undone: cp(history.undone)}; | |
|
209 | }, | |
|
210 | matchBrackets: operation(function(){matchBrackets(true);}), | |
|
211 | getTokenAt: operation(function(pos) { | |
|
212 | pos = clipPos(pos); | |
|
213 | return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), options.tabSize, pos.ch); | |
|
214 | }), | |
|
215 | getStateAfter: function(line) { | |
|
216 | line = clipLine(line == null ? doc.size - 1: line); | |
|
217 | return getStateBefore(line + 1); | |
|
218 | }, | |
|
219 | cursorCoords: function(start, mode) { | |
|
220 | if (start == null) start = sel.inverted; | |
|
221 | return this.charCoords(start ? sel.from : sel.to, mode); | |
|
222 | }, | |
|
223 | charCoords: function(pos, mode) { | |
|
224 | pos = clipPos(pos); | |
|
225 | if (mode == "local") return localCoords(pos, false); | |
|
226 | if (mode == "div") return localCoords(pos, true); | |
|
227 | return pageCoords(pos); | |
|
228 | }, | |
|
229 | coordsChar: function(coords) { | |
|
230 | var off = eltOffset(lineSpace); | |
|
231 | return coordsChar(coords.x - off.left, coords.y - off.top); | |
|
232 | }, | |
|
233 | defaultTextHeight: function() { return textHeight(); }, | |
|
234 | markText: operation(markText), | |
|
235 | setBookmark: setBookmark, | |
|
236 | findMarksAt: findMarksAt, | |
|
237 | setMarker: operation(addGutterMarker), | |
|
238 | clearMarker: operation(removeGutterMarker), | |
|
239 | setLineClass: operation(setLineClass), | |
|
240 | hideLine: operation(function(h) {return setLineHidden(h, true);}), | |
|
241 | showLine: operation(function(h) {return setLineHidden(h, false);}), | |
|
242 | onDeleteLine: function(line, f) { | |
|
243 | if (typeof line == "number") { | |
|
244 | if (!isLine(line)) return null; | |
|
245 | line = getLine(line); | |
|
246 | } | |
|
247 | (line.handlers || (line.handlers = [])).push(f); | |
|
248 | return line; | |
|
249 | }, | |
|
250 | lineInfo: lineInfo, | |
|
251 | getViewport: function() { return {from: showingFrom, to: showingTo};}, | |
|
252 | addWidget: function(pos, node, scroll, vert, horiz) { | |
|
253 | pos = localCoords(clipPos(pos)); | |
|
254 | var top = pos.yBot, left = pos.x; | |
|
255 | node.style.position = "absolute"; | |
|
256 | sizer.appendChild(node); | |
|
257 | if (vert == "over") top = pos.y; | |
|
258 | else if (vert == "near") { | |
|
259 | var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()), | |
|
260 | hspace = Math.max(sizer.clientWidth, lineSpace.clientWidth) - paddingLeft(); | |
|
261 | if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) | |
|
262 | top = pos.y - node.offsetHeight; | |
|
263 | if (left + node.offsetWidth > hspace) | |
|
264 | left = hspace - node.offsetWidth; | |
|
265 | } | |
|
266 | node.style.top = (top + paddingTop()) + "px"; | |
|
267 | node.style.left = node.style.right = ""; | |
|
268 | if (horiz == "right") { | |
|
269 | left = sizer.clientWidth - node.offsetWidth; | |
|
270 | node.style.right = "0px"; | |
|
271 | } else { | |
|
272 | if (horiz == "left") left = 0; | |
|
273 | else if (horiz == "middle") left = (sizer.clientWidth - node.offsetWidth) / 2; | |
|
274 | node.style.left = (left + paddingLeft()) + "px"; | |
|
1572 | } | |
|
1573 | ||
|
1574 | function eventInWidget(display, e) { | |
|
1575 | for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { | |
|
1576 | if (!n) return true; | |
|
1577 | if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) || | |
|
1578 | n.parentNode == display.sizer && n != display.mover) return true; | |
|
1579 | } | |
|
1580 | } | |
|
1581 | ||
|
1582 | function posFromMouse(cm, e, liberal) { | |
|
1583 | var display = cm.display; | |
|
1584 | if (!liberal) { | |
|
1585 | var target = e_target(e); | |
|
1586 | if (target == display.scrollbarH || target == display.scrollbarH.firstChild || | |
|
1587 | target == display.scrollbarV || target == display.scrollbarV.firstChild || | |
|
1588 | target == display.scrollbarFiller) return null; | |
|
1589 | } | |
|
1590 | var x, y, space = getRect(display.lineSpace); | |
|
1591 | // Fails unpredictably on IE[67] when mouse is dragged around quickly. | |
|
1592 | try { x = e.clientX; y = e.clientY; } catch (e) { return null; } | |
|
1593 | return coordsChar(cm, x - space.left, y - space.top); | |
|
1594 | } | |
|
1595 | ||
|
1596 | var lastClick, lastDoubleClick; | |
|
1597 | function onMouseDown(e) { | |
|
1598 | var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel; | |
|
1599 | sel.shift = e.shiftKey; | |
|
1600 | ||
|
1601 | if (eventInWidget(display, e)) { | |
|
1602 | if (!webkit) { | |
|
1603 | display.scroller.draggable = false; | |
|
1604 | setTimeout(function(){display.scroller.draggable = true;}, 100); | |
|
1605 | } | |
|
1606 | return; | |
|
1607 | } | |
|
1608 | if (clickInGutter(cm, e)) return; | |
|
1609 | var start = posFromMouse(cm, e); | |
|
1610 | ||
|
1611 | switch (e_button(e)) { | |
|
1612 | case 3: | |
|
1613 | if (captureMiddleClick) onContextMenu.call(cm, cm, e); | |
|
1614 | return; | |
|
1615 | case 2: | |
|
1616 | if (start) extendSelection(cm.doc, start); | |
|
1617 | setTimeout(bind(focusInput, cm), 20); | |
|
1618 | e_preventDefault(e); | |
|
1619 | return; | |
|
1620 | } | |
|
1621 | // For button 1, if it was clicked inside the editor | |
|
1622 | // (posFromMouse returning non-null), we have to adjust the | |
|
1623 | // selection. | |
|
1624 | if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;} | |
|
1625 | ||
|
1626 | if (!cm.state.focused) onFocus(cm); | |
|
1627 | ||
|
1628 | var now = +new Date, type = "single"; | |
|
1629 | if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { | |
|
1630 | type = "triple"; | |
|
1631 | e_preventDefault(e); | |
|
1632 | setTimeout(bind(focusInput, cm), 20); | |
|
1633 | selectLine(cm, start.line); | |
|
1634 | } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { | |
|
1635 | type = "double"; | |
|
1636 | lastDoubleClick = {time: now, pos: start}; | |
|
1637 | e_preventDefault(e); | |
|
1638 | var word = findWordAt(getLine(doc, start.line).text, start); | |
|
1639 | extendSelection(cm.doc, word.from, word.to); | |
|
1640 | } else { lastClick = {time: now, pos: start}; } | |
|
1641 | ||
|
1642 | var last = start; | |
|
1643 | if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) && | |
|
1644 | !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") { | |
|
1645 | var dragEnd = operation(cm, function(e2) { | |
|
1646 | if (webkit) display.scroller.draggable = false; | |
|
1647 | cm.state.draggingText = false; | |
|
1648 | off(document, "mouseup", dragEnd); | |
|
1649 | off(display.scroller, "drop", dragEnd); | |
|
1650 | if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { | |
|
1651 | e_preventDefault(e2); | |
|
1652 | extendSelection(cm.doc, start); | |
|
1653 | focusInput(cm); | |
|
275 | 1654 | } |
|
276 | if (scroll) | |
|
277 | scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); | |
|
278 | }, | |
|
279 | ||
|
280 | lineCount: function() {return doc.size;}, | |
|
281 | clipPos: clipPos, | |
|
282 | getCursor: function(start) { | |
|
283 | if (start == null) start = sel.inverted; | |
|
284 | return copyPos(start ? sel.from : sel.to); | |
|
285 |
|
|
|
286 | somethingSelected: function() {return !posEq(sel.from, sel.to);}, | |
|
287 | setCursor: operation(function(line, ch, user) { | |
|
288 | if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user); | |
|
289 | else setCursor(line, ch, user); | |
|
290 | }), | |
|
291 | setSelection: operation(function(from, to, user) { | |
|
292 | (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from)); | |
|
293 | }), | |
|
294 | getLine: function(line) {if (isLine(line)) return getLine(line).text;}, | |
|
295 | getLineHandle: function(line) {if (isLine(line)) return getLine(line);}, | |
|
296 | setLine: operation(function(line, text) { | |
|
297 | if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); | |
|
298 |
} |
|
|
299 | removeLine: operation(function(line) { | |
|
300 | if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0})); | |
|
301 | }), | |
|
302 | replaceRange: operation(replaceRange), | |
|
303 | getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);}, | |
|
304 | ||
|
305 | triggerOnKeyDown: operation(onKeyDown), | |
|
306 | execCommand: function(cmd) {return commands[cmd](instance);}, | |
|
307 | // Stuff used by commands, probably not much use to outside code. | |
|
308 | moveH: operation(moveH), | |
|
309 | deleteH: operation(deleteH), | |
|
310 | moveV: operation(moveV), | |
|
311 | toggleOverwrite: function() { | |
|
312 | if(overwrite){ | |
|
313 | overwrite = false; | |
|
314 | cursor.className = cursor.className.replace(" CodeMirror-overwrite", ""); | |
|
315 | } else { | |
|
316 | overwrite = true; | |
|
317 | cursor.className += " CodeMirror-overwrite"; | |
|
318 | } | |
|
319 | }, | |
|
320 | ||
|
321 | posFromIndex: function(off) { | |
|
322 | var lineNo = 0, ch; | |
|
323 | doc.iter(0, doc.size, function(line) { | |
|
324 | var sz = line.text.length + 1; | |
|
325 | if (sz > off) { ch = off; return true; } | |
|
326 | off -= sz; | |
|
327 | ++lineNo; | |
|
328 | }); | |
|
329 | return clipPos({line: lineNo, ch: ch}); | |
|
330 | }, | |
|
331 | indexFromPos: function (coords) { | |
|
332 | if (coords.line < 0 || coords.ch < 0) return 0; | |
|
333 | var index = coords.ch; | |
|
334 | doc.iter(0, coords.line, function (line) { | |
|
335 | index += line.text.length + 1; | |
|
336 | }); | |
|
337 | return index; | |
|
338 | }, | |
|
339 | scrollTo: function(x, y) { | |
|
340 | if (x != null) scroller.scrollLeft = x; | |
|
341 | if (y != null) scrollbar.scrollTop = scroller.scrollTop = y; | |
|
342 | updateDisplay([]); | |
|
343 | }, | |
|
344 | getScrollInfo: function() { | |
|
345 | return {x: scroller.scrollLeft, y: scrollbar.scrollTop, | |
|
346 | height: scrollbar.scrollHeight, width: scroller.scrollWidth}; | |
|
347 | }, | |
|
348 | scrollIntoView: function(pos) { | |
|
349 | var coords = localCoords(pos ? clipPos(pos) : sel.inverted ? sel.from : sel.to); | |
|
350 | scrollIntoView(coords.x, coords.y, coords.x, coords.yBot); | |
|
351 | }, | |
|
352 | ||
|
353 | setSize: function(width, height) { | |
|
354 | function interpret(val) { | |
|
355 | val = String(val); | |
|
356 | return /^\d+$/.test(val) ? val + "px" : val; | |
|
357 | } | |
|
358 | if (width != null) wrapper.style.width = interpret(width); | |
|
359 | if (height != null) scroller.style.height = interpret(height); | |
|
360 | instance.refresh(); | |
|
361 | }, | |
|
362 | ||
|
363 | operation: function(f){return operation(f)();}, | |
|
364 | compoundChange: function(f){return compoundChange(f);}, | |
|
365 | refresh: function(){ | |
|
366 | updateDisplay(true, null, lastScrollTop); | |
|
367 | if (scrollbar.scrollHeight > lastScrollTop) | |
|
368 | scrollbar.scrollTop = lastScrollTop; | |
|
369 | }, | |
|
370 | getInputField: function(){return input;}, | |
|
371 | getWrapperElement: function(){return wrapper;}, | |
|
372 | getScrollerElement: function(){return scroller;}, | |
|
373 | getGutterElement: function(){return gutter;} | |
|
374 | }; | |
|
375 | ||
|
376 | function getLine(n) { return getLineAt(doc, n); } | |
|
377 | function updateLineHeight(line, height) { | |
|
378 | gutterDirty = true; | |
|
379 | var diff = height - line.height; | |
|
380 | for (var n = line; n; n = n.parent) n.height += diff; | |
|
381 | } | |
|
382 | ||
|
383 | function lineContent(line, wrapAt) { | |
|
384 | if (!line.styles) | |
|
385 | line.highlight(mode, line.stateAfter = getStateBefore(lineNo(line)), options.tabSize); | |
|
386 | return line.getContent(options.tabSize, wrapAt, options.lineWrapping); | |
|
387 | } | |
|
388 | ||
|
389 | function setValue(code) { | |
|
390 | var top = {line: 0, ch: 0}; | |
|
391 | updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length}, | |
|
392 | splitLines(code), top, top); | |
|
393 | updateInput = true; | |
|
394 | } | |
|
395 | function getValue(lineSep) { | |
|
396 | var text = []; | |
|
397 | doc.iter(0, doc.size, function(line) { text.push(line.text); }); | |
|
398 | return text.join(lineSep || "\n"); | |
|
399 | } | |
|
400 | ||
|
401 | function onScrollBar(e) { | |
|
402 | if (Math.abs(scrollbar.scrollTop - lastScrollTop) > 1) { | |
|
403 | lastScrollTop = scroller.scrollTop = scrollbar.scrollTop; | |
|
404 | updateDisplay([]); | |
|
1655 | }); | |
|
1656 | // Let the drag handler handle this. | |
|
1657 | if (webkit) display.scroller.draggable = true; | |
|
1658 | cm.state.draggingText = dragEnd; | |
|
1659 | // IE's approach to draggable | |
|
1660 | if (display.scroller.dragDrop) display.scroller.dragDrop(); | |
|
1661 | on(document, "mouseup", dragEnd); | |
|
1662 | on(display.scroller, "drop", dragEnd); | |
|
1663 | return; | |
|
1664 | } | |
|
1665 | e_preventDefault(e); | |
|
1666 | if (type == "single") extendSelection(cm.doc, clipPos(doc, start)); | |
|
1667 | ||
|
1668 | var startstart = sel.from, startend = sel.to, lastPos = start; | |
|
1669 | ||
|
1670 | function doSelect(cur) { | |
|
1671 | if (posEq(lastPos, cur)) return; | |
|
1672 | lastPos = cur; | |
|
1673 | ||
|
1674 | if (type == "single") { | |
|
1675 | extendSelection(cm.doc, clipPos(doc, start), cur); | |
|
1676 | return; | |
|
1677 | } | |
|
1678 | ||
|
1679 | startstart = clipPos(doc, startstart); | |
|
1680 | startend = clipPos(doc, startend); | |
|
1681 | if (type == "double") { | |
|
1682 | var word = findWordAt(getLine(doc, cur.line).text, cur); | |
|
1683 | if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend); | |
|
1684 | else extendSelection(cm.doc, startstart, word.to); | |
|
1685 | } else if (type == "triple") { | |
|
1686 | if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0))); | |
|
1687 | else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0))); | |
|
405 | 1688 | } |
|
406 | 1689 | } |
|
407 | 1690 | |
|
408 | function onScrollMain(e) { | |
|
409 | if (options.fixedGutter && gutter.style.left != scroller.scrollLeft + "px") | |
|
410 | gutter.style.left = scroller.scrollLeft + "px"; | |
|
411 | if (Math.abs(scroller.scrollTop - lastScrollTop) > 1) { | |
|
412 | lastScrollTop = scroller.scrollTop; | |
|
413 | if (scrollbar.scrollTop != lastScrollTop) | |
|
414 | scrollbar.scrollTop = lastScrollTop; | |
|
415 | updateDisplay([]); | |
|
1691 | var editorSize = getRect(display.wrapper); | |
|
1692 | // Used to ensure timeout re-tries don't fire when another extend | |
|
1693 | // happened in the meantime (clearTimeout isn't reliable -- at | |
|
1694 | // least on Chrome, the timeouts still happen even when cleared, | |
|
1695 | // if the clear happens after their scheduled firing time). | |
|
1696 | var counter = 0; | |
|
1697 | ||
|
1698 | function extend(e) { | |
|
1699 | var curCount = ++counter; | |
|
1700 | var cur = posFromMouse(cm, e, true); | |
|
1701 | if (!cur) return; | |
|
1702 | if (!posEq(cur, last)) { | |
|
1703 | if (!cm.state.focused) onFocus(cm); | |
|
1704 | last = cur; | |
|
1705 | doSelect(cur); | |
|
1706 | var visible = visibleLines(display, doc); | |
|
1707 | if (cur.line >= visible.to || cur.line < visible.from) | |
|
1708 | setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); | |
|
1709 | } else { | |
|
1710 | var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; | |
|
1711 | if (outside) setTimeout(operation(cm, function() { | |
|
1712 | if (counter != curCount) return; | |
|
1713 | display.scroller.scrollTop += outside; | |
|
1714 | extend(e); | |
|
1715 | }), 50); | |
|
416 | 1716 | } |
|
417 | if (options.onScroll) options.onScroll(instance); | |
|
418 | } | |
|
419 | ||
|
420 | function onMouseDown(e) { | |
|
421 | setShift(e_prop(e, "shiftKey")); | |
|
422 | // Check whether this is a click in a widget | |
|
423 | for (var n = e_target(e); n != wrapper; n = n.parentNode) | |
|
424 | if (n.parentNode == sizer && n != mover) return; | |
|
425 | ||
|
426 | // See if this is a click in the gutter | |
|
427 | for (var n = e_target(e); n != wrapper; n = n.parentNode) | |
|
428 | if (n.parentNode == gutterText) { | |
|
429 | if (options.onGutterClick) | |
|
430 | options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e); | |
|
431 | return e_preventDefault(e); | |
|
432 | } | |
|
433 | ||
|
434 | var start = posFromMouse(e); | |
|
435 | ||
|
436 | switch (e_button(e)) { | |
|
437 | case 3: | |
|
438 | if (gecko) onContextMenu(e); | |
|
439 | return; | |
|
440 | case 2: | |
|
441 | if (start) setCursor(start.line, start.ch, true); | |
|
442 | setTimeout(focusInput, 20); | |
|
443 | e_preventDefault(e); | |
|
1717 | } | |
|
1718 | ||
|
1719 | function done(e) { | |
|
1720 | counter = Infinity; | |
|
1721 | var cur = posFromMouse(cm, e); | |
|
1722 | if (cur) doSelect(cur); | |
|
1723 | e_preventDefault(e); | |
|
1724 | focusInput(cm); | |
|
1725 | off(document, "mousemove", move); | |
|
1726 | off(document, "mouseup", up); | |
|
1727 | } | |
|
1728 | ||
|
1729 | var move = operation(cm, function(e) { | |
|
1730 | if (!ie && !e_button(e)) done(e); | |
|
1731 | else extend(e); | |
|
1732 | }); | |
|
1733 | var up = operation(cm, done); | |
|
1734 | on(document, "mousemove", move); | |
|
1735 | on(document, "mouseup", up); | |
|
1736 | } | |
|
1737 | ||
|
1738 | function onDrop(e) { | |
|
1739 | var cm = this; | |
|
1740 | if (eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e)))) | |
|
1741 | return; | |
|
1742 | e_preventDefault(e); | |
|
1743 | var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; | |
|
1744 | if (!pos || isReadOnly(cm)) return; | |
|
1745 | if (files && files.length && window.FileReader && window.File) { | |
|
1746 | var n = files.length, text = Array(n), read = 0; | |
|
1747 | var loadFile = function(file, i) { | |
|
1748 | var reader = new FileReader; | |
|
1749 | reader.onload = function() { | |
|
1750 | text[i] = reader.result; | |
|
1751 | if (++read == n) { | |
|
1752 | pos = clipPos(cm.doc, pos); | |
|
1753 | makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around"); | |
|
1754 | } | |
|
1755 | }; | |
|
1756 | reader.readAsText(file); | |
|
1757 | }; | |
|
1758 | for (var i = 0; i < n; ++i) loadFile(files[i], i); | |
|
1759 | } else { | |
|
1760 | // Don't do a replace if the drop happened inside of the selected text. | |
|
1761 | if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) { | |
|
1762 | cm.state.draggingText(e); | |
|
1763 | // Ensure the editor is re-focused | |
|
1764 | setTimeout(bind(focusInput, cm), 20); | |
|
444 | 1765 | return; |
|
445 | 1766 | } |
|
446 | // For button 1, if it was clicked inside the editor | |
|
447 | // (posFromMouse returning non-null), we have to adjust the | |
|
448 | // selection. | |
|
449 | if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;} | |
|
450 | ||
|
451 | if (!focused) onFocus(); | |
|
452 | ||
|
453 | var now = +new Date, type = "single"; | |
|
454 | if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { | |
|
455 | type = "triple"; | |
|
456 | e_preventDefault(e); | |
|
457 | setTimeout(focusInput, 20); | |
|
458 | selectLine(start.line); | |
|
459 | } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { | |
|
460 | type = "double"; | |
|
461 | lastDoubleClick = {time: now, pos: start}; | |
|
462 | e_preventDefault(e); | |
|
463 | var word = findWordAt(start); | |
|
464 | setSelectionUser(word.from, word.to); | |
|
465 | } else { lastClick = {time: now, pos: start}; } | |
|
466 | ||
|
467 | function dragEnd(e2) { | |
|
468 | if (webkit) scroller.draggable = false; | |
|
469 | draggingText = false; | |
|
470 | up(); drop(); | |
|
471 | if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { | |
|
472 | e_preventDefault(e2); | |
|
473 | setCursor(start.line, start.ch, true); | |
|
474 | focusInput(); | |
|
1767 | try { | |
|
1768 | var text = e.dataTransfer.getData("Text"); | |
|
1769 | if (text) { | |
|
1770 | var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to; | |
|
1771 | setSelection(cm.doc, pos, pos); | |
|
1772 | if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste"); | |
|
1773 | cm.replaceSelection(text, null, "paste"); | |
|
1774 | focusInput(cm); | |
|
1775 | onFocus(cm); | |
|
475 | 1776 | } |
|
476 | 1777 | } |
|
477 | var last = start, going; | |
|
478 | if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && | |
|
479 | !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") { | |
|
480 | // Let the drag handler handle this. | |
|
481 | if (webkit) scroller.draggable = true; | |
|
482 | var up = connect(document, "mouseup", operation(dragEnd), true); | |
|
483 | var drop = connect(scroller, "drop", operation(dragEnd), true); | |
|
484 | draggingText = true; | |
|
485 | // IE's approach to draggable | |
|
486 | if (scroller.dragDrop) scroller.dragDrop(); | |
|
487 | return; | |
|
1778 | catch(e){} | |
|
1779 | } | |
|
1780 | } | |
|
1781 | ||
|
1782 | function clickInGutter(cm, e) { | |
|
1783 | var display = cm.display; | |
|
1784 | try { var mX = e.clientX, mY = e.clientY; } | |
|
1785 | catch(e) { return false; } | |
|
1786 | ||
|
1787 | if (mX >= Math.floor(getRect(display.gutters).right)) return false; | |
|
1788 | e_preventDefault(e); | |
|
1789 | if (!hasHandler(cm, "gutterClick")) return true; | |
|
1790 | ||
|
1791 | var lineBox = getRect(display.lineDiv); | |
|
1792 | if (mY > lineBox.bottom) return true; | |
|
1793 | mY -= lineBox.top - display.viewOffset; | |
|
1794 | ||
|
1795 | for (var i = 0; i < cm.options.gutters.length; ++i) { | |
|
1796 | var g = display.gutters.childNodes[i]; | |
|
1797 | if (g && getRect(g).right >= mX) { | |
|
1798 | var line = lineAtHeight(cm.doc, mY); | |
|
1799 | var gutter = cm.options.gutters[i]; | |
|
1800 | signalLater(cm, "gutterClick", cm, line, gutter, e); | |
|
1801 | break; | |
|
1802 | } | |
|
1803 | } | |
|
1804 | return true; | |
|
1805 | } | |
|
1806 | ||
|
1807 | function onDragStart(cm, e) { | |
|
1808 | if (ie && !cm.state.draggingText) { e_stop(e); return; } | |
|
1809 | if (eventInWidget(cm.display, e)) return; | |
|
1810 | ||
|
1811 | var txt = cm.getSelection(); | |
|
1812 | e.dataTransfer.setData("Text", txt); | |
|
1813 | ||
|
1814 | // Use dummy image instead of default browsers image. | |
|
1815 | // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. | |
|
1816 | if (e.dataTransfer.setDragImage) { | |
|
1817 | var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); | |
|
1818 | if (opera) { | |
|
1819 | img.width = img.height = 1; | |
|
1820 | cm.display.wrapper.appendChild(img); | |
|
1821 | // Force a relayout, or Opera won't use our image for some obscure reason | |
|
1822 | img._top = img.offsetTop; | |
|
1823 | } | |
|
1824 | if (safari) { | |
|
1825 | if (cm.display.dragImg) { | |
|
1826 | img = cm.display.dragImg; | |
|
1827 | } else { | |
|
1828 | cm.display.dragImg = img; | |
|
1829 | img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; | |
|
1830 | cm.display.wrapper.appendChild(img); | |
|
1831 | } | |
|
488 | 1832 | } |
|
489 | e_preventDefault(e); | |
|
490 | if (type == "single") setCursor(start.line, start.ch, true); | |
|
491 | ||
|
492 | var startstart = sel.from, startend = sel.to; | |
|
493 | ||
|
494 | function doSelect(cur) { | |
|
495 | if (type == "single") { | |
|
496 | setSelectionUser(start, cur); | |
|
497 | } else if (type == "double") { | |
|
498 | var word = findWordAt(cur); | |
|
499 | if (posLess(cur, startstart)) setSelectionUser(word.from, startend); | |
|
500 | else setSelectionUser(startstart, word.to); | |
|
501 | } else if (type == "triple") { | |
|
502 | if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0})); | |
|
503 | else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0})); | |
|
1833 | e.dataTransfer.setDragImage(img, 0, 0); | |
|
1834 | if (opera) img.parentNode.removeChild(img); | |
|
1835 | } | |
|
1836 | } | |
|
1837 | ||
|
1838 | function setScrollTop(cm, val) { | |
|
1839 | if (Math.abs(cm.doc.scrollTop - val) < 2) return; | |
|
1840 | cm.doc.scrollTop = val; | |
|
1841 | if (!gecko) updateDisplay(cm, [], val); | |
|
1842 | if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; | |
|
1843 | if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; | |
|
1844 | if (gecko) updateDisplay(cm, []); | |
|
1845 | } | |
|
1846 | function setScrollLeft(cm, val, isScroller) { | |
|
1847 | if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; | |
|
1848 | val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); | |
|
1849 | cm.doc.scrollLeft = val; | |
|
1850 | alignHorizontally(cm); | |
|
1851 | if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; | |
|
1852 | if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val; | |
|
1853 | } | |
|
1854 | ||
|
1855 | // Since the delta values reported on mouse wheel events are | |
|
1856 | // unstandardized between browsers and even browser versions, and | |
|
1857 | // generally horribly unpredictable, this code starts by measuring | |
|
1858 | // the scroll effect that the first few mouse wheel events have, | |
|
1859 | // and, from that, detects the way it can convert deltas to pixel | |
|
1860 | // offsets afterwards. | |
|
1861 | // | |
|
1862 | // The reason we want to know the amount a wheel event will scroll | |
|
1863 | // is that it gives us a chance to update the display before the | |
|
1864 | // actual scrolling happens, reducing flickering. | |
|
1865 | ||
|
1866 | var wheelSamples = 0, wheelPixelsPerUnit = null; | |
|
1867 | // Fill in a browser-detected starting value on browsers where we | |
|
1868 | // know one. These don't have to be accurate -- the result of them | |
|
1869 | // being wrong would just be a slight flicker on the first wheel | |
|
1870 | // scroll (if it is large enough). | |
|
1871 | if (ie) wheelPixelsPerUnit = -.53; | |
|
1872 | else if (gecko) wheelPixelsPerUnit = 15; | |
|
1873 | else if (chrome) wheelPixelsPerUnit = -.7; | |
|
1874 | else if (safari) wheelPixelsPerUnit = -1/3; | |
|
1875 | ||
|
1876 | function onScrollWheel(cm, e) { | |
|
1877 | var dx = e.wheelDeltaX, dy = e.wheelDeltaY; | |
|
1878 | if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; | |
|
1879 | if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; | |
|
1880 | else if (dy == null) dy = e.wheelDelta; | |
|
1881 | ||
|
1882 | var display = cm.display, scroll = display.scroller; | |
|
1883 | // Quit if there's nothing to scroll here | |
|
1884 | if (!(dx && scroll.scrollWidth > scroll.clientWidth || | |
|
1885 | dy && scroll.scrollHeight > scroll.clientHeight)) return; | |
|
1886 | ||
|
1887 | // Webkit browsers on OS X abort momentum scrolls when the target | |
|
1888 | // of the scroll event is removed from the scrollable element. | |
|
1889 | // This hack (see related code in patchDisplay) makes sure the | |
|
1890 | // element is kept around. | |
|
1891 | if (dy && mac && webkit) { | |
|
1892 | for (var cur = e.target; cur != scroll; cur = cur.parentNode) { | |
|
1893 | if (cur.lineObj) { | |
|
1894 | cm.display.currentWheelTarget = cur; | |
|
1895 | break; | |
|
504 | 1896 | } |
|
505 | 1897 | } |
|
506 | ||
|
507 | function extend(e) { | |
|
508 | var cur = posFromMouse(e, true); | |
|
509 | if (cur && !posEq(cur, last)) { | |
|
510 | if (!focused) onFocus(); | |
|
511 | last = cur; | |
|
512 | doSelect(cur); | |
|
513 | updateInput = false; | |
|
514 | var visible = visibleLines(); | |
|
515 | if (cur.line >= visible.to || cur.line < visible.from) | |
|
516 | going = setTimeout(operation(function(){extend(e);}), 150); | |
|
1898 | } | |
|
1899 | ||
|
1900 | // On some browsers, horizontal scrolling will cause redraws to | |
|
1901 | // happen before the gutter has been realigned, causing it to | |
|
1902 | // wriggle around in a most unseemly way. When we have an | |
|
1903 | // estimated pixels/delta value, we just handle horizontal | |
|
1904 | // scrolling entirely here. It'll be slightly off from native, but | |
|
1905 | // better than glitching out. | |
|
1906 | if (dx && !gecko && !opera && wheelPixelsPerUnit != null) { | |
|
1907 | if (dy) | |
|
1908 | setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); | |
|
1909 | setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); | |
|
1910 | e_preventDefault(e); | |
|
1911 | display.wheelStartX = null; // Abort measurement, if in progress | |
|
1912 | return; | |
|
1913 | } | |
|
1914 | ||
|
1915 | if (dy && wheelPixelsPerUnit != null) { | |
|
1916 | var pixels = dy * wheelPixelsPerUnit; | |
|
1917 | var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; | |
|
1918 | if (pixels < 0) top = Math.max(0, top + pixels - 50); | |
|
1919 | else bot = Math.min(cm.doc.height, bot + pixels + 50); | |
|
1920 | updateDisplay(cm, [], {top: top, bottom: bot}); | |
|
1921 | } | |
|
1922 | ||
|
1923 | if (wheelSamples < 20) { | |
|
1924 | if (display.wheelStartX == null) { | |
|
1925 | display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; | |
|
1926 | display.wheelDX = dx; display.wheelDY = dy; | |
|
1927 | setTimeout(function() { | |
|
1928 | if (display.wheelStartX == null) return; | |
|
1929 | var movedX = scroll.scrollLeft - display.wheelStartX; | |
|
1930 | var movedY = scroll.scrollTop - display.wheelStartY; | |
|
1931 | var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || | |
|
1932 | (movedX && display.wheelDX && movedX / display.wheelDX); | |
|
1933 | display.wheelStartX = display.wheelStartY = null; | |
|
1934 | if (!sample) return; | |
|
1935 | wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); | |
|
1936 | ++wheelSamples; | |
|
1937 | }, 200); | |
|
1938 | } else { | |
|
1939 | display.wheelDX += dx; display.wheelDY += dy; | |
|
1940 | } | |
|
1941 | } | |
|
1942 | } | |
|
1943 | ||
|
1944 | function doHandleBinding(cm, bound, dropShift) { | |
|
1945 | if (typeof bound == "string") { | |
|
1946 | bound = commands[bound]; | |
|
1947 | if (!bound) return false; | |
|
1948 | } | |
|
1949 | // Ensure previous input has been read, so that the handler sees a | |
|
1950 | // consistent view of the document | |
|
1951 | if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false; | |
|
1952 | var doc = cm.doc, prevShift = doc.sel.shift, done = false; | |
|
1953 | try { | |
|
1954 | if (isReadOnly(cm)) cm.state.suppressEdits = true; | |
|
1955 | if (dropShift) doc.sel.shift = false; | |
|
1956 | done = bound(cm) != Pass; | |
|
1957 | } finally { | |
|
1958 | doc.sel.shift = prevShift; | |
|
1959 | cm.state.suppressEdits = false; | |
|
1960 | } | |
|
1961 | return done; | |
|
1962 | } | |
|
1963 | ||
|
1964 | function allKeyMaps(cm) { | |
|
1965 | var maps = cm.state.keyMaps.slice(0); | |
|
1966 | if (cm.options.extraKeys) maps.push(cm.options.extraKeys); | |
|
1967 | maps.push(cm.options.keyMap); | |
|
1968 | return maps; | |
|
1969 | } | |
|
1970 | ||
|
1971 | var maybeTransition; | |
|
1972 | function handleKeyBinding(cm, e) { | |
|
1973 | // Handle auto keymap transitions | |
|
1974 | var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto; | |
|
1975 | clearTimeout(maybeTransition); | |
|
1976 | if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { | |
|
1977 | if (getKeyMap(cm.options.keyMap) == startMap) | |
|
1978 | cm.options.keyMap = (next.call ? next.call(null, cm) : next); | |
|
1979 | }, 50); | |
|
1980 | ||
|
1981 | var name = keyName(e, true), handled = false; | |
|
1982 | if (!name) return false; | |
|
1983 | var keymaps = allKeyMaps(cm); | |
|
1984 | ||
|
1985 | if (e.shiftKey) { | |
|
1986 | // First try to resolve full name (including 'Shift-'). Failing | |
|
1987 | // that, see if there is a cursor-motion command (starting with | |
|
1988 | // 'go') bound to the keyname without 'Shift-'. | |
|
1989 | handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);}) | |
|
1990 | || lookupKey(name, keymaps, function(b) { | |
|
1991 | if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b); | |
|
1992 | }); | |
|
1993 | } else { | |
|
1994 | handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); }); | |
|
1995 | } | |
|
1996 | if (handled == "stop") handled = false; | |
|
1997 | ||
|
1998 | if (handled) { | |
|
1999 | e_preventDefault(e); | |
|
2000 | restartBlink(cm); | |
|
2001 | if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } | |
|
2002 | } | |
|
2003 | return handled; | |
|
2004 | } | |
|
2005 | ||
|
2006 | function handleCharBinding(cm, e, ch) { | |
|
2007 | var handled = lookupKey("'" + ch + "'", allKeyMaps(cm), | |
|
2008 | function(b) { return doHandleBinding(cm, b, true); }); | |
|
2009 | if (handled) { | |
|
2010 | e_preventDefault(e); | |
|
2011 | restartBlink(cm); | |
|
2012 | } | |
|
2013 | return handled; | |
|
2014 | } | |
|
2015 | ||
|
2016 | var lastStoppedKey = null; | |
|
2017 | function onKeyDown(e) { | |
|
2018 | var cm = this; | |
|
2019 | if (!cm.state.focused) onFocus(cm); | |
|
2020 | if (ie && e.keyCode == 27) { e.returnValue = false; } | |
|
2021 | if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; | |
|
2022 | var code = e.keyCode; | |
|
2023 | // IE does strange things with escape. | |
|
2024 | cm.doc.sel.shift = code == 16 || e.shiftKey; | |
|
2025 | // First give onKeyEvent option a chance to handle this. | |
|
2026 | var handled = handleKeyBinding(cm, e); | |
|
2027 | if (opera) { | |
|
2028 | lastStoppedKey = handled ? code : null; | |
|
2029 | // Opera has no cut event... we try to at least catch the key combo | |
|
2030 | if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) | |
|
2031 | cm.replaceSelection(""); | |
|
2032 | } | |
|
2033 | } | |
|
2034 | ||
|
2035 | function onKeyPress(e) { | |
|
2036 | var cm = this; | |
|
2037 | if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; | |
|
2038 | var keyCode = e.keyCode, charCode = e.charCode; | |
|
2039 | if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} | |
|
2040 | if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; | |
|
2041 | var ch = String.fromCharCode(charCode == null ? keyCode : charCode); | |
|
2042 | if (this.options.electricChars && this.doc.mode.electricChars && | |
|
2043 | this.options.smartIndent && !isReadOnly(this) && | |
|
2044 | this.doc.mode.electricChars.indexOf(ch) > -1) | |
|
2045 | setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75); | |
|
2046 | if (handleCharBinding(cm, e, ch)) return; | |
|
2047 | if (ie && !ie_lt9) cm.display.inputHasSelection = null; | |
|
2048 | fastPoll(cm); | |
|
2049 | } | |
|
2050 | ||
|
2051 | function onFocus(cm) { | |
|
2052 | if (cm.options.readOnly == "nocursor") return; | |
|
2053 | if (!cm.state.focused) { | |
|
2054 | signal(cm, "focus", cm); | |
|
2055 | cm.state.focused = true; | |
|
2056 | if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1) | |
|
2057 | cm.display.wrapper.className += " CodeMirror-focused"; | |
|
2058 | resetInput(cm, true); | |
|
2059 | } | |
|
2060 | slowPoll(cm); | |
|
2061 | restartBlink(cm); | |
|
2062 | } | |
|
2063 | function onBlur(cm) { | |
|
2064 | if (cm.state.focused) { | |
|
2065 | signal(cm, "blur", cm); | |
|
2066 | cm.state.focused = false; | |
|
2067 | cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", ""); | |
|
2068 | } | |
|
2069 | clearInterval(cm.display.blinker); | |
|
2070 | setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150); | |
|
2071 | } | |
|
2072 | ||
|
2073 | var detectingSelectAll; | |
|
2074 | function onContextMenu(cm, e) { | |
|
2075 | var display = cm.display, sel = cm.doc.sel; | |
|
2076 | if (eventInWidget(display, e)) return; | |
|
2077 | ||
|
2078 | var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; | |
|
2079 | if (!pos || opera) return; // Opera is difficult. | |
|
2080 | if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) | |
|
2081 | operation(cm, setSelection)(cm.doc, pos, pos); | |
|
2082 | ||
|
2083 | var oldCSS = display.input.style.cssText; | |
|
2084 | display.inputDiv.style.position = "absolute"; | |
|
2085 | display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + | |
|
2086 | "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" + | |
|
2087 | "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);"; | |
|
2088 | focusInput(cm); | |
|
2089 | resetInput(cm, true); | |
|
2090 | // Adds "Select all" to context menu in FF | |
|
2091 | if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " "; | |
|
2092 | ||
|
2093 | function rehide() { | |
|
2094 | display.inputDiv.style.position = "relative"; | |
|
2095 | display.input.style.cssText = oldCSS; | |
|
2096 | if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos; | |
|
2097 | slowPoll(cm); | |
|
2098 | ||
|
2099 | // Try to detect the user choosing select-all | |
|
2100 | if (display.input.selectionStart != null && (!ie || ie_lt9)) { | |
|
2101 | clearTimeout(detectingSelectAll); | |
|
2102 | var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0; | |
|
2103 | display.prevInput = " "; | |
|
2104 | display.input.selectionStart = 1; display.input.selectionEnd = extval.length; | |
|
2105 | var poll = function(){ | |
|
2106 | if (display.prevInput == " " && display.input.selectionStart == 0) | |
|
2107 | operation(cm, commands.selectAll)(cm); | |
|
2108 | else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); | |
|
2109 | else resetInput(cm); | |
|
2110 | }; | |
|
2111 | detectingSelectAll = setTimeout(poll, 200); | |
|
2112 | } | |
|
2113 | } | |
|
2114 | ||
|
2115 | if (captureMiddleClick) { | |
|
2116 | e_stop(e); | |
|
2117 | var mouseup = function() { | |
|
2118 | off(window, "mouseup", mouseup); | |
|
2119 | setTimeout(rehide, 20); | |
|
2120 | }; | |
|
2121 | on(window, "mouseup", mouseup); | |
|
2122 | } else { | |
|
2123 | setTimeout(rehide, 50); | |
|
2124 | } | |
|
2125 | } | |
|
2126 | ||
|
2127 | // UPDATING | |
|
2128 | ||
|
2129 | function changeEnd(change) { | |
|
2130 | if (!change.text) return change.to; | |
|
2131 | return Pos(change.from.line + change.text.length - 1, | |
|
2132 | lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); | |
|
2133 | } | |
|
2134 | ||
|
2135 | // Make sure a position will be valid after the given change. | |
|
2136 | function clipPostChange(doc, change, pos) { | |
|
2137 | if (!posLess(change.from, pos)) return clipPos(doc, pos); | |
|
2138 | var diff = (change.text.length - 1) - (change.to.line - change.from.line); | |
|
2139 | if (pos.line > change.to.line + diff) { | |
|
2140 | var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1; | |
|
2141 | if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length); | |
|
2142 | return clipToLen(pos, getLine(doc, preLine).text.length); | |
|
2143 | } | |
|
2144 | if (pos.line == change.to.line + diff) | |
|
2145 | return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) + | |
|
2146 | getLine(doc, change.to.line).text.length - change.to.ch); | |
|
2147 | var inside = pos.line - change.from.line; | |
|
2148 | return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch)); | |
|
2149 | } | |
|
2150 | ||
|
2151 | // Hint can be null|"end"|"start"|"around"|{anchor,head} | |
|
2152 | function computeSelAfterChange(doc, change, hint) { | |
|
2153 | if (hint && typeof hint == "object") // Assumed to be {anchor, head} object | |
|
2154 | return {anchor: clipPostChange(doc, change, hint.anchor), | |
|
2155 | head: clipPostChange(doc, change, hint.head)}; | |
|
2156 | ||
|
2157 | if (hint == "start") return {anchor: change.from, head: change.from}; | |
|
2158 | ||
|
2159 | var end = changeEnd(change); | |
|
2160 | if (hint == "around") return {anchor: change.from, head: end}; | |
|
2161 | if (hint == "end") return {anchor: end, head: end}; | |
|
2162 | ||
|
2163 | // hint is null, leave the selection alone as much as possible | |
|
2164 | var adjustPos = function(pos) { | |
|
2165 | if (posLess(pos, change.from)) return pos; | |
|
2166 | if (!posLess(change.to, pos)) return end; | |
|
2167 | ||
|
2168 | var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; | |
|
2169 | if (pos.line == change.to.line) ch += end.ch - change.to.ch; | |
|
2170 | return Pos(line, ch); | |
|
2171 | }; | |
|
2172 | return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)}; | |
|
2173 | } | |
|
2174 | ||
|
2175 | function filterChange(doc, change) { | |
|
2176 | var obj = { | |
|
2177 | canceled: false, | |
|
2178 | from: change.from, | |
|
2179 | to: change.to, | |
|
2180 | text: change.text, | |
|
2181 | origin: change.origin, | |
|
2182 | update: function(from, to, text, origin) { | |
|
2183 | if (from) this.from = clipPos(doc, from); | |
|
2184 | if (to) this.to = clipPos(doc, to); | |
|
2185 | if (text) this.text = text; | |
|
2186 | if (origin !== undefined) this.origin = origin; | |
|
2187 | }, | |
|
2188 | cancel: function() { this.canceled = true; } | |
|
2189 | }; | |
|
2190 | signal(doc, "beforeChange", doc, obj); | |
|
2191 | if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); | |
|
2192 | ||
|
2193 | if (obj.canceled) return null; | |
|
2194 | return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; | |
|
2195 | } | |
|
2196 | ||
|
2197 | // Replace the range from from to to by the strings in replacement. | |
|
2198 | // change is a {from, to, text [, origin]} object | |
|
2199 | function makeChange(doc, change, selUpdate, ignoreReadOnly) { | |
|
2200 | if (doc.cm) { | |
|
2201 | if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly); | |
|
2202 | if (doc.cm.state.suppressEdits) return; | |
|
2203 | } | |
|
2204 | ||
|
2205 | if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { | |
|
2206 | change = filterChange(doc, change); | |
|
2207 | if (!change) return; | |
|
2208 | } | |
|
2209 | ||
|
2210 | // Possibly split or suppress the update based on the presence | |
|
2211 | // of read-only spans in its range. | |
|
2212 | var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); | |
|
2213 | if (split) { | |
|
2214 | for (var i = split.length - 1; i >= 1; --i) | |
|
2215 | makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]}); | |
|
2216 | if (split.length) | |
|
2217 | makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate); | |
|
2218 | } else { | |
|
2219 | makeChangeNoReadonly(doc, change, selUpdate); | |
|
2220 | } | |
|
2221 | } | |
|
2222 | ||
|
2223 | function makeChangeNoReadonly(doc, change, selUpdate) { | |
|
2224 | var selAfter = computeSelAfterChange(doc, change, selUpdate); | |
|
2225 | addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); | |
|
2226 | ||
|
2227 | makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); | |
|
2228 | var rebased = []; | |
|
2229 | ||
|
2230 | linkedDocs(doc, function(doc, sharedHist) { | |
|
2231 | if (!sharedHist && indexOf(rebased, doc.history) == -1) { | |
|
2232 | rebaseHist(doc.history, change); | |
|
2233 | rebased.push(doc.history); | |
|
2234 | } | |
|
2235 | makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); | |
|
2236 | }); | |
|
2237 | } | |
|
2238 | ||
|
2239 | function makeChangeFromHistory(doc, type) { | |
|
2240 | if (doc.cm && doc.cm.state.suppressEdits) return; | |
|
2241 | ||
|
2242 | var hist = doc.history; | |
|
2243 | var event = (type == "undo" ? hist.done : hist.undone).pop(); | |
|
2244 | if (!event) return; | |
|
2245 | hist.dirtyCounter += type == "undo" ? -1 : 1; | |
|
2246 | ||
|
2247 | var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter, | |
|
2248 | anchorAfter: event.anchorBefore, headAfter: event.headBefore}; | |
|
2249 | (type == "undo" ? hist.undone : hist.done).push(anti); | |
|
2250 | ||
|
2251 | for (var i = event.changes.length - 1; i >= 0; --i) { | |
|
2252 | var change = event.changes[i]; | |
|
2253 | change.origin = type; | |
|
2254 | anti.changes.push(historyChangeFromChange(doc, change)); | |
|
2255 | ||
|
2256 | var after = i ? computeSelAfterChange(doc, change, null) | |
|
2257 | : {anchor: event.anchorBefore, head: event.headBefore}; | |
|
2258 | makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); | |
|
2259 | var rebased = []; | |
|
2260 | ||
|
2261 | linkedDocs(doc, function(doc, sharedHist) { | |
|
2262 | if (!sharedHist && indexOf(rebased, doc.history) == -1) { | |
|
2263 | rebaseHist(doc.history, change); | |
|
2264 | rebased.push(doc.history); | |
|
2265 | } | |
|
2266 | makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); | |
|
2267 | }); | |
|
2268 | } | |
|
2269 | } | |
|
2270 | ||
|
2271 | function shiftDoc(doc, distance) { | |
|
2272 | function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);} | |
|
2273 | doc.first += distance; | |
|
2274 | if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance); | |
|
2275 | doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor); | |
|
2276 | doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to); | |
|
2277 | } | |
|
2278 | ||
|
2279 | function makeChangeSingleDoc(doc, change, selAfter, spans) { | |
|
2280 | if (doc.cm && !doc.cm.curOp) | |
|
2281 | return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); | |
|
2282 | ||
|
2283 | if (change.to.line < doc.first) { | |
|
2284 | shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); | |
|
2285 | return; | |
|
2286 | } | |
|
2287 | if (change.from.line > doc.lastLine()) return; | |
|
2288 | ||
|
2289 | // Clip the change to the size of this doc | |
|
2290 | if (change.from.line < doc.first) { | |
|
2291 | var shift = change.text.length - 1 - (doc.first - change.from.line); | |
|
2292 | shiftDoc(doc, shift); | |
|
2293 | change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), | |
|
2294 | text: [lst(change.text)], origin: change.origin}; | |
|
2295 | } | |
|
2296 | var last = doc.lastLine(); | |
|
2297 | if (change.to.line > last) { | |
|
2298 | change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), | |
|
2299 | text: [change.text[0]], origin: change.origin}; | |
|
2300 | } | |
|
2301 | ||
|
2302 | change.removed = getBetween(doc, change.from, change.to); | |
|
2303 | ||
|
2304 | if (!selAfter) selAfter = computeSelAfterChange(doc, change, null); | |
|
2305 | if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter); | |
|
2306 | else updateDoc(doc, change, spans, selAfter); | |
|
2307 | } | |
|
2308 | ||
|
2309 | function makeChangeSingleDocInEditor(cm, change, spans, selAfter) { | |
|
2310 | var doc = cm.doc, display = cm.display, from = change.from, to = change.to; | |
|
2311 | ||
|
2312 | var recomputeMaxLength = false, checkWidthStart = from.line; | |
|
2313 | if (!cm.options.lineWrapping) { | |
|
2314 | checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line))); | |
|
2315 | doc.iter(checkWidthStart, to.line + 1, function(line) { | |
|
2316 | if (line == display.maxLine) { | |
|
2317 | recomputeMaxLength = true; | |
|
2318 | return true; | |
|
2319 | } | |
|
2320 | }); | |
|
2321 | } | |
|
2322 | ||
|
2323 | if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head)) | |
|
2324 | cm.curOp.cursorActivity = true; | |
|
2325 | ||
|
2326 | updateDoc(doc, change, spans, selAfter, estimateHeight(cm)); | |
|
2327 | ||
|
2328 | if (!cm.options.lineWrapping) { | |
|
2329 | doc.iter(checkWidthStart, from.line + change.text.length, function(line) { | |
|
2330 | var len = lineLength(doc, line); | |
|
2331 | if (len > display.maxLineLength) { | |
|
2332 | display.maxLine = line; | |
|
2333 | display.maxLineLength = len; | |
|
2334 | display.maxLineChanged = true; | |
|
2335 | recomputeMaxLength = false; | |
|
2336 | } | |
|
2337 | }); | |
|
2338 | if (recomputeMaxLength) cm.curOp.updateMaxLine = true; | |
|
2339 | } | |
|
2340 | ||
|
2341 | // Adjust frontier, schedule worker | |
|
2342 | doc.frontier = Math.min(doc.frontier, from.line); | |
|
2343 | startWorker(cm, 400); | |
|
2344 | ||
|
2345 | var lendiff = change.text.length - (to.line - from.line) - 1; | |
|
2346 | // Remember that these lines changed, for updating the display | |
|
2347 | regChange(cm, from.line, to.line + 1, lendiff); | |
|
2348 | ||
|
2349 | if (hasHandler(cm, "change")) { | |
|
2350 | var changeObj = {from: from, to: to, | |
|
2351 | text: change.text, | |
|
2352 | removed: change.removed, | |
|
2353 | origin: change.origin}; | |
|
2354 | if (cm.curOp.textChanged) { | |
|
2355 | for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {} | |
|
2356 | cur.next = changeObj; | |
|
2357 | } else cm.curOp.textChanged = changeObj; | |
|
2358 | } | |
|
2359 | } | |
|
2360 | ||
|
2361 | function replaceRange(doc, code, from, to, origin) { | |
|
2362 | if (!to) to = from; | |
|
2363 | if (posLess(to, from)) { var tmp = to; to = from; from = tmp; } | |
|
2364 | if (typeof code == "string") code = splitLines(code); | |
|
2365 | makeChange(doc, {from: from, to: to, text: code, origin: origin}, null); | |
|
2366 | } | |
|
2367 | ||
|
2368 | // POSITION OBJECT | |
|
2369 | ||
|
2370 | function Pos(line, ch) { | |
|
2371 | if (!(this instanceof Pos)) return new Pos(line, ch); | |
|
2372 | this.line = line; this.ch = ch; | |
|
2373 | } | |
|
2374 | CodeMirror.Pos = Pos; | |
|
2375 | ||
|
2376 | function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} | |
|
2377 | function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} | |
|
2378 | function copyPos(x) {return Pos(x.line, x.ch);} | |
|
2379 | ||
|
2380 | // SELECTION | |
|
2381 | ||
|
2382 | function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} | |
|
2383 | function clipPos(doc, pos) { | |
|
2384 | if (pos.line < doc.first) return Pos(doc.first, 0); | |
|
2385 | var last = doc.first + doc.size - 1; | |
|
2386 | if (pos.line > last) return Pos(last, getLine(doc, last).text.length); | |
|
2387 | return clipToLen(pos, getLine(doc, pos.line).text.length); | |
|
2388 | } | |
|
2389 | function clipToLen(pos, linelen) { | |
|
2390 | var ch = pos.ch; | |
|
2391 | if (ch == null || ch > linelen) return Pos(pos.line, linelen); | |
|
2392 | else if (ch < 0) return Pos(pos.line, 0); | |
|
2393 | else return pos; | |
|
2394 | } | |
|
2395 | function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} | |
|
2396 | ||
|
2397 | // If shift is held, this will move the selection anchor. Otherwise, | |
|
2398 | // it'll set the whole selection. | |
|
2399 | function extendSelection(doc, pos, other, bias) { | |
|
2400 | if (doc.sel.shift || doc.sel.extend) { | |
|
2401 | var anchor = doc.sel.anchor; | |
|
2402 | if (other) { | |
|
2403 | var posBefore = posLess(pos, anchor); | |
|
2404 | if (posBefore != posLess(other, anchor)) { | |
|
2405 | anchor = pos; | |
|
2406 | pos = other; | |
|
2407 | } else if (posBefore != posLess(pos, other)) { | |
|
2408 | pos = other; | |
|
517 | 2409 | } |
|
518 | 2410 | } |
|
519 | ||
|
520 | function done(e) { | |
|
521 | clearTimeout(going); | |
|
522 | var cur = posFromMouse(e); | |
|
523 | if (cur) doSelect(cur); | |
|
524 | e_preventDefault(e); | |
|
525 | focusInput(); | |
|
526 | updateInput = true; | |
|
527 | move(); up(); | |
|
528 | } | |
|
529 | var move = connect(document, "mousemove", operation(function(e) { | |
|
530 | clearTimeout(going); | |
|
531 | e_preventDefault(e); | |
|
532 | if (!ie && !e_button(e)) done(e); | |
|
533 | else extend(e); | |
|
534 | }), true); | |
|
535 | var up = connect(document, "mouseup", operation(done), true); | |
|
536 | } | |
|
537 | function onDoubleClick(e) { | |
|
538 | for (var n = e_target(e); n != wrapper; n = n.parentNode) | |
|
539 | if (n.parentNode == gutterText) return e_preventDefault(e); | |
|
540 | e_preventDefault(e); | |
|
541 | } | |
|
542 | function onDrop(e) { | |
|
543 | if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; | |
|
544 | e_preventDefault(e); | |
|
545 | var pos = posFromMouse(e, true), files = e.dataTransfer.files; | |
|
546 | if (!pos || options.readOnly) return; | |
|
547 | if (files && files.length && window.FileReader && window.File) { | |
|
548 | var n = files.length, text = Array(n), read = 0; | |
|
549 | var loadFile = function(file, i) { | |
|
550 | var reader = new FileReader; | |
|
551 | reader.onload = function() { | |
|
552 | text[i] = reader.result; | |
|
553 | if (++read == n) { | |
|
554 | pos = clipPos(pos); | |
|
555 | operation(function() { | |
|
556 | var end = replaceRange(text.join(""), pos, pos); | |
|
557 | setSelectionUser(pos, end); | |
|
558 | })(); | |
|
2411 | setSelection(doc, anchor, pos, bias); | |
|
2412 | } else { | |
|
2413 | setSelection(doc, pos, other || pos, bias); | |
|
2414 | } | |
|
2415 | if (doc.cm) doc.cm.curOp.userSelChange = true; | |
|
2416 | } | |
|
2417 | ||
|
2418 | function filterSelectionChange(doc, anchor, head) { | |
|
2419 | var obj = {anchor: anchor, head: head}; | |
|
2420 | signal(doc, "beforeSelectionChange", doc, obj); | |
|
2421 | if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); | |
|
2422 | obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head); | |
|
2423 | return obj; | |
|
2424 | } | |
|
2425 | ||
|
2426 | // Update the selection. Last two args are only used by | |
|
2427 | // updateDoc, since they have to be expressed in the line | |
|
2428 | // numbers before the update. | |
|
2429 | function setSelection(doc, anchor, head, bias, checkAtomic) { | |
|
2430 | if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) { | |
|
2431 | var filtered = filterSelectionChange(doc, anchor, head); | |
|
2432 | head = filtered.head; | |
|
2433 | anchor = filtered.anchor; | |
|
2434 | } | |
|
2435 | ||
|
2436 | var sel = doc.sel; | |
|
2437 | sel.goalColumn = null; | |
|
2438 | // Skip over atomic spans. | |
|
2439 | if (checkAtomic || !posEq(anchor, sel.anchor)) | |
|
2440 | anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push"); | |
|
2441 | if (checkAtomic || !posEq(head, sel.head)) | |
|
2442 | head = skipAtomic(doc, head, bias, checkAtomic != "push"); | |
|
2443 | ||
|
2444 | if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return; | |
|
2445 | ||
|
2446 | sel.anchor = anchor; sel.head = head; | |
|
2447 | var inv = posLess(head, anchor); | |
|
2448 | sel.from = inv ? head : anchor; | |
|
2449 | sel.to = inv ? anchor : head; | |
|
2450 | ||
|
2451 | if (doc.cm) | |
|
2452 | doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = | |
|
2453 | doc.cm.curOp.cursorActivity = true; | |
|
2454 | ||
|
2455 | signalLater(doc, "cursorActivity", doc); | |
|
2456 | } | |
|
2457 | ||
|
2458 | function reCheckSelection(cm) { | |
|
2459 | setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push"); | |
|
2460 | } | |
|
2461 | ||
|
2462 | function skipAtomic(doc, pos, bias, mayClear) { | |
|
2463 | var flipped = false, curPos = pos; | |
|
2464 | var dir = bias || 1; | |
|
2465 | doc.cantEdit = false; | |
|
2466 | search: for (;;) { | |
|
2467 | var line = getLine(doc, curPos.line); | |
|
2468 | if (line.markedSpans) { | |
|
2469 | for (var i = 0; i < line.markedSpans.length; ++i) { | |
|
2470 | var sp = line.markedSpans[i], m = sp.marker; | |
|
2471 | if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && | |
|
2472 | (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { | |
|
2473 | if (mayClear) { | |
|
2474 | signal(m, "beforeCursorEnter"); | |
|
2475 | if (m.explicitlyCleared) { | |
|
2476 | if (!line.markedSpans) break; | |
|
2477 | else {--i; continue;} | |
|
2478 | } | |
|
559 | 2479 | } |
|
560 | }; | |
|
561 | reader.readAsText(file); | |
|
562 | }; | |
|
563 | for (var i = 0; i < n; ++i) loadFile(files[i], i); | |
|
564 | } else { | |
|
565 | // Don't do a replace if the drop happened inside of the selected text. | |
|
566 | if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return; | |
|
567 | try { | |
|
568 | var text = e.dataTransfer.getData("Text"); | |
|
569 | if (text) { | |
|
570 | compoundChange(function() { | |
|
571 | var curFrom = sel.from, curTo = sel.to; | |
|
572 | setSelectionUser(pos, pos); | |
|
573 | if (draggingText) replaceRange("", curFrom, curTo); | |
|
574 | replaceSelection(text); | |
|
575 | focusInput(); | |
|
576 | }); | |
|
2480 | if (!m.atomic) continue; | |
|
2481 | var newPos = m.find()[dir < 0 ? "from" : "to"]; | |
|
2482 | if (posEq(newPos, curPos)) { | |
|
2483 | newPos.ch += dir; | |
|
2484 | if (newPos.ch < 0) { | |
|
2485 | if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1)); | |
|
2486 | else newPos = null; | |
|
2487 | } else if (newPos.ch > line.text.length) { | |
|
2488 | if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0); | |
|
2489 | else newPos = null; | |
|
2490 | } | |
|
2491 | if (!newPos) { | |
|
2492 | if (flipped) { | |
|
2493 | // Driven in a corner -- no valid cursor position found at all | |
|
2494 | // -- try again *with* clearing, if we didn't already | |
|
2495 | if (!mayClear) return skipAtomic(doc, pos, bias, true); | |
|
2496 | // Otherwise, turn off editing until further notice, and return the start of the doc | |
|
2497 | doc.cantEdit = true; | |
|
2498 | return Pos(doc.first, 0); | |
|
2499 | } | |
|
2500 | flipped = true; newPos = pos; dir = -dir; | |
|
2501 | } | |
|
2502 | } | |
|
2503 | curPos = newPos; | |
|
2504 | continue search; | |
|
577 | 2505 | } |
|
578 | 2506 | } |
|
579 | catch(e){} | |
|
580 | } | |
|
581 | } | |
|
582 | function onDragStart(e) { | |
|
583 | var txt = getSelection(); | |
|
584 | e.dataTransfer.setData("Text", txt); | |
|
585 | ||
|
586 | // Use dummy image instead of default browsers image. | |
|
587 | if (e.dataTransfer.setDragImage) | |
|
588 | e.dataTransfer.setDragImage(elt('img'), 0, 0); | |
|
589 | } | |
|
590 | ||
|
591 | function doHandleBinding(bound, dropShift) { | |
|
592 | if (typeof bound == "string") { | |
|
593 | bound = commands[bound]; | |
|
594 | if (!bound) return false; | |
|
595 | } | |
|
596 | var prevShift = shiftSelecting; | |
|
597 | try { | |
|
598 | if (options.readOnly) suppressEdits = true; | |
|
599 | if (dropShift) shiftSelecting = null; | |
|
600 | bound(instance); | |
|
601 | } catch(e) { | |
|
602 | if (e != Pass) throw e; | |
|
603 | return false; | |
|
604 | } finally { | |
|
605 | shiftSelecting = prevShift; | |
|
606 | suppressEdits = false; | |
|
607 | } | |
|
608 | return true; | |
|
609 | } | |
|
610 | var maybeTransition; | |
|
611 | function handleKeyBinding(e) { | |
|
612 | // Handle auto keymap transitions | |
|
613 | var startMap = getKeyMap(options.keyMap), next = startMap.auto; | |
|
614 | clearTimeout(maybeTransition); | |
|
615 | if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { | |
|
616 | if (getKeyMap(options.keyMap) == startMap) { | |
|
617 | options.keyMap = (next.call ? next.call(null, instance) : next); | |
|
618 | } | |
|
619 | }, 50); | |
|
620 | ||
|
621 | var name = keyNames[e_prop(e, "keyCode")], handled = false; | |
|
622 | var flipCtrlCmd = opera && mac; | |
|
623 | if (name == null || e.altGraphKey) return false; | |
|
624 | if (e_prop(e, "altKey")) name = "Alt-" + name; | |
|
625 | if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name; | |
|
626 | if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name; | |
|
627 | ||
|
628 | var stopped = false; | |
|
629 | function stop() { stopped = true; } | |
|
630 | ||
|
631 | if (e_prop(e, "shiftKey")) { | |
|
632 | handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap, | |
|
633 | function(b) {return doHandleBinding(b, true);}, stop) | |
|
634 | || lookupKey(name, options.extraKeys, options.keyMap, function(b) { | |
|
635 | if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b); | |
|
636 | }, stop); | |
|
637 | } else { | |
|
638 | handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop); | |
|
639 | } | |
|
640 | if (stopped) handled = false; | |
|
641 | if (handled) { | |
|
642 | e_preventDefault(e); | |
|
643 | restartBlink(); | |
|
644 | if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } | |
|
645 | } | |
|
646 | return handled; | |
|
647 | } | |
|
648 | function handleCharBinding(e, ch) { | |
|
649 | var handled = lookupKey("'" + ch + "'", options.extraKeys, | |
|
650 | options.keyMap, function(b) { return doHandleBinding(b, true); }); | |
|
651 | if (handled) { | |
|
652 | e_preventDefault(e); | |
|
653 | restartBlink(); | |
|
654 | 2507 | } |
|
655 |
return |
|
|
656 | } | |
|
657 | ||
|
658 | var lastStoppedKey = null; | |
|
659 | function onKeyDown(e) { | |
|
660 | if (!focused) onFocus(); | |
|
661 | if (ie && e.keyCode == 27) { e.returnValue = false; } | |
|
662 | if (pollingFast) { if (readInput()) pollingFast = false; } | |
|
663 | if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; | |
|
664 | var code = e_prop(e, "keyCode"); | |
|
665 | // IE does strange things with escape. | |
|
666 | setShift(code == 16 || e_prop(e, "shiftKey")); | |
|
667 | // First give onKeyEvent option a chance to handle this. | |
|
668 | var handled = handleKeyBinding(e); | |
|
669 |
if ( |
|
|
670 | lastStoppedKey = handled ? code : null; | |
|
671 | // Opera has no cut event... we try to at least catch the key combo | |
|
672 | if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey")) | |
|
673 | replaceSelection(""); | |
|
674 | } | |
|
675 | } | |
|
676 | function onKeyPress(e) { | |
|
677 | if (pollingFast) readInput(); | |
|
678 | if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; | |
|
679 | var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); | |
|
680 | if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} | |
|
681 | if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return; | |
|
682 | var ch = String.fromCharCode(charCode == null ? keyCode : charCode); | |
|
683 | if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { | |
|
684 | if (mode.electricChars.indexOf(ch) > -1) | |
|
685 | setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75); | |
|
2508 | return curPos; | |
|
2509 | } | |
|
2510 | } | |
|
2511 | ||
|
2512 | // SCROLLING | |
|
2513 | ||
|
2514 | function scrollCursorIntoView(cm) { | |
|
2515 | var coords = scrollPosIntoView(cm, cm.doc.sel.head); | |
|
2516 | if (!cm.state.focused) return; | |
|
2517 | var display = cm.display, box = getRect(display.sizer), doScroll = null, pTop = paddingTop(cm.display); | |
|
2518 | if (coords.top + pTop + box.top < 0) doScroll = true; | |
|
2519 | else if (coords.bottom + pTop + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; | |
|
2520 | if (doScroll != null && !phantom) { | |
|
2521 | var hidden = display.cursor.style.display == "none"; | |
|
2522 | if (hidden) { | |
|
2523 | display.cursor.style.display = ""; | |
|
2524 | display.cursor.style.left = coords.left + "px"; | |
|
2525 | display.cursor.style.top = (coords.top - display.viewOffset) + "px"; | |
|
686 | 2526 | } |
|
687 | if (handleCharBinding(e, ch)) return; | |
|
688 | fastPoll(); | |
|
689 | } | |
|
690 | function onKeyUp(e) { | |
|
691 | if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; | |
|
692 | if (e_prop(e, "keyCode") == 16) shiftSelecting = null; | |
|
693 | } | |
|
694 | ||
|
695 | function onFocus() { | |
|
696 | if (options.readOnly == "nocursor") return; | |
|
697 | if (!focused) { | |
|
698 | if (options.onFocus) options.onFocus(instance); | |
|
699 | focused = true; | |
|
700 | if (scroller.className.search(/\bCodeMirror-focused\b/) == -1) | |
|
701 | scroller.className += " CodeMirror-focused"; | |
|
2527 | display.cursor.scrollIntoView(doScroll); | |
|
2528 | if (hidden) display.cursor.style.display = "none"; | |
|
2529 | } | |
|
2530 | } | |
|
2531 | ||
|
2532 | function scrollPosIntoView(cm, pos, margin) { | |
|
2533 | if (margin == null) margin = 0; | |
|
2534 | for (;;) { | |
|
2535 | var changed = false, coords = cursorCoords(cm, pos); | |
|
2536 | var scrollPos = calculateScrollPos(cm, coords.left, coords.top - margin, coords.left, coords.bottom + margin); | |
|
2537 | var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; | |
|
2538 | if (scrollPos.scrollTop != null) { | |
|
2539 | setScrollTop(cm, scrollPos.scrollTop); | |
|
2540 | if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; | |
|
702 | 2541 | } |
|
703 | slowPoll(); | |
|
704 | restartBlink(); | |
|
705 | } | |
|
706 | function onBlur() { | |
|
707 | if (focused) { | |
|
708 | if (options.onBlur) options.onBlur(instance); | |
|
709 | focused = false; | |
|
710 | if (bracketHighlighted) | |
|
711 | operation(function(){ | |
|
712 | if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; } | |
|
713 | })(); | |
|
714 | scroller.className = scroller.className.replace(" CodeMirror-focused", ""); | |
|
715 | } | |
|
716 | clearInterval(blinker); | |
|
717 | setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); | |
|
718 | } | |
|
719 | ||
|
720 | // Replace the range from from to to by the strings in newText. | |
|
721 | // Afterwards, set the selection to selFrom, selTo. | |
|
722 | function updateLines(from, to, newText, selFrom, selTo) { | |
|
723 | if (suppressEdits) return; | |
|
724 | var old = []; | |
|
725 | doc.iter(from.line, to.line + 1, function(line) { | |
|
726 | old.push(newHL(line.text, line.markedSpans)); | |
|
727 | }); | |
|
728 | if (history) { | |
|
729 | history.addChange(from.line, newText.length, old); | |
|
730 | while (history.done.length > options.undoDepth) history.done.shift(); | |
|
2542 | if (scrollPos.scrollLeft != null) { | |
|
2543 | setScrollLeft(cm, scrollPos.scrollLeft); | |
|
2544 | if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; | |
|
731 | 2545 | } |
|
732 | var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText); | |
|
733 | updateLinesNoUndo(from, to, lines, selFrom, selTo); | |
|
734 |
|
|
|
735 | function unredoHelper(from, to) { | |
|
736 | if (!from.length) return; | |
|
737 | var set = from.pop(), out = []; | |
|
738 | for (var i = set.length - 1; i >= 0; i -= 1) { | |
|
739 | var change = set[i]; | |
|
740 | var replaced = [], end = change.start + change.added; | |
|
741 | doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); }); | |
|
742 | out.push({start: change.start, added: change.old.length, old: replaced}); | |
|
743 | var pos = {line: change.start + change.old.length - 1, | |
|
744 | ch: editEnd(hlText(lst(replaced)), hlText(lst(change.old)))}; | |
|
745 | updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, | |
|
746 | change.old, pos, pos); | |
|
747 | } | |
|
748 | updateInput = true; | |
|
749 | to.push(out); | |
|
750 | } | |
|
751 | function undo() {unredoHelper(history.done, history.undone);} | |
|
752 | function redo() {unredoHelper(history.undone, history.done);} | |
|
753 | ||
|
754 | function updateLinesNoUndo(from, to, lines, selFrom, selTo) { | |
|
755 | if (suppressEdits) return; | |
|
756 | var recomputeMaxLength = false, maxLineLength = maxLine.text.length; | |
|
757 | if (!options.lineWrapping) | |
|
758 | doc.iter(from.line, to.line + 1, function(line) { | |
|
759 | if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} | |
|
760 | }); | |
|
761 | if (from.line != to.line || lines.length > 1) gutterDirty = true; | |
|
762 | ||
|
763 | var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); | |
|
764 | var lastHL = lst(lines); | |
|
765 | ||
|
766 | // First adjust the line structure | |
|
767 | if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") { | |
|
768 | // This is a whole-line replace. Treated specially to make | |
|
769 | // sure line objects move the way they are supposed to. | |
|
770 | var added = [], prevLine = null; | |
|
771 | for (var i = 0, e = lines.length - 1; i < e; ++i) | |
|
772 | added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); | |
|
773 | lastLine.update(lastLine.text, hlSpans(lastHL)); | |
|
774 | if (nlines) doc.remove(from.line, nlines, callbacks); | |
|
775 | if (added.length) doc.insert(from.line, added); | |
|
776 | } else if (firstLine == lastLine) { | |
|
777 | if (lines.length == 1) { | |
|
778 | firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + firstLine.text.slice(to.ch), hlSpans(lines[0])); | |
|
779 | } else { | |
|
780 | for (var added = [], i = 1, e = lines.length - 1; i < e; ++i) | |
|
781 | added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); | |
|
782 | added.push(new Line(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL))); | |
|
783 | firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); | |
|
784 | doc.insert(from.line + 1, added); | |
|
785 | } | |
|
786 | } else if (lines.length == 1) { | |
|
787 | firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + lastLine.text.slice(to.ch), hlSpans(lines[0])); | |
|
788 | doc.remove(from.line + 1, nlines, callbacks); | |
|
789 | } else { | |
|
790 | var added = []; | |
|
791 | firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); | |
|
792 | lastLine.update(hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL)); | |
|
793 | for (var i = 1, e = lines.length - 1; i < e; ++i) | |
|
794 | added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); | |
|
795 | if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); | |
|
796 | doc.insert(from.line + 1, added); | |
|
797 | } | |
|
798 | if (options.lineWrapping) { | |
|
799 | var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3); | |
|
800 | doc.iter(from.line, from.line + lines.length, function(line) { | |
|
801 | if (line.hidden) return; | |
|
802 | var guess = Math.ceil(line.text.length / perLine) || 1; | |
|
803 | if (guess != line.height) updateLineHeight(line, guess); | |
|
804 | }); | |
|
805 | } else { | |
|
806 | doc.iter(from.line, from.line + lines.length, function(line) { | |
|
807 | var l = line.text; | |
|
808 | if (!line.hidden && l.length > maxLineLength) { | |
|
809 | maxLine = line; maxLineLength = l.length; maxLineChanged = true; | |
|
810 | recomputeMaxLength = false; | |
|
811 | } | |
|
812 | }); | |
|
813 | if (recomputeMaxLength) updateMaxLine = true; | |
|
814 | } | |
|
815 | ||
|
816 | // Adjust frontier, schedule worker | |
|
817 | frontier = Math.min(frontier, from.line); | |
|
818 | startWorker(400); | |
|
819 | ||
|
820 | var lendiff = lines.length - nlines - 1; | |
|
821 | // Remember that these lines changed, for updating the display | |
|
822 | changes.push({from: from.line, to: to.line + 1, diff: lendiff}); | |
|
823 | if (options.onChange) { | |
|
824 | // Normalize lines to contain only strings, since that's what | |
|
825 | // the change event handler expects | |
|
826 | for (var i = 0; i < lines.length; ++i) | |
|
827 | if (typeof lines[i] != "string") lines[i] = lines[i].text; | |
|
828 | var changeObj = {from: from, to: to, text: lines}; | |
|
829 | if (textChanged) { | |
|
830 | for (var cur = textChanged; cur.next; cur = cur.next) {} | |
|
831 | cur.next = changeObj; | |
|
832 | } else textChanged = changeObj; | |
|
833 | } | |
|
834 | ||
|
835 | // Update the selection | |
|
836 | function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} | |
|
837 | setSelection(clipPos(selFrom), clipPos(selTo), | |
|
838 | updateLine(sel.from.line), updateLine(sel.to.line)); | |
|
839 | } | |
|
840 | ||
|
841 | function needsScrollbar() { | |
|
842 | var realHeight = doc.height * textHeight() + 2 * paddingTop(); | |
|
843 | return realHeight * .99 > scroller.offsetHeight ? realHeight : false; | |
|
844 | } | |
|
845 | ||
|
846 | function updateVerticalScroll(scrollTop) { | |
|
847 | var scrollHeight = needsScrollbar(); | |
|
848 | scrollbar.style.display = scrollHeight ? "block" : "none"; | |
|
849 | if (scrollHeight) { | |
|
850 | scrollbarInner.style.height = sizer.style.minHeight = scrollHeight + "px"; | |
|
851 | scrollbar.style.height = scroller.clientHeight + "px"; | |
|
852 | if (scrollTop != null) { | |
|
853 | scrollbar.scrollTop = scroller.scrollTop = scrollTop; | |
|
854 | // 'Nudge' the scrollbar to work around a Webkit bug where, | |
|
855 | // in some situations, we'd end up with a scrollbar that | |
|
856 | // reported its scrollTop (and looked) as expected, but | |
|
857 | // *behaved* as if it was still in a previous state (i.e. | |
|
858 | // couldn't scroll up, even though it appeared to be at the | |
|
859 | // bottom). | |
|
860 | if (webkit) setTimeout(function() { | |
|
861 | if (scrollbar.scrollTop != scrollTop) return; | |
|
862 | scrollbar.scrollTop = scrollTop + (scrollTop ? -1 : 1); | |
|
863 | scrollbar.scrollTop = scrollTop; | |
|
864 | }, 0); | |
|
865 | } | |
|
866 | } else { | |
|
867 | sizer.style.minHeight = ""; | |
|
868 | } | |
|
869 | // Position the mover div to align with the current virtual scroll position | |
|
870 | mover.style.top = displayOffset * textHeight() + "px"; | |
|
871 | } | |
|
872 | ||
|
873 | function computeMaxLength() { | |
|
874 | maxLine = getLine(0); maxLineChanged = true; | |
|
875 | var maxLineLength = maxLine.text.length; | |
|
876 | doc.iter(1, doc.size, function(line) { | |
|
877 | var l = line.text; | |
|
878 | if (!line.hidden && l.length > maxLineLength) { | |
|
879 | maxLineLength = l.length; maxLine = line; | |
|
880 | } | |
|
881 | }); | |
|
882 | updateMaxLine = false; | |
|
883 | } | |
|
884 | ||
|
885 | function replaceRange(code, from, to) { | |
|
886 | from = clipPos(from); | |
|
887 | if (!to) to = from; else to = clipPos(to); | |
|
888 | code = splitLines(code); | |
|
889 | function adjustPos(pos) { | |
|
890 | if (posLess(pos, from)) return pos; | |
|
891 | if (!posLess(to, pos)) return end; | |
|
892 | var line = pos.line + code.length - (to.line - from.line) - 1; | |
|
893 | var ch = pos.ch; | |
|
894 | if (pos.line == to.line) | |
|
895 | ch += lst(code).length - (to.ch - (to.line == from.line ? from.ch : 0)); | |
|
896 | return {line: line, ch: ch}; | |
|
2546 | if (!changed) return coords; | |
|
2547 | } | |
|
2548 | } | |
|
2549 | ||
|
2550 | function scrollIntoView(cm, x1, y1, x2, y2) { | |
|
2551 | var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); | |
|
2552 | if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); | |
|
2553 | if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); | |
|
2554 | } | |
|
2555 | ||
|
2556 | function calculateScrollPos(cm, x1, y1, x2, y2) { | |
|
2557 | var display = cm.display, pt = paddingTop(display); | |
|
2558 | y1 += pt; y2 += pt; | |
|
2559 | if (y1 < 0) y1 = 0; | |
|
2560 | var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {}; | |
|
2561 | var docBottom = cm.doc.height + paddingVert(display); | |
|
2562 | var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10; | |
|
2563 | if (y1 < screentop) { | |
|
2564 | result.scrollTop = atTop ? 0 : y1; | |
|
2565 | } else if (y2 > screentop + screen) { | |
|
2566 | var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); | |
|
2567 | if (newTop != screentop) result.scrollTop = newTop; | |
|
2568 | } | |
|
2569 | ||
|
2570 | var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft; | |
|
2571 | x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth; | |
|
2572 | var gutterw = display.gutters.offsetWidth; | |
|
2573 | var atLeft = x1 < gutterw + 10; | |
|
2574 | if (x1 < screenleft + gutterw || atLeft) { | |
|
2575 | if (atLeft) x1 = 0; | |
|
2576 | result.scrollLeft = Math.max(0, x1 - 10 - gutterw); | |
|
2577 | } else if (x2 > screenw + screenleft - 3) { | |
|
2578 | result.scrollLeft = x2 + 10 - screenw; | |
|
2579 | } | |
|
2580 | return result; | |
|
2581 | } | |
|
2582 | ||
|
2583 | function updateScrollPos(cm, left, top) { | |
|
2584 | cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left, | |
|
2585 | scrollTop: top == null ? cm.doc.scrollTop : top}; | |
|
2586 | } | |
|
2587 | ||
|
2588 | function addToScrollPos(cm, left, top) { | |
|
2589 | var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop}); | |
|
2590 | var scroll = cm.display.scroller; | |
|
2591 | pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top)); | |
|
2592 | pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left)); | |
|
2593 | } | |
|
2594 | ||
|
2595 | // API UTILITIES | |
|
2596 | ||
|
2597 | function indentLine(cm, n, how, aggressive) { | |
|
2598 | var doc = cm.doc; | |
|
2599 | if (!how) how = "add"; | |
|
2600 | if (how == "smart") { | |
|
2601 | if (!cm.doc.mode.indent) how = "prev"; | |
|
2602 | else var state = getStateBefore(cm, n); | |
|
2603 | } | |
|
2604 | ||
|
2605 | var tabSize = cm.options.tabSize; | |
|
2606 | var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); | |
|
2607 | var curSpaceString = line.text.match(/^\s*/)[0], indentation; | |
|
2608 | if (how == "smart") { | |
|
2609 | indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); | |
|
2610 | if (indentation == Pass) { | |
|
2611 | if (!aggressive) return; | |
|
2612 | how = "prev"; | |
|
897 | 2613 | } |
|
898 | var end; | |
|
899 | replaceRange1(code, from, to, function(end1) { | |
|
900 | end = end1; | |
|
901 | return {from: adjustPos(sel.from), to: adjustPos(sel.to)}; | |
|
902 | }); | |
|
903 | return end; | |
|
904 | } | |
|
905 | function replaceSelection(code, collapse) { | |
|
906 | replaceRange1(splitLines(code), sel.from, sel.to, function(end) { | |
|
907 | if (collapse == "end") return {from: end, to: end}; | |
|
908 | else if (collapse == "start") return {from: sel.from, to: sel.from}; | |
|
909 | else return {from: sel.from, to: end}; | |
|
910 | }); | |
|
911 | } | |
|
912 | function replaceRange1(code, from, to, computeSel) { | |
|
913 | var endch = code.length == 1 ? code[0].length + from.ch : lst(code).length; | |
|
914 | var newSel = computeSel({line: from.line + code.length - 1, ch: endch}); | |
|
915 | updateLines(from, to, code, newSel.from, newSel.to); | |
|
916 | } | |
|
917 | ||
|
918 | function getRange(from, to, lineSep) { | |
|
919 | var l1 = from.line, l2 = to.line; | |
|
920 | if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch); | |
|
921 | var code = [getLine(l1).text.slice(from.ch)]; | |
|
922 | doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); | |
|
923 | code.push(getLine(l2).text.slice(0, to.ch)); | |
|
924 | return code.join(lineSep || "\n"); | |
|
925 | } | |
|
926 | function getSelection(lineSep) { | |
|
927 | return getRange(sel.from, sel.to, lineSep); | |
|
928 | } | |
|
929 | ||
|
930 | function slowPoll() { | |
|
931 | if (pollingFast) return; | |
|
932 | poll.set(options.pollInterval, function() { | |
|
933 | readInput(); | |
|
934 | if (focused) slowPoll(); | |
|
935 | }); | |
|
936 | } | |
|
937 | function fastPoll() { | |
|
938 | var missed = false; | |
|
939 | pollingFast = true; | |
|
940 | function p() { | |
|
941 | var changed = readInput(); | |
|
942 | if (!changed && !missed) {missed = true; poll.set(60, p);} | |
|
943 | else {pollingFast = false; slowPoll();} | |
|
944 | } | |
|
945 | poll.set(20, p); | |
|
946 | } | |
|
947 | ||
|
948 | // Previnput is a hack to work with IME. If we reset the textarea | |
|
949 | // on every change, that breaks IME. So we look for changes | |
|
950 | // compared to the previous content instead. (Modern browsers have | |
|
951 | // events that indicate IME taking place, but these are not widely | |
|
952 | // supported or compatible enough yet to rely on.) | |
|
953 | var prevInput = ""; | |
|
954 | function readInput() { | |
|
955 | if (!focused || hasSelection(input) || options.readOnly) return false; | |
|
956 | var text = input.value; | |
|
957 | if (text == prevInput) return false; | |
|
958 | if (!nestedOperation) startOperation(); | |
|
959 | shiftSelecting = null; | |
|
960 | var same = 0, l = Math.min(prevInput.length, text.length); | |
|
961 | while (same < l && prevInput[same] == text[same]) ++same; | |
|
962 | if (same < prevInput.length) | |
|
963 | sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)}; | |
|
964 | else if (overwrite && posEq(sel.from, sel.to) && !pasteIncoming) | |
|
965 | sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))}; | |
|
966 | replaceSelection(text.slice(same), "end"); | |
|
967 | if (text.length > 1000) { input.value = prevInput = ""; } | |
|
968 | else prevInput = text; | |
|
969 | if (!nestedOperation) endOperation(); | |
|
970 | pasteIncoming = false; | |
|
2614 | } | |
|
2615 | if (how == "prev") { | |
|
2616 | if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); | |
|
2617 | else indentation = 0; | |
|
2618 | } else if (how == "add") { | |
|
2619 | indentation = curSpace + cm.options.indentUnit; | |
|
2620 | } else if (how == "subtract") { | |
|
2621 | indentation = curSpace - cm.options.indentUnit; | |
|
2622 | } | |
|
2623 | indentation = Math.max(0, indentation); | |
|
2624 | ||
|
2625 | var indentString = "", pos = 0; | |
|
2626 | if (cm.options.indentWithTabs) | |
|
2627 | for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} | |
|
2628 | if (pos < indentation) indentString += spaceStr(indentation - pos); | |
|
2629 | ||
|
2630 | if (indentString != curSpaceString) | |
|
2631 | replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); | |
|
2632 | line.stateAfter = null; | |
|
2633 | } | |
|
2634 | ||
|
2635 | function changeLine(cm, handle, op) { | |
|
2636 | var no = handle, line = handle, doc = cm.doc; | |
|
2637 | if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); | |
|
2638 | else no = lineNo(handle); | |
|
2639 | if (no == null) return null; | |
|
2640 | if (op(line, no)) regChange(cm, no, no + 1); | |
|
2641 | else return null; | |
|
2642 | return line; | |
|
2643 | } | |
|
2644 | ||
|
2645 | function findPosH(doc, pos, dir, unit, visually) { | |
|
2646 | var line = pos.line, ch = pos.ch; | |
|
2647 | var lineObj = getLine(doc, line); | |
|
2648 | var possible = true; | |
|
2649 | function findNextLine() { | |
|
2650 | var l = line + dir; | |
|
2651 | if (l < doc.first || l >= doc.first + doc.size) return (possible = false); | |
|
2652 | line = l; | |
|
2653 | return lineObj = getLine(doc, l); | |
|
2654 | } | |
|
2655 | function moveOnce(boundToLine) { | |
|
2656 | var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); | |
|
2657 | if (next == null) { | |
|
2658 | if (!boundToLine && findNextLine()) { | |
|
2659 | if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); | |
|
2660 | else ch = dir < 0 ? lineObj.text.length : 0; | |
|
2661 | } else return (possible = false); | |
|
2662 | } else ch = next; | |
|
971 | 2663 | return true; |
|
972 | 2664 | } |
|
973 | function resetInput(user) { | |
|
974 | if (!posEq(sel.from, sel.to)) { | |
|
975 | prevInput = ""; | |
|
976 | input.value = getSelection(); | |
|
977 | if (focused) selectInput(input); | |
|
978 | } else if (user) prevInput = input.value = ""; | |
|
979 | } | |
|
980 | ||
|
981 | function focusInput() { | |
|
982 | if (options.readOnly != "nocursor") input.focus(); | |
|
983 | } | |
|
984 | ||
|
985 | function scrollCursorIntoView() { | |
|
986 | var coords = calculateCursorCoords(); | |
|
987 | scrollIntoView(coords.x, coords.y, coords.x, coords.yBot); | |
|
988 | if (!focused) return; | |
|
989 | var box = sizer.getBoundingClientRect(), doScroll = null; | |
|
990 | if (coords.y + box.top < 0) doScroll = true; | |
|
991 | else if (coords.y + box.top + textHeight() > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; | |
|
992 | if (doScroll != null) { | |
|
993 | var hidden = cursor.style.display == "none"; | |
|
994 | if (hidden) { | |
|
995 | cursor.style.display = ""; | |
|
996 | cursor.style.left = coords.x + "px"; | |
|
997 | cursor.style.top = (coords.y - displayOffset) + "px"; | |
|
2665 | ||
|
2666 | if (unit == "char") moveOnce(); | |
|
2667 | else if (unit == "column") moveOnce(true); | |
|
2668 | else if (unit == "word" || unit == "group") { | |
|
2669 | var sawType = null, group = unit == "group"; | |
|
2670 | for (var first = true;; first = false) { | |
|
2671 | if (dir < 0 && !moveOnce(!first)) break; | |
|
2672 | var cur = lineObj.text.charAt(ch) || "\n"; | |
|
2673 | var type = isWordChar(cur) ? "w" | |
|
2674 | : !group ? null | |
|
2675 | : /\s/.test(cur) ? null | |
|
2676 | : "p"; | |
|
2677 | if (sawType && sawType != type) { | |
|
2678 | if (dir < 0) {dir = 1; moveOnce();} | |
|
2679 | break; | |
|
998 | 2680 | } |
|
999 | cursor.scrollIntoView(doScroll); | |
|
1000 | if (hidden) cursor.style.display = "none"; | |
|
1001 | } | |
|
1002 | } | |
|
1003 | function calculateCursorCoords() { | |
|
1004 | var cursor = localCoords(sel.inverted ? sel.from : sel.to); | |
|
1005 | var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x; | |
|
1006 | return {x: x, y: cursor.y, yBot: cursor.yBot}; | |
|
1007 | } | |
|
1008 | function scrollIntoView(x1, y1, x2, y2) { | |
|
1009 | var scrollPos = calculateScrollPos(x1, y1, x2, y2); | |
|
1010 | if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft;} | |
|
1011 | if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scroller.scrollTop = scrollPos.scrollTop;} | |
|
1012 | } | |
|
1013 | function calculateScrollPos(x1, y1, x2, y2) { | |
|
1014 | var pl = paddingLeft(), pt = paddingTop(); | |
|
1015 | y1 += pt; y2 += pt; x1 += pl; x2 += pl; | |
|
1016 | var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {}; | |
|
1017 | var docBottom = needsScrollbar() || Infinity; | |
|
1018 | var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10; | |
|
1019 | if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1); | |
|
1020 | else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen; | |
|
1021 | ||
|
1022 | var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; | |
|
1023 | var gutterw = options.fixedGutter ? gutter.clientWidth : 0; | |
|
1024 | var atLeft = x1 < gutterw + pl + 10; | |
|
1025 | if (x1 < screenleft + gutterw || atLeft) { | |
|
1026 | if (atLeft) x1 = 0; | |
|
1027 | result.scrollLeft = Math.max(0, x1 - 10 - gutterw); | |
|
1028 | } else if (x2 > screenw + screenleft - 3) { | |
|
1029 | result.scrollLeft = x2 + 10 - screenw; | |
|
1030 | } | |
|
1031 | return result; | |
|
1032 | } | |
|
1033 | ||
|
1034 | function visibleLines(scrollTop) { | |
|
1035 | var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop(); | |
|
1036 | var fromHeight = Math.max(0, Math.floor(top / lh)); | |
|
1037 | var toHeight = Math.ceil((top + scroller.clientHeight) / lh); | |
|
1038 | return {from: lineAtHeight(doc, fromHeight), | |
|
1039 | to: lineAtHeight(doc, toHeight)}; | |
|
1040 | } | |
|
1041 | // Uses a set of changes plus the current scroll position to | |
|
1042 | // determine which DOM updates have to be made, and makes the | |
|
1043 | // updates. | |
|
1044 | function updateDisplay(changes, suppressCallback, scrollTop) { | |
|
1045 | if (!scroller.clientWidth) { | |
|
1046 | showingFrom = showingTo = displayOffset = 0; | |
|
1047 | return; | |
|
1048 | } | |
|
1049 | // Compute the new visible window | |
|
1050 | // If scrollTop is specified, use that to determine which lines | |
|
1051 | // to render instead of the current scrollbar position. | |
|
1052 | var visible = visibleLines(scrollTop); | |
|
1053 | // Bail out if the visible area is already rendered and nothing changed. | |
|
1054 | if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) { | |
|
1055 | updateVerticalScroll(scrollTop); | |
|
1056 | return; | |
|
2681 | if (type) sawType = type; | |
|
2682 | if (dir > 0 && !moveOnce(!first)) break; | |
|
1057 | 2683 | } |
|
1058 | var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); | |
|
1059 | if (showingFrom < from && from - showingFrom < 20) from = showingFrom; | |
|
1060 | if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); | |
|
1061 | ||
|
1062 | // Create a range of theoretically intact lines, and punch holes | |
|
1063 | // in that using the change info. | |
|
1064 | var intact = changes === true ? [] : | |
|
1065 | computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes); | |
|
1066 | // Clip off the parts that won't be visible | |
|
1067 | var intactLines = 0; | |
|
1068 | for (var i = 0; i < intact.length; ++i) { | |
|
1069 | var range = intact[i]; | |
|
1070 | if (range.from < from) {range.domStart += (from - range.from); range.from = from;} | |
|
1071 | if (range.to > to) range.to = to; | |
|
1072 | if (range.from >= range.to) intact.splice(i--, 1); | |
|
1073 | else intactLines += range.to - range.from; | |
|
1074 | } | |
|
1075 | if (intactLines == to - from && from == showingFrom && to == showingTo) { | |
|
1076 | updateVerticalScroll(scrollTop); | |
|
1077 | return; | |
|
1078 | } | |
|
1079 | intact.sort(function(a, b) {return a.domStart - b.domStart;}); | |
|
1080 | ||
|
1081 | var th = textHeight(), gutterDisplay = gutter.style.display; | |
|
1082 | lineDiv.style.display = "none"; | |
|
1083 | patchDisplay(from, to, intact); | |
|
1084 | lineDiv.style.display = gutter.style.display = ""; | |
|
1085 | ||
|
1086 | var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th; | |
|
1087 | // This is just a bogus formula that detects when the editor is | |
|
1088 | // resized or the font size changes. | |
|
1089 | if (different) lastSizeC = scroller.clientHeight + th; | |
|
1090 | if (from != showingFrom || to != showingTo && options.onViewportChange) | |
|
1091 | setTimeout(function(){ | |
|
1092 | if (options.onViewportChange) options.onViewportChange(instance, from, to); | |
|
1093 | }); | |
|
1094 | showingFrom = from; showingTo = to; | |
|
1095 | displayOffset = heightAtLine(doc, from); | |
|
1096 | startWorker(100); | |
|
1097 | ||
|
1098 | // Since this is all rather error prone, it is honoured with the | |
|
1099 | // only assertion in the whole file. | |
|
1100 | if (lineDiv.childNodes.length != showingTo - showingFrom) | |
|
1101 | throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + | |
|
1102 | " nodes=" + lineDiv.childNodes.length); | |
|
1103 | ||
|
1104 | function checkHeights() { | |
|
1105 | var curNode = lineDiv.firstChild, heightChanged = false; | |
|
1106 | doc.iter(showingFrom, showingTo, function(line) { | |
|
1107 | // Work around bizarro IE7 bug where, sometimes, our curNode | |
|
1108 | // is magically replaced with a new node in the DOM, leaving | |
|
1109 | // us with a reference to an orphan (nextSibling-less) node. | |
|
1110 | if (!curNode) return; | |
|
1111 | if (!line.hidden) { | |
|
1112 | var height = Math.round(curNode.offsetHeight / th) || 1; | |
|
1113 | if (line.height != height) { | |
|
1114 | updateLineHeight(line, height); | |
|
1115 | gutterDirty = heightChanged = true; | |
|
1116 | } | |
|
1117 | } | |
|
1118 | curNode = curNode.nextSibling; | |
|
1119 | }); | |
|
1120 | return heightChanged; | |
|
1121 | } | |
|
1122 | ||
|
1123 | if (options.lineWrapping) checkHeights(); | |
|
1124 | ||
|
1125 | gutter.style.display = gutterDisplay; | |
|
1126 | if (different || gutterDirty) { | |
|
1127 | // If the gutter grew in size, re-check heights. If those changed, re-draw gutter. | |
|
1128 | updateGutter() && options.lineWrapping && checkHeights() && updateGutter(); | |
|
2684 | } | |
|
2685 | var result = skipAtomic(doc, Pos(line, ch), dir, true); | |
|
2686 | if (!possible) result.hitSide = true; | |
|
2687 | return result; | |
|
2688 | } | |
|
2689 | ||
|
2690 | function findPosV(cm, pos, dir, unit) { | |
|
2691 | var doc = cm.doc, x = pos.left, y; | |
|
2692 | if (unit == "page") { | |
|
2693 | var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); | |
|
2694 | y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); | |
|
2695 | } else if (unit == "line") { | |
|
2696 | y = dir > 0 ? pos.bottom + 3 : pos.top - 3; | |
|
2697 | } | |
|
2698 | for (;;) { | |
|
2699 | var target = coordsChar(cm, x, y); | |
|
2700 | if (!target.outside) break; | |
|
2701 | if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } | |
|
2702 | y += dir * 5; | |
|
2703 | } | |
|
2704 | return target; | |
|
2705 | } | |
|
2706 | ||
|
2707 | function findWordAt(line, pos) { | |
|
2708 | var start = pos.ch, end = pos.ch; | |
|
2709 | if (line) { | |
|
2710 | if (pos.after === false || end == line.length) --start; else ++end; | |
|
2711 | var startChar = line.charAt(start); | |
|
2712 | var check = isWordChar(startChar) ? isWordChar | |
|
2713 | : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} | |
|
2714 | : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; | |
|
2715 | while (start > 0 && check(line.charAt(start - 1))) --start; | |
|
2716 | while (end < line.length && check(line.charAt(end))) ++end; | |
|
2717 | } | |
|
2718 | return {from: Pos(pos.line, start), to: Pos(pos.line, end)}; | |
|
2719 | } | |
|
2720 | ||
|
2721 | function selectLine(cm, line) { | |
|
2722 | extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0))); | |
|
2723 | } | |
|
2724 | ||
|
2725 | // PROTOTYPE | |
|
2726 | ||
|
2727 | // The publicly visible API. Note that operation(null, f) means | |
|
2728 | // 'wrap f in an operation, performed on its `this` parameter' | |
|
2729 | ||
|
2730 | CodeMirror.prototype = { | |
|
2731 | focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);}, | |
|
2732 | ||
|
2733 | setOption: function(option, value) { | |
|
2734 | var options = this.options, old = options[option]; | |
|
2735 | if (options[option] == value && option != "mode") return; | |
|
2736 | options[option] = value; | |
|
2737 | if (optionHandlers.hasOwnProperty(option)) | |
|
2738 | operation(this, optionHandlers[option])(this, value, old); | |
|
2739 | }, | |
|
2740 | ||
|
2741 | getOption: function(option) {return this.options[option];}, | |
|
2742 | getDoc: function() {return this.doc;}, | |
|
2743 | ||
|
2744 | addKeyMap: function(map, bottom) { | |
|
2745 | this.state.keyMaps[bottom ? "push" : "unshift"](map); | |
|
2746 | }, | |
|
2747 | removeKeyMap: function(map) { | |
|
2748 | var maps = this.state.keyMaps; | |
|
2749 | for (var i = 0; i < maps.length; ++i) | |
|
2750 | if ((typeof map == "string" ? maps[i].name : maps[i]) == map) { | |
|
2751 | maps.splice(i, 1); | |
|
2752 | return true; | |
|
2753 | } | |
|
2754 | }, | |
|
2755 | ||
|
2756 | addOverlay: operation(null, function(spec, options) { | |
|
2757 | var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); | |
|
2758 | if (mode.startState) throw new Error("Overlays may not be stateful."); | |
|
2759 | this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); | |
|
2760 | this.state.modeGen++; | |
|
2761 | regChange(this); | |
|
2762 | }), | |
|
2763 | removeOverlay: operation(null, function(spec) { | |
|
2764 | var overlays = this.state.overlays; | |
|
2765 | for (var i = 0; i < overlays.length; ++i) { | |
|
2766 | if (overlays[i].modeSpec == spec) { | |
|
2767 | overlays.splice(i, 1); | |
|
2768 | this.state.modeGen++; | |
|
2769 | regChange(this); | |
|
2770 | return; | |
|
2771 | } | |
|
1129 | 2772 | } |
|
1130 | updateVerticalScroll(scrollTop); | |
|
1131 | updateSelection(); | |
|
1132 | if (!suppressCallback && options.onUpdate) options.onUpdate(instance); | |
|
1133 | return true; | |
|
1134 | } | |
|
1135 | ||
|
1136 | function computeIntact(intact, changes) { | |
|
1137 | for (var i = 0, l = changes.length || 0; i < l; ++i) { | |
|
1138 | var change = changes[i], intact2 = [], diff = change.diff || 0; | |
|
1139 | for (var j = 0, l2 = intact.length; j < l2; ++j) { | |
|
1140 | var range = intact[j]; | |
|
1141 | if (change.to <= range.from && change.diff) | |
|
1142 | intact2.push({from: range.from + diff, to: range.to + diff, | |
|
1143 | domStart: range.domStart}); | |
|
1144 | else if (change.to <= range.from || change.from >= range.to) | |
|
1145 | intact2.push(range); | |
|
1146 | else { | |
|
1147 | if (change.from > range.from) | |
|
1148 | intact2.push({from: range.from, to: change.from, domStart: range.domStart}); | |
|
1149 | if (change.to < range.to) | |
|
1150 | intact2.push({from: change.to + diff, to: range.to + diff, | |
|
1151 | domStart: range.domStart + (change.to - range.from)}); | |
|
1152 | } | |
|
1153 | } | |
|
1154 | intact = intact2; | |
|
2773 | }), | |
|
2774 | ||
|
2775 | indentLine: operation(null, function(n, dir, aggressive) { | |
|
2776 | if (typeof dir != "string") { | |
|
2777 | if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; | |
|
2778 | else dir = dir ? "add" : "subtract"; | |
|
1155 | 2779 | } |
|
1156 | return intact; | |
|
1157 | } | |
|
1158 | ||
|
1159 | function patchDisplay(from, to, intact) { | |
|
1160 | function killNode(node) { | |
|
1161 | var tmp = node.nextSibling; | |
|
1162 | node.parentNode.removeChild(node); | |
|
1163 | return tmp; | |
|
1164 | } | |
|
1165 | // The first pass removes the DOM nodes that aren't intact. | |
|
1166 | if (!intact.length) removeChildren(lineDiv); | |
|
1167 | else { | |
|
1168 | var domPos = 0, curNode = lineDiv.firstChild, n; | |
|
1169 | for (var i = 0; i < intact.length; ++i) { | |
|
1170 | var cur = intact[i]; | |
|
1171 | while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;} | |
|
1172 | for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;} | |
|
1173 | } | |
|
1174 | while (curNode) curNode = killNode(curNode); | |
|
2780 | if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); | |
|
2781 | }), | |
|
2782 | indentSelection: operation(null, function(how) { | |
|
2783 | var sel = this.doc.sel; | |
|
2784 | if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how); | |
|
2785 | var e = sel.to.line - (sel.to.ch ? 0 : 1); | |
|
2786 | for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how); | |
|
2787 | }), | |
|
2788 | ||
|
2789 | // Fetch the parser token for a given character. Useful for hacks | |
|
2790 | // that want to inspect the mode state (say, for completion). | |
|
2791 | getTokenAt: function(pos) { | |
|
2792 | var doc = this.doc; | |
|
2793 | pos = clipPos(doc, pos); | |
|
2794 | var state = getStateBefore(this, pos.line), mode = this.doc.mode; | |
|
2795 | var line = getLine(doc, pos.line); | |
|
2796 | var stream = new StringStream(line.text, this.options.tabSize); | |
|
2797 | while (stream.pos < pos.ch && !stream.eol()) { | |
|
2798 | stream.start = stream.pos; | |
|
2799 | var style = mode.token(stream, state); | |
|
1175 | 2800 | } |
|
1176 | // This pass fills in the lines that actually changed. | |
|
1177 | var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; | |
|
1178 | doc.iter(from, to, function(line) { | |
|
1179 | if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); | |
|
1180 | if (!nextIntact || nextIntact.from > j) { | |
|
1181 | if (line.hidden) var lineElement = elt("pre"); | |
|
1182 | else { | |
|
1183 | var lineElement = lineContent(line); | |
|
1184 | if (line.className) lineElement.className = line.className; | |
|
1185 | // Kludge to make sure the styled element lies behind the selection (by z-index) | |
|
1186 | if (line.bgClassName) { | |
|
1187 | var pre = elt("pre", "\u00a0", line.bgClassName, "position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2"); | |
|
1188 | lineElement = elt("div", [pre, lineElement], null, "position: relative"); | |
|
1189 | } | |
|
1190 | } | |
|
1191 | lineDiv.insertBefore(lineElement, curNode); | |
|
1192 | } else { | |
|
1193 | curNode = curNode.nextSibling; | |
|
1194 | } | |
|
1195 | ++j; | |
|
2801 | return {start: stream.start, | |
|
2802 | end: stream.pos, | |
|
2803 | string: stream.current(), | |
|
2804 | className: style || null, // Deprecated, use 'type' instead | |
|
2805 | type: style || null, | |
|
2806 | state: state}; | |
|
2807 | }, | |
|
2808 | ||
|
2809 | getStateAfter: function(line) { | |
|
2810 | var doc = this.doc; | |
|
2811 | line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); | |
|
2812 | return getStateBefore(this, line + 1); | |
|
2813 | }, | |
|
2814 | ||
|
2815 | cursorCoords: function(start, mode) { | |
|
2816 | var pos, sel = this.doc.sel; | |
|
2817 | if (start == null) pos = sel.head; | |
|
2818 | else if (typeof start == "object") pos = clipPos(this.doc, start); | |
|
2819 | else pos = start ? sel.from : sel.to; | |
|
2820 | return cursorCoords(this, pos, mode || "page"); | |
|
2821 | }, | |
|
2822 | ||
|
2823 | charCoords: function(pos, mode) { | |
|
2824 | return charCoords(this, clipPos(this.doc, pos), mode || "page"); | |
|
2825 | }, | |
|
2826 | ||
|
2827 | coordsChar: function(coords, mode) { | |
|
2828 | coords = fromCoordSystem(this, coords, mode || "page"); | |
|
2829 | return coordsChar(this, coords.left, coords.top); | |
|
2830 | }, | |
|
2831 | ||
|
2832 | defaultTextHeight: function() { return textHeight(this.display); }, | |
|
2833 | defaultCharWidth: function() { return charWidth(this.display); }, | |
|
2834 | ||
|
2835 | setGutterMarker: operation(null, function(line, gutterID, value) { | |
|
2836 | return changeLine(this, line, function(line) { | |
|
2837 | var markers = line.gutterMarkers || (line.gutterMarkers = {}); | |
|
2838 | markers[gutterID] = value; | |
|
2839 | if (!value && isEmpty(markers)) line.gutterMarkers = null; | |
|
2840 | return true; | |
|
1196 | 2841 | }); |
|
1197 | } | |
|
1198 | ||
|
1199 | function updateGutter() { | |
|
1200 | if (!options.gutter && !options.lineNumbers) return; | |
|
1201 | var hText = mover.offsetHeight, hEditor = scroller.clientHeight; | |
|
1202 | gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; | |
|
1203 | var fragment = document.createDocumentFragment(), i = showingFrom, normalNode; | |
|
1204 | doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) { | |
|
1205 | if (line.hidden) { | |
|
1206 | fragment.appendChild(elt("pre")); | |
|
1207 | } else { | |
|
1208 | var marker = line.gutterMarker; | |
|
1209 | var text = options.lineNumbers ? options.lineNumberFormatter(i + options.firstLineNumber) : null; | |
|
1210 | if (marker && marker.text) | |
|
1211 | text = marker.text.replace("%N%", text != null ? text : ""); | |
|
1212 | else if (text == null) | |
|
1213 | text = "\u00a0"; | |
|
1214 | var markerElement = fragment.appendChild(elt("pre", null, marker && marker.style)); | |
|
1215 | markerElement.innerHTML = text; | |
|
1216 | for (var j = 1; j < line.height; ++j) { | |
|
1217 | markerElement.appendChild(elt("br")); | |
|
1218 | markerElement.appendChild(document.createTextNode("\u00a0")); | |
|
1219 | } | |
|
1220 | if (!marker) normalNode = i; | |
|
2842 | }), | |
|
2843 | ||
|
2844 | clearGutter: operation(null, function(gutterID) { | |
|
2845 | var cm = this, doc = cm.doc, i = doc.first; | |
|
2846 | doc.iter(function(line) { | |
|
2847 | if (line.gutterMarkers && line.gutterMarkers[gutterID]) { | |
|
2848 | line.gutterMarkers[gutterID] = null; | |
|
2849 | regChange(cm, i, i + 1); | |
|
2850 | if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; | |
|
1221 | 2851 | } |
|
1222 | 2852 | ++i; |
|
1223 | 2853 | }); |
|
1224 | gutter.style.display = "none"; | |
|
1225 | removeChildrenAndAdd(gutterText, fragment); | |
|
1226 | // Make sure scrolling doesn't cause number gutter size to pop | |
|
1227 | if (normalNode != null && options.lineNumbers) { | |
|
1228 | var node = gutterText.childNodes[normalNode - showingFrom]; | |
|
1229 | var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = ""; | |
|
1230 | while (val.length + pad.length < minwidth) pad += "\u00a0"; | |
|
1231 | if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild); | |
|
1232 | } | |
|
1233 | gutter.style.display = ""; | |
|
1234 | var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2; | |
|
1235 | lineSpace.style.marginLeft = gutter.offsetWidth + "px"; | |
|
1236 | gutterDirty = false; | |
|
1237 | return resized; | |
|
1238 | } | |
|
1239 | function updateSelection() { | |
|
1240 | var collapsed = posEq(sel.from, sel.to); | |
|
1241 | var fromPos = localCoords(sel.from, true); | |
|
1242 | var toPos = collapsed ? fromPos : localCoords(sel.to, true); | |
|
1243 | var headPos = sel.inverted ? fromPos : toPos, th = textHeight(); | |
|
1244 | var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv); | |
|
1245 | inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px"; | |
|
1246 | inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px"; | |
|
1247 | if (collapsed) { | |
|
1248 | cursor.style.top = headPos.y + "px"; | |
|
1249 | cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px"; | |
|
1250 | cursor.style.display = ""; | |
|
1251 | selectionDiv.style.display = "none"; | |
|
1252 | } else { | |
|
1253 | var sameLine = fromPos.y == toPos.y, fragment = document.createDocumentFragment(); | |
|
1254 | var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth; | |
|
1255 | var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight; | |
|
1256 | var add = function(left, top, right, height) { | |
|
1257 | var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px" | |
|
1258 | : "right: " + right + "px"; | |
|
1259 | fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + | |
|
1260 | "px; top: " + top + "px; " + rstyle + "; height: " + height + "px")); | |
|
1261 | }; | |
|
1262 | if (sel.from.ch && fromPos.y >= 0) { | |
|
1263 | var right = sameLine ? clientWidth - toPos.x : 0; | |
|
1264 | add(fromPos.x, fromPos.y, right, th); | |
|
1265 | } | |
|
1266 | var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); | |
|
1267 | var middleHeight = Math.min(toPos.y, clientHeight) - middleStart; | |
|
1268 | if (middleHeight > 0.2 * th) | |
|
1269 | add(0, middleStart, 0, middleHeight); | |
|
1270 | if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th) | |
|
1271 | add(0, toPos.y, clientWidth - toPos.x, th); | |
|
1272 | removeChildrenAndAdd(selectionDiv, fragment); | |
|
1273 | cursor.style.display = "none"; | |
|
1274 | selectionDiv.style.display = ""; | |
|
1275 | } | |
|
1276 | } | |
|
1277 | ||
|
1278 | function setShift(val) { | |
|
1279 | if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); | |
|
1280 | else shiftSelecting = null; | |
|
1281 | } | |
|
1282 | function setSelectionUser(from, to) { | |
|
1283 | var sh = shiftSelecting && clipPos(shiftSelecting); | |
|
1284 | if (sh) { | |
|
1285 | if (posLess(sh, from)) from = sh; | |
|
1286 | else if (posLess(to, sh)) to = sh; | |
|
1287 | } | |
|
1288 | setSelection(from, to); | |
|
1289 | userSelChange = true; | |
|
1290 | } | |
|
1291 | // Update the selection. Last two args are only used by | |
|
1292 | // updateLines, since they have to be expressed in the line | |
|
1293 | // numbers before the update. | |
|
1294 | function setSelection(from, to, oldFrom, oldTo) { | |
|
1295 | goalColumn = null; | |
|
1296 | if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;} | |
|
1297 | if (posEq(sel.from, from) && posEq(sel.to, to)) return; | |
|
1298 | if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} | |
|
1299 | ||
|
1300 | // Skip over hidden lines. | |
|
1301 | if (from.line != oldFrom) { | |
|
1302 | var from1 = skipHidden(from, oldFrom, sel.from.ch); | |
|
1303 | // If there is no non-hidden line left, force visibility on current line | |
|
1304 | if (!from1) setLineHidden(from.line, false); | |
|
1305 | else from = from1; | |
|
1306 | } | |
|
1307 | if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); | |
|
1308 | ||
|
1309 | if (posEq(from, to)) sel.inverted = false; | |
|
1310 | else if (posEq(from, sel.to)) sel.inverted = false; | |
|
1311 | else if (posEq(to, sel.from)) sel.inverted = true; | |
|
1312 | ||
|
1313 | if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { | |
|
1314 | var head = sel.inverted ? from : to; | |
|
1315 | if (head.line != sel.from.line && sel.from.line < doc.size) { | |
|
1316 | var oldLine = getLine(sel.from.line); | |
|
1317 | if (/^\s+$/.test(oldLine.text)) | |
|
1318 | setTimeout(operation(function() { | |
|
1319 | if (oldLine.parent && /^\s+$/.test(oldLine.text)) { | |
|
1320 | var no = lineNo(oldLine); | |
|
1321 | replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length}); | |
|
1322 | } | |
|
1323 | }, 10)); | |
|
1324 | } | |
|
1325 | } | |
|
1326 | ||
|
1327 | sel.from = from; sel.to = to; | |
|
1328 | selectionChanged = true; | |
|
1329 | } | |
|
1330 | function skipHidden(pos, oldLine, oldCh) { | |
|
1331 | function getNonHidden(dir) { | |
|
1332 | var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1; | |
|
1333 | while (lNo != end) { | |
|
1334 | var line = getLine(lNo); | |
|
1335 | if (!line.hidden) { | |
|
1336 | var ch = pos.ch; | |
|
1337 | if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length; | |
|
1338 | return {line: lNo, ch: ch}; | |
|
1339 | } | |
|
1340 | lNo += dir; | |
|
1341 | } | |
|
1342 | } | |
|
1343 | var line = getLine(pos.line); | |
|
1344 | var toEnd = pos.ch == line.text.length && pos.ch != oldCh; | |
|
1345 | if (!line.hidden) return pos; | |
|
1346 | if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); | |
|
1347 | else return getNonHidden(-1) || getNonHidden(1); | |
|
1348 | } | |
|
1349 | function setCursor(line, ch, user) { | |
|
1350 | var pos = clipPos({line: line, ch: ch || 0}); | |
|
1351 | (user ? setSelectionUser : setSelection)(pos, pos); | |
|
1352 | } | |
|
1353 | ||
|
1354 | function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));} | |
|
1355 | function clipPos(pos) { | |
|
1356 | if (pos.line < 0) return {line: 0, ch: 0}; | |
|
1357 | if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length}; | |
|
1358 | var ch = pos.ch, linelen = getLine(pos.line).text.length; | |
|
1359 | if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; | |
|
1360 | else if (ch < 0) return {line: pos.line, ch: 0}; | |
|
1361 | else return pos; | |
|
1362 | } | |
|
1363 | ||
|
1364 | function findPosH(dir, unit) { | |
|
1365 | var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch; | |
|
1366 | var lineObj = getLine(line); | |
|
1367 | function findNextLine() { | |
|
1368 | for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) { | |
|
1369 | var lo = getLine(l); | |
|
1370 | if (!lo.hidden) { line = l; lineObj = lo; return true; } | |
|
1371 | } | |
|
1372 | } | |
|
1373 | function moveOnce(boundToLine) { | |
|
1374 | if (ch == (dir < 0 ? 0 : lineObj.text.length)) { | |
|
1375 | if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0; | |
|
1376 | else return false; | |
|
1377 | } else ch += dir; | |
|
2854 | }), | |
|
2855 | ||
|
2856 | addLineClass: operation(null, function(handle, where, cls) { | |
|
2857 | return changeLine(this, handle, function(line) { | |
|
2858 | var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; | |
|
2859 | if (!line[prop]) line[prop] = cls; | |
|
2860 | else if (new RegExp("\\b" + cls + "\\b").test(line[prop])) return false; | |
|
2861 | else line[prop] += " " + cls; | |
|
1378 | 2862 | return true; |
|
1379 | } | |
|
1380 | if (unit == "char") moveOnce(); | |
|
1381 | else if (unit == "column") moveOnce(true); | |
|
1382 | else if (unit == "word") { | |
|
1383 | var sawWord = false; | |
|
1384 | for (;;) { | |
|
1385 | if (dir < 0) if (!moveOnce()) break; | |
|
1386 | if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; | |
|
1387 | else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;} | |
|
1388 | if (dir > 0) if (!moveOnce()) break; | |
|
2863 | }); | |
|
2864 | }), | |
|
2865 | ||
|
2866 | removeLineClass: operation(null, function(handle, where, cls) { | |
|
2867 | return changeLine(this, handle, function(line) { | |
|
2868 | var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; | |
|
2869 | var cur = line[prop]; | |
|
2870 | if (!cur) return false; | |
|
2871 | else if (cls == null) line[prop] = null; | |
|
2872 | else { | |
|
2873 | var upd = cur.replace(new RegExp("^" + cls + "\\b\\s*|\\s*\\b" + cls + "\\b"), ""); | |
|
2874 | if (upd == cur) return false; | |
|
2875 | line[prop] = upd || null; | |
|
1389 | 2876 | } |
|
1390 | } | |
|
1391 | return {line: line, ch: ch}; | |
|
1392 | } | |
|
1393 | function moveH(dir, unit) { | |
|
1394 | var pos = dir < 0 ? sel.from : sel.to; | |
|
1395 | if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit); | |
|
1396 | setCursor(pos.line, pos.ch, true); | |
|
1397 | } | |
|
1398 | function deleteH(dir, unit) { | |
|
1399 | if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to); | |
|
1400 | else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to); | |
|
1401 | else replaceRange("", sel.from, findPosH(dir, unit)); | |
|
1402 | userSelChange = true; | |
|
1403 | } | |
|
1404 | function moveV(dir, unit) { | |
|
1405 | var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true); | |
|
1406 | if (goalColumn != null) pos.x = goalColumn; | |
|
1407 | if (unit == "page") { | |
|
1408 | var screen = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); | |
|
1409 | var target = coordsChar(pos.x, pos.y + screen * dir); | |
|
1410 | } else if (unit == "line") { | |
|
1411 | var th = textHeight(); | |
|
1412 | var target = coordsChar(pos.x, pos.y + .5 * th + dir * th); | |
|
1413 | } | |
|
1414 | if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y; | |
|
1415 | setCursor(target.line, target.ch, true); | |
|
1416 | goalColumn = pos.x; | |
|
1417 | } | |
|
1418 | ||
|
1419 | function findWordAt(pos) { | |
|
1420 | var line = getLine(pos.line).text; | |
|
1421 | var start = pos.ch, end = pos.ch; | |
|
1422 | if (line) { | |
|
1423 | if (pos.after === false || end == line.length) --start; else ++end; | |
|
1424 | var startChar = line.charAt(start); | |
|
1425 | var check = isWordChar(startChar) ? isWordChar : | |
|
1426 | /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} : | |
|
1427 | function(ch) {return !/\s/.test(ch) && isWordChar(ch);}; | |
|
1428 | while (start > 0 && check(line.charAt(start - 1))) --start; | |
|
1429 | while (end < line.length && check(line.charAt(end))) ++end; | |
|
1430 | } | |
|
1431 | return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}}; | |
|
1432 | } | |
|
1433 | function selectLine(line) { | |
|
1434 | setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0})); | |
|
1435 | } | |
|
1436 | function indentSelected(mode) { | |
|
1437 | if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); | |
|
1438 | var e = sel.to.line - (sel.to.ch ? 0 : 1); | |
|
1439 | for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); | |
|
1440 | } | |
|
1441 | ||
|
1442 | function indentLine(n, how) { | |
|
1443 | if (!how) how = "add"; | |
|
1444 | if (how == "smart") { | |
|
1445 | if (!mode.indent) how = "prev"; | |
|
1446 | else var state = getStateBefore(n); | |
|
1447 | } | |
|
1448 | ||
|
1449 | var line = getLine(n), curSpace = line.indentation(options.tabSize), | |
|
1450 | curSpaceString = line.text.match(/^\s*/)[0], indentation; | |
|
1451 | if (how == "smart") { | |
|
1452 | indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); | |
|
1453 | if (indentation == Pass) how = "prev"; | |
|
1454 | } | |
|
1455 | if (how == "prev") { | |
|
1456 | if (n) indentation = getLine(n-1).indentation(options.tabSize); | |
|
1457 | else indentation = 0; | |
|
1458 | } | |
|
1459 | else if (how == "add") indentation = curSpace + options.indentUnit; | |
|
1460 | else if (how == "subtract") indentation = curSpace - options.indentUnit; | |
|
1461 | indentation = Math.max(0, indentation); | |
|
1462 | var diff = indentation - curSpace; | |
|
1463 | ||
|
1464 | var indentString = "", pos = 0; | |
|
1465 | if (options.indentWithTabs) | |
|
1466 | for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} | |
|
1467 | if (pos < indentation) indentString += spaceStr(indentation - pos); | |
|
1468 | ||
|
1469 | if (indentString != curSpaceString) | |
|
1470 | replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); | |
|
1471 | line.stateAfter = null; | |
|
1472 | } | |
|
1473 | ||
|
1474 | function loadMode() { | |
|
1475 | mode = CodeMirror.getMode(options, options.mode); | |
|
1476 | doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); | |
|
1477 | frontier = 0; | |
|
1478 | startWorker(100); | |
|
1479 | } | |
|
1480 | function gutterChanged() { | |
|
1481 | var visible = options.gutter || options.lineNumbers; | |
|
1482 | gutter.style.display = visible ? "" : "none"; | |
|
1483 | if (visible) gutterDirty = true; | |
|
1484 | else lineDiv.parentNode.style.marginLeft = 0; | |
|
1485 | } | |
|
1486 | function wrappingChanged(from, to) { | |
|
1487 | if (options.lineWrapping) { | |
|
1488 | wrapper.className += " CodeMirror-wrap"; | |
|
1489 | var perLine = scroller.clientWidth / charWidth() - 3; | |
|
1490 | doc.iter(0, doc.size, function(line) { | |
|
1491 | if (line.hidden) return; | |
|
1492 | var guess = Math.ceil(line.text.length / perLine) || 1; | |
|
1493 | if (guess != 1) updateLineHeight(line, guess); | |
|
1494 | }); | |
|
1495 | lineSpace.style.minWidth = widthForcer.style.left = ""; | |
|
1496 | } else { | |
|
1497 | wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); | |
|
1498 | computeMaxLength(); | |
|
1499 | doc.iter(0, doc.size, function(line) { | |
|
1500 | if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); | |
|
1501 | }); | |
|
1502 | } | |
|
1503 | changes.push({from: 0, to: doc.size}); | |
|
1504 | } | |
|
1505 | function themeChanged() { | |
|
1506 | scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") + | |
|
1507 | options.theme.replace(/(^|\s)\s*/g, " cm-s-"); | |
|
1508 | } | |
|
1509 | function keyMapChanged() { | |
|
1510 | var style = keyMap[options.keyMap].style; | |
|
1511 | wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + | |
|
1512 | (style ? " cm-keymap-" + style : ""); | |
|
1513 | } | |
|
1514 | ||
|
1515 | function TextMarker(type, style) { this.lines = []; this.type = type; if (style) this.style = style; } | |
|
1516 | TextMarker.prototype.clear = operation(function() { | |
|
1517 | var min, max; | |
|
1518 | for (var i = 0; i < this.lines.length; ++i) { | |
|
1519 | var line = this.lines[i]; | |
|
1520 | var span = getMarkedSpanFor(line.markedSpans, this); | |
|
1521 | if (span.from != null) min = lineNo(line); | |
|
1522 | if (span.to != null) max = lineNo(line); | |
|
1523 | line.markedSpans = removeMarkedSpan(line.markedSpans, span); | |
|
1524 | } | |
|
1525 | if (min != null) changes.push({from: min, to: max + 1}); | |
|
1526 | this.lines.length = 0; | |
|
1527 | this.explicitlyCleared = true; | |
|
1528 | }); | |
|
1529 | TextMarker.prototype.find = function() { | |
|
1530 | var from, to; | |
|
1531 | for (var i = 0; i < this.lines.length; ++i) { | |
|
1532 | var line = this.lines[i]; | |
|
1533 | var span = getMarkedSpanFor(line.markedSpans, this); | |
|
1534 | if (span.from != null || span.to != null) { | |
|
1535 | var found = lineNo(line); | |
|
1536 | if (span.from != null) from = {line: found, ch: span.from}; | |
|
1537 | if (span.to != null) to = {line: found, ch: span.to}; | |
|
1538 | } | |
|
1539 | } | |
|
1540 | if (this.type == "bookmark") return from; | |
|
1541 | return from && {from: from, to: to}; | |
|
1542 | }; | |
|
1543 | ||
|
1544 | function markText(from, to, className, options) { | |
|
1545 | from = clipPos(from); to = clipPos(to); | |
|
1546 | var marker = new TextMarker("range", className); | |
|
1547 | if (options) for (var opt in options) if (options.hasOwnProperty(opt)) | |
|
1548 | marker[opt] = options[opt]; | |
|
1549 | var curLine = from.line; | |
|
1550 | doc.iter(curLine, to.line + 1, function(line) { | |
|
1551 | var span = {from: curLine == from.line ? from.ch : null, | |
|
1552 | to: curLine == to.line ? to.ch : null, | |
|
1553 | marker: marker}; | |
|
1554 | line.markedSpans = (line.markedSpans || []).concat([span]); | |
|
1555 | marker.lines.push(line); | |
|
1556 | ++curLine; | |
|
2877 | return true; | |
|
1557 | 2878 | }); |
|
1558 | changes.push({from: from.line, to: to.line + 1}); | |
|
1559 | return marker; | |
|
1560 | } | |
|
1561 | ||
|
1562 | function setBookmark(pos) { | |
|
1563 | pos = clipPos(pos); | |
|
1564 | var marker = new TextMarker("bookmark"), line = getLine(pos.line); | |
|
1565 | history.addChange(pos.line, 1, [newHL(line.text, line.markedSpans)], true); | |
|
1566 | var span = {from: pos.ch, to: pos.ch, marker: marker}; | |
|
1567 | line.markedSpans = (line.markedSpans || []).concat([span]); | |
|
1568 | marker.lines.push(line); | |
|
1569 | return marker; | |
|
1570 | } | |
|
1571 | ||
|
1572 | function findMarksAt(pos) { | |
|
1573 | pos = clipPos(pos); | |
|
1574 | var markers = [], spans = getLine(pos.line).markedSpans; | |
|
1575 | if (spans) for (var i = 0; i < spans.length; ++i) { | |
|
1576 | var span = spans[i]; | |
|
1577 | if ((span.from == null || span.from <= pos.ch) && | |
|
1578 | (span.to == null || span.to >= pos.ch)) | |
|
1579 | markers.push(span.marker); | |
|
1580 | } | |
|
1581 | return markers; | |
|
1582 | } | |
|
1583 | ||
|
1584 | function addGutterMarker(line, text, className) { | |
|
1585 | if (typeof line == "number") line = getLine(clipLine(line)); | |
|
1586 | line.gutterMarker = {text: text, style: className}; | |
|
1587 | gutterDirty = true; | |
|
1588 | return line; | |
|
1589 | } | |
|
1590 | function removeGutterMarker(line) { | |
|
1591 | if (typeof line == "number") line = getLine(clipLine(line)); | |
|
1592 | line.gutterMarker = null; | |
|
1593 | gutterDirty = true; | |
|
1594 | } | |
|
1595 | ||
|
1596 | function changeLine(handle, op) { | |
|
1597 | var no = handle, line = handle; | |
|
1598 | if (typeof handle == "number") line = getLine(clipLine(handle)); | |
|
1599 | else no = lineNo(handle); | |
|
1600 | if (no == null) return null; | |
|
1601 | if (op(line, no)) changes.push({from: no, to: no + 1}); | |
|
1602 | else return null; | |
|
1603 | return line; | |
|
1604 | } | |
|
1605 | function setLineClass(handle, className, bgClassName) { | |
|
1606 | return changeLine(handle, function(line) { | |
|
1607 | if (line.className != className || line.bgClassName != bgClassName) { | |
|
1608 | line.className = className; | |
|
1609 | line.bgClassName = bgClassName; | |
|
1610 | return true; | |
|
1611 | } | |
|
1612 | }); | |
|
1613 | } | |
|
1614 | function setLineHidden(handle, hidden) { | |
|
1615 | return changeLine(handle, function(line, no) { | |
|
1616 | if (line.hidden != hidden) { | |
|
1617 | line.hidden = hidden; | |
|
1618 | if (!options.lineWrapping) { | |
|
1619 | if (hidden && line.text.length == maxLine.text.length) { | |
|
1620 | updateMaxLine = true; | |
|
1621 | } else if (!hidden && line.text.length > maxLine.text.length) { | |
|
1622 | maxLine = line; updateMaxLine = false; | |
|
1623 | } | |
|
1624 | } | |
|
1625 | updateLineHeight(line, hidden ? 0 : 1); | |
|
1626 | var fline = sel.from.line, tline = sel.to.line; | |
|
1627 | if (hidden && (fline == no || tline == no)) { | |
|
1628 | var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; | |
|
1629 | var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; | |
|
1630 | // Can't hide the last visible line, we'd have no place to put the cursor | |
|
1631 | if (!to) return; | |
|
1632 | setSelection(from, to); | |
|
1633 | } | |
|
1634 | return (gutterDirty = true); | |
|
1635 | } | |
|
1636 | }); | |
|
1637 | } | |
|
1638 | ||
|
1639 | function lineInfo(line) { | |
|
2879 | }), | |
|
2880 | ||
|
2881 | addLineWidget: operation(null, function(handle, node, options) { | |
|
2882 | return addLineWidget(this, handle, node, options); | |
|
2883 | }), | |
|
2884 | ||
|
2885 | removeLineWidget: function(widget) { widget.clear(); }, | |
|
2886 | ||
|
2887 | lineInfo: function(line) { | |
|
1640 | 2888 | if (typeof line == "number") { |
|
1641 | if (!isLine(line)) return null; | |
|
2889 | if (!isLine(this.doc, line)) return null; | |
|
1642 | 2890 | var n = line; |
|
1643 | line = getLine(line); | |
|
2891 | line = getLine(this.doc, line); | |
|
1644 | 2892 | if (!line) return null; |
|
1645 | 2893 | } else { |
|
1646 | 2894 | var n = lineNo(line); |
|
1647 | 2895 | if (n == null) return null; |
|
1648 | 2896 | } |
|
1649 | var marker = line.gutterMarker; | |
|
1650 | return {line: n, handle: line, text: line.text, markerText: marker && marker.text, | |
|
1651 | markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName}; | |
|
1652 | } | |
|
1653 | ||
|
1654 | function measureLine(line, ch) { | |
|
1655 | if (ch == 0) return {top: 0, left: 0}; | |
|
1656 | var pre = lineContent(line, ch); | |
|
1657 | removeChildrenAndAdd(measure, pre); | |
|
1658 | var anchor = pre.anchor; | |
|
1659 |
var top = |
|
|
1660 | // Older IEs report zero offsets for spans directly after a wrap | |
|
1661 | if (ie && top == 0 && left == 0) { | |
|
1662 | var backup = elt("span", "x"); | |
|
1663 | anchor.parentNode.insertBefore(backup, anchor.nextSibling); | |
|
1664 | top = backup.offsetTop; | |
|
1665 | } | |
|
1666 | return {top: top, left: left}; | |
|
1667 | } | |
|
1668 | function localCoords(pos, inLineWrap) { | |
|
1669 | var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0)); | |
|
1670 | if (pos.ch == 0) x = 0; | |
|
1671 | else { | |
|
1672 | var sp = measureLine(getLine(pos.line), pos.ch); | |
|
1673 | x = sp.left; | |
|
1674 | if (options.lineWrapping) y += Math.max(0, sp.top); | |
|
2897 | return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, | |
|
2898 | textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, | |
|
2899 | widgets: line.widgets}; | |
|
2900 | }, | |
|
2901 | ||
|
2902 | getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};}, | |
|
2903 | ||
|
2904 | addWidget: function(pos, node, scroll, vert, horiz) { | |
|
2905 | var display = this.display; | |
|
2906 | pos = cursorCoords(this, clipPos(this.doc, pos)); | |
|
2907 | var top = pos.bottom, left = pos.left; | |
|
2908 | node.style.position = "absolute"; | |
|
2909 | display.sizer.appendChild(node); | |
|
2910 | if (vert == "over") { | |
|
2911 | top = pos.top; | |
|
2912 | } else if (vert == "above" || vert == "near") { | |
|
2913 | var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), | |
|
2914 | hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); | |
|
2915 | // Default to positioning above (if specified and possible); otherwise default to positioning below | |
|
2916 | if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) | |
|
2917 | top = pos.top - node.offsetHeight; | |
|
2918 | else if (pos.bottom + node.offsetHeight <= vspace) | |
|
2919 | top = pos.bottom; | |
|
2920 | if (left + node.offsetWidth > hspace) | |
|
2921 | left = hspace - node.offsetWidth; | |
|
1675 | 2922 | } |
|
1676 | return {x: x, y: y, yBot: y + lh}; | |
|
1677 | } | |
|
1678 | // Coords must be lineSpace-local | |
|
1679 | function coordsChar(x, y) { | |
|
1680 | var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th); | |
|
1681 | if (heightPos < 0) return {line: 0, ch: 0}; | |
|
1682 | var lineNo = lineAtHeight(doc, heightPos); | |
|
1683 | if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length}; | |
|
1684 | var lineObj = getLine(lineNo), text = lineObj.text; | |
|
1685 | var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; | |
|
1686 | if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0}; | |
|
1687 | var wrongLine = false; | |
|
1688 | function getX(len) { | |
|
1689 | var sp = measureLine(lineObj, len); | |
|
1690 | if (tw) { | |
|
1691 | var off = Math.round(sp.top / th); | |
|
1692 | wrongLine = off != innerOff; | |
|
1693 | return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth); | |
|
1694 | } | |
|
1695 | return sp.left; | |
|
1696 | } | |
|
1697 | var from = 0, fromX = 0, to = text.length, toX; | |
|
1698 | // Guess a suitable upper bound for our search. | |
|
1699 | var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw)); | |
|
1700 | for (;;) { | |
|
1701 | var estX = getX(estimated); | |
|
1702 | if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); | |
|
1703 | else {toX = estX; to = estimated; break;} | |
|
1704 | } | |
|
1705 | if (x > toX) return {line: lineNo, ch: to}; | |
|
1706 | // Try to guess a suitable lower bound as well. | |
|
1707 | estimated = Math.floor(to * 0.8); estX = getX(estimated); | |
|
1708 | if (estX < x) {from = estimated; fromX = estX;} | |
|
1709 | // Do a binary search between these bounds. | |
|
1710 | for (;;) { | |
|
1711 | if (to - from <= 1) { | |
|
1712 | var after = x - fromX < toX - x; | |
|
1713 | return {line: lineNo, ch: after ? from : to, after: after}; | |
|
1714 | } | |
|
1715 | var middle = Math.ceil((from + to) / 2), middleX = getX(middle); | |
|
1716 | if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; } | |
|
1717 | else {from = middle; fromX = middleX;} | |
|
2923 | node.style.top = (top + paddingTop(display)) + "px"; | |
|
2924 | node.style.left = node.style.right = ""; | |
|
2925 | if (horiz == "right") { | |
|
2926 | left = display.sizer.clientWidth - node.offsetWidth; | |
|
2927 | node.style.right = "0px"; | |
|
2928 | } else { | |
|
2929 | if (horiz == "left") left = 0; | |
|
2930 | else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; | |
|
2931 | node.style.left = left + "px"; | |
|
1718 | 2932 | } |
|
1719 | } | |
|
1720 | function pageCoords(pos) { | |
|
1721 | var local = localCoords(pos, true), off = eltOffset(lineSpace); | |
|
1722 | return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot}; | |
|
1723 | } | |
|
1724 | ||
|
1725 | var cachedHeight, cachedHeightFor, measurePre; | |
|
1726 | function textHeight() { | |
|
1727 | if (measurePre == null) { | |
|
1728 | measurePre = elt("pre"); | |
|
1729 | for (var i = 0; i < 49; ++i) { | |
|
1730 | measurePre.appendChild(document.createTextNode("x")); | |
|
1731 | measurePre.appendChild(elt("br")); | |
|
1732 | } | |
|
1733 | measurePre.appendChild(document.createTextNode("x")); | |
|
2933 | if (scroll) | |
|
2934 | scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); | |
|
2935 | }, | |
|
2936 | ||
|
2937 | triggerOnKeyDown: operation(null, onKeyDown), | |
|
2938 | ||
|
2939 | execCommand: function(cmd) {return commands[cmd](this);}, | |
|
2940 | ||
|
2941 | findPosH: function(from, amount, unit, visually) { | |
|
2942 | var dir = 1; | |
|
2943 | if (amount < 0) { dir = -1; amount = -amount; } | |
|
2944 | for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { | |
|
2945 | cur = findPosH(this.doc, cur, dir, unit, visually); | |
|
2946 | if (cur.hitSide) break; | |
|
1734 | 2947 | } |
|
1735 | var offsetHeight = lineDiv.clientHeight; | |
|
1736 | if (offsetHeight == cachedHeightFor) return cachedHeight; | |
|
1737 | cachedHeightFor = offsetHeight; | |
|
1738 | removeChildrenAndAdd(measure, measurePre.cloneNode(true)); | |
|
1739 | cachedHeight = measure.firstChild.offsetHeight / 50 || 1; | |
|
1740 | removeChildren(measure); | |
|
1741 | return cachedHeight; | |
|
1742 | } | |
|
1743 | var cachedWidth, cachedWidthFor = 0; | |
|
1744 | function charWidth() { | |
|
1745 | if (scroller.clientWidth == cachedWidthFor) return cachedWidth; | |
|
1746 | cachedWidthFor = scroller.clientWidth; | |
|
1747 | var anchor = elt("span", "x"); | |
|
1748 | var pre = elt("pre", [anchor]); | |
|
1749 | removeChildrenAndAdd(measure, pre); | |
|
1750 | return (cachedWidth = anchor.offsetWidth || 10); | |
|
1751 | } | |
|
1752 | function paddingTop() {return lineSpace.offsetTop;} | |
|
1753 | function paddingLeft() {return lineSpace.offsetLeft;} | |
|
1754 | ||
|
1755 | function posFromMouse(e, liberal) { | |
|
1756 | var offW = eltOffset(scroller, true), x, y; | |
|
1757 | // Fails unpredictably on IE[67] when mouse is dragged around quickly. | |
|
1758 | try { x = e.clientX; y = e.clientY; } catch (e) { return null; } | |
|
1759 | // This is a mess of a heuristic to try and determine whether a | |
|
1760 | // scroll-bar was clicked or not, and to return null if one was | |
|
1761 | // (and !liberal). | |
|
1762 | if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight)) | |
|
1763 | return null; | |
|
1764 | var offL = eltOffset(lineSpace, true); | |
|
1765 | return coordsChar(x - offL.left, y - offL.top); | |
|
1766 | } | |
|
1767 | var detectingSelectAll; | |
|
1768 | function onContextMenu(e) { | |
|
1769 | var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop; | |
|
1770 | if (!pos || opera) return; // Opera is difficult. | |
|
1771 | if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) | |
|
1772 | operation(setCursor)(pos.line, pos.ch); | |
|
1773 | ||
|
1774 | var oldCSS = input.style.cssText; | |
|
1775 | inputDiv.style.position = "absolute"; | |
|
1776 | input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + | |
|
1777 | "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + | |
|
1778 | "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; | |
|
1779 | focusInput(); | |
|
1780 | resetInput(true); | |
|
1781 | // Adds "Select all" to context menu in FF | |
|
1782 | if (posEq(sel.from, sel.to)) input.value = prevInput = " "; | |
|
1783 | ||
|
1784 | function rehide() { | |
|
1785 | inputDiv.style.position = "relative"; | |
|
1786 | input.style.cssText = oldCSS; | |
|
1787 | if (ie_lt9) scrollbar.scrollTop = scrollPos; | |
|
1788 | slowPoll(); | |
|
1789 | ||
|
1790 | // Try to detect the user choosing select-all | |
|
1791 | if (input.selectionStart != null) { | |
|
1792 | clearTimeout(detectingSelectAll); | |
|
1793 | var extval = input.value = " " + (posEq(sel.from, sel.to) ? "" : input.value), i = 0; | |
|
1794 | prevInput = " "; | |
|
1795 | input.selectionStart = 1; input.selectionEnd = extval.length; | |
|
1796 | detectingSelectAll = setTimeout(function poll(){ | |
|
1797 | if (prevInput == " " && input.selectionStart == 0) | |
|
1798 | operation(commands.selectAll)(instance); | |
|
1799 | else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); | |
|
1800 | else resetInput(); | |
|
1801 | }, 200); | |
|
1802 | } | |
|
1803 | } | |
|
1804 | ||
|
1805 | if (gecko) { | |
|
1806 | e_stop(e); | |
|
1807 | var mouseup = connect(window, "mouseup", function() { | |
|
1808 | mouseup(); | |
|
1809 | setTimeout(rehide, 20); | |
|
1810 | }, true); | |
|
1811 | } else { | |
|
1812 | setTimeout(rehide, 50); | |
|
2948 | return cur; | |
|
2949 | }, | |
|
2950 | ||
|
2951 | moveH: operation(null, function(dir, unit) { | |
|
2952 | var sel = this.doc.sel, pos; | |
|
2953 | if (sel.shift || sel.extend || posEq(sel.from, sel.to)) | |
|
2954 | pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually); | |
|
2955 | else | |
|
2956 | pos = dir < 0 ? sel.from : sel.to; | |
|
2957 | extendSelection(this.doc, pos, pos, dir); | |
|
2958 | }), | |
|
2959 | ||
|
2960 | deleteH: operation(null, function(dir, unit) { | |
|
2961 | var sel = this.doc.sel; | |
|
2962 | if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete"); | |
|
2963 | else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete"); | |
|
2964 | this.curOp.userSelChange = true; | |
|
2965 | }), | |
|
2966 | ||
|
2967 | findPosV: function(from, amount, unit, goalColumn) { | |
|
2968 | var dir = 1, x = goalColumn; | |
|
2969 | if (amount < 0) { dir = -1; amount = -amount; } | |
|
2970 | for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { | |
|
2971 | var coords = cursorCoords(this, cur, "div"); | |
|
2972 | if (x == null) x = coords.left; | |
|
2973 | else coords.left = x; | |
|
2974 | cur = findPosV(this, coords, dir, unit); | |
|
2975 | if (cur.hitSide) break; | |
|
1813 | 2976 | } |
|
1814 | } | |
|
1815 | ||
|
1816 | // Cursor-blinking | |
|
1817 | function restartBlink() { | |
|
1818 | clearInterval(blinker); | |
|
1819 | var on = true; | |
|
1820 | cursor.style.visibility = ""; | |
|
1821 | blinker = setInterval(function() { | |
|
1822 | cursor.style.visibility = (on = !on) ? "" : "hidden"; | |
|
1823 | }, options.cursorBlinkRate); | |
|
1824 | } | |
|
1825 | ||
|
1826 | var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; | |
|
1827 | function matchBrackets(autoclear) { | |
|
1828 | var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1; | |
|
1829 | var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; | |
|
1830 | if (!match) return; | |
|
1831 | var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles; | |
|
1832 | for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2) | |
|
1833 | if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;} | |
|
1834 | ||
|
1835 | var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; | |
|
1836 | function scan(line, from, to) { | |
|
1837 | if (!line.text) return; | |
|
1838 | var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; | |
|
1839 | for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { | |
|
1840 | var text = st[i]; | |
|
1841 | if (st[i+1] != style) {pos += d * text.length; continue;} | |
|
1842 | for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { | |
|
1843 | if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { | |
|
1844 | var match = matching[cur]; | |
|
1845 | if (match.charAt(1) == ">" == forward) stack.push(cur); | |
|
1846 | else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false}; | |
|
1847 | else if (!stack.length) return {pos: pos, match: true}; | |
|
1848 | } | |
|
1849 | } | |
|
1850 | } | |
|
1851 | } | |
|
1852 | for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) { | |
|
1853 | var line = getLine(i), first = i == head.line; | |
|
1854 | var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); | |
|
1855 | if (found) break; | |
|
1856 | } | |
|
1857 | if (!found) found = {pos: null, match: false}; | |
|
1858 | var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; | |
|
1859 | var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style), | |
|
1860 | two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style); | |
|
1861 | var clear = operation(function(){one.clear(); two && two.clear();}); | |
|
1862 | if (autoclear) setTimeout(clear, 800); | |
|
1863 | else bracketHighlighted = clear; | |
|
1864 | } | |
|
1865 | ||
|
1866 | // Finds the line to start with when starting a parse. Tries to | |
|
1867 | // find a line with a stateAfter, so that it can start with a | |
|
1868 | // valid state. If that fails, it returns the line with the | |
|
1869 | // smallest indentation, which tends to need the least context to | |
|
1870 | // parse correctly. | |
|
1871 | function findStartLine(n) { | |
|
1872 | var minindent, minline; | |
|
1873 | for (var search = n, lim = n - 40; search > lim; --search) { | |
|
1874 | if (search == 0) return 0; | |
|
1875 | var line = getLine(search-1); | |
|
1876 | if (line.stateAfter) return search; | |
|
1877 | var indented = line.indentation(options.tabSize); | |
|
1878 | if (minline == null || minindent > indented) { | |
|
1879 | minline = search - 1; | |
|
1880 | minindent = indented; | |
|
1881 | } | |
|
2977 | return cur; | |
|
2978 | }, | |
|
2979 | ||
|
2980 | moveV: operation(null, function(dir, unit) { | |
|
2981 | var sel = this.doc.sel; | |
|
2982 | var pos = cursorCoords(this, sel.head, "div"); | |
|
2983 | if (sel.goalColumn != null) pos.left = sel.goalColumn; | |
|
2984 | var target = findPosV(this, pos, dir, unit); | |
|
2985 | ||
|
2986 | if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top); | |
|
2987 | extendSelection(this.doc, target, target, dir); | |
|
2988 | sel.goalColumn = pos.left; | |
|
2989 | }), | |
|
2990 | ||
|
2991 | toggleOverwrite: function() { | |
|
2992 | if (this.state.overwrite = !this.state.overwrite) | |
|
2993 | this.display.cursor.className += " CodeMirror-overwrite"; | |
|
2994 | else | |
|
2995 | this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", ""); | |
|
2996 | }, | |
|
2997 | hasFocus: function() { return this.state.focused; }, | |
|
2998 | ||
|
2999 | scrollTo: operation(null, function(x, y) { | |
|
3000 | updateScrollPos(this, x, y); | |
|
3001 | }), | |
|
3002 | getScrollInfo: function() { | |
|
3003 | var scroller = this.display.scroller, co = scrollerCutOff; | |
|
3004 | return {left: scroller.scrollLeft, top: scroller.scrollTop, | |
|
3005 | height: scroller.scrollHeight - co, width: scroller.scrollWidth - co, | |
|
3006 | clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; | |
|
3007 | }, | |
|
3008 | ||
|
3009 | scrollIntoView: operation(null, function(pos, margin) { | |
|
3010 | if (typeof pos == "number") pos = Pos(pos, 0); | |
|
3011 | if (!margin) margin = 0; | |
|
3012 | var coords = pos; | |
|
3013 | ||
|
3014 | if (!pos || pos.line != null) { | |
|
3015 | this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head; | |
|
3016 | this.curOp.scrollToPosMargin = margin; | |
|
3017 | coords = cursorCoords(this, this.curOp.scrollToPos); | |
|
1882 | 3018 | } |
|
1883 | return minline; | |
|
1884 | } | |
|
1885 | function getStateBefore(n) { | |
|
1886 | var pos = findStartLine(n), state = pos && getLine(pos-1).stateAfter; | |
|
1887 | if (!state) state = startState(mode); | |
|
1888 | else state = copyState(mode, state); | |
|
1889 | doc.iter(pos, n, function(line) { | |
|
1890 | line.process(mode, state, options.tabSize); | |
|
1891 | line.stateAfter = (pos == n - 1 || pos % 5 == 0) ? copyState(mode, state) : null; | |
|
1892 | }); | |
|
1893 | return state; | |
|
1894 | } | |
|
1895 | function highlightWorker() { | |
|
1896 | if (frontier >= showingTo) return; | |
|
1897 | var end = +new Date + options.workTime, state = copyState(mode, getStateBefore(frontier)); | |
|
1898 | var startFrontier = frontier; | |
|
1899 | doc.iter(frontier, showingTo, function(line) { | |
|
1900 | if (frontier >= showingFrom) { // Visible | |
|
1901 | line.highlight(mode, state, options.tabSize); | |
|
1902 | line.stateAfter = copyState(mode, state); | |
|
1903 | } else { | |
|
1904 | line.process(mode, state, options.tabSize); | |
|
1905 | line.stateAfter = frontier % 5 == 0 ? copyState(mode, state) : null; | |
|
1906 | } | |
|
1907 | ++frontier; | |
|
1908 | if (+new Date > end) { | |
|
1909 | startWorker(options.workDelay); | |
|
1910 | return true; | |
|
1911 | } | |
|
1912 | }); | |
|
1913 | if (showingTo > startFrontier && frontier >= showingFrom) | |
|
1914 | operation(function() {changes.push({from: startFrontier, to: frontier});})(); | |
|
1915 | } | |
|
1916 | function startWorker(time) { | |
|
1917 | if (frontier < showingTo) | |
|
1918 | highlight.set(time, highlightWorker); | |
|
1919 | } | |
|
1920 | ||
|
1921 | // Operations are used to wrap changes in such a way that each | |
|
1922 | // change won't have to update the cursor and display (which would | |
|
1923 | // be awkward, slow, and error-prone), but instead updates are | |
|
1924 | // batched and then all combined and executed at once. | |
|
1925 | function startOperation() { | |
|
1926 | updateInput = userSelChange = textChanged = null; | |
|
1927 | changes = []; selectionChanged = false; callbacks = []; | |
|
1928 | } | |
|
1929 | function endOperation() { | |
|
1930 | if (updateMaxLine) computeMaxLength(); | |
|
1931 | if (maxLineChanged && !options.lineWrapping) { | |
|
1932 | var cursorWidth = widthForcer.offsetWidth, left = measureLine(maxLine, maxLine.text.length).left; | |
|
1933 | if (!ie_lt8) { | |
|
1934 | widthForcer.style.left = left + "px"; | |
|
1935 | lineSpace.style.minWidth = (left + cursorWidth) + "px"; | |
|
1936 | } | |
|
1937 | maxLineChanged = false; | |
|
3019 | var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin); | |
|
3020 | updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop); | |
|
3021 | }), | |
|
3022 | ||
|
3023 | setSize: function(width, height) { | |
|
3024 | function interpret(val) { | |
|
3025 | return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; | |
|
1938 | 3026 | } |
|
1939 | var newScrollPos, updated; | |
|
1940 | if (selectionChanged) { | |
|
1941 | var coords = calculateCursorCoords(); | |
|
1942 | newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot); | |
|
1943 | } | |
|
1944 | if (changes.length || newScrollPos && newScrollPos.scrollTop != null) | |
|
1945 | updated = updateDisplay(changes, true, newScrollPos && newScrollPos.scrollTop); | |
|
1946 | if (!updated) { | |
|
1947 | if (selectionChanged) updateSelection(); | |
|
1948 | if (gutterDirty) updateGutter(); | |
|
1949 | } | |
|
1950 | if (newScrollPos) scrollCursorIntoView(); | |
|
1951 | if (selectionChanged) restartBlink(); | |
|
1952 | ||
|
1953 | if (focused && (updateInput === true || (updateInput !== false && selectionChanged))) | |
|
1954 | resetInput(userSelChange); | |
|
1955 | ||
|
1956 | if (selectionChanged && options.matchBrackets) | |
|
1957 | setTimeout(operation(function() { | |
|
1958 | if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} | |
|
1959 | if (posEq(sel.from, sel.to)) matchBrackets(false); | |
|
1960 | }), 20); | |
|
1961 | var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks | |
|
1962 | if (textChanged && options.onChange && instance) | |
|
1963 | options.onChange(instance, textChanged); | |
|
1964 | if (sc && options.onCursorActivity) | |
|
1965 | options.onCursorActivity(instance); | |
|
1966 | for (var i = 0; i < cbs.length; ++i) cbs[i](instance); | |
|
1967 | if (updated && options.onUpdate) options.onUpdate(instance); | |
|
1968 | } | |
|
1969 | var nestedOperation = 0; | |
|
1970 | function operation(f) { | |
|
1971 | return function() { | |
|
1972 | if (!nestedOperation++) startOperation(); | |
|
1973 | try {var result = f.apply(this, arguments);} | |
|
1974 | finally {if (!--nestedOperation) endOperation();} | |
|
1975 | return result; | |
|
1976 | }; | |
|
1977 | } | |
|
1978 | ||
|
1979 | function compoundChange(f) { | |
|
1980 | history.startCompound(); | |
|
1981 | try { return f(); } finally { history.endCompound(); } | |
|
1982 | } | |
|
1983 | ||
|
1984 | for (var ext in extensions) | |
|
1985 | if (extensions.propertyIsEnumerable(ext) && | |
|
1986 | !instance.propertyIsEnumerable(ext)) | |
|
1987 | instance[ext] = extensions[ext]; | |
|
1988 | for (var i = 0; i < initHooks.length; ++i) initHooks[i](instance); | |
|
1989 | return instance; | |
|
1990 | } // (end of function CodeMirror) | |
|
3027 | if (width != null) this.display.wrapper.style.width = interpret(width); | |
|
3028 | if (height != null) this.display.wrapper.style.height = interpret(height); | |
|
3029 | this.refresh(); | |
|
3030 | }, | |
|
3031 | ||
|
3032 | on: function(type, f) {on(this, type, f);}, | |
|
3033 | off: function(type, f) {off(this, type, f);}, | |
|
3034 | ||
|
3035 | operation: function(f){return runInOp(this, f);}, | |
|
3036 | ||
|
3037 | refresh: operation(null, function() { | |
|
3038 | clearCaches(this); | |
|
3039 | updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop); | |
|
3040 | regChange(this); | |
|
3041 | }), | |
|
3042 | ||
|
3043 | swapDoc: operation(null, function(doc) { | |
|
3044 | var old = this.doc; | |
|
3045 | old.cm = null; | |
|
3046 | attachDoc(this, doc); | |
|
3047 | clearCaches(this); | |
|
3048 | resetInput(this, true); | |
|
3049 | updateScrollPos(this, doc.scrollLeft, doc.scrollTop); | |
|
3050 | return old; | |
|
3051 | }), | |
|
3052 | ||
|
3053 | getInputField: function(){return this.display.input;}, | |
|
3054 | getWrapperElement: function(){return this.display.wrapper;}, | |
|
3055 | getScrollerElement: function(){return this.display.scroller;}, | |
|
3056 | getGutterElement: function(){return this.display.gutters;} | |
|
3057 | }; | |
|
3058 | ||
|
3059 | // OPTION DEFAULTS | |
|
3060 | ||
|
3061 | var optionHandlers = CodeMirror.optionHandlers = {}; | |
|
1991 | 3062 | |
|
1992 | 3063 | // The default configuration options. |
|
1993 | CodeMirror.defaults = { | |
|
1994 | value: "", | |
|
1995 | mode: null, | |
|
1996 | theme: "default", | |
|
1997 | indentUnit: 2, | |
|
1998 | indentWithTabs: false, | |
|
1999 | smartIndent: true, | |
|
2000 | tabSize: 4, | |
|
2001 | keyMap: "default", | |
|
2002 | extraKeys: null, | |
|
2003 | electricChars: true, | |
|
2004 | autoClearEmptyLines: false, | |
|
2005 | onKeyEvent: null, | |
|
2006 | onDragEvent: null, | |
|
2007 | lineWrapping: false, | |
|
2008 | lineNumbers: false, | |
|
2009 | gutter: false, | |
|
2010 | fixedGutter: false, | |
|
2011 | firstLineNumber: 1, | |
|
2012 | readOnly: false, | |
|
2013 | dragDrop: true, | |
|
2014 | onChange: null, | |
|
2015 | onCursorActivity: null, | |
|
2016 | onViewportChange: null, | |
|
2017 | onGutterClick: null, | |
|
2018 | onUpdate: null, | |
|
2019 | onFocus: null, onBlur: null, onScroll: null, | |
|
2020 | matchBrackets: false, | |
|
2021 | cursorBlinkRate: 530, | |
|
2022 | workTime: 100, | |
|
2023 | workDelay: 200, | |
|
2024 | pollInterval: 100, | |
|
2025 | undoDepth: 40, | |
|
2026 | tabindex: null, | |
|
2027 | autofocus: null, | |
|
2028 | lineNumberFormatter: function(integer) { return integer; } | |
|
2029 | }; | |
|
2030 | ||
|
2031 | var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); | |
|
2032 | var mac = ios || /Mac/.test(navigator.platform); | |
|
2033 | var win = /Win/.test(navigator.platform); | |
|
3064 | var defaults = CodeMirror.defaults = {}; | |
|
3065 | ||
|
3066 | function option(name, deflt, handle, notOnInit) { | |
|
3067 | CodeMirror.defaults[name] = deflt; | |
|
3068 | if (handle) optionHandlers[name] = | |
|
3069 | notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; | |
|
3070 | } | |
|
3071 | ||
|
3072 | var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; | |
|
3073 | ||
|
3074 | // These two are, on init, called from the constructor because they | |
|
3075 | // have to be initialized before the editor can start at all. | |
|
3076 | option("value", "", function(cm, val) { | |
|
3077 | cm.setValue(val); | |
|
3078 | }, true); | |
|
3079 | option("mode", null, function(cm, val) { | |
|
3080 | cm.doc.modeOption = val; | |
|
3081 | loadMode(cm); | |
|
3082 | }, true); | |
|
3083 | ||
|
3084 | option("indentUnit", 2, loadMode, true); | |
|
3085 | option("indentWithTabs", false); | |
|
3086 | option("smartIndent", true); | |
|
3087 | option("tabSize", 4, function(cm) { | |
|
3088 | loadMode(cm); | |
|
3089 | clearCaches(cm); | |
|
3090 | regChange(cm); | |
|
3091 | }, true); | |
|
3092 | option("electricChars", true); | |
|
3093 | option("rtlMoveVisually", !windows); | |
|
3094 | ||
|
3095 | option("theme", "default", function(cm) { | |
|
3096 | themeChanged(cm); | |
|
3097 | guttersChanged(cm); | |
|
3098 | }, true); | |
|
3099 | option("keyMap", "default", keyMapChanged); | |
|
3100 | option("extraKeys", null); | |
|
3101 | ||
|
3102 | option("onKeyEvent", null); | |
|
3103 | option("onDragEvent", null); | |
|
3104 | ||
|
3105 | option("lineWrapping", false, wrappingChanged, true); | |
|
3106 | option("gutters", [], function(cm) { | |
|
3107 | setGuttersForLineNumbers(cm.options); | |
|
3108 | guttersChanged(cm); | |
|
3109 | }, true); | |
|
3110 | option("fixedGutter", true, function(cm, val) { | |
|
3111 | cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; | |
|
3112 | cm.refresh(); | |
|
3113 | }, true); | |
|
3114 | option("lineNumbers", false, function(cm) { | |
|
3115 | setGuttersForLineNumbers(cm.options); | |
|
3116 | guttersChanged(cm); | |
|
3117 | }, true); | |
|
3118 | option("firstLineNumber", 1, guttersChanged, true); | |
|
3119 | option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); | |
|
3120 | option("showCursorWhenSelecting", false, updateSelection, true); | |
|
3121 | ||
|
3122 | option("readOnly", false, function(cm, val) { | |
|
3123 | if (val == "nocursor") {onBlur(cm); cm.display.input.blur();} | |
|
3124 | else if (!val) resetInput(cm, true); | |
|
3125 | }); | |
|
3126 | option("dragDrop", true); | |
|
3127 | ||
|
3128 | option("cursorBlinkRate", 530); | |
|
3129 | option("cursorHeight", 1); | |
|
3130 | option("workTime", 100); | |
|
3131 | option("workDelay", 100); | |
|
3132 | option("flattenSpans", true); | |
|
3133 | option("pollInterval", 100); | |
|
3134 | option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;}); | |
|
3135 | option("historyEventDelay", 500); | |
|
3136 | option("viewportMargin", 10, function(cm){cm.refresh();}, true); | |
|
3137 | option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true); | |
|
3138 | option("moveInputWithCursor", true, function(cm, val) { | |
|
3139 | if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0; | |
|
3140 | }); | |
|
3141 | ||
|
3142 | option("tabindex", null, function(cm, val) { | |
|
3143 | cm.display.input.tabIndex = val || ""; | |
|
3144 | }); | |
|
3145 | option("autofocus", null); | |
|
3146 | ||
|
3147 | // MODE DEFINITION AND QUERYING | |
|
2034 | 3148 | |
|
2035 | 3149 | // Known modes, by name and by MIME |
|
2036 | 3150 | var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; |
|
3151 | ||
|
2037 | 3152 | CodeMirror.defineMode = function(name, mode) { |
|
2038 | 3153 | if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; |
|
2039 | 3154 | if (arguments.length > 2) { |
|
2040 | 3155 | mode.dependencies = []; |
|
2041 | 3156 | for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); |
|
2042 | 3157 | } |
|
2043 | 3158 | modes[name] = mode; |
|
2044 | 3159 | }; |
|
3160 | ||
|
2045 | 3161 | CodeMirror.defineMIME = function(mime, spec) { |
|
2046 | 3162 | mimeModes[mime] = spec; |
|
2047 | 3163 | }; |
|
3164 | ||
|
2048 | 3165 | CodeMirror.resolveMode = function(spec) { |
|
2049 | 3166 | if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) |
|
2050 | 3167 | spec = mimeModes[spec]; |
|
2051 | 3168 | else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) |
|
2052 | 3169 | return CodeMirror.resolveMode("application/xml"); |
|
2053 | 3170 | if (typeof spec == "string") return {name: spec}; |
|
2054 | 3171 | else return spec || {name: "null"}; |
|
2055 | 3172 | }; |
|
3173 | ||
|
2056 | 3174 | CodeMirror.getMode = function(options, spec) { |
|
2057 |
|
|
|
3175 | spec = CodeMirror.resolveMode(spec); | |
|
2058 | 3176 | var mfactory = modes[spec.name]; |
|
2059 | 3177 | if (!mfactory) return CodeMirror.getMode(options, "text/plain"); |
|
2060 | 3178 | var modeObj = mfactory(options, spec); |
|
2061 | 3179 | if (modeExtensions.hasOwnProperty(spec.name)) { |
|
2062 | 3180 | var exts = modeExtensions[spec.name]; |
|
2063 | for (var prop in exts) if (exts.hasOwnProperty(prop)) modeObj[prop] = exts[prop]; | |
|
3181 | for (var prop in exts) { | |
|
3182 | if (!exts.hasOwnProperty(prop)) continue; | |
|
3183 | if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; | |
|
3184 | modeObj[prop] = exts[prop]; | |
|
3185 | } | |
|
2064 | 3186 | } |
|
2065 | 3187 | modeObj.name = spec.name; |
|
2066 | 3188 | return modeObj; |
|
2067 | 3189 | }; |
|
2068 | CodeMirror.listModes = function() { | |
|
2069 | var list = []; | |
|
2070 | for (var m in modes) | |
|
2071 | if (modes.propertyIsEnumerable(m)) list.push(m); | |
|
2072 | return list; | |
|
2073 | }; | |
|
2074 | CodeMirror.listMIMEs = function() { | |
|
2075 | var list = []; | |
|
2076 | for (var m in mimeModes) | |
|
2077 | if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]}); | |
|
2078 | return list; | |
|
2079 | }; | |
|
2080 | ||
|
2081 | var extensions = CodeMirror.extensions = {}; | |
|
2082 | CodeMirror.defineExtension = function(name, func) { | |
|
2083 | extensions[name] = func; | |
|
2084 | }; | |
|
2085 | ||
|
2086 | var initHooks = []; | |
|
2087 | CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; | |
|
3190 | ||
|
3191 | CodeMirror.defineMode("null", function() { | |
|
3192 | return {token: function(stream) {stream.skipToEnd();}}; | |
|
3193 | }); | |
|
3194 | CodeMirror.defineMIME("text/plain", "null"); | |
|
2088 | 3195 | |
|
2089 | 3196 | var modeExtensions = CodeMirror.modeExtensions = {}; |
|
2090 | 3197 | CodeMirror.extendMode = function(mode, properties) { |
|
2091 | 3198 | var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); |
|
2092 | for (var prop in properties) if (properties.hasOwnProperty(prop)) | |
|
2093 | exts[prop] = properties[prop]; | |
|
3199 | copyObj(properties, exts); | |
|
2094 | 3200 | }; |
|
2095 | 3201 | |
|
2096 | var commands = CodeMirror.commands = { | |
|
2097 | selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, | |
|
2098 | killLine: function(cm) { | |
|
2099 | var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); | |
|
2100 | if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0}); | |
|
2101 | else cm.replaceRange("", from, sel ? to : {line: from.line}); | |
|
2102 | }, | |
|
2103 | deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});}, | |
|
2104 | undo: function(cm) {cm.undo();}, | |
|
2105 | redo: function(cm) {cm.redo();}, | |
|
2106 | goDocStart: function(cm) {cm.setCursor(0, 0, true);}, | |
|
2107 | goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);}, | |
|
2108 | goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);}, | |
|
2109 | goLineStartSmart: function(cm) { | |
|
2110 | var cur = cm.getCursor(); | |
|
2111 | var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/)); | |
|
2112 | cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true); | |
|
2113 | }, | |
|
2114 | goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);}, | |
|
2115 | goLineUp: function(cm) {cm.moveV(-1, "line");}, | |
|
2116 | goLineDown: function(cm) {cm.moveV(1, "line");}, | |
|
2117 | goPageUp: function(cm) {cm.moveV(-1, "page");}, | |
|
2118 | goPageDown: function(cm) {cm.moveV(1, "page");}, | |
|
2119 | goCharLeft: function(cm) {cm.moveH(-1, "char");}, | |
|
2120 | goCharRight: function(cm) {cm.moveH(1, "char");}, | |
|
2121 | goColumnLeft: function(cm) {cm.moveH(-1, "column");}, | |
|
2122 | goColumnRight: function(cm) {cm.moveH(1, "column");}, | |
|
2123 | goWordLeft: function(cm) {cm.moveH(-1, "word");}, | |
|
2124 | goWordRight: function(cm) {cm.moveH(1, "word");}, | |
|
2125 | delCharLeft: function(cm) {cm.deleteH(-1, "char");}, | |
|
2126 | delCharRight: function(cm) {cm.deleteH(1, "char");}, | |
|
2127 | delWordLeft: function(cm) {cm.deleteH(-1, "word");}, | |
|
2128 | delWordRight: function(cm) {cm.deleteH(1, "word");}, | |
|
2129 | indentAuto: function(cm) {cm.indentSelection("smart");}, | |
|
2130 | indentMore: function(cm) {cm.indentSelection("add");}, | |
|
2131 | indentLess: function(cm) {cm.indentSelection("subtract");}, | |
|
2132 | insertTab: function(cm) {cm.replaceSelection("\t", "end");}, | |
|
2133 | defaultTab: function(cm) { | |
|
2134 | if (cm.somethingSelected()) cm.indentSelection("add"); | |
|
2135 | else cm.replaceSelection("\t", "end"); | |
|
2136 | }, | |
|
2137 | transposeChars: function(cm) { | |
|
2138 | var cur = cm.getCursor(), line = cm.getLine(cur.line); | |
|
2139 | if (cur.ch > 0 && cur.ch < line.length - 1) | |
|
2140 | cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), | |
|
2141 | {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1}); | |
|
2142 | }, | |
|
2143 | newlineAndIndent: function(cm) { | |
|
2144 | cm.replaceSelection("\n", "end"); | |
|
2145 | cm.indentLine(cm.getCursor().line); | |
|
2146 | }, | |
|
2147 | toggleOverwrite: function(cm) {cm.toggleOverwrite();} | |
|
2148 | }; | |
|
2149 | ||
|
2150 | var keyMap = CodeMirror.keyMap = {}; | |
|
2151 | keyMap.basic = { | |
|
2152 | "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", | |
|
2153 | "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", | |
|
2154 | "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto", | |
|
2155 | "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" | |
|
2156 | }; | |
|
2157 | // Note that the save and find-related commands aren't defined by | |
|
2158 | // default. Unknown commands are simply ignored. | |
|
2159 | keyMap.pcDefault = { | |
|
2160 | "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", | |
|
2161 | "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", | |
|
2162 | "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", | |
|
2163 | "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find", | |
|
2164 | "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", | |
|
2165 | "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", | |
|
2166 | fallthrough: "basic" | |
|
2167 | }; | |
|
2168 | keyMap.macDefault = { | |
|
2169 | "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", | |
|
2170 | "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft", | |
|
2171 | "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft", | |
|
2172 | "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find", | |
|
2173 | "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", | |
|
2174 | "Cmd-[": "indentLess", "Cmd-]": "indentMore", | |
|
2175 | fallthrough: ["basic", "emacsy"] | |
|
2176 | }; | |
|
2177 | keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; | |
|
2178 | keyMap.emacsy = { | |
|
2179 | "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", | |
|
2180 | "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", | |
|
2181 | "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft", | |
|
2182 | "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" | |
|
3202 | // EXTENSIONS | |
|
3203 | ||
|
3204 | CodeMirror.defineExtension = function(name, func) { | |
|
3205 | CodeMirror.prototype[name] = func; | |
|
2183 | 3206 | }; |
|
2184 | ||
|
2185 | function getKeyMap(val) { | |
|
2186 | if (typeof val == "string") return keyMap[val]; | |
|
2187 | else return val; | |
|
2188 | } | |
|
2189 | function lookupKey(name, extraMap, map, handle, stop) { | |
|
2190 | function lookup(map) { | |
|
2191 | map = getKeyMap(map); | |
|
2192 | var found = map[name]; | |
|
2193 | if (found === false) { | |
|
2194 | if (stop) stop(); | |
|
2195 | return true; | |
|
2196 | } | |
|
2197 | if (found != null && handle(found)) return true; | |
|
2198 | if (map.nofallthrough) { | |
|
2199 | if (stop) stop(); | |
|
2200 | return true; | |
|
2201 | } | |
|
2202 | var fallthrough = map.fallthrough; | |
|
2203 | if (fallthrough == null) return false; | |
|
2204 | if (Object.prototype.toString.call(fallthrough) != "[object Array]") | |
|
2205 | return lookup(fallthrough); | |
|
2206 | for (var i = 0, e = fallthrough.length; i < e; ++i) { | |
|
2207 | if (lookup(fallthrough[i])) return true; | |
|
2208 | } | |
|
2209 | return false; | |
|
2210 | } | |
|
2211 | if (extraMap && lookup(extraMap)) return true; | |
|
2212 | return lookup(map); | |
|
2213 | } | |
|
2214 | function isModifierKey(event) { | |
|
2215 | var name = keyNames[e_prop(event, "keyCode")]; | |
|
2216 | return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; | |
|
2217 | } | |
|
2218 | CodeMirror.isModifierKey = isModifierKey; | |
|
2219 | ||
|
2220 | CodeMirror.fromTextArea = function(textarea, options) { | |
|
2221 | if (!options) options = {}; | |
|
2222 | options.value = textarea.value; | |
|
2223 | if (!options.tabindex && textarea.tabindex) | |
|
2224 | options.tabindex = textarea.tabindex; | |
|
2225 | // Set autofocus to true if this textarea is focused, or if it has | |
|
2226 | // autofocus and no other element is focused. | |
|
2227 | if (options.autofocus == null) { | |
|
2228 | var hasFocus = document.body; | |
|
2229 | // doc.activeElement occasionally throws on IE | |
|
2230 | try { hasFocus = document.activeElement; } catch(e) {} | |
|
2231 | options.autofocus = hasFocus == textarea || | |
|
2232 | textarea.getAttribute("autofocus") != null && hasFocus == document.body; | |
|
2233 | } | |
|
2234 | ||
|
2235 | function save() {textarea.value = instance.getValue();} | |
|
2236 | if (textarea.form) { | |
|
2237 | // Deplorable hack to make the submit method do the right thing. | |
|
2238 | var rmSubmit = connect(textarea.form, "submit", save, true); | |
|
2239 | var realSubmit = textarea.form.submit; | |
|
2240 | textarea.form.submit = function wrappedSubmit() { | |
|
2241 | save(); | |
|
2242 | textarea.form.submit = realSubmit; | |
|
2243 | textarea.form.submit(); | |
|
2244 | textarea.form.submit = wrappedSubmit; | |
|
2245 | }; | |
|
2246 | } | |
|
2247 | ||
|
2248 | textarea.style.display = "none"; | |
|
2249 | var instance = CodeMirror(function(node) { | |
|
2250 | textarea.parentNode.insertBefore(node, textarea.nextSibling); | |
|
2251 | }, options); | |
|
2252 | instance.save = save; | |
|
2253 | instance.getTextArea = function() { return textarea; }; | |
|
2254 | instance.toTextArea = function() { | |
|
2255 | save(); | |
|
2256 | textarea.parentNode.removeChild(instance.getWrapperElement()); | |
|
2257 | textarea.style.display = ""; | |
|
2258 | if (textarea.form) { | |
|
2259 | rmSubmit(); | |
|
2260 | if (typeof textarea.form.submit == "function") | |
|
2261 | textarea.form.submit = realSubmit; | |
|
2262 | } | |
|
2263 | }; | |
|
2264 | return instance; | |
|
3207 | CodeMirror.defineDocExtension = function(name, func) { | |
|
3208 | Doc.prototype[name] = func; | |
|
2265 | 3209 | }; |
|
2266 | ||
|
2267 | var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); | |
|
2268 | var ie = /MSIE \d/.test(navigator.userAgent); | |
|
2269 | var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); | |
|
2270 | var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); | |
|
2271 | var quirksMode = ie && document.documentMode == 5; | |
|
2272 | var webkit = /WebKit\//.test(navigator.userAgent); | |
|
2273 | var chrome = /Chrome\//.test(navigator.userAgent); | |
|
2274 | var opera = /Opera\//.test(navigator.userAgent); | |
|
2275 | var safari = /Apple Computer/.test(navigator.vendor); | |
|
2276 | var khtml = /KHTML\//.test(navigator.userAgent); | |
|
2277 | var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent); | |
|
3210 | CodeMirror.defineOption = option; | |
|
3211 | ||
|
3212 | var initHooks = []; | |
|
3213 | CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; | |
|
3214 | ||
|
3215 | // MODE STATE HANDLING | |
|
2278 | 3216 | |
|
2279 | 3217 | // Utility functions for working with state. Exported because modes |
|
2280 | 3218 | // sometimes need to do this. |
|
2281 | 3219 | function copyState(mode, state) { |
|
2282 | 3220 | if (state === true) return state; |
|
2283 | 3221 | if (mode.copyState) return mode.copyState(state); |
|
2284 | 3222 | var nstate = {}; |
|
2285 | 3223 | for (var n in state) { |
|
2286 | 3224 | var val = state[n]; |
|
2287 | 3225 | if (val instanceof Array) val = val.concat([]); |
|
2288 | 3226 | nstate[n] = val; |
|
2289 | 3227 | } |
|
2290 | 3228 | return nstate; |
|
2291 | 3229 | } |
|
2292 | 3230 | CodeMirror.copyState = copyState; |
|
3231 | ||
|
2293 | 3232 | function startState(mode, a1, a2) { |
|
2294 | 3233 | return mode.startState ? mode.startState(a1, a2) : true; |
|
2295 | 3234 | } |
|
2296 | 3235 | CodeMirror.startState = startState; |
|
3236 | ||
|
2297 | 3237 | CodeMirror.innerMode = function(mode, state) { |
|
2298 | 3238 | while (mode.innerMode) { |
|
2299 | 3239 | var info = mode.innerMode(state); |
|
2300 | 3240 | state = info.state; |
|
2301 | 3241 | mode = info.mode; |
|
2302 | 3242 | } |
|
2303 | 3243 | return info || {mode: mode, state: state}; |
|
2304 | 3244 | }; |
|
2305 | 3245 | |
|
3246 | // STANDARD COMMANDS | |
|
3247 | ||
|
3248 | var commands = CodeMirror.commands = { | |
|
3249 | selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));}, | |
|
3250 | killLine: function(cm) { | |
|
3251 | var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); | |
|
3252 | if (!sel && cm.getLine(from.line).length == from.ch) | |
|
3253 | cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete"); | |
|
3254 | else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete"); | |
|
3255 | }, | |
|
3256 | deleteLine: function(cm) { | |
|
3257 | var l = cm.getCursor().line; | |
|
3258 | cm.replaceRange("", Pos(l, 0), Pos(l), "+delete"); | |
|
3259 | }, | |
|
3260 | undo: function(cm) {cm.undo();}, | |
|
3261 | redo: function(cm) {cm.redo();}, | |
|
3262 | goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, | |
|
3263 | goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, | |
|
3264 | goLineStart: function(cm) { | |
|
3265 | cm.extendSelection(lineStart(cm, cm.getCursor().line)); | |
|
3266 | }, | |
|
3267 | goLineStartSmart: function(cm) { | |
|
3268 | var cur = cm.getCursor(), start = lineStart(cm, cur.line); | |
|
3269 | var line = cm.getLineHandle(start.line); | |
|
3270 | var order = getOrder(line); | |
|
3271 | if (!order || order[0].level == 0) { | |
|
3272 | var firstNonWS = Math.max(0, line.text.search(/\S/)); | |
|
3273 | var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch; | |
|
3274 | cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS)); | |
|
3275 | } else cm.extendSelection(start); | |
|
3276 | }, | |
|
3277 | goLineEnd: function(cm) { | |
|
3278 | cm.extendSelection(lineEnd(cm, cm.getCursor().line)); | |
|
3279 | }, | |
|
3280 | goLineRight: function(cm) { | |
|
3281 | var top = cm.charCoords(cm.getCursor(), "div").top + 5; | |
|
3282 | cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")); | |
|
3283 | }, | |
|
3284 | goLineLeft: function(cm) { | |
|
3285 | var top = cm.charCoords(cm.getCursor(), "div").top + 5; | |
|
3286 | cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div")); | |
|
3287 | }, | |
|
3288 | goLineUp: function(cm) {cm.moveV(-1, "line");}, | |
|
3289 | goLineDown: function(cm) {cm.moveV(1, "line");}, | |
|
3290 | goPageUp: function(cm) {cm.moveV(-1, "page");}, | |
|
3291 | goPageDown: function(cm) {cm.moveV(1, "page");}, | |
|
3292 | goCharLeft: function(cm) {cm.moveH(-1, "char");}, | |
|
3293 | goCharRight: function(cm) {cm.moveH(1, "char");}, | |
|
3294 | goColumnLeft: function(cm) {cm.moveH(-1, "column");}, | |
|
3295 | goColumnRight: function(cm) {cm.moveH(1, "column");}, | |
|
3296 | goWordLeft: function(cm) {cm.moveH(-1, "word");}, | |
|
3297 | goGroupRight: function(cm) {cm.moveH(1, "group");}, | |
|
3298 | goGroupLeft: function(cm) {cm.moveH(-1, "group");}, | |
|
3299 | goWordRight: function(cm) {cm.moveH(1, "word");}, | |
|
3300 | delCharBefore: function(cm) {cm.deleteH(-1, "char");}, | |
|
3301 | delCharAfter: function(cm) {cm.deleteH(1, "char");}, | |
|
3302 | delWordBefore: function(cm) {cm.deleteH(-1, "word");}, | |
|
3303 | delWordAfter: function(cm) {cm.deleteH(1, "word");}, | |
|
3304 | delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, | |
|
3305 | delGroupAfter: function(cm) {cm.deleteH(1, "group");}, | |
|
3306 | indentAuto: function(cm) {cm.indentSelection("smart");}, | |
|
3307 | indentMore: function(cm) {cm.indentSelection("add");}, | |
|
3308 | indentLess: function(cm) {cm.indentSelection("subtract");}, | |
|
3309 | insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");}, | |
|
3310 | defaultTab: function(cm) { | |
|
3311 | if (cm.somethingSelected()) cm.indentSelection("add"); | |
|
3312 | else cm.replaceSelection("\t", "end", "+input"); | |
|
3313 | }, | |
|
3314 | transposeChars: function(cm) { | |
|
3315 | var cur = cm.getCursor(), line = cm.getLine(cur.line); | |
|
3316 | if (cur.ch > 0 && cur.ch < line.length - 1) | |
|
3317 | cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), | |
|
3318 | Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1)); | |
|
3319 | }, | |
|
3320 | newlineAndIndent: function(cm) { | |
|
3321 | operation(cm, function() { | |
|
3322 | cm.replaceSelection("\n", "end", "+input"); | |
|
3323 | cm.indentLine(cm.getCursor().line, null, true); | |
|
3324 | })(); | |
|
3325 | }, | |
|
3326 | toggleOverwrite: function(cm) {cm.toggleOverwrite();} | |
|
3327 | }; | |
|
3328 | ||
|
3329 | // STANDARD KEYMAPS | |
|
3330 | ||
|
3331 | var keyMap = CodeMirror.keyMap = {}; | |
|
3332 | keyMap.basic = { | |
|
3333 | "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", | |
|
3334 | "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", | |
|
3335 | "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto", | |
|
3336 | "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" | |
|
3337 | }; | |
|
3338 | // Note that the save and find-related commands aren't defined by | |
|
3339 | // default. Unknown commands are simply ignored. | |
|
3340 | keyMap.pcDefault = { | |
|
3341 | "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", | |
|
3342 | "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", | |
|
3343 | "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", | |
|
3344 | "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", | |
|
3345 | "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", | |
|
3346 | "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", | |
|
3347 | fallthrough: "basic" | |
|
3348 | }; | |
|
3349 | keyMap.macDefault = { | |
|
3350 | "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", | |
|
3351 | "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", | |
|
3352 | "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore", | |
|
3353 | "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", | |
|
3354 | "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", | |
|
3355 | "Cmd-[": "indentLess", "Cmd-]": "indentMore", | |
|
3356 | fallthrough: ["basic", "emacsy"] | |
|
3357 | }; | |
|
3358 | keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; | |
|
3359 | keyMap.emacsy = { | |
|
3360 | "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", | |
|
3361 | "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", | |
|
3362 | "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", | |
|
3363 | "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" | |
|
3364 | }; | |
|
3365 | ||
|
3366 | // KEYMAP DISPATCH | |
|
3367 | ||
|
3368 | function getKeyMap(val) { | |
|
3369 | if (typeof val == "string") return keyMap[val]; | |
|
3370 | else return val; | |
|
3371 | } | |
|
3372 | ||
|
3373 | function lookupKey(name, maps, handle) { | |
|
3374 | function lookup(map) { | |
|
3375 | map = getKeyMap(map); | |
|
3376 | var found = map[name]; | |
|
3377 | if (found === false) return "stop"; | |
|
3378 | if (found != null && handle(found)) return true; | |
|
3379 | if (map.nofallthrough) return "stop"; | |
|
3380 | ||
|
3381 | var fallthrough = map.fallthrough; | |
|
3382 | if (fallthrough == null) return false; | |
|
3383 | if (Object.prototype.toString.call(fallthrough) != "[object Array]") | |
|
3384 | return lookup(fallthrough); | |
|
3385 | for (var i = 0, e = fallthrough.length; i < e; ++i) { | |
|
3386 | var done = lookup(fallthrough[i]); | |
|
3387 | if (done) return done; | |
|
3388 | } | |
|
3389 | return false; | |
|
3390 | } | |
|
3391 | ||
|
3392 | for (var i = 0; i < maps.length; ++i) { | |
|
3393 | var done = lookup(maps[i]); | |
|
3394 | if (done) return done; | |
|
3395 | } | |
|
3396 | } | |
|
3397 | function isModifierKey(event) { | |
|
3398 | var name = keyNames[event.keyCode]; | |
|
3399 | return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; | |
|
3400 | } | |
|
3401 | function keyName(event, noShift) { | |
|
3402 | if (opera && event.keyCode == 34 && event["char"]) return false; | |
|
3403 | var name = keyNames[event.keyCode]; | |
|
3404 | if (name == null || event.altGraphKey) return false; | |
|
3405 | if (event.altKey) name = "Alt-" + name; | |
|
3406 | if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name; | |
|
3407 | if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name; | |
|
3408 | if (!noShift && event.shiftKey) name = "Shift-" + name; | |
|
3409 | return name; | |
|
3410 | } | |
|
3411 | CodeMirror.lookupKey = lookupKey; | |
|
3412 | CodeMirror.isModifierKey = isModifierKey; | |
|
3413 | CodeMirror.keyName = keyName; | |
|
3414 | ||
|
3415 | // FROMTEXTAREA | |
|
3416 | ||
|
3417 | CodeMirror.fromTextArea = function(textarea, options) { | |
|
3418 | if (!options) options = {}; | |
|
3419 | options.value = textarea.value; | |
|
3420 | if (!options.tabindex && textarea.tabindex) | |
|
3421 | options.tabindex = textarea.tabindex; | |
|
3422 | if (!options.placeholder && textarea.placeholder) | |
|
3423 | options.placeholder = textarea.placeholder; | |
|
3424 | // Set autofocus to true if this textarea is focused, or if it has | |
|
3425 | // autofocus and no other element is focused. | |
|
3426 | if (options.autofocus == null) { | |
|
3427 | var hasFocus = document.body; | |
|
3428 | // doc.activeElement occasionally throws on IE | |
|
3429 | try { hasFocus = document.activeElement; } catch(e) {} | |
|
3430 | options.autofocus = hasFocus == textarea || | |
|
3431 | textarea.getAttribute("autofocus") != null && hasFocus == document.body; | |
|
3432 | } | |
|
3433 | ||
|
3434 | function save() {textarea.value = cm.getValue();} | |
|
3435 | if (textarea.form) { | |
|
3436 | on(textarea.form, "submit", save); | |
|
3437 | // Deplorable hack to make the submit method do the right thing. | |
|
3438 | if (!options.leaveSubmitMethodAlone) { | |
|
3439 | var form = textarea.form, realSubmit = form.submit; | |
|
3440 | try { | |
|
3441 | var wrappedSubmit = form.submit = function() { | |
|
3442 | save(); | |
|
3443 | form.submit = realSubmit; | |
|
3444 | form.submit(); | |
|
3445 | form.submit = wrappedSubmit; | |
|
3446 | }; | |
|
3447 | } catch(e) {} | |
|
3448 | } | |
|
3449 | } | |
|
3450 | ||
|
3451 | textarea.style.display = "none"; | |
|
3452 | var cm = CodeMirror(function(node) { | |
|
3453 | textarea.parentNode.insertBefore(node, textarea.nextSibling); | |
|
3454 | }, options); | |
|
3455 | cm.save = save; | |
|
3456 | cm.getTextArea = function() { return textarea; }; | |
|
3457 | cm.toTextArea = function() { | |
|
3458 | save(); | |
|
3459 | textarea.parentNode.removeChild(cm.getWrapperElement()); | |
|
3460 | textarea.style.display = ""; | |
|
3461 | if (textarea.form) { | |
|
3462 | off(textarea.form, "submit", save); | |
|
3463 | if (typeof textarea.form.submit == "function") | |
|
3464 | textarea.form.submit = realSubmit; | |
|
3465 | } | |
|
3466 | }; | |
|
3467 | return cm; | |
|
3468 | }; | |
|
3469 | ||
|
3470 | // STRING STREAM | |
|
3471 | ||
|
3472 | // Fed to the mode parsers, provides helper functions to make | |
|
3473 | // parsers more succinct. | |
|
3474 | ||
|
2306 | 3475 | // The character stream used by a mode's parser. |
|
2307 | 3476 | function StringStream(string, tabSize) { |
|
2308 | 3477 | this.pos = this.start = 0; |
|
2309 | 3478 | this.string = string; |
|
2310 | 3479 | this.tabSize = tabSize || 8; |
|
2311 | } | |
|
3480 | this.lastColumnPos = this.lastColumnValue = 0; | |
|
3481 | } | |
|
3482 | ||
|
2312 | 3483 | StringStream.prototype = { |
|
2313 | 3484 | eol: function() {return this.pos >= this.string.length;}, |
|
2314 | 3485 | sol: function() {return this.pos == 0;}, |
|
2315 | 3486 | peek: function() {return this.string.charAt(this.pos) || undefined;}, |
|
2316 | 3487 | next: function() { |
|
2317 | 3488 | if (this.pos < this.string.length) |
|
2318 | 3489 | return this.string.charAt(this.pos++); |
|
2319 | 3490 | }, |
|
2320 | 3491 | eat: function(match) { |
|
2321 | 3492 | var ch = this.string.charAt(this.pos); |
|
2322 | 3493 | if (typeof match == "string") var ok = ch == match; |
|
2323 | 3494 | else var ok = ch && (match.test ? match.test(ch) : match(ch)); |
|
2324 | 3495 | if (ok) {++this.pos; return ch;} |
|
2325 | 3496 | }, |
|
2326 | 3497 | eatWhile: function(match) { |
|
2327 | 3498 | var start = this.pos; |
|
2328 | 3499 | while (this.eat(match)){} |
|
2329 | 3500 | return this.pos > start; |
|
2330 | 3501 | }, |
|
2331 | 3502 | eatSpace: function() { |
|
2332 | 3503 | var start = this.pos; |
|
2333 | 3504 | while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; |
|
2334 | 3505 | return this.pos > start; |
|
2335 | 3506 | }, |
|
2336 | 3507 | skipToEnd: function() {this.pos = this.string.length;}, |
|
2337 | 3508 | skipTo: function(ch) { |
|
2338 | 3509 | var found = this.string.indexOf(ch, this.pos); |
|
2339 | 3510 | if (found > -1) {this.pos = found; return true;} |
|
2340 | 3511 | }, |
|
2341 | 3512 | backUp: function(n) {this.pos -= n;}, |
|
2342 | column: function() {return countColumn(this.string, this.start, this.tabSize);}, | |
|
3513 | column: function() { | |
|
3514 | if (this.lastColumnPos < this.start) { | |
|
3515 | this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); | |
|
3516 | this.lastColumnPos = this.start; | |
|
3517 | } | |
|
3518 | return this.lastColumnValue; | |
|
3519 | }, | |
|
2343 | 3520 | indentation: function() {return countColumn(this.string, null, this.tabSize);}, |
|
2344 | 3521 | match: function(pattern, consume, caseInsensitive) { |
|
2345 | 3522 | if (typeof pattern == "string") { |
|
2346 | 3523 | var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; |
|
2347 | if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { | |
|
3524 | var substr = this.string.substr(this.pos, pattern.length); | |
|
3525 | if (cased(substr) == cased(pattern)) { | |
|
2348 | 3526 | if (consume !== false) this.pos += pattern.length; |
|
2349 | 3527 | return true; |
|
2350 | 3528 | } |
|
2351 | 3529 | } else { |
|
2352 | 3530 | var match = this.string.slice(this.pos).match(pattern); |
|
2353 | 3531 | if (match && match.index > 0) return null; |
|
2354 | 3532 | if (match && consume !== false) this.pos += match[0].length; |
|
2355 | 3533 | return match; |
|
2356 | 3534 | } |
|
2357 | 3535 | }, |
|
2358 | 3536 | current: function(){return this.string.slice(this.start, this.pos);} |
|
2359 | 3537 | }; |
|
2360 | 3538 | CodeMirror.StringStream = StringStream; |
|
2361 | 3539 | |
|
2362 | function MarkedSpan(from, to, marker) { | |
|
2363 | this.from = from; this.to = to; this.marker = marker; | |
|
2364 | } | |
|
3540 | // TEXTMARKERS | |
|
3541 | ||
|
3542 | function TextMarker(doc, type) { | |
|
3543 | this.lines = []; | |
|
3544 | this.type = type; | |
|
3545 | this.doc = doc; | |
|
3546 | } | |
|
3547 | CodeMirror.TextMarker = TextMarker; | |
|
3548 | ||
|
3549 | TextMarker.prototype.clear = function() { | |
|
3550 | if (this.explicitlyCleared) return; | |
|
3551 | var cm = this.doc.cm, withOp = cm && !cm.curOp; | |
|
3552 | if (withOp) startOperation(cm); | |
|
3553 | var min = null, max = null; | |
|
3554 | for (var i = 0; i < this.lines.length; ++i) { | |
|
3555 | var line = this.lines[i]; | |
|
3556 | var span = getMarkedSpanFor(line.markedSpans, this); | |
|
3557 | if (span.to != null) max = lineNo(line); | |
|
3558 | line.markedSpans = removeMarkedSpan(line.markedSpans, span); | |
|
3559 | if (span.from != null) | |
|
3560 | min = lineNo(line); | |
|
3561 | else if (this.collapsed && !lineIsHidden(this.doc, line) && cm) | |
|
3562 | updateLineHeight(line, textHeight(cm.display)); | |
|
3563 | } | |
|
3564 | if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { | |
|
3565 | var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual); | |
|
3566 | if (len > cm.display.maxLineLength) { | |
|
3567 | cm.display.maxLine = visual; | |
|
3568 | cm.display.maxLineLength = len; | |
|
3569 | cm.display.maxLineChanged = true; | |
|
3570 | } | |
|
3571 | } | |
|
3572 | ||
|
3573 | if (min != null && cm) regChange(cm, min, max + 1); | |
|
3574 | this.lines.length = 0; | |
|
3575 | this.explicitlyCleared = true; | |
|
3576 | if (this.collapsed && this.doc.cantEdit) { | |
|
3577 | this.doc.cantEdit = false; | |
|
3578 | if (cm) reCheckSelection(cm); | |
|
3579 | } | |
|
3580 | if (withOp) endOperation(cm); | |
|
3581 | signalLater(this, "clear"); | |
|
3582 | }; | |
|
3583 | ||
|
3584 | TextMarker.prototype.find = function() { | |
|
3585 | var from, to; | |
|
3586 | for (var i = 0; i < this.lines.length; ++i) { | |
|
3587 | var line = this.lines[i]; | |
|
3588 | var span = getMarkedSpanFor(line.markedSpans, this); | |
|
3589 | if (span.from != null || span.to != null) { | |
|
3590 | var found = lineNo(line); | |
|
3591 | if (span.from != null) from = Pos(found, span.from); | |
|
3592 | if (span.to != null) to = Pos(found, span.to); | |
|
3593 | } | |
|
3594 | } | |
|
3595 | if (this.type == "bookmark") return from; | |
|
3596 | return from && {from: from, to: to}; | |
|
3597 | }; | |
|
3598 | ||
|
3599 | TextMarker.prototype.getOptions = function(copyWidget) { | |
|
3600 | var repl = this.replacedWith; | |
|
3601 | return {className: this.className, | |
|
3602 | inclusiveLeft: this.inclusiveLeft, inclusiveRight: this.inclusiveRight, | |
|
3603 | atomic: this.atomic, | |
|
3604 | collapsed: this.collapsed, | |
|
3605 | replacedWith: copyWidget ? repl && repl.cloneNode(true) : repl, | |
|
3606 | readOnly: this.readOnly, | |
|
3607 | startStyle: this.startStyle, endStyle: this.endStyle}; | |
|
3608 | }; | |
|
3609 | ||
|
3610 | TextMarker.prototype.attachLine = function(line) { | |
|
3611 | if (!this.lines.length && this.doc.cm) { | |
|
3612 | var op = this.doc.cm.curOp; | |
|
3613 | if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) | |
|
3614 | (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); | |
|
3615 | } | |
|
3616 | this.lines.push(line); | |
|
3617 | }; | |
|
3618 | TextMarker.prototype.detachLine = function(line) { | |
|
3619 | this.lines.splice(indexOf(this.lines, line), 1); | |
|
3620 | if (!this.lines.length && this.doc.cm) { | |
|
3621 | var op = this.doc.cm.curOp; | |
|
3622 | (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); | |
|
3623 | } | |
|
3624 | }; | |
|
3625 | ||
|
3626 | function markText(doc, from, to, options, type) { | |
|
3627 | if (options && options.shared) return markTextShared(doc, from, to, options, type); | |
|
3628 | if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); | |
|
3629 | ||
|
3630 | var marker = new TextMarker(doc, type); | |
|
3631 | if (type == "range" && !posLess(from, to)) return marker; | |
|
3632 | if (options) copyObj(options, marker); | |
|
3633 | if (marker.replacedWith) { | |
|
3634 | marker.collapsed = true; | |
|
3635 | marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget"); | |
|
3636 | } | |
|
3637 | if (marker.collapsed) sawCollapsedSpans = true; | |
|
3638 | ||
|
3639 | if (marker.addToHistory) | |
|
3640 | addToHistory(doc, {from: from, to: to, origin: "markText"}, | |
|
3641 | {head: doc.sel.head, anchor: doc.sel.anchor}, NaN); | |
|
3642 | ||
|
3643 | var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine; | |
|
3644 | doc.iter(curLine, to.line + 1, function(line) { | |
|
3645 | if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine) | |
|
3646 | updateMaxLine = true; | |
|
3647 | var span = {from: null, to: null, marker: marker}; | |
|
3648 | size += line.text.length; | |
|
3649 | if (curLine == from.line) {span.from = from.ch; size -= from.ch;} | |
|
3650 | if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;} | |
|
3651 | if (marker.collapsed) { | |
|
3652 | if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch); | |
|
3653 | if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch); | |
|
3654 | else updateLineHeight(line, 0); | |
|
3655 | } | |
|
3656 | addMarkedSpan(line, span); | |
|
3657 | ++curLine; | |
|
3658 | }); | |
|
3659 | if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { | |
|
3660 | if (lineIsHidden(doc, line)) updateLineHeight(line, 0); | |
|
3661 | }); | |
|
3662 | ||
|
3663 | if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); | |
|
3664 | ||
|
3665 | if (marker.readOnly) { | |
|
3666 | sawReadOnlySpans = true; | |
|
3667 | if (doc.history.done.length || doc.history.undone.length) | |
|
3668 | doc.clearHistory(); | |
|
3669 | } | |
|
3670 | if (marker.collapsed) { | |
|
3671 | if (collapsedAtStart != collapsedAtEnd) | |
|
3672 | throw new Error("Inserting collapsed marker overlapping an existing one"); | |
|
3673 | marker.size = size; | |
|
3674 | marker.atomic = true; | |
|
3675 | } | |
|
3676 | if (cm) { | |
|
3677 | if (updateMaxLine) cm.curOp.updateMaxLine = true; | |
|
3678 | if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed) | |
|
3679 | regChange(cm, from.line, to.line + 1); | |
|
3680 | if (marker.atomic) reCheckSelection(cm); | |
|
3681 | } | |
|
3682 | return marker; | |
|
3683 | } | |
|
3684 | ||
|
3685 | // SHARED TEXTMARKERS | |
|
3686 | ||
|
3687 | function SharedTextMarker(markers, primary) { | |
|
3688 | this.markers = markers; | |
|
3689 | this.primary = primary; | |
|
3690 | for (var i = 0, me = this; i < markers.length; ++i) { | |
|
3691 | markers[i].parent = this; | |
|
3692 | on(markers[i], "clear", function(){me.clear();}); | |
|
3693 | } | |
|
3694 | } | |
|
3695 | CodeMirror.SharedTextMarker = SharedTextMarker; | |
|
3696 | ||
|
3697 | SharedTextMarker.prototype.clear = function() { | |
|
3698 | if (this.explicitlyCleared) return; | |
|
3699 | this.explicitlyCleared = true; | |
|
3700 | for (var i = 0; i < this.markers.length; ++i) | |
|
3701 | this.markers[i].clear(); | |
|
3702 | signalLater(this, "clear"); | |
|
3703 | }; | |
|
3704 | SharedTextMarker.prototype.find = function() { | |
|
3705 | return this.primary.find(); | |
|
3706 | }; | |
|
3707 | SharedTextMarker.prototype.getOptions = function(copyWidget) { | |
|
3708 | var inner = this.primary.getOptions(copyWidget); | |
|
3709 | inner.shared = true; | |
|
3710 | return inner; | |
|
3711 | }; | |
|
3712 | ||
|
3713 | function markTextShared(doc, from, to, options, type) { | |
|
3714 | options = copyObj(options); | |
|
3715 | options.shared = false; | |
|
3716 | var markers = [markText(doc, from, to, options, type)], primary = markers[0]; | |
|
3717 | var widget = options.replacedWith; | |
|
3718 | linkedDocs(doc, function(doc) { | |
|
3719 | if (widget) options.replacedWith = widget.cloneNode(true); | |
|
3720 | markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); | |
|
3721 | for (var i = 0; i < doc.linked.length; ++i) | |
|
3722 | if (doc.linked[i].isParent) return; | |
|
3723 | primary = lst(markers); | |
|
3724 | }); | |
|
3725 | return new SharedTextMarker(markers, primary); | |
|
3726 | } | |
|
3727 | ||
|
3728 | // TEXTMARKER SPANS | |
|
2365 | 3729 | |
|
2366 | 3730 | function getMarkedSpanFor(spans, marker) { |
|
2367 | 3731 | if (spans) for (var i = 0; i < spans.length; ++i) { |
|
2368 | 3732 | var span = spans[i]; |
|
2369 | 3733 | if (span.marker == marker) return span; |
|
2370 | 3734 | } |
|
2371 | 3735 | } |
|
2372 | ||
|
2373 | 3736 | function removeMarkedSpan(spans, span) { |
|
2374 | var r; | |
|
2375 | for (var i = 0; i < spans.length; ++i) | |
|
3737 | for (var r, i = 0; i < spans.length; ++i) | |
|
2376 | 3738 | if (spans[i] != span) (r || (r = [])).push(spans[i]); |
|
2377 | 3739 | return r; |
|
2378 | 3740 | } |
|
2379 | ||
|
2380 | function markedSpansBefore(old, startCh, endCh) { | |
|
3741 | function addMarkedSpan(line, span) { | |
|
3742 | line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; | |
|
3743 | span.marker.attachLine(line); | |
|
3744 | } | |
|
3745 | ||
|
3746 | function markedSpansBefore(old, startCh, isInsert) { | |
|
2381 | 3747 | if (old) for (var i = 0, nw; i < old.length; ++i) { |
|
2382 | 3748 | var span = old[i], marker = span.marker; |
|
2383 | 3749 | var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); |
|
2384 |
if (startsBefore || marker.type == "bookmark" && span.from == startCh && span. |
|
|
3750 | if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) { | |
|
2385 | 3751 | var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); |
|
2386 | 3752 | (nw || (nw = [])).push({from: span.from, |
|
2387 | 3753 | to: endsAfter ? null : span.to, |
|
2388 | 3754 | marker: marker}); |
|
2389 | 3755 | } |
|
2390 | 3756 | } |
|
2391 | 3757 | return nw; |
|
2392 | 3758 | } |
|
2393 | 3759 | |
|
2394 | function markedSpansAfter(old, endCh) { | |
|
3760 | function markedSpansAfter(old, endCh, isInsert) { | |
|
2395 | 3761 | if (old) for (var i = 0, nw; i < old.length; ++i) { |
|
2396 | 3762 | var span = old[i], marker = span.marker; |
|
2397 | 3763 | var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); |
|
2398 | if (endsAfter || marker.type == "bookmark" && span.from == endCh) { | |
|
3764 | if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) { | |
|
2399 | 3765 | var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); |
|
2400 | 3766 | (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, |
|
2401 | 3767 | to: span.to == null ? null : span.to - endCh, |
|
2402 | 3768 | marker: marker}); |
|
2403 | 3769 | } |
|
2404 | 3770 | } |
|
2405 | 3771 | return nw; |
|
2406 | 3772 | } |
|
2407 | 3773 | |
|
2408 | function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) { | |
|
2409 | if (!oldFirst && !oldLast) return newText; | |
|
3774 | function stretchSpansOverChange(doc, change) { | |
|
3775 | var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; | |
|
3776 | var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; | |
|
3777 | if (!oldFirst && !oldLast) return null; | |
|
3778 | ||
|
3779 | var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to); | |
|
2410 | 3780 | // Get the spans that 'stick out' on both sides |
|
2411 | var first = markedSpansBefore(oldFirst, startCh); | |
|
2412 | var last = markedSpansAfter(oldLast, endCh); | |
|
3781 | var first = markedSpansBefore(oldFirst, startCh, isInsert); | |
|
3782 | var last = markedSpansAfter(oldLast, endCh, isInsert); | |
|
2413 | 3783 | |
|
2414 | 3784 | // Next, merge those two ends |
|
2415 |
var sameLine = |
|
|
3785 | var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); | |
|
2416 | 3786 | if (first) { |
|
2417 | 3787 | // Fix up .to properties of first |
|
2418 | 3788 | for (var i = 0; i < first.length; ++i) { |
|
2419 | 3789 | var span = first[i]; |
|
2420 | 3790 | if (span.to == null) { |
|
2421 | 3791 | var found = getMarkedSpanFor(last, span.marker); |
|
2422 | 3792 | if (!found) span.to = startCh; |
|
2423 | 3793 | else if (sameLine) span.to = found.to == null ? null : found.to + offset; |
|
2424 | 3794 | } |
|
2425 | 3795 | } |
|
2426 | 3796 | } |
|
2427 | 3797 | if (last) { |
|
2428 | 3798 | // Fix up .from in last (or move them into first in case of sameLine) |
|
2429 | 3799 | for (var i = 0; i < last.length; ++i) { |
|
2430 | 3800 | var span = last[i]; |
|
2431 | 3801 | if (span.to != null) span.to += offset; |
|
2432 | 3802 | if (span.from == null) { |
|
2433 | 3803 | var found = getMarkedSpanFor(first, span.marker); |
|
2434 | 3804 | if (!found) { |
|
2435 | 3805 | span.from = offset; |
|
2436 | 3806 | if (sameLine) (first || (first = [])).push(span); |
|
2437 | 3807 | } |
|
2438 | 3808 | } else { |
|
2439 | 3809 | span.from += offset; |
|
2440 | 3810 | if (sameLine) (first || (first = [])).push(span); |
|
2441 | 3811 | } |
|
2442 | 3812 | } |
|
2443 | 3813 | } |
|
2444 | 3814 | |
|
2445 |
var newMarkers = [ |
|
|
3815 | var newMarkers = [first]; | |
|
2446 | 3816 | if (!sameLine) { |
|
2447 | 3817 | // Fill gap with whole-line-spans |
|
2448 |
var gap = |
|
|
3818 | var gap = change.text.length - 2, gapMarkers; | |
|
2449 | 3819 | if (gap > 0 && first) |
|
2450 | 3820 | for (var i = 0; i < first.length; ++i) |
|
2451 | 3821 | if (first[i].to == null) |
|
2452 | 3822 | (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); |
|
2453 | 3823 | for (var i = 0; i < gap; ++i) |
|
2454 |
newMarkers.push( |
|
|
2455 |
newMarkers.push( |
|
|
3824 | newMarkers.push(gapMarkers); | |
|
3825 | newMarkers.push(last); | |
|
2456 | 3826 | } |
|
2457 | 3827 | return newMarkers; |
|
2458 | 3828 | } |
|
2459 | 3829 | |
|
2460 | // hl stands for history-line, a data structure that can be either a | |
|
2461 | // string (line without markers) or a {text, markedSpans} object. | |
|
2462 | function hlText(val) { return typeof val == "string" ? val : val.text; } | |
|
2463 | function hlSpans(val) { | |
|
2464 | if (typeof val == "string") return null; | |
|
2465 | var spans = val.markedSpans, out = null; | |
|
2466 |
for (var i = 0; i < |
|
|
2467 | if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } | |
|
2468 | else if (out) out.push(spans[i]); | |
|
2469 | } | |
|
2470 | return !out ? spans : out.length ? out : null; | |
|
2471 | } | |
|
2472 | function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; } | |
|
3830 | function mergeOldSpans(doc, change) { | |
|
3831 | var old = getOldSpans(doc, change); | |
|
3832 | var stretched = stretchSpansOverChange(doc, change); | |
|
3833 | if (!old) return stretched; | |
|
3834 | if (!stretched) return old; | |
|
3835 | ||
|
3836 | for (var i = 0; i < old.length; ++i) { | |
|
3837 | var oldCur = old[i], stretchCur = stretched[i]; | |
|
3838 | if (oldCur && stretchCur) { | |
|
3839 | spans: for (var j = 0; j < stretchCur.length; ++j) { | |
|
3840 | var span = stretchCur[j]; | |
|
3841 | for (var k = 0; k < oldCur.length; ++k) | |
|
3842 | if (oldCur[k].marker == span.marker) continue spans; | |
|
3843 | oldCur.push(span); | |
|
3844 | } | |
|
3845 | } else if (stretchCur) { | |
|
3846 | old[i] = stretchCur; | |
|
3847 | } | |
|
3848 | } | |
|
3849 | return old; | |
|
3850 | } | |
|
3851 | ||
|
3852 | function removeReadOnlyRanges(doc, from, to) { | |
|
3853 | var markers = null; | |
|
3854 | doc.iter(from.line, to.line + 1, function(line) { | |
|
3855 | if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { | |
|
3856 | var mark = line.markedSpans[i].marker; | |
|
3857 | if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) | |
|
3858 | (markers || (markers = [])).push(mark); | |
|
3859 | } | |
|
3860 | }); | |
|
3861 | if (!markers) return null; | |
|
3862 | var parts = [{from: from, to: to}]; | |
|
3863 | for (var i = 0; i < markers.length; ++i) { | |
|
3864 | var mk = markers[i], m = mk.find(); | |
|
3865 | for (var j = 0; j < parts.length; ++j) { | |
|
3866 | var p = parts[j]; | |
|
3867 | if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue; | |
|
3868 | var newParts = [j, 1]; | |
|
3869 | if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from)) | |
|
3870 | newParts.push({from: p.from, to: m.from}); | |
|
3871 | if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to)) | |
|
3872 | newParts.push({from: m.to, to: p.to}); | |
|
3873 | parts.splice.apply(parts, newParts); | |
|
3874 | j += newParts.length - 1; | |
|
3875 | } | |
|
3876 | } | |
|
3877 | return parts; | |
|
3878 | } | |
|
3879 | ||
|
3880 | function collapsedSpanAt(line, ch) { | |
|
3881 | var sps = sawCollapsedSpans && line.markedSpans, found; | |
|
3882 | if (sps) for (var sp, i = 0; i < sps.length; ++i) { | |
|
3883 | sp = sps[i]; | |
|
3884 | if (!sp.marker.collapsed) continue; | |
|
3885 | if ((sp.from == null || sp.from < ch) && | |
|
3886 | (sp.to == null || sp.to > ch) && | |
|
3887 | (!found || found.width < sp.marker.width)) | |
|
3888 | found = sp.marker; | |
|
3889 | } | |
|
3890 | return found; | |
|
3891 | } | |
|
3892 | function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); } | |
|
3893 | function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); } | |
|
3894 | ||
|
3895 | function visualLine(doc, line) { | |
|
3896 | var merged; | |
|
3897 | while (merged = collapsedSpanAtStart(line)) | |
|
3898 | line = getLine(doc, merged.find().from.line); | |
|
3899 | return line; | |
|
3900 | } | |
|
3901 | ||
|
3902 | function lineIsHidden(doc, line) { | |
|
3903 | var sps = sawCollapsedSpans && line.markedSpans; | |
|
3904 | if (sps) for (var sp, i = 0; i < sps.length; ++i) { | |
|
3905 | sp = sps[i]; | |
|
3906 | if (!sp.marker.collapsed) continue; | |
|
3907 | if (sp.from == null) return true; | |
|
3908 | if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) | |
|
3909 | return true; | |
|
3910 | } | |
|
3911 | } | |
|
3912 | function lineIsHiddenInner(doc, line, span) { | |
|
3913 | if (span.to == null) { | |
|
3914 | var end = span.marker.find().to, endLine = getLine(doc, end.line); | |
|
3915 | return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker)); | |
|
3916 | } | |
|
3917 | if (span.marker.inclusiveRight && span.to == line.text.length) | |
|
3918 | return true; | |
|
3919 | for (var sp, i = 0; i < line.markedSpans.length; ++i) { | |
|
3920 | sp = line.markedSpans[i]; | |
|
3921 | if (sp.marker.collapsed && sp.from == span.to && | |
|
3922 | (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && | |
|
3923 | lineIsHiddenInner(doc, line, sp)) return true; | |
|
3924 | } | |
|
3925 | } | |
|
2473 | 3926 | |
|
2474 | 3927 | function detachMarkedSpans(line) { |
|
2475 | 3928 | var spans = line.markedSpans; |
|
2476 | 3929 | if (!spans) return; |
|
2477 |
for (var i = 0; i < spans.length; ++i) |
|
|
2478 |
|
|
|
2479 | var ix = indexOf(lines, line); | |
|
2480 | lines.splice(ix, 1); | |
|
2481 | } | |
|
3930 | for (var i = 0; i < spans.length; ++i) | |
|
3931 | spans[i].marker.detachLine(line); | |
|
2482 | 3932 | line.markedSpans = null; |
|
2483 | 3933 | } |
|
2484 | 3934 | |
|
2485 | 3935 | function attachMarkedSpans(line, spans) { |
|
2486 | 3936 | if (!spans) return; |
|
2487 | 3937 | for (var i = 0; i < spans.length; ++i) |
|
2488 |
|
|
|
3938 | spans[i].marker.attachLine(line); | |
|
2489 | 3939 | line.markedSpans = spans; |
|
2490 | 3940 | } |
|
2491 | 3941 | |
|
2492 | // When measuring the position of the end of a line, different | |
|
2493 | // browsers require different approaches. If an empty span is added, | |
|
2494 | // many browsers report bogus offsets. Of those, some (Webkit, | |
|
2495 | // recent IE) will accept a space without moving the whole span to | |
|
2496 | // the next line when wrapping it, others work with a zero-width | |
|
2497 | // space. | |
|
2498 | var eolSpanContent = " "; | |
|
2499 | if (gecko || (ie && !ie_lt8)) eolSpanContent = "\u200b"; | |
|
2500 | else if (opera) eolSpanContent = ""; | |
|
3942 | // LINE WIDGETS | |
|
3943 | ||
|
3944 | var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { | |
|
3945 | for (var opt in options) if (options.hasOwnProperty(opt)) | |
|
3946 | this[opt] = options[opt]; | |
|
3947 | this.cm = cm; | |
|
3948 | this.node = node; | |
|
3949 | }; | |
|
3950 | function widgetOperation(f) { | |
|
3951 | return function() { | |
|
3952 | var withOp = !this.cm.curOp; | |
|
3953 | if (withOp) startOperation(this.cm); | |
|
3954 | try {var result = f.apply(this, arguments);} | |
|
3955 | finally {if (withOp) endOperation(this.cm);} | |
|
3956 | return result; | |
|
3957 | }; | |
|
3958 | } | |
|
3959 | LineWidget.prototype.clear = widgetOperation(function() { | |
|
3960 | var ws = this.line.widgets, no = lineNo(this.line); | |
|
3961 | if (no == null || !ws) return; | |
|
3962 | for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); | |
|
3963 | if (!ws.length) this.line.widgets = null; | |
|
3964 | updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this))); | |
|
3965 | regChange(this.cm, no, no + 1); | |
|
3966 | }); | |
|
3967 | LineWidget.prototype.changed = widgetOperation(function() { | |
|
3968 | var oldH = this.height; | |
|
3969 | this.height = null; | |
|
3970 | var diff = widgetHeight(this) - oldH; | |
|
3971 | if (!diff) return; | |
|
3972 | updateLineHeight(this.line, this.line.height + diff); | |
|
3973 | var no = lineNo(this.line); | |
|
3974 | regChange(this.cm, no, no + 1); | |
|
3975 | }); | |
|
3976 | ||
|
3977 | function widgetHeight(widget) { | |
|
3978 | if (widget.height != null) return widget.height; | |
|
3979 | if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1) | |
|
3980 | removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative")); | |
|
3981 | return widget.height = widget.node.offsetHeight; | |
|
3982 | } | |
|
3983 | ||
|
3984 | function addLineWidget(cm, handle, node, options) { | |
|
3985 | var widget = new LineWidget(cm, node, options); | |
|
3986 | if (widget.noHScroll) cm.display.alignWidgets = true; | |
|
3987 | changeLine(cm, handle, function(line) { | |
|
3988 | (line.widgets || (line.widgets = [])).push(widget); | |
|
3989 | widget.line = line; | |
|
3990 | if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) { | |
|
3991 | var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop; | |
|
3992 | updateLineHeight(line, line.height + widgetHeight(widget)); | |
|
3993 | if (aboveVisible) addToScrollPos(cm, 0, widget.height); | |
|
3994 | } | |
|
3995 | return true; | |
|
3996 | }); | |
|
3997 | return widget; | |
|
3998 | } | |
|
3999 | ||
|
4000 | // LINE DATA STRUCTURE | |
|
2501 | 4001 | |
|
2502 | 4002 | // Line objects. These hold state related to a line, including |
|
2503 | 4003 | // highlighting info (the styles array). |
|
2504 | function Line(text, markedSpans) { | |
|
2505 |
|
|
|
2506 | this.height = 1; | |
|
2507 | attachMarkedSpans(this, markedSpans); | |
|
2508 | } | |
|
2509 | Line.prototype = { | |
|
2510 | update: function(text, markedSpans) { | |
|
2511 | this.text = text; | |
|
2512 | this.stateAfter = this.styles = null; | |
|
2513 | detachMarkedSpans(this); | |
|
2514 | attachMarkedSpans(this, markedSpans); | |
|
2515 | }, | |
|
2516 | // Run the given mode's parser over a line, update the styles | |
|
2517 | // array, which contains alternating fragments of text and CSS | |
|
2518 | // classes. | |
|
2519 | highlight: function(mode, state, tabSize) { | |
|
2520 | var stream = new StringStream(this.text, tabSize), st = this.styles || (this.styles = []); | |
|
2521 | var pos = st.length = 0; | |
|
2522 | if (this.text == "" && mode.blankLine) mode.blankLine(state); | |
|
2523 | while (!stream.eol()) { | |
|
2524 | var style = mode.token(stream, state), substr = stream.current(); | |
|
2525 | stream.start = stream.pos; | |
|
2526 | if (pos && st[pos-1] == style) { | |
|
2527 | st[pos-2] += substr; | |
|
2528 | } else if (substr) { | |
|
2529 | st[pos++] = substr; st[pos++] = style; | |
|
2530 | } | |
|
2531 | // Give up when line is ridiculously long | |
|
2532 | if (stream.pos > 5000) { | |
|
2533 | st[pos++] = this.text.slice(stream.pos); st[pos++] = null; | |
|
2534 | break; | |
|
2535 | } | |
|
2536 | } | |
|
2537 | }, | |
|
2538 | process: function(mode, state, tabSize) { | |
|
2539 | var stream = new StringStream(this.text, tabSize); | |
|
2540 | if (this.text == "" && mode.blankLine) mode.blankLine(state); | |
|
2541 | while (!stream.eol() && stream.pos <= 5000) { | |
|
2542 | mode.token(stream, state); | |
|
2543 | stream.start = stream.pos; | |
|
4004 | function makeLine(text, markedSpans, estimateHeight) { | |
|
4005 | var line = {text: text}; | |
|
4006 | attachMarkedSpans(line, markedSpans); | |
|
4007 | line.height = estimateHeight ? estimateHeight(line) : 1; | |
|
4008 | return line; | |
|
4009 | } | |
|
4010 | ||
|
4011 | function updateLine(line, text, markedSpans, estimateHeight) { | |
|
4012 | line.text = text; | |
|
4013 | if (line.stateAfter) line.stateAfter = null; | |
|
4014 | if (line.styles) line.styles = null; | |
|
4015 | if (line.order != null) line.order = null; | |
|
4016 | detachMarkedSpans(line); | |
|
4017 | attachMarkedSpans(line, markedSpans); | |
|
4018 | var estHeight = estimateHeight ? estimateHeight(line) : 1; | |
|
4019 | if (estHeight != line.height) updateLineHeight(line, estHeight); | |
|
4020 | } | |
|
4021 | ||
|
4022 | function cleanUpLine(line) { | |
|
4023 | line.parent = null; | |
|
4024 | detachMarkedSpans(line); | |
|
4025 | } | |
|
4026 | ||
|
4027 | // Run the given mode's parser over a line, update the styles | |
|
4028 | // array, which contains alternating fragments of text and CSS | |
|
4029 | // classes. | |
|
4030 | function runMode(cm, text, mode, state, f) { | |
|
4031 | var flattenSpans = mode.flattenSpans; | |
|
4032 | if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; | |
|
4033 | var curText = "", curStyle = null; | |
|
4034 | var stream = new StringStream(text, cm.options.tabSize), style; | |
|
4035 | if (text == "" && mode.blankLine) mode.blankLine(state); | |
|
4036 | while (!stream.eol()) { | |
|
4037 | if (stream.pos > cm.options.maxHighlightLength) { | |
|
4038 | flattenSpans = false; | |
|
4039 | // Webkit seems to refuse to render text nodes longer than 57444 characters | |
|
4040 | stream.pos = Math.min(text.length, stream.start + 50000); | |
|
4041 | style = null; | |
|
4042 | } else { | |
|
4043 | style = mode.token(stream, state); | |
|
2544 | 4044 | } |
|
2545 | }, | |
|
2546 | // Fetch the parser token for a given character. Useful for hacks | |
|
2547 | // that want to inspect the mode state (say, for completion). | |
|
2548 | getTokenAt: function(mode, state, tabSize, ch) { | |
|
2549 | var txt = this.text, stream = new StringStream(txt, tabSize); | |
|
2550 | while (stream.pos < ch && !stream.eol()) { | |
|
2551 | stream.start = stream.pos; | |
|
2552 | var style = mode.token(stream, state); | |
|
2553 | } | |
|
2554 | return {start: stream.start, | |
|
2555 | end: stream.pos, | |
|
2556 | string: stream.current(), | |
|
2557 | className: style || null, | |
|
2558 | state: state}; | |
|
2559 | }, | |
|
2560 | indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, | |
|
2561 | // Produces an HTML fragment for the line, taking selection, | |
|
2562 | // marking, and highlighting into account. | |
|
2563 | getContent: function(tabSize, wrapAt, compensateForWrapping) { | |
|
2564 | var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; | |
|
2565 | var pre = elt("pre"); | |
|
2566 | function span_(html, text, style) { | |
|
2567 | if (!text) return; | |
|
2568 | // Work around a bug where, in some compat modes, IE ignores leading spaces | |
|
2569 | if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); | |
|
2570 | first = false; | |
|
2571 | if (!specials.test(text)) { | |
|
2572 | col += text.length; | |
|
2573 | var content = document.createTextNode(text); | |
|
4045 | var substr = stream.current(); | |
|
4046 | stream.start = stream.pos; | |
|
4047 | if (!flattenSpans || curStyle != style) { | |
|
4048 | if (curText) f(curText, curStyle); | |
|
4049 | curText = substr; curStyle = style; | |
|
4050 | } else curText = curText + substr; | |
|
4051 | } | |
|
4052 | if (curText) f(curText, curStyle); | |
|
4053 | } | |
|
4054 | ||
|
4055 | function highlightLine(cm, line, state) { | |
|
4056 | // A styles array always starts with a number identifying the | |
|
4057 | // mode/overlays that it is based on (for easy invalidation). | |
|
4058 | var st = [cm.state.modeGen]; | |
|
4059 | // Compute the base array of styles | |
|
4060 | runMode(cm, line.text, cm.doc.mode, state, function(txt, style) {st.push(txt, style);}); | |
|
4061 | ||
|
4062 | // Run overlays, adjust style array. | |
|
4063 | for (var o = 0; o < cm.state.overlays.length; ++o) { | |
|
4064 | var overlay = cm.state.overlays[o], i = 1; | |
|
4065 | runMode(cm, line.text, overlay.mode, true, function(txt, style) { | |
|
4066 | var start = i, len = txt.length; | |
|
4067 | // Ensure there's a token end at the current position, and that i points at it | |
|
4068 | while (len) { | |
|
4069 | var cur = st[i], len_ = cur.length; | |
|
4070 | if (len_ <= len) { | |
|
4071 | len -= len_; | |
|
4072 | } else { | |
|
4073 | st.splice(i, 1, cur.slice(0, len), st[i+1], cur.slice(len)); | |
|
4074 | len = 0; | |
|
4075 | } | |
|
4076 | i += 2; | |
|
4077 | } | |
|
4078 | if (!style) return; | |
|
4079 | if (overlay.opaque) { | |
|
4080 | st.splice(start, i - start, txt, style); | |
|
4081 | i = start + 2; | |
|
2574 | 4082 | } else { |
|
2575 | var content = document.createDocumentFragment(), pos = 0; | |
|
2576 | while (true) { | |
|
2577 | specials.lastIndex = pos; | |
|
2578 | var m = specials.exec(text); | |
|
2579 | var skipped = m ? m.index - pos : text.length - pos; | |
|
2580 | if (skipped) { | |
|
2581 | content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); | |
|
2582 | col += skipped; | |
|
2583 | } | |
|
2584 | if (!m) break; | |
|
2585 | pos += skipped + 1; | |
|
2586 | if (m[0] == "\t") { | |
|
2587 | var tabWidth = tabSize - col % tabSize; | |
|
2588 | content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); | |
|
2589 | col += tabWidth; | |
|
2590 | } else { | |
|
2591 | var token = elt("span", "\u2022", "cm-invalidchar"); | |
|
2592 | token.title = "\\u" + m[0].charCodeAt(0).toString(16); | |
|
2593 | content.appendChild(token); | |
|
2594 | col += 1; | |
|
2595 | } | |
|
4083 | for (; start < i; start += 2) { | |
|
4084 | var cur = st[start+1]; | |
|
4085 | st[start+1] = cur ? cur + " " + style : style; | |
|
2596 | 4086 | } |
|
2597 | 4087 | } |
|
2598 | if (style) html.appendChild(elt("span", [content], style)); | |
|
2599 | else html.appendChild(content); | |
|
2600 | } | |
|
2601 | var span = span_; | |
|
2602 | if (wrapAt != null) { | |
|
2603 | var outPos = 0, anchor = pre.anchor = elt("span"); | |
|
2604 | span = function(html, text, style) { | |
|
2605 | var l = text.length; | |
|
2606 | if (wrapAt >= outPos && wrapAt < outPos + l) { | |
|
2607 | var cut = wrapAt - outPos; | |
|
2608 | if (cut) { | |
|
2609 | span_(html, text.slice(0, cut), style); | |
|
2610 | // See comment at the definition of spanAffectsWrapping | |
|
2611 | if (compensateForWrapping) { | |
|
2612 | var view = text.slice(cut - 1, cut + 1); | |
|
2613 | if (spanAffectsWrapping.test(view)) html.appendChild(elt("wbr")); | |
|
2614 | else if (!ie_lt8 && /\w\w/.test(view)) html.appendChild(document.createTextNode("\u200d")); | |
|
2615 | } | |
|
2616 | } | |
|
2617 | html.appendChild(anchor); | |
|
2618 | span_(anchor, opera ? text.slice(cut, cut + 1) : text.slice(cut), style); | |
|
2619 | if (opera) span_(html, text.slice(cut + 1), style); | |
|
2620 | wrapAt--; | |
|
2621 | outPos += l; | |
|
2622 | } else { | |
|
2623 | outPos += l; | |
|
2624 | span_(html, text, style); | |
|
2625 | if (outPos == wrapAt && outPos == len) { | |
|
2626 | setTextContent(anchor, eolSpanContent); | |
|
2627 | html.appendChild(anchor); | |
|
2628 | } | |
|
2629 | // Stop outputting HTML when gone sufficiently far beyond measure | |
|
2630 | else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){}; | |
|
2631 | } | |
|
2632 | }; | |
|
2633 | } | |
|
2634 | ||
|
2635 | var st = this.styles, allText = this.text, marked = this.markedSpans; | |
|
2636 | var len = allText.length; | |
|
2637 | function styleToClass(style) { | |
|
2638 | if (!style) return null; | |
|
2639 | return "cm-" + style.replace(/ +/g, " cm-"); | |
|
4088 | }); | |
|
4089 | } | |
|
4090 | ||
|
4091 | return st; | |
|
4092 | } | |
|
4093 | ||
|
4094 | function getLineStyles(cm, line) { | |
|
4095 | if (!line.styles || line.styles[0] != cm.state.modeGen) | |
|
4096 | line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); | |
|
4097 | return line.styles; | |
|
4098 | } | |
|
4099 | ||
|
4100 | // Lightweight form of highlight -- proceed over this line and | |
|
4101 | // update state, but don't save a style array. | |
|
4102 | function processLine(cm, line, state) { | |
|
4103 | var mode = cm.doc.mode; | |
|
4104 | var stream = new StringStream(line.text, cm.options.tabSize); | |
|
4105 | if (line.text == "" && mode.blankLine) mode.blankLine(state); | |
|
4106 | while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { | |
|
4107 | mode.token(stream, state); | |
|
4108 | stream.start = stream.pos; | |
|
4109 | } | |
|
4110 | } | |
|
4111 | ||
|
4112 | var styleToClassCache = {}; | |
|
4113 | function styleToClass(style) { | |
|
4114 | if (!style) return null; | |
|
4115 | return styleToClassCache[style] || | |
|
4116 | (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); | |
|
4117 | } | |
|
4118 | ||
|
4119 | function lineContent(cm, realLine, measure) { | |
|
4120 | var merged, line = realLine, lineBefore, sawBefore, simple = true; | |
|
4121 | while (merged = collapsedSpanAtStart(line)) { | |
|
4122 | simple = false; | |
|
4123 | line = getLine(cm.doc, merged.find().from.line); | |
|
4124 | if (!lineBefore) lineBefore = line; | |
|
4125 | } | |
|
4126 | ||
|
4127 | var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure, | |
|
4128 | measure: null, addedOne: false, cm: cm}; | |
|
4129 | if (line.textClass) builder.pre.className = line.textClass; | |
|
4130 | ||
|
4131 | do { | |
|
4132 | builder.measure = line == realLine && measure; | |
|
4133 | builder.pos = 0; | |
|
4134 | builder.addToken = builder.measure ? buildTokenMeasure : buildToken; | |
|
4135 | if ((ie || webkit) && cm.getOption("lineWrapping")) | |
|
4136 | builder.addToken = buildTokenSplitSpaces(builder.addToken); | |
|
4137 | if (measure && sawBefore && line != realLine && !builder.addedOne) { | |
|
4138 | measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure)); | |
|
4139 | builder.addedOne = true; | |
|
2640 | 4140 | } |
|
2641 | if (!allText && wrapAt == null) { | |
|
2642 | span(pre, " "); | |
|
2643 | } else if (!marked || !marked.length) { | |
|
2644 | for (var i = 0, ch = 0; ch < len; i+=2) { | |
|
2645 | var str = st[i], style = st[i+1], l = str.length; | |
|
2646 | if (ch + l > len) str = str.slice(0, len - ch); | |
|
2647 | ch += l; | |
|
2648 | span(pre, str, styleToClass(style)); | |
|
4141 | var next = insertLineContent(line, builder, getLineStyles(cm, line)); | |
|
4142 | sawBefore = line == lineBefore; | |
|
4143 | if (next) { | |
|
4144 | line = getLine(cm.doc, next.to.line); | |
|
4145 | simple = false; | |
|
4146 | } | |
|
4147 | } while (next); | |
|
4148 | ||
|
4149 | if (measure && !builder.addedOne) | |
|
4150 | measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); | |
|
4151 | if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine)) | |
|
4152 | builder.pre.appendChild(document.createTextNode("\u00a0")); | |
|
4153 | ||
|
4154 | var order; | |
|
4155 | // Work around problem with the reported dimensions of single-char | |
|
4156 | // direction spans on IE (issue #1129). See also the comment in | |
|
4157 | // cursorCoords. | |
|
4158 | if (measure && ie && (order = getOrder(line))) { | |
|
4159 | var l = order.length - 1; | |
|
4160 | if (order[l].from == order[l].to) --l; | |
|
4161 | var last = order[l], prev = order[l - 1]; | |
|
4162 | if (last.from + 1 == last.to && prev && last.level < prev.level) { | |
|
4163 | var span = measure[builder.pos - 1]; | |
|
4164 | if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure), | |
|
4165 | span.nextSibling); | |
|
4166 | } | |
|
4167 | } | |
|
4168 | ||
|
4169 | signal(cm, "renderLine", cm, realLine, builder.pre); | |
|
4170 | return builder.pre; | |
|
4171 | } | |
|
4172 | ||
|
4173 | var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g; | |
|
4174 | function buildToken(builder, text, style, startStyle, endStyle) { | |
|
4175 | if (!text) return; | |
|
4176 | if (!tokenSpecialChars.test(text)) { | |
|
4177 | builder.col += text.length; | |
|
4178 | var content = document.createTextNode(text); | |
|
4179 | } else { | |
|
4180 | var content = document.createDocumentFragment(), pos = 0; | |
|
4181 | while (true) { | |
|
4182 | tokenSpecialChars.lastIndex = pos; | |
|
4183 | var m = tokenSpecialChars.exec(text); | |
|
4184 | var skipped = m ? m.index - pos : text.length - pos; | |
|
4185 | if (skipped) { | |
|
4186 | content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); | |
|
4187 | builder.col += skipped; | |
|
2649 | 4188 | } |
|
2650 | } else { | |
|
2651 | marked.sort(function(a, b) { return a.from - b.from; }); | |
|
2652 | var pos = 0, i = 0, text = "", style, sg = 0; | |
|
2653 | var nextChange = marked[0].from || 0, marks = [], markpos = 0; | |
|
2654 | var advanceMarks = function() { | |
|
2655 | var m; | |
|
2656 | while (markpos < marked.length && | |
|
2657 | ((m = marked[markpos]).from == pos || m.from == null)) { | |
|
2658 | if (m.marker.type == "range") marks.push(m); | |
|
2659 | ++markpos; | |
|
2660 | } | |
|
2661 | nextChange = markpos < marked.length ? marked[markpos].from : Infinity; | |
|
2662 | for (var i = 0; i < marks.length; ++i) { | |
|
2663 | var to = marks[i].to; | |
|
2664 | if (to == null) to = Infinity; | |
|
2665 | if (to == pos) marks.splice(i--, 1); | |
|
2666 | else nextChange = Math.min(to, nextChange); | |
|
2667 | } | |
|
2668 | }; | |
|
2669 | var m = 0; | |
|
2670 | while (pos < len) { | |
|
2671 | if (nextChange == pos) advanceMarks(); | |
|
2672 | var upto = Math.min(len, nextChange); | |
|
2673 | while (true) { | |
|
2674 | if (text) { | |
|
2675 | var end = pos + text.length; | |
|
2676 | var appliedStyle = style; | |
|
2677 | for (var j = 0; j < marks.length; ++j) { | |
|
2678 | var mark = marks[j]; | |
|
2679 | appliedStyle = (appliedStyle ? appliedStyle + " " : "") + mark.marker.style; | |
|
2680 | if (mark.marker.endStyle && mark.to === Math.min(end, upto)) appliedStyle += " " + mark.marker.endStyle; | |
|
2681 | if (mark.marker.startStyle && mark.from === pos) appliedStyle += " " + mark.marker.startStyle; | |
|
2682 | } | |
|
2683 | span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle); | |
|
2684 | if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} | |
|
2685 | pos = end; | |
|
2686 | } | |
|
2687 | text = st[i++]; style = styleToClass(st[i++]); | |
|
2688 | } | |
|
4189 | if (!m) break; | |
|
4190 | pos += skipped + 1; | |
|
4191 | if (m[0] == "\t") { | |
|
4192 | var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; | |
|
4193 | content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); | |
|
4194 | builder.col += tabWidth; | |
|
4195 | } else { | |
|
4196 | var token = elt("span", "\u2022", "cm-invalidchar"); | |
|
4197 | token.title = "\\u" + m[0].charCodeAt(0).toString(16); | |
|
4198 | content.appendChild(token); | |
|
4199 | builder.col += 1; | |
|
2689 | 4200 | } |
|
2690 | 4201 | } |
|
2691 | return pre; | |
|
2692 | }, | |
|
2693 | cleanUp: function() { | |
|
2694 | this.parent = null; | |
|
2695 | detachMarkedSpans(this); | |
|
2696 | } | |
|
2697 |
} |
|
|
2698 | ||
|
2699 | // Data structure that holds the sequence of lines. | |
|
4202 | } | |
|
4203 | if (style || startStyle || endStyle || builder.measure) { | |
|
4204 | var fullStyle = style || ""; | |
|
4205 | if (startStyle) fullStyle += startStyle; | |
|
4206 | if (endStyle) fullStyle += endStyle; | |
|
4207 | return builder.pre.appendChild(elt("span", [content], fullStyle)); | |
|
4208 | } | |
|
4209 | builder.pre.appendChild(content); | |
|
4210 | } | |
|
4211 | ||
|
4212 | function buildTokenMeasure(builder, text, style, startStyle, endStyle) { | |
|
4213 | var wrapping = builder.cm.options.lineWrapping; | |
|
4214 | for (var i = 0; i < text.length; ++i) { | |
|
4215 | var ch = text.charAt(i), start = i == 0; | |
|
4216 | if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) { | |
|
4217 | ch = text.slice(i, i + 2); | |
|
4218 | ++i; | |
|
4219 | } else if (i && wrapping && | |
|
4220 | spanAffectsWrapping.test(text.slice(i - 1, i + 1))) { | |
|
4221 | builder.pre.appendChild(elt("wbr")); | |
|
4222 | } | |
|
4223 | var span = builder.measure[builder.pos] = | |
|
4224 | buildToken(builder, ch, style, | |
|
4225 | start && startStyle, i == text.length - 1 && endStyle); | |
|
4226 | // In IE single-space nodes wrap differently than spaces | |
|
4227 | // embedded in larger text nodes, except when set to | |
|
4228 | // white-space: normal (issue #1268). | |
|
4229 | if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) && | |
|
4230 | i < text.length - 1 && !/\s/.test(text.charAt(i + 1))) | |
|
4231 | span.style.whiteSpace = "normal"; | |
|
4232 | builder.pos += ch.length; | |
|
4233 | } | |
|
4234 | if (text.length) builder.addedOne = true; | |
|
4235 | } | |
|
4236 | ||
|
4237 | function buildTokenSplitSpaces(inner) { | |
|
4238 | function split(old) { | |
|
4239 | var out = " "; | |
|
4240 | for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; | |
|
4241 | out += " "; | |
|
4242 | return out; | |
|
4243 | } | |
|
4244 | return function(builder, text, style, startStyle, endStyle) { | |
|
4245 | return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle); | |
|
4246 | }; | |
|
4247 | } | |
|
4248 | ||
|
4249 | function buildCollapsedSpan(builder, size, widget) { | |
|
4250 | if (widget) { | |
|
4251 | if (!builder.display) widget = widget.cloneNode(true); | |
|
4252 | builder.pre.appendChild(widget); | |
|
4253 | if (builder.measure && size) { | |
|
4254 | builder.measure[builder.pos] = widget; | |
|
4255 | builder.addedOne = true; | |
|
4256 | } | |
|
4257 | } | |
|
4258 | builder.pos += size; | |
|
4259 | } | |
|
4260 | ||
|
4261 | // Outputs a number of spans to make up a line, taking highlighting | |
|
4262 | // and marked text into account. | |
|
4263 | function insertLineContent(line, builder, styles) { | |
|
4264 | var spans = line.markedSpans; | |
|
4265 | if (!spans) { | |
|
4266 | for (var i = 1; i < styles.length; i+=2) | |
|
4267 | builder.addToken(builder, styles[i], styleToClass(styles[i+1])); | |
|
4268 | return; | |
|
4269 | } | |
|
4270 | ||
|
4271 | var allText = line.text, len = allText.length; | |
|
4272 | var pos = 0, i = 1, text = "", style; | |
|
4273 | var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed; | |
|
4274 | for (;;) { | |
|
4275 | if (nextChange == pos) { // Update current marker set | |
|
4276 | spanStyle = spanEndStyle = spanStartStyle = ""; | |
|
4277 | collapsed = null; nextChange = Infinity; | |
|
4278 | var foundBookmark = null; | |
|
4279 | for (var j = 0; j < spans.length; ++j) { | |
|
4280 | var sp = spans[j], m = sp.marker; | |
|
4281 | if (sp.from <= pos && (sp.to == null || sp.to > pos)) { | |
|
4282 | if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } | |
|
4283 | if (m.className) spanStyle += " " + m.className; | |
|
4284 | if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; | |
|
4285 | if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; | |
|
4286 | if (m.collapsed && (!collapsed || collapsed.marker.width < m.width)) | |
|
4287 | collapsed = sp; | |
|
4288 | } else if (sp.from > pos && nextChange > sp.from) { | |
|
4289 | nextChange = sp.from; | |
|
4290 | } | |
|
4291 | if (m.type == "bookmark" && sp.from == pos && m.replacedWith) | |
|
4292 | foundBookmark = m.replacedWith; | |
|
4293 | } | |
|
4294 | if (collapsed && (collapsed.from || 0) == pos) { | |
|
4295 | buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos, | |
|
4296 | collapsed.from != null && collapsed.marker.replacedWith); | |
|
4297 | if (collapsed.to == null) return collapsed.marker.find(); | |
|
4298 | } | |
|
4299 | if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark); | |
|
4300 | } | |
|
4301 | if (pos >= len) break; | |
|
4302 | ||
|
4303 | var upto = Math.min(len, nextChange); | |
|
4304 | while (true) { | |
|
4305 | if (text) { | |
|
4306 | var end = pos + text.length; | |
|
4307 | if (!collapsed) { | |
|
4308 | var tokenText = end > upto ? text.slice(0, upto - pos) : text; | |
|
4309 | builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, | |
|
4310 | spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : ""); | |
|
4311 | } | |
|
4312 | if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} | |
|
4313 | pos = end; | |
|
4314 | spanStartStyle = ""; | |
|
4315 | } | |
|
4316 | text = styles[i++]; style = styleToClass(styles[i++]); | |
|
4317 | } | |
|
4318 | } | |
|
4319 | } | |
|
4320 | ||
|
4321 | // DOCUMENT DATA STRUCTURE | |
|
4322 | ||
|
4323 | function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) { | |
|
4324 | function spansFor(n) {return markedSpans ? markedSpans[n] : null;} | |
|
4325 | function update(line, text, spans) { | |
|
4326 | updateLine(line, text, spans, estimateHeight); | |
|
4327 | signalLater(line, "change", line, change); | |
|
4328 | } | |
|
4329 | ||
|
4330 | var from = change.from, to = change.to, text = change.text; | |
|
4331 | var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); | |
|
4332 | var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; | |
|
4333 | ||
|
4334 | // First adjust the line structure | |
|
4335 | if (from.ch == 0 && to.ch == 0 && lastText == "") { | |
|
4336 | // This is a whole-line replace. Treated specially to make | |
|
4337 | // sure line objects move the way they are supposed to. | |
|
4338 | for (var i = 0, e = text.length - 1, added = []; i < e; ++i) | |
|
4339 | added.push(makeLine(text[i], spansFor(i), estimateHeight)); | |
|
4340 | update(lastLine, lastLine.text, lastSpans); | |
|
4341 | if (nlines) doc.remove(from.line, nlines); | |
|
4342 | if (added.length) doc.insert(from.line, added); | |
|
4343 | } else if (firstLine == lastLine) { | |
|
4344 | if (text.length == 1) { | |
|
4345 | update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); | |
|
4346 | } else { | |
|
4347 | for (var added = [], i = 1, e = text.length - 1; i < e; ++i) | |
|
4348 | added.push(makeLine(text[i], spansFor(i), estimateHeight)); | |
|
4349 | added.push(makeLine(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); | |
|
4350 | update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); | |
|
4351 | doc.insert(from.line + 1, added); | |
|
4352 | } | |
|
4353 | } else if (text.length == 1) { | |
|
4354 | update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); | |
|
4355 | doc.remove(from.line + 1, nlines); | |
|
4356 | } else { | |
|
4357 | update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); | |
|
4358 | update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); | |
|
4359 | for (var i = 1, e = text.length - 1, added = []; i < e; ++i) | |
|
4360 | added.push(makeLine(text[i], spansFor(i), estimateHeight)); | |
|
4361 | if (nlines > 1) doc.remove(from.line + 1, nlines - 1); | |
|
4362 | doc.insert(from.line + 1, added); | |
|
4363 | } | |
|
4364 | ||
|
4365 | signalLater(doc, "change", doc, change); | |
|
4366 | setSelection(doc, selAfter.anchor, selAfter.head, null, true); | |
|
4367 | } | |
|
4368 | ||
|
2700 | 4369 | function LeafChunk(lines) { |
|
2701 | 4370 | this.lines = lines; |
|
2702 | 4371 | this.parent = null; |
|
2703 | 4372 | for (var i = 0, e = lines.length, height = 0; i < e; ++i) { |
|
2704 | 4373 | lines[i].parent = this; |
|
2705 | 4374 | height += lines[i].height; |
|
2706 | 4375 | } |
|
2707 | 4376 | this.height = height; |
|
2708 | 4377 | } |
|
4378 | ||
|
2709 | 4379 | LeafChunk.prototype = { |
|
2710 | 4380 | chunkSize: function() { return this.lines.length; }, |
|
2711 |
remove: function(at, n |
|
|
4381 | removeInner: function(at, n) { | |
|
2712 | 4382 | for (var i = at, e = at + n; i < e; ++i) { |
|
2713 | 4383 | var line = this.lines[i]; |
|
2714 | 4384 | this.height -= line.height; |
|
2715 |
line |
|
|
2716 | if (line.handlers) | |
|
2717 | for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]); | |
|
4385 | cleanUpLine(line); | |
|
4386 | signalLater(line, "delete"); | |
|
2718 | 4387 | } |
|
2719 | 4388 | this.lines.splice(at, n); |
|
2720 | 4389 | }, |
|
2721 | 4390 | collapse: function(lines) { |
|
2722 | 4391 | lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); |
|
2723 | 4392 | }, |
|
2724 |
insert |
|
|
4393 | insertInner: function(at, lines, height) { | |
|
2725 | 4394 | this.height += height; |
|
2726 | 4395 | this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); |
|
2727 | 4396 | for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; |
|
2728 | 4397 | }, |
|
2729 | 4398 | iterN: function(at, n, op) { |
|
2730 | 4399 | for (var e = at + n; at < e; ++at) |
|
2731 | 4400 | if (op(this.lines[at])) return true; |
|
2732 | 4401 | } |
|
2733 | 4402 | }; |
|
4403 | ||
|
2734 | 4404 | function BranchChunk(children) { |
|
2735 | 4405 | this.children = children; |
|
2736 | 4406 | var size = 0, height = 0; |
|
2737 | 4407 | for (var i = 0, e = children.length; i < e; ++i) { |
|
2738 | 4408 | var ch = children[i]; |
|
2739 | 4409 | size += ch.chunkSize(); height += ch.height; |
|
2740 | 4410 | ch.parent = this; |
|
2741 | 4411 | } |
|
2742 | 4412 | this.size = size; |
|
2743 | 4413 | this.height = height; |
|
2744 | 4414 | this.parent = null; |
|
2745 | 4415 | } |
|
4416 | ||
|
2746 | 4417 | BranchChunk.prototype = { |
|
2747 | 4418 | chunkSize: function() { return this.size; }, |
|
2748 |
remove: function(at, n |
|
|
4419 | removeInner: function(at, n) { | |
|
2749 | 4420 | this.size -= n; |
|
2750 | 4421 | for (var i = 0; i < this.children.length; ++i) { |
|
2751 | 4422 | var child = this.children[i], sz = child.chunkSize(); |
|
2752 | 4423 | if (at < sz) { |
|
2753 | 4424 | var rm = Math.min(n, sz - at), oldHeight = child.height; |
|
2754 |
child.remove(at, rm |
|
|
4425 | child.removeInner(at, rm); | |
|
2755 | 4426 | this.height -= oldHeight - child.height; |
|
2756 | 4427 | if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } |
|
2757 | 4428 | if ((n -= rm) == 0) break; |
|
2758 | 4429 | at = 0; |
|
2759 | 4430 | } else at -= sz; |
|
2760 | 4431 | } |
|
2761 | 4432 | if (this.size - n < 25) { |
|
2762 | 4433 | var lines = []; |
|
2763 | 4434 | this.collapse(lines); |
|
2764 | 4435 | this.children = [new LeafChunk(lines)]; |
|
2765 | 4436 | this.children[0].parent = this; |
|
2766 | 4437 | } |
|
2767 | 4438 | }, |
|
2768 | 4439 | collapse: function(lines) { |
|
2769 | 4440 | for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); |
|
2770 | 4441 | }, |
|
2771 | insert: function(at, lines) { | |
|
2772 | var height = 0; | |
|
2773 | for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; | |
|
2774 | this.insertHeight(at, lines, height); | |
|
2775 | }, | |
|
2776 | insertHeight: function(at, lines, height) { | |
|
4442 | insertInner: function(at, lines, height) { | |
|
2777 | 4443 | this.size += lines.length; |
|
2778 | 4444 | this.height += height; |
|
2779 | 4445 | for (var i = 0, e = this.children.length; i < e; ++i) { |
|
2780 | 4446 | var child = this.children[i], sz = child.chunkSize(); |
|
2781 | 4447 | if (at <= sz) { |
|
2782 |
child.insert |
|
|
4448 | child.insertInner(at, lines, height); | |
|
2783 | 4449 | if (child.lines && child.lines.length > 50) { |
|
2784 | 4450 | while (child.lines.length > 50) { |
|
2785 | 4451 | var spilled = child.lines.splice(child.lines.length - 25, 25); |
|
2786 | 4452 | var newleaf = new LeafChunk(spilled); |
|
2787 | 4453 | child.height -= newleaf.height; |
|
2788 | 4454 | this.children.splice(i + 1, 0, newleaf); |
|
2789 | 4455 | newleaf.parent = this; |
|
2790 | 4456 | } |
|
2791 | 4457 | this.maybeSpill(); |
|
2792 | 4458 | } |
|
2793 | 4459 | break; |
|
2794 | 4460 | } |
|
2795 | 4461 | at -= sz; |
|
2796 | 4462 | } |
|
2797 | 4463 | }, |
|
2798 | 4464 | maybeSpill: function() { |
|
2799 | 4465 | if (this.children.length <= 10) return; |
|
2800 | 4466 | var me = this; |
|
2801 | 4467 | do { |
|
2802 | 4468 | var spilled = me.children.splice(me.children.length - 5, 5); |
|
2803 | 4469 | var sibling = new BranchChunk(spilled); |
|
2804 | 4470 | if (!me.parent) { // Become the parent node |
|
2805 | 4471 | var copy = new BranchChunk(me.children); |
|
2806 | 4472 | copy.parent = me; |
|
2807 | 4473 | me.children = [copy, sibling]; |
|
2808 | 4474 | me = copy; |
|
2809 | 4475 | } else { |
|
2810 | 4476 | me.size -= sibling.size; |
|
2811 | 4477 | me.height -= sibling.height; |
|
2812 | 4478 | var myIndex = indexOf(me.parent.children, me); |
|
2813 | 4479 | me.parent.children.splice(myIndex + 1, 0, sibling); |
|
2814 | 4480 | } |
|
2815 | 4481 | sibling.parent = me.parent; |
|
2816 | 4482 | } while (me.children.length > 10); |
|
2817 | 4483 | me.parent.maybeSpill(); |
|
2818 | 4484 | }, |
|
2819 | iter: function(from, to, op) { this.iterN(from, to - from, op); }, | |
|
2820 | 4485 | iterN: function(at, n, op) { |
|
2821 | 4486 | for (var i = 0, e = this.children.length; i < e; ++i) { |
|
2822 | 4487 | var child = this.children[i], sz = child.chunkSize(); |
|
2823 | 4488 | if (at < sz) { |
|
2824 | 4489 | var used = Math.min(n, sz - at); |
|
2825 | 4490 | if (child.iterN(at, used, op)) return true; |
|
2826 | 4491 | if ((n -= used) == 0) break; |
|
2827 | 4492 | at = 0; |
|
2828 | 4493 | } else at -= sz; |
|
2829 | 4494 | } |
|
2830 | 4495 | } |
|
2831 | 4496 | }; |
|
2832 | 4497 | |
|
2833 | function getLineAt(chunk, n) { | |
|
4498 | var nextDocId = 0; | |
|
4499 | var Doc = CodeMirror.Doc = function(text, mode, firstLine) { | |
|
4500 | if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); | |
|
4501 | if (firstLine == null) firstLine = 0; | |
|
4502 | ||
|
4503 | BranchChunk.call(this, [new LeafChunk([makeLine("", null)])]); | |
|
4504 | this.first = firstLine; | |
|
4505 | this.scrollTop = this.scrollLeft = 0; | |
|
4506 | this.cantEdit = false; | |
|
4507 | this.history = makeHistory(); | |
|
4508 | this.frontier = firstLine; | |
|
4509 | var start = Pos(firstLine, 0); | |
|
4510 | this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null}; | |
|
4511 | this.id = ++nextDocId; | |
|
4512 | this.modeOption = mode; | |
|
4513 | ||
|
4514 | if (typeof text == "string") text = splitLines(text); | |
|
4515 | updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start}); | |
|
4516 | }; | |
|
4517 | ||
|
4518 | Doc.prototype = createObj(BranchChunk.prototype, { | |
|
4519 | iter: function(from, to, op) { | |
|
4520 | if (op) this.iterN(from - this.first, to - from, op); | |
|
4521 | else this.iterN(this.first, this.first + this.size, from); | |
|
4522 | }, | |
|
4523 | ||
|
4524 | insert: function(at, lines) { | |
|
4525 | var height = 0; | |
|
4526 | for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; | |
|
4527 | this.insertInner(at - this.first, lines, height); | |
|
4528 | }, | |
|
4529 | remove: function(at, n) { this.removeInner(at - this.first, n); }, | |
|
4530 | ||
|
4531 | getValue: function(lineSep) { | |
|
4532 | var lines = getLines(this, this.first, this.first + this.size); | |
|
4533 | if (lineSep === false) return lines; | |
|
4534 | return lines.join(lineSep || "\n"); | |
|
4535 | }, | |
|
4536 | setValue: function(code) { | |
|
4537 | var top = Pos(this.first, 0), last = this.first + this.size - 1; | |
|
4538 | makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), | |
|
4539 | text: splitLines(code), origin: "setValue"}, | |
|
4540 | {head: top, anchor: top}, true); | |
|
4541 | }, | |
|
4542 | replaceRange: function(code, from, to, origin) { | |
|
4543 | from = clipPos(this, from); | |
|
4544 | to = to ? clipPos(this, to) : from; | |
|
4545 | replaceRange(this, code, from, to, origin); | |
|
4546 | }, | |
|
4547 | getRange: function(from, to, lineSep) { | |
|
4548 | var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); | |
|
4549 | if (lineSep === false) return lines; | |
|
4550 | return lines.join(lineSep || "\n"); | |
|
4551 | }, | |
|
4552 | ||
|
4553 | getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, | |
|
4554 | setLine: function(line, text) { | |
|
4555 | if (isLine(this, line)) | |
|
4556 | replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line))); | |
|
4557 | }, | |
|
4558 | removeLine: function(line) { | |
|
4559 | if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line))); | |
|
4560 | else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0))); | |
|
4561 | }, | |
|
4562 | ||
|
4563 | getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, | |
|
4564 | getLineNumber: function(line) {return lineNo(line);}, | |
|
4565 | ||
|
4566 | lineCount: function() {return this.size;}, | |
|
4567 | firstLine: function() {return this.first;}, | |
|
4568 | lastLine: function() {return this.first + this.size - 1;}, | |
|
4569 | ||
|
4570 | clipPos: function(pos) {return clipPos(this, pos);}, | |
|
4571 | ||
|
4572 | getCursor: function(start) { | |
|
4573 | var sel = this.sel, pos; | |
|
4574 | if (start == null || start == "head") pos = sel.head; | |
|
4575 | else if (start == "anchor") pos = sel.anchor; | |
|
4576 | else if (start == "end" || start === false) pos = sel.to; | |
|
4577 | else pos = sel.from; | |
|
4578 | return copyPos(pos); | |
|
4579 | }, | |
|
4580 | somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);}, | |
|
4581 | ||
|
4582 | setCursor: docOperation(function(line, ch, extend) { | |
|
4583 | var pos = clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line); | |
|
4584 | if (extend) extendSelection(this, pos); | |
|
4585 | else setSelection(this, pos, pos); | |
|
4586 | }), | |
|
4587 | setSelection: docOperation(function(anchor, head) { | |
|
4588 | setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor)); | |
|
4589 | }), | |
|
4590 | extendSelection: docOperation(function(from, to) { | |
|
4591 | extendSelection(this, clipPos(this, from), to && clipPos(this, to)); | |
|
4592 | }), | |
|
4593 | ||
|
4594 | getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);}, | |
|
4595 | replaceSelection: function(code, collapse, origin) { | |
|
4596 | makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || "around"); | |
|
4597 | }, | |
|
4598 | undo: docOperation(function() {makeChangeFromHistory(this, "undo");}), | |
|
4599 | redo: docOperation(function() {makeChangeFromHistory(this, "redo");}), | |
|
4600 | ||
|
4601 | setExtending: function(val) {this.sel.extend = val;}, | |
|
4602 | ||
|
4603 | historySize: function() { | |
|
4604 | var hist = this.history; | |
|
4605 | return {undo: hist.done.length, redo: hist.undone.length}; | |
|
4606 | }, | |
|
4607 | clearHistory: function() {this.history = makeHistory();}, | |
|
4608 | ||
|
4609 | markClean: function() { | |
|
4610 | this.history.dirtyCounter = 0; | |
|
4611 | this.history.lastOp = this.history.lastOrigin = null; | |
|
4612 | }, | |
|
4613 | isClean: function () {return this.history.dirtyCounter == 0;}, | |
|
4614 | ||
|
4615 | getHistory: function() { | |
|
4616 | return {done: copyHistoryArray(this.history.done), | |
|
4617 | undone: copyHistoryArray(this.history.undone)}; | |
|
4618 | }, | |
|
4619 | setHistory: function(histData) { | |
|
4620 | var hist = this.history = makeHistory(); | |
|
4621 | hist.done = histData.done.slice(0); | |
|
4622 | hist.undone = histData.undone.slice(0); | |
|
4623 | }, | |
|
4624 | ||
|
4625 | markText: function(from, to, options) { | |
|
4626 | return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); | |
|
4627 | }, | |
|
4628 | setBookmark: function(pos, options) { | |
|
4629 | var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), | |
|
4630 | insertLeft: options && options.insertLeft}; | |
|
4631 | pos = clipPos(this, pos); | |
|
4632 | return markText(this, pos, pos, realOpts, "bookmark"); | |
|
4633 | }, | |
|
4634 | findMarksAt: function(pos) { | |
|
4635 | pos = clipPos(this, pos); | |
|
4636 | var markers = [], spans = getLine(this, pos.line).markedSpans; | |
|
4637 | if (spans) for (var i = 0; i < spans.length; ++i) { | |
|
4638 | var span = spans[i]; | |
|
4639 | if ((span.from == null || span.from <= pos.ch) && | |
|
4640 | (span.to == null || span.to >= pos.ch)) | |
|
4641 | markers.push(span.marker.parent || span.marker); | |
|
4642 | } | |
|
4643 | return markers; | |
|
4644 | }, | |
|
4645 | getAllMarks: function() { | |
|
4646 | var markers = []; | |
|
4647 | this.iter(function(line) { | |
|
4648 | var sps = line.markedSpans; | |
|
4649 | if (sps) for (var i = 0; i < sps.length; ++i) | |
|
4650 | if (sps[i].from != null) markers.push(sps[i].marker); | |
|
4651 | }); | |
|
4652 | return markers; | |
|
4653 | }, | |
|
4654 | ||
|
4655 | posFromIndex: function(off) { | |
|
4656 | var ch, lineNo = this.first; | |
|
4657 | this.iter(function(line) { | |
|
4658 | var sz = line.text.length + 1; | |
|
4659 | if (sz > off) { ch = off; return true; } | |
|
4660 | off -= sz; | |
|
4661 | ++lineNo; | |
|
4662 | }); | |
|
4663 | return clipPos(this, Pos(lineNo, ch)); | |
|
4664 | }, | |
|
4665 | indexFromPos: function (coords) { | |
|
4666 | coords = clipPos(this, coords); | |
|
4667 | var index = coords.ch; | |
|
4668 | if (coords.line < this.first || coords.ch < 0) return 0; | |
|
4669 | this.iter(this.first, coords.line, function (line) { | |
|
4670 | index += line.text.length + 1; | |
|
4671 | }); | |
|
4672 | return index; | |
|
4673 | }, | |
|
4674 | ||
|
4675 | copy: function(copyHistory) { | |
|
4676 | var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first); | |
|
4677 | doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; | |
|
4678 | doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor, | |
|
4679 | shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn}; | |
|
4680 | if (copyHistory) { | |
|
4681 | doc.history.undoDepth = this.history.undoDepth; | |
|
4682 | doc.setHistory(this.getHistory()); | |
|
4683 | } | |
|
4684 | return doc; | |
|
4685 | }, | |
|
4686 | ||
|
4687 | linkedDoc: function(options) { | |
|
4688 | if (!options) options = {}; | |
|
4689 | var from = this.first, to = this.first + this.size; | |
|
4690 | if (options.from != null && options.from > from) from = options.from; | |
|
4691 | if (options.to != null && options.to < to) to = options.to; | |
|
4692 | var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from); | |
|
4693 | if (options.sharedHist) copy.history = this.history; | |
|
4694 | (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); | |
|
4695 | copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; | |
|
4696 | return copy; | |
|
4697 | }, | |
|
4698 | unlinkDoc: function(other) { | |
|
4699 | if (other instanceof CodeMirror) other = other.doc; | |
|
4700 | if (this.linked) for (var i = 0; i < this.linked.length; ++i) { | |
|
4701 | var link = this.linked[i]; | |
|
4702 | if (link.doc != other) continue; | |
|
4703 | this.linked.splice(i, 1); | |
|
4704 | other.unlinkDoc(this); | |
|
4705 | break; | |
|
4706 | } | |
|
4707 | // If the histories were shared, split them again | |
|
4708 | if (other.history == this.history) { | |
|
4709 | var splitIds = [other.id]; | |
|
4710 | linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); | |
|
4711 | other.history = makeHistory(); | |
|
4712 | other.history.done = copyHistoryArray(this.history.done, splitIds); | |
|
4713 | other.history.undone = copyHistoryArray(this.history.undone, splitIds); | |
|
4714 | } | |
|
4715 | }, | |
|
4716 | iterLinkedDocs: function(f) {linkedDocs(this, f);}, | |
|
4717 | ||
|
4718 | getMode: function() {return this.mode;}, | |
|
4719 | getEditor: function() {return this.cm;} | |
|
4720 | }); | |
|
4721 | ||
|
4722 | Doc.prototype.eachLine = Doc.prototype.iter; | |
|
4723 | ||
|
4724 | // The Doc methods that should be available on CodeMirror instances | |
|
4725 | var dontDelegate = "iter insert remove copy getEditor".split(" "); | |
|
4726 | for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) | |
|
4727 | CodeMirror.prototype[prop] = (function(method) { | |
|
4728 | return function() {return method.apply(this.doc, arguments);}; | |
|
4729 | })(Doc.prototype[prop]); | |
|
4730 | ||
|
4731 | function linkedDocs(doc, f, sharedHistOnly) { | |
|
4732 | function propagate(doc, skip, sharedHist) { | |
|
4733 | if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { | |
|
4734 | var rel = doc.linked[i]; | |
|
4735 | if (rel.doc == skip) continue; | |
|
4736 | var shared = sharedHist && rel.sharedHist; | |
|
4737 | if (sharedHistOnly && !shared) continue; | |
|
4738 | f(rel.doc, shared); | |
|
4739 | propagate(rel.doc, doc, shared); | |
|
4740 | } | |
|
4741 | } | |
|
4742 | propagate(doc, null, true); | |
|
4743 | } | |
|
4744 | ||
|
4745 | function attachDoc(cm, doc) { | |
|
4746 | if (doc.cm) throw new Error("This document is already in use."); | |
|
4747 | cm.doc = doc; | |
|
4748 | doc.cm = cm; | |
|
4749 | estimateLineHeights(cm); | |
|
4750 | loadMode(cm); | |
|
4751 | if (!cm.options.lineWrapping) computeMaxLength(cm); | |
|
4752 | cm.options.mode = doc.modeOption; | |
|
4753 | regChange(cm); | |
|
4754 | } | |
|
4755 | ||
|
4756 | // LINE UTILITIES | |
|
4757 | ||
|
4758 | function getLine(chunk, n) { | |
|
4759 | n -= chunk.first; | |
|
2834 | 4760 | while (!chunk.lines) { |
|
2835 | 4761 | for (var i = 0;; ++i) { |
|
2836 | 4762 | var child = chunk.children[i], sz = child.chunkSize(); |
|
2837 | 4763 | if (n < sz) { chunk = child; break; } |
|
2838 | 4764 | n -= sz; |
|
2839 | 4765 | } |
|
2840 | 4766 | } |
|
2841 | 4767 | return chunk.lines[n]; |
|
2842 | 4768 | } |
|
4769 | ||
|
4770 | function getBetween(doc, start, end) { | |
|
4771 | var out = [], n = start.line; | |
|
4772 | doc.iter(start.line, end.line + 1, function(line) { | |
|
4773 | var text = line.text; | |
|
4774 | if (n == end.line) text = text.slice(0, end.ch); | |
|
4775 | if (n == start.line) text = text.slice(start.ch); | |
|
4776 | out.push(text); | |
|
4777 | ++n; | |
|
4778 | }); | |
|
4779 | return out; | |
|
4780 | } | |
|
4781 | function getLines(doc, from, to) { | |
|
4782 | var out = []; | |
|
4783 | doc.iter(from, to, function(line) { out.push(line.text); }); | |
|
4784 | return out; | |
|
4785 | } | |
|
4786 | ||
|
4787 | function updateLineHeight(line, height) { | |
|
4788 | var diff = height - line.height; | |
|
4789 | for (var n = line; n; n = n.parent) n.height += diff; | |
|
4790 | } | |
|
4791 | ||
|
2843 | 4792 | function lineNo(line) { |
|
2844 | 4793 | if (line.parent == null) return null; |
|
2845 | 4794 | var cur = line.parent, no = indexOf(cur.lines, line); |
|
2846 | 4795 | for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { |
|
2847 |
for (var i = 0 |
|
|
4796 | for (var i = 0;; ++i) { | |
|
2848 | 4797 | if (chunk.children[i] == cur) break; |
|
2849 | 4798 | no += chunk.children[i].chunkSize(); |
|
2850 | 4799 | } |
|
2851 | 4800 | } |
|
2852 | return no; | |
|
2853 | } | |
|
4801 | return no + cur.first; | |
|
4802 | } | |
|
4803 | ||
|
2854 | 4804 | function lineAtHeight(chunk, h) { |
|
2855 |
var n = |
|
|
4805 | var n = chunk.first; | |
|
2856 | 4806 | outer: do { |
|
2857 | 4807 | for (var i = 0, e = chunk.children.length; i < e; ++i) { |
|
2858 | 4808 | var child = chunk.children[i], ch = child.height; |
|
2859 | 4809 | if (h < ch) { chunk = child; continue outer; } |
|
2860 | 4810 | h -= ch; |
|
2861 | 4811 | n += child.chunkSize(); |
|
2862 | 4812 | } |
|
2863 | 4813 | return n; |
|
2864 | 4814 | } while (!chunk.lines); |
|
2865 | 4815 | for (var i = 0, e = chunk.lines.length; i < e; ++i) { |
|
2866 | 4816 | var line = chunk.lines[i], lh = line.height; |
|
2867 | 4817 | if (h < lh) break; |
|
2868 | 4818 | h -= lh; |
|
2869 | 4819 | } |
|
2870 | 4820 | return n + i; |
|
2871 | 4821 | } |
|
2872 | function heightAtLine(chunk, n) { | |
|
2873 | var h = 0; | |
|
2874 | outer: do { | |
|
2875 | for (var i = 0, e = chunk.children.length; i < e; ++i) { | |
|
2876 | var child = chunk.children[i], sz = child.chunkSize(); | |
|
2877 | if (n < sz) { chunk = child; continue outer; } | |
|
2878 | n -= sz; | |
|
2879 | h += child.height; | |
|
4822 | ||
|
4823 | function heightAtLine(cm, lineObj) { | |
|
4824 | lineObj = visualLine(cm.doc, lineObj); | |
|
4825 | ||
|
4826 | var h = 0, chunk = lineObj.parent; | |
|
4827 | for (var i = 0; i < chunk.lines.length; ++i) { | |
|
4828 | var line = chunk.lines[i]; | |
|
4829 | if (line == lineObj) break; | |
|
4830 | else h += line.height; | |
|
4831 | } | |
|
4832 | for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { | |
|
4833 | for (var i = 0; i < p.children.length; ++i) { | |
|
4834 | var cur = p.children[i]; | |
|
4835 | if (cur == chunk) break; | |
|
4836 | else h += cur.height; | |
|
2880 | 4837 | } |
|
2881 | return h; | |
|
2882 | } while (!chunk.lines); | |
|
2883 | for (var i = 0; i < n; ++i) h += chunk.lines[i].height; | |
|
4838 | } | |
|
2884 | 4839 | return h; |
|
2885 | 4840 | } |
|
2886 | 4841 | |
|
2887 | // The history object 'chunks' changes that are made close together | |
|
2888 | // and at almost the same time into bigger undoable units. | |
|
2889 | function History() { | |
|
2890 | this.time = 0; | |
|
2891 | this.done = []; this.undone = []; | |
|
2892 | this.compound = 0; | |
|
2893 | this.closed = false; | |
|
2894 | } | |
|
2895 | History.prototype = { | |
|
2896 | addChange: function(start, added, old) { | |
|
2897 | this.undone.length = 0; | |
|
2898 | var time = +new Date, cur = lst(this.done), last = cur && lst(cur); | |
|
2899 | var dtime = time - this.time; | |
|
2900 | ||
|
2901 | if (cur && !this.closed && this.compound) { | |
|
2902 | cur.push({start: start, added: added, old: old}); | |
|
2903 | } else if (dtime > 400 || !last || this.closed || | |
|
2904 | last.start > start + old.length || last.start + last.added < start) { | |
|
2905 | this.done.push([{start: start, added: added, old: old}]); | |
|
2906 | this.closed = false; | |
|
4842 | function getOrder(line) { | |
|
4843 | var order = line.order; | |
|
4844 | if (order == null) order = line.order = bidiOrdering(line.text); | |
|
4845 | return order; | |
|
4846 | } | |
|
4847 | ||
|
4848 | // HISTORY | |
|
4849 | ||
|
4850 | function makeHistory() { | |
|
4851 | return { | |
|
4852 | // Arrays of history events. Doing something adds an event to | |
|
4853 | // done and clears undo. Undoing moves events from done to | |
|
4854 | // undone, redoing moves them in the other direction. | |
|
4855 | done: [], undone: [], undoDepth: Infinity, | |
|
4856 | // Used to track when changes can be merged into a single undo | |
|
4857 | // event | |
|
4858 | lastTime: 0, lastOp: null, lastOrigin: null, | |
|
4859 | // Used by the isClean() method | |
|
4860 | dirtyCounter: 0 | |
|
4861 | }; | |
|
4862 | } | |
|
4863 | ||
|
4864 | function attachLocalSpans(doc, change, from, to) { | |
|
4865 | var existing = change["spans_" + doc.id], n = 0; | |
|
4866 | doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { | |
|
4867 | if (line.markedSpans) | |
|
4868 | (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; | |
|
4869 | ++n; | |
|
4870 | }); | |
|
4871 | } | |
|
4872 | ||
|
4873 | function historyChangeFromChange(doc, change) { | |
|
4874 | var histChange = {from: change.from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; | |
|
4875 | attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); | |
|
4876 | linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); | |
|
4877 | return histChange; | |
|
4878 | } | |
|
4879 | ||
|
4880 | function addToHistory(doc, change, selAfter, opId) { | |
|
4881 | var hist = doc.history; | |
|
4882 | hist.undone.length = 0; | |
|
4883 | var time = +new Date, cur = lst(hist.done); | |
|
4884 | ||
|
4885 | if (cur && | |
|
4886 | (hist.lastOp == opId || | |
|
4887 | hist.lastOrigin == change.origin && change.origin && | |
|
4888 | ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) || | |
|
4889 | change.origin.charAt(0) == "*"))) { | |
|
4890 | // Merge this change into the last event | |
|
4891 | var last = lst(cur.changes); | |
|
4892 | if (posEq(change.from, change.to) && posEq(change.from, last.to)) { | |
|
4893 | // Optimized case for simple insertion -- don't want to add | |
|
4894 | // new changesets for every character typed | |
|
4895 | last.to = changeEnd(change); | |
|
2907 | 4896 | } else { |
|
2908 | var startBefore = Math.max(0, last.start - start), | |
|
2909 | endAfter = Math.max(0, (start + old.length) - (last.start + last.added)); | |
|
2910 | for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]); | |
|
2911 | for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]); | |
|
2912 | if (startBefore) last.start = start; | |
|
2913 | last.added += added - (old.length - startBefore - endAfter); | |
|
4897 | // Add new sub-event | |
|
4898 | cur.changes.push(historyChangeFromChange(doc, change)); | |
|
4899 | } | |
|
4900 | cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head; | |
|
4901 | } else { | |
|
4902 | // Can not be merged, start a new event. | |
|
4903 | cur = {changes: [historyChangeFromChange(doc, change)], | |
|
4904 | anchorBefore: doc.sel.anchor, headBefore: doc.sel.head, | |
|
4905 | anchorAfter: selAfter.anchor, headAfter: selAfter.head}; | |
|
4906 | hist.done.push(cur); | |
|
4907 | while (hist.done.length > hist.undoDepth) | |
|
4908 | hist.done.shift(); | |
|
4909 | if (hist.dirtyCounter < 0) | |
|
4910 | // The user has made a change after undoing past the last clean state. | |
|
4911 | // We can never get back to a clean state now until markClean() is called. | |
|
4912 | hist.dirtyCounter = NaN; | |
|
4913 | else | |
|
4914 | hist.dirtyCounter++; | |
|
4915 | } | |
|
4916 | hist.lastTime = time; | |
|
4917 | hist.lastOp = opId; | |
|
4918 | hist.lastOrigin = change.origin; | |
|
4919 | } | |
|
4920 | ||
|
4921 | function removeClearedSpans(spans) { | |
|
4922 | if (!spans) return null; | |
|
4923 | for (var i = 0, out; i < spans.length; ++i) { | |
|
4924 | if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } | |
|
4925 | else if (out) out.push(spans[i]); | |
|
4926 | } | |
|
4927 | return !out ? spans : out.length ? out : null; | |
|
4928 | } | |
|
4929 | ||
|
4930 | function getOldSpans(doc, change) { | |
|
4931 | var found = change["spans_" + doc.id]; | |
|
4932 | if (!found) return null; | |
|
4933 | for (var i = 0, nw = []; i < change.text.length; ++i) | |
|
4934 | nw.push(removeClearedSpans(found[i])); | |
|
4935 | return nw; | |
|
4936 | } | |
|
4937 | ||
|
4938 | // Used both to provide a JSON-safe object in .getHistory, and, when | |
|
4939 | // detaching a document, to split the history in two | |
|
4940 | function copyHistoryArray(events, newGroup) { | |
|
4941 | for (var i = 0, copy = []; i < events.length; ++i) { | |
|
4942 | var event = events[i], changes = event.changes, newChanges = []; | |
|
4943 | copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore, | |
|
4944 | anchorAfter: event.anchorAfter, headAfter: event.headAfter}); | |
|
4945 | for (var j = 0; j < changes.length; ++j) { | |
|
4946 | var change = changes[j], m; | |
|
4947 | newChanges.push({from: change.from, to: change.to, text: change.text}); | |
|
4948 | if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { | |
|
4949 | if (indexOf(newGroup, Number(m[1])) > -1) { | |
|
4950 | lst(newChanges)[prop] = change[prop]; | |
|
4951 | delete change[prop]; | |
|
4952 | } | |
|
4953 | } | |
|
2914 | 4954 | } |
|
2915 | this.time = time; | |
|
2916 | }, | |
|
2917 | startCompound: function() { | |
|
2918 | if (!this.compound++) this.closed = true; | |
|
2919 | }, | |
|
2920 | endCompound: function() { | |
|
2921 | if (!--this.compound) this.closed = true; | |
|
2922 | } | |
|
2923 | }; | |
|
4955 | } | |
|
4956 | return copy; | |
|
4957 | } | |
|
4958 | ||
|
4959 | // Rebasing/resetting history to deal with externally-sourced changes | |
|
4960 | ||
|
4961 | function rebaseHistSel(pos, from, to, diff) { | |
|
4962 | if (to < pos.line) { | |
|
4963 | pos.line += diff; | |
|
4964 | } else if (from < pos.line) { | |
|
4965 | pos.line = from; | |
|
4966 | pos.ch = 0; | |
|
4967 | } | |
|
4968 | } | |
|
4969 | ||
|
4970 | // Tries to rebase an array of history events given a change in the | |
|
4971 | // document. If the change touches the same lines as the event, the | |
|
4972 | // event, and everything 'behind' it, is discarded. If the change is | |
|
4973 | // before the event, the event's positions are updated. Uses a | |
|
4974 | // copy-on-write scheme for the positions, to avoid having to | |
|
4975 | // reallocate them all on every rebase, but also avoid problems with | |
|
4976 | // shared position objects being unsafely updated. | |
|
4977 | function rebaseHistArray(array, from, to, diff) { | |
|
4978 | for (var i = 0; i < array.length; ++i) { | |
|
4979 | var sub = array[i], ok = true; | |
|
4980 | for (var j = 0; j < sub.changes.length; ++j) { | |
|
4981 | var cur = sub.changes[j]; | |
|
4982 | if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); } | |
|
4983 | if (to < cur.from.line) { | |
|
4984 | cur.from.line += diff; | |
|
4985 | cur.to.line += diff; | |
|
4986 | } else if (from <= cur.to.line) { | |
|
4987 | ok = false; | |
|
4988 | break; | |
|
4989 | } | |
|
4990 | } | |
|
4991 | if (!sub.copied) { | |
|
4992 | sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore); | |
|
4993 | sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter); | |
|
4994 | sub.copied = true; | |
|
4995 | } | |
|
4996 | if (!ok) { | |
|
4997 | array.splice(0, i + 1); | |
|
4998 | i = 0; | |
|
4999 | } else { | |
|
5000 | rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore); | |
|
5001 | rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter); | |
|
5002 | } | |
|
5003 | } | |
|
5004 | } | |
|
5005 | ||
|
5006 | function rebaseHist(hist, change) { | |
|
5007 | var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; | |
|
5008 | rebaseHistArray(hist.done, from, to, diff); | |
|
5009 | rebaseHistArray(hist.undone, from, to, diff); | |
|
5010 | } | |
|
5011 | ||
|
5012 | // EVENT OPERATORS | |
|
2924 | 5013 | |
|
2925 | 5014 | function stopMethod() {e_stop(this);} |
|
2926 | 5015 | // Ensure an event has a stop method. |
|
2927 | 5016 | function addStop(event) { |
|
2928 | 5017 | if (!event.stop) event.stop = stopMethod; |
|
2929 | 5018 | return event; |
|
2930 | 5019 | } |
|
2931 | 5020 | |
|
2932 | 5021 | function e_preventDefault(e) { |
|
2933 | 5022 | if (e.preventDefault) e.preventDefault(); |
|
2934 | 5023 | else e.returnValue = false; |
|
2935 | 5024 | } |
|
2936 | 5025 | function e_stopPropagation(e) { |
|
2937 | 5026 | if (e.stopPropagation) e.stopPropagation(); |
|
2938 | 5027 | else e.cancelBubble = true; |
|
2939 | 5028 | } |
|
2940 | 5029 | function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} |
|
2941 | 5030 | CodeMirror.e_stop = e_stop; |
|
2942 | 5031 | CodeMirror.e_preventDefault = e_preventDefault; |
|
2943 | 5032 | CodeMirror.e_stopPropagation = e_stopPropagation; |
|
2944 | 5033 | |
|
2945 | 5034 | function e_target(e) {return e.target || e.srcElement;} |
|
2946 | 5035 | function e_button(e) { |
|
2947 | 5036 | var b = e.which; |
|
2948 | 5037 | if (b == null) { |
|
2949 | 5038 | if (e.button & 1) b = 1; |
|
2950 | 5039 | else if (e.button & 2) b = 3; |
|
2951 | 5040 | else if (e.button & 4) b = 2; |
|
2952 | 5041 | } |
|
2953 | 5042 | if (mac && e.ctrlKey && b == 1) b = 3; |
|
2954 | 5043 | return b; |
|
2955 | 5044 | } |
|
2956 | 5045 | |
|
2957 | // Allow 3rd-party code to override event properties by adding an override | |
|
2958 | // object to an event object. | |
|
2959 |
function |
|
|
2960 | var overridden = e.override && e.override.hasOwnProperty(prop); | |
|
2961 | return overridden ? e.override[prop] : e[prop]; | |
|
2962 | } | |
|
2963 | ||
|
2964 | // Event handler registration. If disconnect is true, it'll return a | |
|
2965 | // function that unregisters the handler. | |
|
2966 | function connect(node, type, handler, disconnect) { | |
|
2967 | if (typeof node.addEventListener == "function") { | |
|
2968 | node.addEventListener(type, handler, false); | |
|
2969 | if (disconnect) return function() {node.removeEventListener(type, handler, false);}; | |
|
2970 | } else { | |
|
2971 | var wrapHandler = function(event) {handler(event || window.event);}; | |
|
2972 | node.attachEvent("on" + type, wrapHandler); | |
|
2973 | if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; | |
|
2974 | } | |
|
2975 | } | |
|
2976 | CodeMirror.connect = connect; | |
|
5046 | // EVENT HANDLING | |
|
5047 | ||
|
5048 | function on(emitter, type, f) { | |
|
5049 | if (emitter.addEventListener) | |
|
5050 | emitter.addEventListener(type, f, false); | |
|
5051 | else if (emitter.attachEvent) | |
|
5052 | emitter.attachEvent("on" + type, f); | |
|
5053 | else { | |
|
5054 | var map = emitter._handlers || (emitter._handlers = {}); | |
|
5055 | var arr = map[type] || (map[type] = []); | |
|
5056 | arr.push(f); | |
|
5057 | } | |
|
5058 | } | |
|
5059 | ||
|
5060 | function off(emitter, type, f) { | |
|
5061 | if (emitter.removeEventListener) | |
|
5062 | emitter.removeEventListener(type, f, false); | |
|
5063 | else if (emitter.detachEvent) | |
|
5064 | emitter.detachEvent("on" + type, f); | |
|
5065 | else { | |
|
5066 | var arr = emitter._handlers && emitter._handlers[type]; | |
|
5067 | if (!arr) return; | |
|
5068 | for (var i = 0; i < arr.length; ++i) | |
|
5069 | if (arr[i] == f) { arr.splice(i, 1); break; } | |
|
5070 | } | |
|
5071 | } | |
|
5072 | ||
|
5073 | function signal(emitter, type /*, values...*/) { | |
|
5074 | var arr = emitter._handlers && emitter._handlers[type]; | |
|
5075 | if (!arr) return; | |
|
5076 | var args = Array.prototype.slice.call(arguments, 2); | |
|
5077 | for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); | |
|
5078 | } | |
|
5079 | ||
|
5080 | var delayedCallbacks, delayedCallbackDepth = 0; | |
|
5081 | function signalLater(emitter, type /*, values...*/) { | |
|
5082 | var arr = emitter._handlers && emitter._handlers[type]; | |
|
5083 | if (!arr) return; | |
|
5084 | var args = Array.prototype.slice.call(arguments, 2); | |
|
5085 | if (!delayedCallbacks) { | |
|
5086 | ++delayedCallbackDepth; | |
|
5087 | delayedCallbacks = []; | |
|
5088 | setTimeout(fireDelayed, 0); | |
|
5089 | } | |
|
5090 | function bnd(f) {return function(){f.apply(null, args);};}; | |
|
5091 | for (var i = 0; i < arr.length; ++i) | |
|
5092 | delayedCallbacks.push(bnd(arr[i])); | |
|
5093 | } | |
|
5094 | ||
|
5095 | function fireDelayed() { | |
|
5096 | --delayedCallbackDepth; | |
|
5097 | var delayed = delayedCallbacks; | |
|
5098 | delayedCallbacks = null; | |
|
5099 | for (var i = 0; i < delayed.length; ++i) delayed[i](); | |
|
5100 | } | |
|
5101 | ||
|
5102 | function hasHandler(emitter, type) { | |
|
5103 | var arr = emitter._handlers && emitter._handlers[type]; | |
|
5104 | return arr && arr.length > 0; | |
|
5105 | } | |
|
5106 | ||
|
5107 | CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal; | |
|
5108 | ||
|
5109 | // MISC UTILITIES | |
|
5110 | ||
|
5111 | // Number of pixels added to scroller and sizer to hide scrollbar | |
|
5112 | var scrollerCutOff = 30; | |
|
5113 | ||
|
5114 | // Returned or thrown by various protocols to signal 'I'm not | |
|
5115 | // handling this'. | |
|
5116 | var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; | |
|
2977 | 5117 | |
|
2978 | 5118 | function Delayed() {this.id = null;} |
|
2979 | 5119 | Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; |
|
2980 | 5120 | |
|
2981 | var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; | |
|
2982 | ||
|
2983 | // Detect drag-and-drop | |
|
2984 | var dragAndDrop = function() { | |
|
2985 | // There is *some* kind of drag-and-drop support in IE6-8, but I | |
|
2986 | // couldn't get it to work yet. | |
|
2987 | if (ie_lt9) return false; | |
|
2988 | var div = elt('div'); | |
|
2989 | return "draggable" in div || "dragDrop" in div; | |
|
2990 | }(); | |
|
2991 | ||
|
2992 | // Feature-detect whether newlines in textareas are converted to \r\n | |
|
2993 | var lineSep = function () { | |
|
2994 | var te = elt("textarea"); | |
|
2995 | te.value = "foo\nbar"; | |
|
2996 | if (te.value.indexOf("\r") > -1) return "\r\n"; | |
|
2997 | return "\n"; | |
|
2998 | }(); | |
|
2999 | ||
|
3000 | // For a reason I have yet to figure out, some browsers disallow | |
|
3001 | // word wrapping between certain characters *only* if a new inline | |
|
3002 | // element is started between them. This makes it hard to reliably | |
|
3003 | // measure the position of things, since that requires inserting an | |
|
3004 | // extra span. This terribly fragile set of regexps matches the | |
|
3005 | // character combinations that suffer from this phenomenon on the | |
|
3006 | // various browsers. | |
|
3007 | var spanAffectsWrapping = /^$/; // Won't match any two-character string | |
|
3008 | if (gecko) spanAffectsWrapping = /$'/; | |
|
3009 | else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; | |
|
3010 | else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; | |
|
3011 | ||
|
3012 | 5121 | // Counts the column offset in a string, taking tabs into account. |
|
3013 | 5122 | // Used mostly to find indentation. |
|
3014 | function countColumn(string, end, tabSize) { | |
|
5123 | function countColumn(string, end, tabSize, startIndex, startValue) { | |
|
3015 | 5124 | if (end == null) { |
|
3016 | 5125 | end = string.search(/[^\s\u00a0]/); |
|
3017 | 5126 | if (end == -1) end = string.length; |
|
3018 | 5127 | } |
|
3019 | for (var i = 0, n = 0; i < end; ++i) { | |
|
5128 | for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) { | |
|
3020 | 5129 | if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); |
|
3021 | 5130 | else ++n; |
|
3022 | 5131 | } |
|
3023 | 5132 | return n; |
|
3024 | 5133 | } |
|
3025 | ||
|
3026 | function eltOffset(node, screen) { | |
|
3027 | // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, | |
|
3028 | // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) | |
|
3029 | try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; } | |
|
3030 | catch(e) { box = {top: 0, left: 0}; } | |
|
3031 | if (!screen) { | |
|
3032 | // Get the toplevel scroll, working around browser differences. | |
|
3033 | if (window.pageYOffset == null) { | |
|
3034 | var t = document.documentElement || document.body.parentNode; | |
|
3035 | if (t.scrollTop == null) t = document.body; | |
|
3036 | box.top += t.scrollTop; box.left += t.scrollLeft; | |
|
3037 | } else { | |
|
3038 | box.top += window.pageYOffset; box.left += window.pageXOffset; | |
|
3039 | } | |
|
3040 | } | |
|
3041 | return box; | |
|
3042 | } | |
|
3043 | ||
|
3044 | function eltText(node) { | |
|
3045 | return node.textContent || node.innerText || node.nodeValue || ""; | |
|
3046 | } | |
|
5134 | CodeMirror.countColumn = countColumn; | |
|
3047 | 5135 | |
|
3048 | 5136 | var spaceStrs = [""]; |
|
3049 | 5137 | function spaceStr(n) { |
|
3050 | 5138 | while (spaceStrs.length <= n) |
|
3051 | 5139 | spaceStrs.push(lst(spaceStrs) + " "); |
|
3052 | 5140 | return spaceStrs[n]; |
|
3053 | 5141 | } |
|
3054 | 5142 | |
|
3055 | 5143 | function lst(arr) { return arr[arr.length-1]; } |
|
3056 | 5144 | |
|
3057 | 5145 | function selectInput(node) { |
|
3058 | 5146 | if (ios) { // Mobile Safari apparently has a bug where select() is broken. |
|
3059 | 5147 | node.selectionStart = 0; |
|
3060 | 5148 | node.selectionEnd = node.value.length; |
|
3061 | 5149 | } else node.select(); |
|
3062 | 5150 | } |
|
3063 | 5151 | |
|
3064 | // Operations on {line, ch} objects. | |
|
3065 | function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} | |
|
3066 | function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} | |
|
3067 | function copyPos(x) {return {line: x.line, ch: x.ch};} | |
|
5152 | function indexOf(collection, elt) { | |
|
5153 | if (collection.indexOf) return collection.indexOf(elt); | |
|
5154 | for (var i = 0, e = collection.length; i < e; ++i) | |
|
5155 | if (collection[i] == elt) return i; | |
|
5156 | return -1; | |
|
5157 | } | |
|
5158 | ||
|
5159 | function createObj(base, props) { | |
|
5160 | function Obj() {} | |
|
5161 | Obj.prototype = base; | |
|
5162 | var inst = new Obj(); | |
|
5163 | if (props) copyObj(props, inst); | |
|
5164 | return inst; | |
|
5165 | } | |
|
5166 | ||
|
5167 | function copyObj(obj, target) { | |
|
5168 | if (!target) target = {}; | |
|
5169 | for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; | |
|
5170 | return target; | |
|
5171 | } | |
|
5172 | ||
|
5173 | function emptyArray(size) { | |
|
5174 | for (var a = [], i = 0; i < size; ++i) a.push(undefined); | |
|
5175 | return a; | |
|
5176 | } | |
|
5177 | ||
|
5178 | function bind(f) { | |
|
5179 | var args = Array.prototype.slice.call(arguments, 1); | |
|
5180 | return function(){return f.apply(null, args);}; | |
|
5181 | } | |
|
5182 | ||
|
5183 | var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/; | |
|
5184 | function isWordChar(ch) { | |
|
5185 | return /\w/.test(ch) || ch > "\x80" && | |
|
5186 | (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); | |
|
5187 | } | |
|
5188 | ||
|
5189 | function isEmpty(obj) { | |
|
5190 | for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; | |
|
5191 | return true; | |
|
5192 | } | |
|
5193 | ||
|
5194 | var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/; | |
|
5195 | ||
|
5196 | // DOM UTILITIES | |
|
3068 | 5197 | |
|
3069 | 5198 | function elt(tag, content, className, style) { |
|
3070 | 5199 | var e = document.createElement(tag); |
|
3071 | 5200 | if (className) e.className = className; |
|
3072 | 5201 | if (style) e.style.cssText = style; |
|
3073 | 5202 | if (typeof content == "string") setTextContent(e, content); |
|
3074 | 5203 | else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); |
|
3075 | 5204 | return e; |
|
3076 | 5205 | } |
|
5206 | ||
|
3077 | 5207 | function removeChildren(e) { |
|
3078 | e.innerHTML = ""; | |
|
5208 | for (var count = e.childNodes.length; count > 0; --count) | |
|
5209 | e.removeChild(e.firstChild); | |
|
3079 | 5210 | return e; |
|
3080 | 5211 | } |
|
5212 | ||
|
3081 | 5213 | function removeChildrenAndAdd(parent, e) { |
|
3082 | removeChildren(parent).appendChild(e); | |
|
3083 | } | |
|
5214 | return removeChildren(parent).appendChild(e); | |
|
5215 | } | |
|
5216 | ||
|
3084 | 5217 | function setTextContent(e, str) { |
|
3085 | 5218 | if (ie_lt9) { |
|
3086 | 5219 | e.innerHTML = ""; |
|
3087 | 5220 | e.appendChild(document.createTextNode(str)); |
|
3088 | 5221 | } else e.textContent = str; |
|
3089 | 5222 | } |
|
3090 | 5223 | |
|
3091 | // Used to position the cursor after an undo/redo by finding the | |
|
3092 | // last edited character. | |
|
3093 | function editEnd(from, to) { | |
|
3094 | if (!to) return 0; | |
|
3095 | if (!from) return to.length; | |
|
3096 | for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j) | |
|
3097 | if (from.charAt(i) != to.charAt(j)) break; | |
|
3098 | return j + 1; | |
|
3099 | } | |
|
3100 | ||
|
3101 | function indexOf(collection, elt) { | |
|
3102 | if (collection.indexOf) return collection.indexOf(elt); | |
|
3103 | for (var i = 0, e = collection.length; i < e; ++i) | |
|
3104 | if (collection[i] == elt) return i; | |
|
3105 | return -1; | |
|
3106 | } | |
|
3107 | var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/; | |
|
3108 | function isWordChar(ch) { | |
|
3109 | return /\w/.test(ch) || ch > "\x80" && | |
|
3110 | (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); | |
|
5224 | function getRect(node) { | |
|
5225 | return node.getBoundingClientRect(); | |
|
5226 | } | |
|
5227 | CodeMirror.replaceGetRect = function(f) { getRect = f; }; | |
|
5228 | ||
|
5229 | // FEATURE DETECTION | |
|
5230 | ||
|
5231 | // Detect drag-and-drop | |
|
5232 | var dragAndDrop = function() { | |
|
5233 | // There is *some* kind of drag-and-drop support in IE6-8, but I | |
|
5234 | // couldn't get it to work yet. | |
|
5235 | if (ie_lt9) return false; | |
|
5236 | var div = elt('div'); | |
|
5237 | return "draggable" in div || "dragDrop" in div; | |
|
5238 | }(); | |
|
5239 | ||
|
5240 | // For a reason I have yet to figure out, some browsers disallow | |
|
5241 | // word wrapping between certain characters *only* if a new inline | |
|
5242 | // element is started between them. This makes it hard to reliably | |
|
5243 | // measure the position of things, since that requires inserting an | |
|
5244 | // extra span. This terribly fragile set of regexps matches the | |
|
5245 | // character combinations that suffer from this phenomenon on the | |
|
5246 | // various browsers. | |
|
5247 | var spanAffectsWrapping = /^$/; // Won't match any two-character string | |
|
5248 | if (gecko) spanAffectsWrapping = /$'/; | |
|
5249 | else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent)) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; | |
|
5250 | else if (webkit) spanAffectsWrapping = /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.]|\?[\w~`@#$%\^&*(_=+{[|><]/; | |
|
5251 | ||
|
5252 | var knownScrollbarWidth; | |
|
5253 | function scrollbarWidth(measure) { | |
|
5254 | if (knownScrollbarWidth != null) return knownScrollbarWidth; | |
|
5255 | var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll"); | |
|
5256 | removeChildrenAndAdd(measure, test); | |
|
5257 | if (test.offsetWidth) | |
|
5258 | knownScrollbarWidth = test.offsetHeight - test.clientHeight; | |
|
5259 | return knownScrollbarWidth || 0; | |
|
5260 | } | |
|
5261 | ||
|
5262 | var zwspSupported; | |
|
5263 | function zeroWidthElement(measure) { | |
|
5264 | if (zwspSupported == null) { | |
|
5265 | var test = elt("span", "\u200b"); | |
|
5266 | removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); | |
|
5267 | if (measure.firstChild.offsetHeight != 0) | |
|
5268 | zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8; | |
|
5269 | } | |
|
5270 | if (zwspSupported) return elt("span", "\u200b"); | |
|
5271 | else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); | |
|
3111 | 5272 | } |
|
3112 | 5273 | |
|
3113 | 5274 | // See if "".split is the broken IE version, if so, provide an |
|
3114 | 5275 | // alternative way to split lines. |
|
3115 | 5276 | var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { |
|
3116 | 5277 | var pos = 0, result = [], l = string.length; |
|
3117 | 5278 | while (pos <= l) { |
|
3118 | 5279 | var nl = string.indexOf("\n", pos); |
|
3119 | 5280 | if (nl == -1) nl = string.length; |
|
3120 | 5281 | var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); |
|
3121 | 5282 | var rt = line.indexOf("\r"); |
|
3122 | 5283 | if (rt != -1) { |
|
3123 | 5284 | result.push(line.slice(0, rt)); |
|
3124 | 5285 | pos += rt + 1; |
|
3125 | 5286 | } else { |
|
3126 | 5287 | result.push(line); |
|
3127 | 5288 | pos = nl + 1; |
|
3128 | 5289 | } |
|
3129 | 5290 | } |
|
3130 | 5291 | return result; |
|
3131 | 5292 | } : function(string){return string.split(/\r\n?|\n/);}; |
|
3132 | 5293 | CodeMirror.splitLines = splitLines; |
|
3133 | 5294 | |
|
3134 | 5295 | var hasSelection = window.getSelection ? function(te) { |
|
3135 | 5296 | try { return te.selectionStart != te.selectionEnd; } |
|
3136 | 5297 | catch(e) { return false; } |
|
3137 | 5298 | } : function(te) { |
|
3138 | 5299 | try {var range = te.ownerDocument.selection.createRange();} |
|
3139 | 5300 | catch(e) {} |
|
3140 | 5301 | if (!range || range.parentElement() != te) return false; |
|
3141 | 5302 | return range.compareEndPoints("StartToEnd", range) != 0; |
|
3142 | 5303 | }; |
|
3143 | 5304 | |
|
3144 | CodeMirror.defineMode("null", function() { | |
|
3145 | return {token: function(stream) {stream.skipToEnd();}}; | |
|
3146 | }); | |
|
3147 | CodeMirror.defineMIME("text/plain", "null"); | |
|
5305 | var hasCopyEvent = (function() { | |
|
5306 | var e = elt("div"); | |
|
5307 | if ("oncopy" in e) return true; | |
|
5308 | e.setAttribute("oncopy", "return;"); | |
|
5309 | return typeof e.oncopy == 'function'; | |
|
5310 | })(); | |
|
5311 | ||
|
5312 | // KEY NAMING | |
|
3148 | 5313 | |
|
3149 | 5314 | var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", |
|
3150 | 5315 | 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", |
|
3151 | 5316 | 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", |
|
3152 | 5317 | 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", |
|
3153 | 5318 | 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", |
|
3154 | 5319 | 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", |
|
3155 | 5320 | 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; |
|
3156 | 5321 | CodeMirror.keyNames = keyNames; |
|
3157 | 5322 | (function() { |
|
3158 | 5323 | // Number keys |
|
3159 | 5324 | for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); |
|
3160 | 5325 | // Alphabetic keys |
|
3161 | 5326 | for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); |
|
3162 | 5327 | // Function keys |
|
3163 | 5328 | for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; |
|
3164 | 5329 | })(); |
|
3165 | 5330 | |
|
3166 | CodeMirror.version = "2.36"; | |
|
5331 | // BIDI HELPERS | |
|
5332 | ||
|
5333 | function iterateBidiSections(order, from, to, f) { | |
|
5334 | if (!order) return f(from, to, "ltr"); | |
|
5335 | for (var i = 0; i < order.length; ++i) { | |
|
5336 | var part = order[i]; | |
|
5337 | if (part.from < to && part.to > from || from == to && part.to == from) | |
|
5338 | f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); | |
|
5339 | } | |
|
5340 | } | |
|
5341 | ||
|
5342 | function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } | |
|
5343 | function bidiRight(part) { return part.level % 2 ? part.from : part.to; } | |
|
5344 | ||
|
5345 | function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } | |
|
5346 | function lineRight(line) { | |
|
5347 | var order = getOrder(line); | |
|
5348 | if (!order) return line.text.length; | |
|
5349 | return bidiRight(lst(order)); | |
|
5350 | } | |
|
5351 | ||
|
5352 | function lineStart(cm, lineN) { | |
|
5353 | var line = getLine(cm.doc, lineN); | |
|
5354 | var visual = visualLine(cm.doc, line); | |
|
5355 | if (visual != line) lineN = lineNo(visual); | |
|
5356 | var order = getOrder(visual); | |
|
5357 | var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); | |
|
5358 | return Pos(lineN, ch); | |
|
5359 | } | |
|
5360 | function lineEnd(cm, lineN) { | |
|
5361 | var merged, line; | |
|
5362 | while (merged = collapsedSpanAtEnd(line = getLine(cm.doc, lineN))) | |
|
5363 | lineN = merged.find().to.line; | |
|
5364 | var order = getOrder(line); | |
|
5365 | var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); | |
|
5366 | return Pos(lineN, ch); | |
|
5367 | } | |
|
5368 | ||
|
5369 | // This is somewhat involved. It is needed in order to move | |
|
5370 | // 'visually' through bi-directional text -- i.e., pressing left | |
|
5371 | // should make the cursor go left, even when in RTL text. The | |
|
5372 | // tricky part is the 'jumps', where RTL and LTR text touch each | |
|
5373 | // other. This often requires the cursor offset to move more than | |
|
5374 | // one unit, in order to visually move one unit. | |
|
5375 | function moveVisually(line, start, dir, byUnit) { | |
|
5376 | var bidi = getOrder(line); | |
|
5377 | if (!bidi) return moveLogically(line, start, dir, byUnit); | |
|
5378 | var moveOneUnit = byUnit ? function(pos, dir) { | |
|
5379 | do pos += dir; | |
|
5380 | while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); | |
|
5381 | return pos; | |
|
5382 | } : function(pos, dir) { return pos + dir; }; | |
|
5383 | var linedir = bidi[0].level; | |
|
5384 | for (var i = 0; i < bidi.length; ++i) { | |
|
5385 | var part = bidi[i], sticky = part.level % 2 == linedir; | |
|
5386 | if ((part.from < start && part.to > start) || | |
|
5387 | (sticky && (part.from == start || part.to == start))) break; | |
|
5388 | } | |
|
5389 | var target = moveOneUnit(start, part.level % 2 ? -dir : dir); | |
|
5390 | ||
|
5391 | while (target != null) { | |
|
5392 | if (part.level % 2 == linedir) { | |
|
5393 | if (target < part.from || target > part.to) { | |
|
5394 | part = bidi[i += dir]; | |
|
5395 | target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1)); | |
|
5396 | } else break; | |
|
5397 | } else { | |
|
5398 | if (target == bidiLeft(part)) { | |
|
5399 | part = bidi[--i]; | |
|
5400 | target = part && bidiRight(part); | |
|
5401 | } else if (target == bidiRight(part)) { | |
|
5402 | part = bidi[++i]; | |
|
5403 | target = part && bidiLeft(part); | |
|
5404 | } else break; | |
|
5405 | } | |
|
5406 | } | |
|
5407 | ||
|
5408 | return target < 0 || target > line.text.length ? null : target; | |
|
5409 | } | |
|
5410 | ||
|
5411 | function moveLogically(line, start, dir, byUnit) { | |
|
5412 | var target = start + dir; | |
|
5413 | if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir; | |
|
5414 | return target < 0 || target > line.text.length ? null : target; | |
|
5415 | } | |
|
5416 | ||
|
5417 | // Bidirectional ordering algorithm | |
|
5418 | // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm | |
|
5419 | // that this (partially) implements. | |
|
5420 | ||
|
5421 | // One-char codes used for character types: | |
|
5422 | // L (L): Left-to-Right | |
|
5423 | // R (R): Right-to-Left | |
|
5424 | // r (AL): Right-to-Left Arabic | |
|
5425 | // 1 (EN): European Number | |
|
5426 | // + (ES): European Number Separator | |
|
5427 | // % (ET): European Number Terminator | |
|
5428 | // n (AN): Arabic Number | |
|
5429 | // , (CS): Common Number Separator | |
|
5430 | // m (NSM): Non-Spacing Mark | |
|
5431 | // b (BN): Boundary Neutral | |
|
5432 | // s (B): Paragraph Separator | |
|
5433 | // t (S): Segment Separator | |
|
5434 | // w (WS): Whitespace | |
|
5435 | // N (ON): Other Neutrals | |
|
5436 | ||
|
5437 | // Returns null if characters are ordered as they appear | |
|
5438 | // (left-to-right), or an array of sections ({from, to, level} | |
|
5439 | // objects) in the order in which they occur visually. | |
|
5440 | var bidiOrdering = (function() { | |
|
5441 | // Character types for codepoints 0 to 0xff | |
|
5442 | var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL"; | |
|
5443 | // Character types for codepoints 0x600 to 0x6ff | |
|
5444 | var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr"; | |
|
5445 | function charType(code) { | |
|
5446 | if (code <= 0xff) return lowTypes.charAt(code); | |
|
5447 | else if (0x590 <= code && code <= 0x5f4) return "R"; | |
|
5448 | else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600); | |
|
5449 | else if (0x700 <= code && code <= 0x8ac) return "r"; | |
|
5450 | else return "L"; | |
|
5451 | } | |
|
5452 | ||
|
5453 | var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; | |
|
5454 | var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; | |
|
5455 | // Browsers seem to always treat the boundaries of block elements as being L. | |
|
5456 | var outerType = "L"; | |
|
5457 | ||
|
5458 | return function(str) { | |
|
5459 | if (!bidiRE.test(str)) return false; | |
|
5460 | var len = str.length, types = []; | |
|
5461 | for (var i = 0, type; i < len; ++i) | |
|
5462 | types.push(type = charType(str.charCodeAt(i))); | |
|
5463 | ||
|
5464 | // W1. Examine each non-spacing mark (NSM) in the level run, and | |
|
5465 | // change the type of the NSM to the type of the previous | |
|
5466 | // character. If the NSM is at the start of the level run, it will | |
|
5467 | // get the type of sor. | |
|
5468 | for (var i = 0, prev = outerType; i < len; ++i) { | |
|
5469 | var type = types[i]; | |
|
5470 | if (type == "m") types[i] = prev; | |
|
5471 | else prev = type; | |
|
5472 | } | |
|
5473 | ||
|
5474 | // W2. Search backwards from each instance of a European number | |
|
5475 | // until the first strong type (R, L, AL, or sor) is found. If an | |
|
5476 | // AL is found, change the type of the European number to Arabic | |
|
5477 | // number. | |
|
5478 | // W3. Change all ALs to R. | |
|
5479 | for (var i = 0, cur = outerType; i < len; ++i) { | |
|
5480 | var type = types[i]; | |
|
5481 | if (type == "1" && cur == "r") types[i] = "n"; | |
|
5482 | else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } | |
|
5483 | } | |
|
5484 | ||
|
5485 | // W4. A single European separator between two European numbers | |
|
5486 | // changes to a European number. A single common separator between | |
|
5487 | // two numbers of the same type changes to that type. | |
|
5488 | for (var i = 1, prev = types[0]; i < len - 1; ++i) { | |
|
5489 | var type = types[i]; | |
|
5490 | if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; | |
|
5491 | else if (type == "," && prev == types[i+1] && | |
|
5492 | (prev == "1" || prev == "n")) types[i] = prev; | |
|
5493 | prev = type; | |
|
5494 | } | |
|
5495 | ||
|
5496 | // W5. A sequence of European terminators adjacent to European | |
|
5497 | // numbers changes to all European numbers. | |
|
5498 | // W6. Otherwise, separators and terminators change to Other | |
|
5499 | // Neutral. | |
|
5500 | for (var i = 0; i < len; ++i) { | |
|
5501 | var type = types[i]; | |
|
5502 | if (type == ",") types[i] = "N"; | |
|
5503 | else if (type == "%") { | |
|
5504 | for (var end = i + 1; end < len && types[end] == "%"; ++end) {} | |
|
5505 | var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N"; | |
|
5506 | for (var j = i; j < end; ++j) types[j] = replace; | |
|
5507 | i = end - 1; | |
|
5508 | } | |
|
5509 | } | |
|
5510 | ||
|
5511 | // W7. Search backwards from each instance of a European number | |
|
5512 | // until the first strong type (R, L, or sor) is found. If an L is | |
|
5513 | // found, then change the type of the European number to L. | |
|
5514 | for (var i = 0, cur = outerType; i < len; ++i) { | |
|
5515 | var type = types[i]; | |
|
5516 | if (cur == "L" && type == "1") types[i] = "L"; | |
|
5517 | else if (isStrong.test(type)) cur = type; | |
|
5518 | } | |
|
5519 | ||
|
5520 | // N1. A sequence of neutrals takes the direction of the | |
|
5521 | // surrounding strong text if the text on both sides has the same | |
|
5522 | // direction. European and Arabic numbers act as if they were R in | |
|
5523 | // terms of their influence on neutrals. Start-of-level-run (sor) | |
|
5524 | // and end-of-level-run (eor) are used at level run boundaries. | |
|
5525 | // N2. Any remaining neutrals take the embedding direction. | |
|
5526 | for (var i = 0; i < len; ++i) { | |
|
5527 | if (isNeutral.test(types[i])) { | |
|
5528 | for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} | |
|
5529 | var before = (i ? types[i-1] : outerType) == "L"; | |
|
5530 | var after = (end < len - 1 ? types[end] : outerType) == "L"; | |
|
5531 | var replace = before || after ? "L" : "R"; | |
|
5532 | for (var j = i; j < end; ++j) types[j] = replace; | |
|
5533 | i = end - 1; | |
|
5534 | } | |
|
5535 | } | |
|
5536 | ||
|
5537 | // Here we depart from the documented algorithm, in order to avoid | |
|
5538 | // building up an actual levels array. Since there are only three | |
|
5539 | // levels (0, 1, 2) in an implementation that doesn't take | |
|
5540 | // explicit embedding into account, we can build up the order on | |
|
5541 | // the fly, without following the level-based algorithm. | |
|
5542 | var order = [], m; | |
|
5543 | for (var i = 0; i < len;) { | |
|
5544 | if (countsAsLeft.test(types[i])) { | |
|
5545 | var start = i; | |
|
5546 | for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} | |
|
5547 | order.push({from: start, to: i, level: 0}); | |
|
5548 | } else { | |
|
5549 | var pos = i, at = order.length; | |
|
5550 | for (++i; i < len && types[i] != "L"; ++i) {} | |
|
5551 | for (var j = pos; j < i;) { | |
|
5552 | if (countsAsNum.test(types[j])) { | |
|
5553 | if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1}); | |
|
5554 | var nstart = j; | |
|
5555 | for (++j; j < i && countsAsNum.test(types[j]); ++j) {} | |
|
5556 | order.splice(at, 0, {from: nstart, to: j, level: 2}); | |
|
5557 | pos = j; | |
|
5558 | } else ++j; | |
|
5559 | } | |
|
5560 | if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1}); | |
|
5561 | } | |
|
5562 | } | |
|
5563 | if (order[0].level == 1 && (m = str.match(/^\s+/))) { | |
|
5564 | order[0].from = m[0].length; | |
|
5565 | order.unshift({from: 0, to: m[0].length, level: 0}); | |
|
5566 | } | |
|
5567 | if (lst(order).level == 1 && (m = str.match(/\s+$/))) { | |
|
5568 | lst(order).to -= m[0].length; | |
|
5569 | order.push({from: len - m[0].length, to: len, level: 0}); | |
|
5570 | } | |
|
5571 | if (order[0].level != lst(order).level) | |
|
5572 | order.push({from: len, to: len, level: order[0].level}); | |
|
5573 | ||
|
5574 | return order; | |
|
5575 | }; | |
|
5576 | })(); | |
|
5577 | ||
|
5578 | // THE END | |
|
5579 | ||
|
5580 | CodeMirror.version = "3.12"; | |
|
3167 | 5581 | |
|
3168 | 5582 | return CodeMirror; |
|
3169 | 5583 |
|
General Comments 0
You need to be logged in to leave comments.
Login now