##// END OF EJS Templates
update codemirror
marcink -
r4025:cd23cc2c default
parent child Browse files
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.cm-sb-overlap {
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.cm-sb-nonoverlap {
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-text {
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 -o-border-radius: 0;
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 padding: 0; margin: 0;
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 textarea {
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 pre.CodeMirror-cursor {
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 .cm-keymap-fat-cursor pre.CodeMirror-cursor {
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 div.CodeMirror-selected { background: #d9d9d9; }
124 .CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
230 .CodeMirror-selected { background: #d9d9d9; }
231 .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
125 232
126 .CodeMirror-searching {
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 pre.CodeMirror-cursor {
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,2039 +1,3154 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 window.CodeMirror = (function() {
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 var inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
21 // The empty scrollbar content, used solely for managing the scrollbar thumb.
22 var scrollbarInner = elt("div", null, "CodeMirror-scrollbar-inner");
23 // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself.
24 var scrollbar = elt("div", [scrollbarInner], "CodeMirror-scrollbar");
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 var mover = elt("div", [gutter, elt("div", [lineSpace], "CodeMirror-lines")], null, "position: relative");
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 var sizer = elt("div", [mover], null, "position: relative");
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 var scroller = elt("div", [sizer], "CodeMirror-scroll");
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", inputDiv.style.position = "absolute";
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 (!gecko) connect(scroller, "contextmenu", onContextMenu);
106 connect(scroller, "scroll", onScrollMain);
107 connect(scrollbar, "scroll", onScrollBar);
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(instance, addStop(e))) return;
1537 if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
122 1538 e_stop(e);
123 1539 }
124 if (options.dragDrop) {
125 connect(scroller, "dragstart", onDragStart);
126 connect(scroller, "dragenter", drag_);
127 connect(scroller, "dragover", drag_);
128 connect(scroller, "drop", operation(onDrop));
129 }
130 connect(scroller, "paste", function(){focusInput(); fastPoll();});
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) connect(sizer, "mouseup", function() {
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 handled;
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 (opera) {
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 = anchor.offsetTop, left = anchor.offsetLeft;
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) {
@@ -2042,9 +3157,11 b' window.CodeMirror = (function() {'
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];
@@ -2053,228 +3170,49 b' window.CodeMirror = (function() {'
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 var spec = CodeMirror.resolveMode(spec);
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.
@@ -2290,10 +3228,12 b' window.CodeMirror = (function() {'
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);
@@ -2303,12 +3243,243 b' window.CodeMirror = (function() {'
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;},
@@ -2339,12 +3510,19 b' window.CodeMirror = (function() {'
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 }
@@ -2359,9 +3537,195 b' window.CodeMirror = (function() {'
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) {
@@ -2369,19 +3733,21 b' window.CodeMirror = (function() {'
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.from != endCh) {
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,
@@ -2391,11 +3757,11 b' window.CodeMirror = (function() {'
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,
@@ -2405,14 +3771,18 b' window.CodeMirror = (function() {'
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 = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0);
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) {
@@ -2442,261 +3812,560 b' window.CodeMirror = (function() {'
2442 3812 }
2443 3813 }
2444 3814
2445 var newMarkers = [newHL(newText[0], first)];
3815 var newMarkers = [first];
2446 3816 if (!sameLine) {
2447 3817 // Fill gap with whole-line-spans
2448 var gap = newText.length - 2, gapMarkers;
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(newHL(newText[i+1], gapMarkers));
2455 newMarkers.push(newHL(lst(newText), last));
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 < spans.length; ++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 var lines = spans[i].marker.lines;
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 var marker = spans[i].marker.lines.push(line);
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 this.text = text;
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;
@@ -2706,22 +4375,22 b' window.CodeMirror = (function() {'
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, callbacks) {
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.cleanUp();
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 insertHeight: function(at, lines, height) {
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;
@@ -2731,6 +4400,7 b' window.CodeMirror = (function() {'
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;
@@ -2743,15 +4413,16 b' window.CodeMirror = (function() {'
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, callbacks) {
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, callbacks);
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;
@@ -2768,18 +4439,13 b' window.CodeMirror = (function() {'
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.insertHeight(at, lines, height);
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);
@@ -2816,7 +4482,6 b' window.CodeMirror = (function() {'
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();
@@ -2830,7 +4495,268 b' window.CodeMirror = (function() {'
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();
@@ -2840,19 +4766,43 b' window.CodeMirror = (function() {'
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, e = chunk.children.length; ; ++i) {
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 = 0;
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;
@@ -2869,58 +4819,197 b' window.CodeMirror = (function() {'
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.
@@ -2954,96 +5043,95 b' window.CodeMirror = (function() {'
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 e_prop(e, prop) {
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) {
@@ -3061,10 +5149,51 b' window.CodeMirror = (function() {'
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);
@@ -3074,13 +5203,17 b' window.CodeMirror = (function() {'
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 = "";
@@ -3088,26 +5221,54 b' window.CodeMirror = (function() {'
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
@@ -3141,10 +5302,14 b' window.CodeMirror = (function() {'
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",
@@ -3163,7 +5328,256 b' window.CodeMirror = (function() {'
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