##// END OF EJS Templates
Updating to CodeMirror 2.2, latest stable release.
Brian Granger -
Show More
@@ -0,0 +1,23 b''
1 .CodeMirror-dialog {
2 position: relative;
3 }
4
5 .CodeMirror-dialog > div {
6 position: absolute;
7 top: 0; left: 0; right: 0;
8 background: white;
9 border-bottom: 1px solid #eee;
10 z-index: 15;
11 padding: .1em .8em;
12 overflow: hidden;
13 color: #333;
14 }
15
16 .CodeMirror-dialog input {
17 border: none;
18 outline: none;
19 background: transparent;
20 width: 20em;
21 color: inherit;
22 font-family: monospace;
23 }
@@ -0,0 +1,63 b''
1 // Open simple dialogs on top of an editor. Relies on dialog.css.
2
3 (function() {
4 function dialogDiv(cm, template) {
5 var wrap = cm.getWrapperElement();
6 var dialog = wrap.insertBefore(document.createElement("div"), wrap.firstChild);
7 dialog.className = "CodeMirror-dialog";
8 dialog.innerHTML = '<div>' + template + '</div>';
9 return dialog;
10 }
11
12 CodeMirror.defineExtension("openDialog", function(template, callback) {
13 var dialog = dialogDiv(this, template);
14 var closed = false, me = this;
15 function close() {
16 if (closed) return;
17 closed = true;
18 dialog.parentNode.removeChild(dialog);
19 }
20 var inp = dialog.getElementsByTagName("input")[0];
21 if (inp) {
22 CodeMirror.connect(inp, "keydown", function(e) {
23 if (e.keyCode == 13 || e.keyCode == 27) {
24 CodeMirror.e_stop(e);
25 close();
26 me.focus();
27 if (e.keyCode == 13) callback(inp.value);
28 }
29 });
30 inp.focus();
31 CodeMirror.connect(inp, "blur", close);
32 }
33 return close;
34 });
35
36 CodeMirror.defineExtension("openConfirm", function(template, callbacks) {
37 var dialog = dialogDiv(this, template);
38 var buttons = dialog.getElementsByTagName("button");
39 var closed = false, me = this, blurring = 1;
40 function close() {
41 if (closed) return;
42 closed = true;
43 dialog.parentNode.removeChild(dialog);
44 me.focus();
45 }
46 buttons[0].focus();
47 for (var i = 0; i < buttons.length; ++i) {
48 var b = buttons[i];
49 (function(callback) {
50 CodeMirror.connect(b, "click", function(e) {
51 CodeMirror.e_preventDefault(e);
52 close();
53 if (callback) callback(me);
54 });
55 })(callbacks[i]);
56 CodeMirror.connect(b, "blur", function() {
57 --blurring;
58 setTimeout(function() { if (blurring <= 0) close(); }, 200);
59 });
60 CodeMirror.connect(b, "focus", function() { ++blurring; });
61 }
62 });
63 })(); No newline at end of file
@@ -0,0 +1,66 b''
1 CodeMirror.braceRangeFinder = function(cm, line) {
2 var lineText = cm.getLine(line);
3 var startChar = lineText.lastIndexOf("{");
4 if (startChar < 0 || lineText.lastIndexOf("}") > startChar) return;
5 var tokenType = cm.getTokenAt({line: line, ch: startChar}).className;
6 var count = 1, lastLine = cm.lineCount(), end;
7 outer: for (var i = line + 1; i < lastLine; ++i) {
8 var text = cm.getLine(i), pos = 0;
9 for (;;) {
10 var nextOpen = text.indexOf("{", pos), nextClose = text.indexOf("}", pos);
11 if (nextOpen < 0) nextOpen = text.length;
12 if (nextClose < 0) nextClose = text.length;
13 pos = Math.min(nextOpen, nextClose);
14 if (pos == text.length) break;
15 if (cm.getTokenAt({line: i, ch: pos + 1}).className == tokenType) {
16 if (pos == nextOpen) ++count;
17 else if (!--count) { end = i; break outer; }
18 }
19 ++pos;
20 }
21 }
22 if (end == null || end == line + 1) return;
23 return end;
24 };
25
26
27 CodeMirror.newFoldFunction = function(rangeFinder, markText) {
28 var folded = [];
29 if (markText == null) markText = '<div style="position: absolute; left: 2px; color:#600">&#x25bc;</div>%N%';
30
31 function isFolded(cm, n) {
32 for (var i = 0; i < folded.length; ++i) {
33 var start = cm.lineInfo(folded[i].start);
34 if (!start) folded.splice(i--, 1);
35 else if (start.line == n) return {pos: i, region: folded[i]};
36 }
37 }
38
39 function expand(cm, region) {
40 cm.clearMarker(region.start);
41 for (var i = 0; i < region.hidden.length; ++i)
42 cm.showLine(region.hidden[i]);
43 }
44
45 return function(cm, line) {
46 cm.operation(function() {
47 var known = isFolded(cm, line);
48 if (known) {
49 folded.splice(known.pos, 1);
50 expand(cm, known.region);
51 } else {
52 var end = rangeFinder(cm, line);
53 if (end == null) return;
54 var hidden = [];
55 for (var i = line + 1; i < end; ++i) {
56 var handle = cm.hideLine(i);
57 if (handle) hidden.push(handle);
58 }
59 var first = cm.setMarker(line, markText);
60 var region = {start: first, hidden: hidden};
61 cm.onDeleteLine(first, function() { expand(cm, region); });
62 folded.push(region);
63 }
64 });
65 };
66 };
@@ -0,0 +1,291 b''
1 // ============== Formatting extensions ============================
2 // A common storage for all mode-specific formatting features
3 if (!CodeMirror.modeExtensions) CodeMirror.modeExtensions = {};
4
5 // Returns the extension of the editor's current mode
6 CodeMirror.defineExtension("getModeExt", function () {
7 return CodeMirror.modeExtensions[this.getOption("mode")];
8 });
9
10 // If the current mode is 'htmlmixed', returns the extension of a mode located at
11 // the specified position (can be htmlmixed, css or javascript). Otherwise, simply
12 // returns the extension of the editor's current mode.
13 CodeMirror.defineExtension("getModeExtAtPos", function (pos) {
14 var token = this.getTokenAt(pos);
15 if (token && token.state && token.state.mode)
16 return CodeMirror.modeExtensions[token.state.mode == "html" ? "htmlmixed" : token.state.mode];
17 else
18 return this.getModeExt();
19 });
20
21 // Comment/uncomment the specified range
22 CodeMirror.defineExtension("commentRange", function (isComment, from, to) {
23 var curMode = this.getModeExtAtPos(this.getCursor());
24 if (isComment) { // Comment range
25 var commentedText = this.getRange(from, to);
26 this.replaceRange(curMode.commentStart + this.getRange(from, to) + curMode.commentEnd
27 , from, to);
28 if (from.line == to.line && from.ch == to.ch) { // An empty comment inserted - put cursor inside
29 this.setCursor(from.line, from.ch + curMode.commentStart.length);
30 }
31 }
32 else { // Uncomment range
33 var selText = this.getRange(from, to);
34 var startIndex = selText.indexOf(curMode.commentStart);
35 var endIndex = selText.lastIndexOf(curMode.commentEnd);
36 if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) {
37 // Take string till comment start
38 selText = selText.substr(0, startIndex)
39 // From comment start till comment end
40 + selText.substring(startIndex + curMode.commentStart.length, endIndex)
41 // From comment end till string end
42 + selText.substr(endIndex + curMode.commentEnd.length);
43 }
44 this.replaceRange(selText, from, to);
45 }
46 });
47
48 // Applies automatic mode-aware indentation to the specified range
49 CodeMirror.defineExtension("autoIndentRange", function (from, to) {
50 var cmInstance = this;
51 this.operation(function () {
52 for (var i = from.line; i <= to.line; i++) {
53 cmInstance.indentLine(i);
54 }
55 });
56 });
57
58 // Applies automatic formatting to the specified range
59 CodeMirror.defineExtension("autoFormatRange", function (from, to) {
60 var absStart = this.indexFromPos(from);
61 var absEnd = this.indexFromPos(to);
62 // Insert additional line breaks where necessary according to the
63 // mode's syntax
64 var res = this.getModeExt().autoFormatLineBreaks(this.getValue(), absStart, absEnd);
65 var cmInstance = this;
66
67 // Replace and auto-indent the range
68 this.operation(function () {
69 cmInstance.replaceRange(res, from, to);
70 var startLine = cmInstance.posFromIndex(absStart).line;
71 var endLine = cmInstance.posFromIndex(absStart + res.length).line;
72 for (var i = startLine; i <= endLine; i++) {
73 cmInstance.indentLine(i);
74 }
75 });
76 });
77
78 // Define extensions for a few modes
79
80 CodeMirror.modeExtensions["css"] = {
81 commentStart: "/*",
82 commentEnd: "*/",
83 wordWrapChars: [";", "\\{", "\\}"],
84 autoFormatLineBreaks: function (text) {
85 return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2");
86 }
87 };
88
89 CodeMirror.modeExtensions["javascript"] = {
90 commentStart: "/*",
91 commentEnd: "*/",
92 wordWrapChars: [";", "\\{", "\\}"],
93
94 getNonBreakableBlocks: function (text) {
95 var nonBreakableRegexes = [
96 new RegExp("for\\s*?\\(([\\s\\S]*?)\\)"),
97 new RegExp("'([\\s\\S]*?)('|$)"),
98 new RegExp("\"([\\s\\S]*?)(\"|$)"),
99 new RegExp("//.*([\r\n]|$)")
100 ];
101 var nonBreakableBlocks = new Array();
102 for (var i = 0; i < nonBreakableRegexes.length; i++) {
103 var curPos = 0;
104 while (curPos < text.length) {
105 var m = text.substr(curPos).match(nonBreakableRegexes[i]);
106 if (m != null) {
107 nonBreakableBlocks.push({
108 start: curPos + m.index,
109 end: curPos + m.index + m[0].length
110 });
111 curPos += m.index + Math.max(1, m[0].length);
112 }
113 else { // No more matches
114 break;
115 }
116 }
117 }
118 nonBreakableBlocks.sort(function (a, b) {
119 return a.start - b.start;
120 });
121
122 return nonBreakableBlocks;
123 },
124
125 autoFormatLineBreaks: function (text) {
126 var curPos = 0;
127 var reLinesSplitter = new RegExp("(;|\\{|\\})([^\r\n])", "g");
128 var nonBreakableBlocks = this.getNonBreakableBlocks(text);
129 if (nonBreakableBlocks != null) {
130 var res = "";
131 for (var i = 0; i < nonBreakableBlocks.length; i++) {
132 if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block
133 res += text.substring(curPos, nonBreakableBlocks[i].start).replace(reLinesSplitter, "$1\n$2");
134 curPos = nonBreakableBlocks[i].start;
135 }
136 if (nonBreakableBlocks[i].start <= curPos
137 && nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block
138 res += text.substring(curPos, nonBreakableBlocks[i].end);
139 curPos = nonBreakableBlocks[i].end;
140 }
141 }
142 if (curPos < text.length - 1) {
143 res += text.substr(curPos).replace(reLinesSplitter, "$1\n$2");
144 }
145 return res;
146 }
147 else {
148 return text.replace(reLinesSplitter, "$1\n$2");
149 }
150 }
151 };
152
153 CodeMirror.modeExtensions["xml"] = {
154 commentStart: "<!--",
155 commentEnd: "-->",
156 wordWrapChars: [">"],
157
158 autoFormatLineBreaks: function (text) {
159 var lines = text.split("\n");
160 var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)");
161 var reOpenBrackets = new RegExp("<", "g");
162 var reCloseBrackets = new RegExp("(>)([^\r\n])", "g");
163 for (var i = 0; i < lines.length; i++) {
164 var mToProcess = lines[i].match(reProcessedPortion);
165 if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces
166 lines[i] = mToProcess[1]
167 + mToProcess[2].replace(reOpenBrackets, "\n$&").replace(reCloseBrackets, "$1\n$2")
168 + mToProcess[3];
169 continue;
170 }
171 }
172
173 return lines.join("\n");
174 }
175 };
176
177 CodeMirror.modeExtensions["htmlmixed"] = {
178 commentStart: "<!--",
179 commentEnd: "-->",
180 wordWrapChars: [">", ";", "\\{", "\\}"],
181
182 getModeInfos: function (text, absPos) {
183 var modeInfos = new Array();
184 modeInfos[0] =
185 {
186 pos: 0,
187 modeExt: CodeMirror.modeExtensions["xml"],
188 modeName: "xml"
189 };
190
191 var modeMatchers = new Array();
192 modeMatchers[0] =
193 {
194 regex: new RegExp("<style[^>]*>([\\s\\S]*?)(</style[^>]*>|$)", "i"),
195 modeExt: CodeMirror.modeExtensions["css"],
196 modeName: "css"
197 };
198 modeMatchers[1] =
199 {
200 regex: new RegExp("<script[^>]*>([\\s\\S]*?)(</script[^>]*>|$)", "i"),
201 modeExt: CodeMirror.modeExtensions["javascript"],
202 modeName: "javascript"
203 };
204
205 var lastCharPos = (typeof (absPos) !== "undefined" ? absPos : text.length - 1);
206 // Detect modes for the entire text
207 for (var i = 0; i < modeMatchers.length; i++) {
208 var curPos = 0;
209 while (curPos <= lastCharPos) {
210 var m = text.substr(curPos).match(modeMatchers[i].regex);
211 if (m != null) {
212 if (m.length > 1 && m[1].length > 0) {
213 // Push block begin pos
214 var blockBegin = curPos + m.index + m[0].indexOf(m[1]);
215 modeInfos.push(
216 {
217 pos: blockBegin,
218 modeExt: modeMatchers[i].modeExt,
219 modeName: modeMatchers[i].modeName
220 });
221 // Push block end pos
222 modeInfos.push(
223 {
224 pos: blockBegin + m[1].length,
225 modeExt: modeInfos[0].modeExt,
226 modeName: modeInfos[0].modeName
227 });
228 curPos += m.index + m[0].length;
229 continue;
230 }
231 else {
232 curPos += m.index + Math.max(m[0].length, 1);
233 }
234 }
235 else { // No more matches
236 break;
237 }
238 }
239 }
240 // Sort mode infos
241 modeInfos.sort(function sortModeInfo(a, b) {
242 return a.pos - b.pos;
243 });
244
245 return modeInfos;
246 },
247
248 autoFormatLineBreaks: function (text, startPos, endPos) {
249 var modeInfos = this.getModeInfos(text);
250 var reBlockStartsWithNewline = new RegExp("^\\s*?\n");
251 var reBlockEndsWithNewline = new RegExp("\n\\s*?$");
252 var res = "";
253 // Use modes info to break lines correspondingly
254 if (modeInfos.length > 1) { // Deal with multi-mode text
255 for (var i = 1; i <= modeInfos.length; i++) {
256 var selStart = modeInfos[i - 1].pos;
257 var selEnd = (i < modeInfos.length ? modeInfos[i].pos : endPos);
258
259 if (selStart >= endPos) { // The block starts later than the needed fragment
260 break;
261 }
262 if (selStart < startPos) {
263 if (selEnd <= startPos) { // The block starts earlier than the needed fragment
264 continue;
265 }
266 selStart = startPos;
267 }
268 if (selEnd > endPos) {
269 selEnd = endPos;
270 }
271 var textPortion = text.substring(selStart, selEnd);
272 if (modeInfos[i - 1].modeName != "xml") { // Starting a CSS or JavaScript block
273 if (!reBlockStartsWithNewline.test(textPortion)
274 && selStart > 0) { // The block does not start with a line break
275 textPortion = "\n" + textPortion;
276 }
277 if (!reBlockEndsWithNewline.test(textPortion)
278 && selEnd < text.length - 1) { // The block does not end with a line break
279 textPortion += "\n";
280 }
281 }
282 res += modeInfos[i - 1].modeExt.autoFormatLineBreaks(textPortion);
283 }
284 }
285 else { // Single-mode text
286 res = modeInfos[0].modeExt.autoFormatLineBreaks(text.substring(startPos, endPos));
287 }
288
289 return res;
290 }
291 };
@@ -0,0 +1,83 b''
1 (function () {
2 function forEach(arr, f) {
3 for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
4 }
5
6 function arrayContains(arr, item) {
7 if (!Array.prototype.indexOf) {
8 var i = arr.length;
9 while (i--) {
10 if (arr[i] === item) {
11 return true;
12 }
13 }
14 return false;
15 }
16 return arr.indexOf(item) != -1;
17 }
18
19 CodeMirror.javascriptHint = function(editor) {
20 // Find the token at the cursor
21 var cur = editor.getCursor(), token = editor.getTokenAt(cur), tprop = token;
22 // If it's not a 'word-style' token, ignore the token.
23 if (!/^[\w$_]*$/.test(token.string)) {
24 token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
25 className: token.string == "." ? "property" : null};
26 }
27 // If it is a property, find out what it is a property of.
28 while (tprop.className == "property") {
29 tprop = editor.getTokenAt({line: cur.line, ch: tprop.start});
30 if (tprop.string != ".") return;
31 tprop = editor.getTokenAt({line: cur.line, ch: tprop.start});
32 if (!context) var context = [];
33 context.push(tprop);
34 }
35 return {list: getCompletions(token, context),
36 from: {line: cur.line, ch: token.start},
37 to: {line: cur.line, ch: token.end}};
38 }
39
40 var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
41 "toUpperCase toLowerCase split concat match replace search").split(" ");
42 var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
43 "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
44 var funcProps = "prototype apply call bind".split(" ");
45 var keywords = ("break case catch continue debugger default delete do else false finally for function " +
46 "if in instanceof new null return switch throw true try typeof var void while with").split(" ");
47
48 function getCompletions(token, context) {
49 var found = [], start = token.string;
50 function maybeAdd(str) {
51 if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
52 }
53 function gatherCompletions(obj) {
54 if (typeof obj == "string") forEach(stringProps, maybeAdd);
55 else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
56 else if (obj instanceof Function) forEach(funcProps, maybeAdd);
57 for (var name in obj) maybeAdd(name);
58 }
59
60 if (context) {
61 // If this is a property, see if it belongs to some object we can
62 // find in the current environment.
63 var obj = context.pop(), base;
64 if (obj.className == "variable")
65 base = window[obj.string];
66 else if (obj.className == "string")
67 base = "";
68 else if (obj.className == "atom")
69 base = 1;
70 while (base != null && context.length)
71 base = base[context.pop().string];
72 if (base != null) gatherCompletions(base);
73 }
74 else {
75 // If not, just look in the window object and any local scope
76 // (reading into JS mode internals to get at the local variables)
77 for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
78 gatherCompletions(window);
79 forEach(keywords, maybeAdd);
80 }
81 return found;
82 }
83 })();
@@ -0,0 +1,114 b''
1 // Define search commands. Depends on dialog.js or another
2 // implementation of the openDialog method.
3
4 // Replace works a little oddly -- it will do the replace on the next
5 // Ctrl-G (or whatever is bound to findNext) press. You prevent a
6 // replace by making sure the match is no longer selected when hitting
7 // Ctrl-G.
8
9 (function() {
10 function SearchState() {
11 this.posFrom = this.posTo = this.query = null;
12 this.marked = [];
13 }
14 function getSearchState(cm) {
15 return cm._searchState || (cm._searchState = new SearchState());
16 }
17 function dialog(cm, text, shortText, f) {
18 if (cm.openDialog) cm.openDialog(text, f);
19 else f(prompt(shortText, ""));
20 }
21 function confirmDialog(cm, text, shortText, fs) {
22 if (cm.openConfirm) cm.openConfirm(text, fs);
23 else if (confirm(shortText)) fs[0]();
24 }
25 function parseQuery(query) {
26 var isRE = query.match(/^\/(.*)\/$/);
27 return isRE ? new RegExp(isRE[1]) : query;
28 }
29 var queryDialog =
30 'Search: <input type="text" style="width: 10em"> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
31 function doSearch(cm, rev) {
32 var state = getSearchState(cm);
33 if (state.query) return findNext(cm, rev);
34 dialog(cm, queryDialog, "Search for:", function(query) {
35 cm.operation(function() {
36 if (!query || state.query) return;
37 state.query = parseQuery(query);
38 if (cm.lineCount() < 2000) { // This is too expensive on big documents.
39 for (var cursor = cm.getSearchCursor(query); cursor.findNext();)
40 state.marked.push(cm.markText(cursor.from(), cursor.to(), "CodeMirror-searching"));
41 }
42 state.posFrom = state.posTo = cm.getCursor();
43 findNext(cm, rev);
44 });
45 });
46 }
47 function findNext(cm, rev) {cm.operation(function() {
48 var state = getSearchState(cm);
49 var cursor = cm.getSearchCursor(state.query, rev ? state.posFrom : state.posTo);
50 if (!cursor.find(rev)) {
51 cursor = cm.getSearchCursor(state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0});
52 if (!cursor.find(rev)) return;
53 }
54 cm.setSelection(cursor.from(), cursor.to());
55 state.posFrom = cursor.from(); state.posTo = cursor.to();
56 })}
57 function clearSearch(cm) {cm.operation(function() {
58 var state = getSearchState(cm);
59 if (!state.query) return;
60 state.query = null;
61 for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear();
62 state.marked.length = 0;
63 })}
64
65 var replaceQueryDialog =
66 'Replace: <input type="text" style="width: 10em"> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
67 var replacementQueryDialog = 'With: <input type="text" style="width: 10em">';
68 var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
69 function replace(cm, all) {
70 dialog(cm, replaceQueryDialog, "Replace:", function(query) {
71 if (!query) return;
72 query = parseQuery(query);
73 dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
74 if (all) {
75 cm.operation(function() {
76 for (var cursor = cm.getSearchCursor(query); cursor.findNext();) {
77 if (typeof query != "string") {
78 var match = cm.getRange(cursor.from(), cursor.to()).match(query);
79 cursor.replace(text.replace(/\$(\d)/, function(w, i) {return match[i];}));
80 } else cursor.replace(text);
81 }
82 });
83 } else {
84 clearSearch(cm);
85 var cursor = cm.getSearchCursor(query, cm.getCursor());
86 function advance() {
87 var start = cursor.from(), match;
88 if (!(match = cursor.findNext())) {
89 cursor = cm.getSearchCursor(query);
90 if (!(match = cursor.findNext()) ||
91 (cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
92 }
93 cm.setSelection(cursor.from(), cursor.to());
94 confirmDialog(cm, doReplaceConfirm, "Replace?",
95 [function() {doReplace(match);}, advance]);
96 }
97 function doReplace(match) {
98 cursor.replace(typeof query == "string" ? text :
99 text.replace(/\$(\d)/, function(w, i) {return match[i];}));
100 advance();
101 }
102 advance();
103 }
104 });
105 });
106 }
107
108 CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
109 CodeMirror.commands.findNext = doSearch;
110 CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
111 CodeMirror.commands.clearSearch = clearSearch;
112 CodeMirror.commands.replace = replace;
113 CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
114 })();
@@ -0,0 +1,117 b''
1 (function(){
2 function SearchCursor(cm, query, pos, caseFold) {
3 this.atOccurrence = false; this.cm = cm;
4 if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase();
5
6 pos = pos ? cm.clipPos(pos) : {line: 0, ch: 0};
7 this.pos = {from: pos, to: pos};
8
9 // The matches method is filled in based on the type of query.
10 // It takes a position and a direction, and returns an object
11 // describing the next occurrence of the query, or null if no
12 // more matches were found.
13 if (typeof query != "string") // Regexp match
14 this.matches = function(reverse, pos) {
15 if (reverse) {
16 var line = cm.getLine(pos.line).slice(0, pos.ch), match = line.match(query), start = 0;
17 while (match) {
18 var ind = line.indexOf(match[0]);
19 start += ind;
20 line = line.slice(ind + 1);
21 var newmatch = line.match(query);
22 if (newmatch) match = newmatch;
23 else break;
24 start++;
25 }
26 }
27 else {
28 var line = cm.getLine(pos.line).slice(pos.ch), match = line.match(query),
29 start = match && pos.ch + line.indexOf(match[0]);
30 }
31 if (match)
32 return {from: {line: pos.line, ch: start},
33 to: {line: pos.line, ch: start + match[0].length},
34 match: match};
35 };
36 else { // String query
37 if (caseFold) query = query.toLowerCase();
38 var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
39 var target = query.split("\n");
40 // Different methods for single-line and multi-line queries
41 if (target.length == 1)
42 this.matches = function(reverse, pos) {
43 var line = fold(cm.getLine(pos.line)), len = query.length, match;
44 if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
45 : (match = line.indexOf(query, pos.ch)) != -1)
46 return {from: {line: pos.line, ch: match},
47 to: {line: pos.line, ch: match + len}};
48 };
49 else
50 this.matches = function(reverse, pos) {
51 var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(cm.getLine(ln));
52 var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
53 if (reverse ? offsetA >= pos.ch || offsetA != match.length
54 : offsetA <= pos.ch || offsetA != line.length - match.length)
55 return;
56 for (;;) {
57 if (reverse ? !ln : ln == cm.lineCount() - 1) return;
58 line = fold(cm.getLine(ln += reverse ? -1 : 1));
59 match = target[reverse ? --idx : ++idx];
60 if (idx > 0 && idx < target.length - 1) {
61 if (line != match) return;
62 else continue;
63 }
64 var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
65 if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
66 return;
67 var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB};
68 return {from: reverse ? end : start, to: reverse ? start : end};
69 }
70 };
71 }
72 }
73
74 SearchCursor.prototype = {
75 findNext: function() {return this.find(false);},
76 findPrevious: function() {return this.find(true);},
77
78 find: function(reverse) {
79 var self = this, pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to);
80 function savePosAndFail(line) {
81 var pos = {line: line, ch: 0};
82 self.pos = {from: pos, to: pos};
83 self.atOccurrence = false;
84 return false;
85 }
86
87 for (;;) {
88 if (this.pos = this.matches(reverse, pos)) {
89 this.atOccurrence = true;
90 return this.pos.match || true;
91 }
92 if (reverse) {
93 if (!pos.line) return savePosAndFail(0);
94 pos = {line: pos.line-1, ch: this.cm.getLine(pos.line-1).length};
95 }
96 else {
97 var maxLine = this.cm.lineCount();
98 if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
99 pos = {line: pos.line+1, ch: 0};
100 }
101 }
102 },
103
104 from: function() {if (this.atOccurrence) return this.pos.from;},
105 to: function() {if (this.atOccurrence) return this.pos.to;},
106
107 replace: function(newText) {
108 var self = this;
109 if (this.atOccurrence)
110 self.pos.to = this.cm.replaceRange(newText, self.pos.from, self.pos.to);
111 }
112 };
113
114 CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
115 return new SearchCursor(this, query, pos, caseFold);
116 });
117 })();
@@ -0,0 +1,16 b''
1 .CodeMirror-completions {
2 position: absolute;
3 z-index: 10;
4 overflow: hidden;
5 -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
6 -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
7 box-shadow: 2px 3px 5px rgba(0,0,0,.2);
8 }
9 .CodeMirror-completions select {
10 background: #fafafa;
11 outline: none;
12 border: none;
13 padding: 0;
14 margin: 0;
15 font-family: monospace;
16 }
@@ -0,0 +1,66 b''
1 (function() {
2 CodeMirror.simpleHint = function(editor, getHints) {
3 // We want a single cursor position.
4 if (editor.somethingSelected()) return;
5 var result = getHints(editor);
6 if (!result || !result.list.length) return;
7 var completions = result.list;
8 function insert(str) {
9 editor.replaceRange(str, result.from, result.to);
10 }
11 // When there is only one completion, use it directly.
12 if (completions.length == 1) {insert(completions[0]); return true;}
13
14 // Build the select widget
15 var complete = document.createElement("div");
16 complete.className = "CodeMirror-completions";
17 var sel = complete.appendChild(document.createElement("select"));
18 // Opera doesn't move the selection when pressing up/down in a
19 // multi-select, but it does properly support the size property on
20 // single-selects, so no multi-select is necessary.
21 if (!window.opera) sel.multiple = true;
22 for (var i = 0; i < completions.length; ++i) {
23 var opt = sel.appendChild(document.createElement("option"));
24 opt.appendChild(document.createTextNode(completions[i]));
25 }
26 sel.firstChild.selected = true;
27 sel.size = Math.min(10, completions.length);
28 var pos = editor.cursorCoords();
29 complete.style.left = pos.x + "px";
30 complete.style.top = pos.yBot + "px";
31 document.body.appendChild(complete);
32 // Hack to hide the scrollbar.
33 if (completions.length <= 10)
34 complete.style.width = (sel.clientWidth - 1) + "px";
35
36 var done = false;
37 function close() {
38 if (done) return;
39 done = true;
40 complete.parentNode.removeChild(complete);
41 }
42 function pick() {
43 insert(completions[sel.selectedIndex]);
44 close();
45 setTimeout(function(){editor.focus();}, 50);
46 }
47 CodeMirror.connect(sel, "blur", close);
48 CodeMirror.connect(sel, "keydown", function(event) {
49 var code = event.keyCode;
50 // Enter
51 if (code == 13) {CodeMirror.e_stop(event); pick();}
52 // Escape
53 else if (code == 27) {CodeMirror.e_stop(event); close(); editor.focus();}
54 else if (code != 38 && code != 40) {
55 close(); editor.focus();
56 setTimeout(function(){CodeMirror.simpleHint(editor, getHints);}, 50);
57 }
58 });
59 CodeMirror.connect(sel, "dblclick", pick);
60
61 sel.focus();
62 // Opera sometimes ignores focusing a freshly created node
63 if (window.opera) setTimeout(function(){if (!done) sel.focus();}, 100);
64 return true;
65 };
66 })();
@@ -0,0 +1,18 b''
1 .cm-s-cobalt { background: #002240; color: white; }
2 .cm-s-cobalt span.CodeMirror-selected { background: #b36539 !important; }
3 .cm-s-cobalt .CodeMirror-gutter { background: #002240; border-right: 1px solid #aaa; }
4 .cm-s-cobalt .CodeMirror-gutter-text { color: #d0d0d0; }
5 .cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white !important; }
6
7 .cm-s-cobalt span.cm-comment { color: #08f; }
8 .cm-s-cobalt span.cm-atom { color: #845dc4; }
9 .cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; }
10 .cm-s-cobalt span.cm-keyword { color: #ffee80; }
11 .cm-s-cobalt span.cm-string { color: #3ad900; }
12 .cm-s-cobalt span.cm-meta { color: #ff9d00; }
13 .cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; }
14 .cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def { color: white; }
15 .cm-s-cobalt span.cm-error { color: #9d1e15; }
16 .cm-s-cobalt span.cm-bracket { color: #d8d8d8; }
17 .cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; }
18 .cm-s-cobalt span.cm-link { color: #845dc4; }
@@ -0,0 +1,25 b''
1 .cm-s-eclipse span.cm-meta {color: #FF1717;}
2 .cm-s-eclipse span.cm-keyword { font-weight: bold; color: #7F0055; }
3 .cm-s-eclipse span.cm-atom {color: #219;}
4 .cm-s-eclipse span.cm-number {color: #164;}
5 .cm-s-eclipse span.cm-def {color: #00f;}
6 .cm-s-eclipse span.cm-variable {color: black;}
7 .cm-s-eclipse span.cm-variable-2 {color: #0000C0;}
8 .cm-s-eclipse span.cm-variable-3 {color: #0000C0;}
9 .cm-s-eclipse span.cm-property {color: black;}
10 .cm-s-eclipse span.cm-operator {color: black;}
11 .cm-s-eclipse span.cm-comment {color: #3F7F5F;}
12 .cm-s-eclipse span.cm-string {color: #2A00FF;}
13 .cm-s-eclipse span.cm-string-2 {color: #f50;}
14 .cm-s-eclipse span.cm-error {color: #f00;}
15 .cm-s-eclipse span.cm-qualifier {color: #555;}
16 .cm-s-eclipse span.cm-builtin {color: #30a;}
17 .cm-s-eclipse span.cm-bracket {color: #cc7;}
18 .cm-s-eclipse span.cm-tag {color: #170;}
19 .cm-s-eclipse span.cm-attribute {color: #00c;}
20 .cm-s-eclipse span.cm-link {color: #219;}
21
22 .cm-s-eclipse .CodeMirror-matchingbracket {
23 border:1px solid grey;
24 color:black !important;;
25 }
@@ -0,0 +1,28 b''
1 /* Based on Sublime Text's Monokai theme */
2
3 .cm-s-monokai {background: #272822; color: #f8f8f2;}
4 .cm-s-monokai span.CodeMirror-selected {background: #ffe792 !important;}
5 .cm-s-monokai .CodeMirror-gutter {background: #272822; border-right: 0px;}
6 .cm-s-monokai .CodeMirror-gutter-text {color: #d0d0d0;}
7 .cm-s-monokai .CodeMirror-cursor {border-left: 1px solid #f8f8f0 !important;}
8
9 .cm-s-monokai span.cm-comment {color: #75715e;}
10 .cm-s-monokai span.cm-atom {color: #ae81ff;}
11 .cm-s-monokai span.cm-number {color: #ae81ff;}
12
13 .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {color: #a6e22e;}
14 .cm-s-monokai span.cm-keyword {color: #f92672;}
15 .cm-s-monokai span.cm-string {color: #e6db74;}
16
17 .cm-s-monokai span.cm-variable {color: #a6e22e;}
18 .cm-s-monokai span.cm-variable-2 {color: #9effff;}
19 .cm-s-monokai span.cm-def {color: #fd971f;}
20 .cm-s-monokai span.cm-error {background: #f92672; color: #f8f8f0;}
21 .cm-s-monokai span.cm-bracket {color: #f8f8f2;}
22 .cm-s-monokai span.cm-tag {color: #f92672;}
23 .cm-s-monokai span.cm-link {color: #ae81ff;}
24
25 .cm-s-monokai .CodeMirror-matchingbracket {
26 text-decoration: underline;
27 color: white !important;
28 }
@@ -0,0 +1,21 b''
1 .cm-s-rubyblue { font:13px/1.4em Trebuchet, Verdana, sans-serif; } /* - customized editor font - */
2
3 .cm-s-rubyblue { background: #112435; color: white; }
4 .cm-s-rubyblue span.CodeMirror-selected { background: #0000FF !important; }
5 .cm-s-rubyblue .CodeMirror-gutter { background: #1F4661; border-right: 7px solid #3E7087; min-width:2.5em; }
6 .cm-s-rubyblue .CodeMirror-gutter-text { color: white; }
7 .cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white !important; }
8
9 .cm-s-rubyblue span.cm-comment { color: #999; font-style:italic; }
10 .cm-s-rubyblue span.cm-atom { color: #F4C20B; }
11 .cm-s-rubyblue span.cm-number, .cm-s-rubyblue span.cm-attribute { color: #82C6E0; }
12 .cm-s-rubyblue span.cm-keyword { color: #F0F; }
13 .cm-s-rubyblue span.cm-string { color: #F08047; }
14 .cm-s-rubyblue span.cm-meta { color: #F0F; }
15 .cm-s-rubyblue span.cm-variable-2, .cm-s-rubyblue span.cm-tag { color: #7BD827; }
16 .cm-s-rubyblue span.cm-variable-3, .cm-s-rubyblue span.cm-def { color: white; }
17 .cm-s-rubyblue span.cm-error { color: #AF2018; }
18 .cm-s-rubyblue span.cm-bracket { color: #F0F; }
19 .cm-s-rubyblue span.cm-link { color: #F4C20B; }
20 .cm-s-rubyblue span.CodeMirror-matchingbracket { color:#F0F !important; }
21 .cm-s-rubyblue span.cm-builtin, .cm-s-rubyblue span.cm-special { color: #FF9D00; }
@@ -1,33 +1,33 b''
1 1 =======================
2 2 CodeMirror in IPython
3 3 =======================
4 4
5 5 We carry a mostly unmodified copy of CodeMirror. The current version we use
6 6 is (*please update this information when updating versions*)::
7 7
8 CodeMirror 2.15
8 CodeMirror 2.2
9 9
10 10 The only changes we've applied so far are these::
11 11
12 12 diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/python/python.js b/IPython/frontend/html/notebook/static/codemirror/mode/python/python.js
13 13 index ca94e7a..fc9a503 100644
14 14 --- a/IPython/frontend/html/notebook/static/codemirror/mode/python/python.js
15 15 +++ b/IPython/frontend/html/notebook/static/codemirror/mode/python/python.js
16 16 @@ -5,7 +5,11 @@ CodeMirror.defineMode("python", function(conf, parserConf) {
17 17 return new RegExp("^((" + words.join(")|(") + "))\\b");
18 18 }
19 19
20 20 - var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
21 21 + // IPython-specific changes: add '?' as recognized character.
22 22 + //var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
23 23 + var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
24 24 + // End IPython changes.
25 25 +
26 26 var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
27 27 var doubleOperators = new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
28 28 var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
29 29
30 30
31 31 In practice it's just a one-line change, adding `\\?` to singleOperators,
32 32 surrounded by a comment. We'll turn this into a proper patchset if it ever
33 33 gets more complicated than this, but for now this note should be enough.
@@ -1,6 +1,6 b''
1 1 # CodeMirror 2
2 2
3 3 CodeMirror 2 is a rewrite of [CodeMirror
4 4 1](http://github.com/marijnh/CodeMirror). The docs live
5 [here](http://codemirror.net/manual.html), and the project page is
5 [here](http://codemirror.net/doc/manual.html), and the project page is
6 6 [http://codemirror.net/](http://codemirror.net/).
@@ -1,68 +1,104 b''
1 1 .CodeMirror {
2 2 line-height: 1em;
3 3 font-family: monospace;
4 4 }
5 5
6 6 .CodeMirror-scroll {
7 7 overflow: auto;
8 8 height: 300px;
9 9 /* This is needed to prevent an IE[67] bug where the scrolled content
10 10 is visible outside of the scrolling box. */
11 11 position: relative;
12 12 }
13 13
14 14 .CodeMirror-gutter {
15 15 position: absolute; left: 0; top: 0;
16 16 z-index: 10;
17 17 background-color: #f7f7f7;
18 18 border-right: 1px solid #eee;
19 19 min-width: 2em;
20 20 height: 100%;
21 21 }
22 22 .CodeMirror-gutter-text {
23 23 color: #aaa;
24 24 text-align: right;
25 25 padding: .4em .2em .4em .4em;
26 white-space: pre !important;
26 27 }
27 28 .CodeMirror-lines {
28 29 padding: .4em;
29 30 }
30 31
31 32 .CodeMirror pre {
32 33 -moz-border-radius: 0;
33 34 -webkit-border-radius: 0;
34 35 -o-border-radius: 0;
35 36 border-radius: 0;
36 37 border-width: 0; margin: 0; padding: 0; background: transparent;
37 38 font-family: inherit;
38 39 font-size: inherit;
39 40 padding: 0; margin: 0;
40 41 white-space: pre;
41 42 word-wrap: normal;
42 43 }
43 44
45 .CodeMirror-wrap pre {
46 word-wrap: break-word;
47 white-space: pre-wrap;
48 }
49 .CodeMirror-wrap .CodeMirror-scroll {
50 overflow-x: hidden;
51 }
52
44 53 .CodeMirror textarea {
45 font-family: inherit !important;
46 font-size: inherit !important;
54 outline: none !important;
47 55 }
48 56
49 .CodeMirror-cursor {
57 .CodeMirror pre.CodeMirror-cursor {
50 58 z-index: 10;
51 59 position: absolute;
52 60 visibility: hidden;
53 border-left: 1px solid black !important;
61 border-left: 1px solid black;
54 62 }
55 .CodeMirror-focused .CodeMirror-cursor {
63 .CodeMirror-focused pre.CodeMirror-cursor {
56 64 visibility: visible;
57 65 }
58 66
59 span.CodeMirror-selected {
60 background: #ccc !important;
61 color: HighlightText !important;
62 }
63 .CodeMirror-focused span.CodeMirror-selected {
64 background: Highlight !important;
65 }
67 span.CodeMirror-selected { background: #d9d9d9; }
68 .CodeMirror-focused span.CodeMirror-selected { background: #d2dcf8; }
69
70 .CodeMirror-searching {background: #ffa;}
71
72 /* Default theme */
73
74 .cm-s-default span.cm-keyword {color: #708;}
75 .cm-s-default span.cm-atom {color: #219;}
76 .cm-s-default span.cm-number {color: #164;}
77 .cm-s-default span.cm-def {color: #00f;}
78 .cm-s-default span.cm-variable {color: black;}
79 .cm-s-default span.cm-variable-2 {color: #05a;}
80 .cm-s-default span.cm-variable-3 {color: #085;}
81 .cm-s-default span.cm-property {color: black;}
82 .cm-s-default span.cm-operator {color: black;}
83 .cm-s-default span.cm-comment {color: #a50;}
84 .cm-s-default span.cm-string {color: #a11;}
85 .cm-s-default span.cm-string-2 {color: #f50;}
86 .cm-s-default span.cm-meta {color: #555;}
87 .cm-s-default span.cm-error {color: #f00;}
88 .cm-s-default span.cm-qualifier {color: #555;}
89 .cm-s-default span.cm-builtin {color: #30a;}
90 .cm-s-default span.cm-bracket {color: #cc7;}
91 .cm-s-default span.cm-tag {color: #170;}
92 .cm-s-default span.cm-attribute {color: #00c;}
93 .cm-s-default span.cm-header {color: #a0a;}
94 .cm-s-default span.cm-quote {color: #090;}
95 .cm-s-default span.cm-hr {color: #999;}
96 .cm-s-default span.cm-link {color: #00c;}
97
98 span.cm-header, span.cm-strong {font-weight: bold;}
99 span.cm-em {font-style: italic;}
100 span.cm-emstrong {font-style: italic; font-weight: bold;}
101 span.cm-link {text-decoration: underline;}
66 102
67 .CodeMirror-matchingbracket {color: #0f0 !important;}
68 .CodeMirror-nonmatchingbracket {color: #f22 !important;}
103 div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
104 div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
This diff has been collapsed as it changes many lines, (2190 lines changed) Show them Hide them
@@ -1,2197 +1,2761 b''
1 // CodeMirror version 2.2
2 //
1 3 // All functions that need access to the editor's state live inside
2 4 // the CodeMirror function. Below that, at the bottom of the file,
3 5 // some utilities are defined.
4 6
5 7 // CodeMirror is the only global var we claim
6 8 var CodeMirror = (function() {
7 9 // This is the function that produces an editor instance. It's
8 10 // closure is used to store the editor state.
9 11 function CodeMirror(place, givenOptions) {
10 12 // Determine effective options based on given values and defaults.
11 13 var options = {}, defaults = CodeMirror.defaults;
12 14 for (var opt in defaults)
13 15 if (defaults.hasOwnProperty(opt))
14 16 options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
15 17
16 18 var targetDocument = options["document"];
17 19 // The element in which the editor lives.
18 20 var wrapper = targetDocument.createElement("div");
19 wrapper.className = "CodeMirror";
21 wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
20 22 // This mess creates the base DOM structure for the editor.
21 23 wrapper.innerHTML =
22 '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea
23 '<textarea style="position: absolute; width: 2px;" wrap="off" ' +
24 '<div style="overflow: hidden; position: relative; width: 3px; height: 0px;">' + // Wraps and hides input textarea
25 '<textarea style="position: absolute; padding: 0; width: 1px;" wrap="off" ' +
24 26 'autocorrect="off" autocapitalize="off"></textarea></div>' +
25 '<div class="CodeMirror-scroll cm-s-' + options.theme + '">' +
27 '<div class="CodeMirror-scroll" tabindex="-1">' +
26 28 '<div style="position: relative">' + // Set to the height of the text, causes scrolling
27 '<div style="position: absolute; height: 0; width: 0; overflow: hidden;"></div>' +
28 29 '<div style="position: relative">' + // Moved around its parent to cover visible view
29 30 '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
30 31 // Provides positioning relative to (visible) text origin
31 '<div class="CodeMirror-lines"><div style="position: relative" draggable="true">' +
32 '<div class="CodeMirror-lines"><div style="position: relative">' +
33 '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden"></div>' +
32 34 '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
33 35 '<div></div>' + // This DIV contains the actual code
34 36 '</div></div></div></div></div>';
35 37 if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
36 38 // I've never seen more elegant code in my life.
37 39 var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
38 40 scroller = wrapper.lastChild, code = scroller.firstChild,
39 measure = code.firstChild, mover = measure.nextSibling,
40 gutter = mover.firstChild, gutterText = gutter.firstChild,
41 lineSpace = gutter.nextSibling.firstChild,
42 cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
43 if (options.tabindex != null) input.tabindex = options.tabindex;
41 mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
42 lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
43 cursor = measure.nextSibling, lineDiv = cursor.nextSibling;
44 themeChanged();
45 // Needed to hide big blue blinking cursor on Mobile Safari
46 if (/AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent)) input.style.width = "0px";
47 if (!webkit) lineSpace.draggable = true;
48 if (options.tabindex != null) input.tabIndex = options.tabindex;
44 49 if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
45 50
51 // Check for problem with IE innerHTML not working when we have a
52 // P (or similar) parent node.
53 try { stringWidth("x"); }
54 catch (e) {
55 if (e.message.match(/runtime/i))
56 e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
57 throw e;
58 }
59
46 60 // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
47 61 var poll = new Delayed(), highlight = new Delayed(), blinker;
48 62
49 // mode holds a mode API object. lines an array of Line objects
50 // (see Line constructor), work an array of lines that should be
51 // parsed, and history the undo history (instance of History
52 // constructor).
53 var mode, lines = [new Line("")], work, history = new History(), focused;
63 // mode holds a mode API object. doc is the tree of Line objects,
64 // work an array of lines that should be parsed, and history the
65 // undo history (instance of History constructor).
66 var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
54 67 loadMode();
55 68 // The selection. These are always maintained to point at valid
56 69 // positions. Inverted is used to remember that the user is
57 70 // selecting bottom-to-top.
58 71 var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
59 72 // Selection-related flags. shiftSelecting obviously tracks
60 // whether the user is holding shift. reducedSelection is a hack
61 // to get around the fact that we can't create inverted
62 // selections. See below.
63 var shiftSelecting, reducedSelection, lastClick, lastDoubleClick;
73 // whether the user is holding shift.
74 var shiftSelecting, lastClick, lastDoubleClick, draggingText, overwrite = false;
64 75 // Variables used by startOperation/endOperation to track what
65 76 // happened during the operation.
66 var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty;
77 var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,
78 gutterDirty, callbacks;
67 79 // Current visible range (may be bigger than the view window).
68 var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
69 // editing will hold an object describing the things we put in the
70 // textarea, to help figure out whether something changed.
80 var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
71 81 // bracketHighlighted is used to remember that a backet has been
72 82 // marked.
73 var editing, bracketHighlighted;
83 var bracketHighlighted;
74 84 // Tracks the maximum line length so that the horizontal scrollbar
75 85 // can be kept static when scrolling.
76 var maxLine = "", maxWidth;
86 var maxLine = "", maxWidth, tabText = computeTabText();
77 87
78 88 // Initialize the content.
79 89 operation(function(){setValue(options.value || ""); updateInput = false;})();
90 var history = new History();
80 91
81 92 // Register our event handlers.
82 93 connect(scroller, "mousedown", operation(onMouseDown));
94 connect(scroller, "dblclick", operation(onDoubleClick));
83 95 connect(lineSpace, "dragstart", onDragStart);
96 connect(lineSpace, "selectstart", e_preventDefault);
84 97 // Gecko browsers fire contextmenu *after* opening the menu, at
85 98 // which point we can't mess with it anymore. Context menu is
86 99 // handled in onMouseDown for Gecko.
87 100 if (!gecko) connect(scroller, "contextmenu", onContextMenu);
88 101 connect(scroller, "scroll", function() {
89 102 updateDisplay([]);
90 103 if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
91 104 if (options.onScroll) options.onScroll(instance);
92 105 });
93 106 connect(window, "resize", function() {updateDisplay(true);});
94 107 connect(input, "keyup", operation(onKeyUp));
108 connect(input, "input", fastPoll);
95 109 connect(input, "keydown", operation(onKeyDown));
96 110 connect(input, "keypress", operation(onKeyPress));
97 111 connect(input, "focus", onFocus);
98 112 connect(input, "blur", onBlur);
99 113
100 114 connect(scroller, "dragenter", e_stop);
101 115 connect(scroller, "dragover", e_stop);
102 116 connect(scroller, "drop", operation(onDrop));
103 117 connect(scroller, "paste", function(){focusInput(); fastPoll();});
104 connect(input, "paste", function(){fastPoll();});
105 connect(input, "cut", function(){fastPoll();});
118 connect(input, "paste", fastPoll);
119 connect(input, "cut", operation(function(){replaceSelection("");}));
106 120
107 121 // IE throws unspecified error in certain cases, when
108 122 // trying to access activeElement before onload
109 123 var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }
110 124 if (hasFocus) setTimeout(onFocus, 20);
111 125 else onBlur();
112 126
113 function isLine(l) {return l >= 0 && l < lines.length;}
127 function isLine(l) {return l >= 0 && l < doc.size;}
114 128 // The instance object that we'll return. Mostly calls out to
115 129 // local functions in the CodeMirror function. Some do some extra
116 130 // range checking and/or clipping. operation is used to wrap the
117 131 // call so that changes it makes are tracked, and the display is
118 132 // updated afterwards.
119 133 var instance = wrapper.CodeMirror = {
120 134 getValue: getValue,
121 135 setValue: operation(setValue),
122 136 getSelection: getSelection,
123 137 replaceSelection: operation(replaceSelection),
124 138 focus: function(){focusInput(); onFocus(); fastPoll();},
125 139 setOption: function(option, value) {
140 var oldVal = options[option];
126 141 options[option] = value;
127 if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber")
142 if (option == "mode" || option == "indentUnit") loadMode();
143 else if (option == "readOnly" && value) {onBlur(); input.blur();}
144 else if (option == "theme") themeChanged();
145 else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
146 else if (option == "tabSize") operation(tabsChanged)();
147 if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme")
128 148 operation(gutterChanged)();
129 else if (option == "mode" || option == "indentUnit") loadMode();
130 else if (option == "readOnly" && value == "nocursor") input.blur();
131 else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value);
132 149 },
133 150 getOption: function(option) {return options[option];},
134 151 undo: operation(undo),
135 152 redo: operation(redo),
136 153 indentLine: operation(function(n, dir) {
137 154 if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract");
138 155 }),
156 indentSelection: operation(indentSelected),
139 157 historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
158 clearHistory: function() {history = new History();},
140 159 matchBrackets: operation(function(){matchBrackets(true);}),
141 getTokenAt: function(pos) {
160 getTokenAt: operation(function(pos) {
142 161 pos = clipPos(pos);
143 return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch);
144 },
162 return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);
163 }),
145 164 getStateAfter: function(line) {
146 line = clipLine(line == null ? lines.length - 1: line);
165 line = clipLine(line == null ? doc.size - 1: line);
147 166 return getStateBefore(line + 1);
148 167 },
149 168 cursorCoords: function(start){
150 169 if (start == null) start = sel.inverted;
151 170 return pageCoords(start ? sel.from : sel.to);
152 171 },
153 172 charCoords: function(pos){return pageCoords(clipPos(pos));},
154 173 coordsChar: function(coords) {
155 174 var off = eltOffset(lineSpace);
156 var line = clipLine(Math.min(lines.length - 1, showingFrom + Math.floor((coords.y - off.top) / lineHeight())));
157 return clipPos({line: line, ch: charFromX(clipLine(line), coords.x - off.left)});
175 return coordsChar(coords.x - off.left, coords.y - off.top);
158 176 },
159 getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);},
160 markText: operation(function(a, b, c){return operation(markText(a, b, c));}),
177 markText: operation(markText),
178 setBookmark: setBookmark,
161 179 setMarker: operation(addGutterMarker),
162 180 clearMarker: operation(removeGutterMarker),
163 181 setLineClass: operation(setLineClass),
182 hideLine: operation(function(h) {return setLineHidden(h, true);}),
183 showLine: operation(function(h) {return setLineHidden(h, false);}),
184 onDeleteLine: function(line, f) {
185 if (typeof line == "number") {
186 if (!isLine(line)) return null;
187 line = getLine(line);
188 }
189 (line.handlers || (line.handlers = [])).push(f);
190 return line;
191 },
164 192 lineInfo: lineInfo,
165 193 addWidget: function(pos, node, scroll, vert, horiz) {
166 194 pos = localCoords(clipPos(pos));
167 195 var top = pos.yBot, left = pos.x;
168 196 node.style.position = "absolute";
169 197 code.appendChild(node);
170 198 if (vert == "over") top = pos.y;
171 199 else if (vert == "near") {
172 var vspace = Math.max(scroller.offsetHeight, lines.length * lineHeight()),
200 var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
173 201 hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
174 202 if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
175 203 top = pos.y - node.offsetHeight;
176 204 if (left + node.offsetWidth > hspace)
177 205 left = hspace - node.offsetWidth;
178 206 }
179 207 node.style.top = (top + paddingTop()) + "px";
180 208 node.style.left = node.style.right = "";
181 209 if (horiz == "right") {
182 210 left = code.clientWidth - node.offsetWidth;
183 211 node.style.right = "0px";
184 212 } else {
185 213 if (horiz == "left") left = 0;
186 214 else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2;
187 215 node.style.left = (left + paddingLeft()) + "px";
188 216 }
189 217 if (scroll)
190 218 scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
191 219 },
192 220
193 lineCount: function() {return lines.length;},
221 lineCount: function() {return doc.size;},
222 clipPos: clipPos,
194 223 getCursor: function(start) {
195 224 if (start == null) start = sel.inverted;
196 225 return copyPos(start ? sel.from : sel.to);
197 226 },
198 227 somethingSelected: function() {return !posEq(sel.from, sel.to);},
199 setCursor: operation(function(line, ch) {
200 if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch);
201 else setCursor(line, ch);
228 setCursor: operation(function(line, ch, user) {
229 if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
230 else setCursor(line, ch, user);
202 231 }),
203 setSelection: operation(function(from, to) {setSelection(clipPos(from), clipPos(to || from));}),
204 getLine: function(line) {if (isLine(line)) return lines[line].text;},
232 setSelection: operation(function(from, to, user) {
233 (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
234 }),
235 getLine: function(line) {if (isLine(line)) return getLine(line).text;},
236 getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
205 237 setLine: operation(function(line, text) {
206 if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: lines[line].text.length});
238 if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
207 239 }),
208 240 removeLine: operation(function(line) {
209 241 if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
210 242 }),
211 243 replaceRange: operation(replaceRange),
212 244 getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
213 245
246 execCommand: function(cmd) {return commands[cmd](instance);},
247 // Stuff used by commands, probably not much use to outside code.
248 moveH: operation(moveH),
249 deleteH: operation(deleteH),
250 moveV: operation(moveV),
251 toggleOverwrite: function() {overwrite = !overwrite;},
252
253 posFromIndex: function(off) {
254 var lineNo = 0, ch;
255 doc.iter(0, doc.size, function(line) {
256 var sz = line.text.length + 1;
257 if (sz > off) { ch = off; return true; }
258 off -= sz;
259 ++lineNo;
260 });
261 return clipPos({line: lineNo, ch: ch});
262 },
263 indexFromPos: function (coords) {
264 if (coords.line < 0 || coords.ch < 0) return 0;
265 var index = coords.ch;
266 doc.iter(0, coords.line, function (line) {
267 index += line.text.length + 1;
268 });
269 return index;
270 },
271
214 272 operation: function(f){return operation(f)();},
215 273 refresh: function(){updateDisplay(true);},
216 274 getInputField: function(){return input;},
217 275 getWrapperElement: function(){return wrapper;},
218 276 getScrollerElement: function(){return scroller;},
219 277 getGutterElement: function(){return gutter;}
220 278 };
221 279
280 function getLine(n) { return getLineAt(doc, n); }
281 function updateLineHeight(line, height) {
282 gutterDirty = true;
283 var diff = height - line.height;
284 for (var n = line; n; n = n.parent) n.height += diff;
285 }
286
222 287 function setValue(code) {
223 history = null;
224 288 var top = {line: 0, ch: 0};
225 updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length},
289 updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
226 290 splitLines(code), top, top);
227 history = new History();
228 291 updateInput = true;
229 292 }
230 293 function getValue(code) {
231 294 var text = [];
232 for (var i = 0, l = lines.length; i < l; ++i)
233 text.push(lines[i].text);
295 doc.iter(0, doc.size, function(line) { text.push(line.text); });
234 296 return text.join("\n");
235 297 }
236 298
237 299 function onMouseDown(e) {
300 setShift(e.shiftKey);
238 301 // Check whether this is a click in a widget
239 302 for (var n = e_target(e); n != wrapper; n = n.parentNode)
240 303 if (n.parentNode == code && n != mover) return;
241 304
242 // First, see if this is a click in the gutter
305 // See if this is a click in the gutter
243 306 for (var n = e_target(e); n != wrapper; n = n.parentNode)
244 307 if (n.parentNode == gutterText) {
245 308 if (options.onGutterClick)
246 309 options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
247 310 return e_preventDefault(e);
248 311 }
249 312
250 313 var start = posFromMouse(e);
251 314
252 315 switch (e_button(e)) {
253 316 case 3:
254 317 if (gecko && !mac) onContextMenu(e);
255 318 return;
256 319 case 2:
257 320 if (start) setCursor(start.line, start.ch, true);
258 321 return;
259 322 }
260 323 // For button 1, if it was clicked inside the editor
261 324 // (posFromMouse returning non-null), we have to adjust the
262 325 // selection.
263 326 if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
264 327
265 328 if (!focused) onFocus();
266 329
267 330 var now = +new Date;
268 if (lastDoubleClick > now - 400) {
331 if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
269 332 e_preventDefault(e);
333 setTimeout(focusInput, 20);
270 334 return selectLine(start.line);
271 } else if (lastClick > now - 400) {
272 lastDoubleClick = now;
335 } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
336 lastDoubleClick = {time: now, pos: start};
273 337 e_preventDefault(e);
274 338 return selectWordAt(start);
275 } else { lastClick = now; }
339 } else { lastClick = {time: now, pos: start}; }
276 340
277 341 var last = start, going;
278 342 if (dragAndDrop && !posEq(sel.from, sel.to) &&
279 343 !posLess(start, sel.from) && !posLess(sel.to, start)) {
280 344 // Let the drag handler handle this.
345 if (webkit) lineSpace.draggable = true;
346 var up = connect(targetDocument, "mouseup", operation(function(e2) {
347 if (webkit) lineSpace.draggable = false;
348 draggingText = false;
349 up();
350 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
351 e_preventDefault(e2);
352 setCursor(start.line, start.ch, true);
353 focusInput();
354 }
355 }), true);
356 draggingText = true;
281 357 return;
282 358 }
283 359 e_preventDefault(e);
284 360 setCursor(start.line, start.ch, true);
285 361
286 362 function extend(e) {
287 363 var cur = posFromMouse(e, true);
288 364 if (cur && !posEq(cur, last)) {
289 365 if (!focused) onFocus();
290 366 last = cur;
291 367 setSelectionUser(start, cur);
292 368 updateInput = false;
293 369 var visible = visibleLines();
294 370 if (cur.line >= visible.to || cur.line < visible.from)
295 371 going = setTimeout(operation(function(){extend(e);}), 150);
296 372 }
297 373 }
298 374
299 375 var move = connect(targetDocument, "mousemove", operation(function(e) {
300 376 clearTimeout(going);
301 377 e_preventDefault(e);
302 378 extend(e);
303 379 }), true);
304 380 var up = connect(targetDocument, "mouseup", operation(function(e) {
305 381 clearTimeout(going);
306 382 var cur = posFromMouse(e);
307 383 if (cur) setSelectionUser(start, cur);
308 384 e_preventDefault(e);
309 385 focusInput();
310 386 updateInput = true;
311 387 move(); up();
312 388 }), true);
313 389 }
390 function onDoubleClick(e) {
391 for (var n = e_target(e); n != wrapper; n = n.parentNode)
392 if (n.parentNode == gutterText) return e_preventDefault(e);
393 var start = posFromMouse(e);
394 if (!start) return;
395 lastDoubleClick = {time: +new Date, pos: start};
396 e_preventDefault(e);
397 selectWordAt(start);
398 }
314 399 function onDrop(e) {
315 400 e.preventDefault();
316 401 var pos = posFromMouse(e, true), files = e.dataTransfer.files;
317 402 if (!pos || options.readOnly) return;
318 403 if (files && files.length && window.FileReader && window.File) {
319 404 function loadFile(file, i) {
320 405 var reader = new FileReader;
321 406 reader.onload = function() {
322 407 text[i] = reader.result;
323 if (++read == n) replaceRange(text.join(""), clipPos(pos), clipPos(pos));
408 if (++read == n) {
409 pos = clipPos(pos);
410 operation(function() {
411 var end = replaceRange(text.join(""), pos, pos);
412 setSelectionUser(pos, end);
413 })();
414 }
324 415 };
325 416 reader.readAsText(file);
326 417 }
327 418 var n = files.length, text = Array(n), read = 0;
328 419 for (var i = 0; i < n; ++i) loadFile(files[i], i);
329 420 }
330 421 else {
331 422 try {
332 423 var text = e.dataTransfer.getData("Text");
333 if (text) replaceRange(text, pos, pos);
424 if (text) {
425 var end = replaceRange(text, pos, pos);
426 var curFrom = sel.from, curTo = sel.to;
427 setSelectionUser(pos, end);
428 if (draggingText) replaceRange("", curFrom, curTo);
429 focusInput();
430 }
334 431 }
335 432 catch(e){}
336 433 }
337 434 }
338 435 function onDragStart(e) {
339 436 var txt = getSelection();
340 437 // This will reset escapeElement
341 438 htmlEscape(txt);
342 439 e.dataTransfer.setDragImage(escapeElement, 0, 0);
343 440 e.dataTransfer.setData("Text", txt);
344 441 }
442 function handleKeyBinding(e) {
443 var name = keyNames[e.keyCode], next = keyMap[options.keyMap].auto, bound, dropShift;
444 if (name == null || e.altGraphKey) {
445 if (next) options.keyMap = next;
446 return null;
447 }
448 if (e.altKey) name = "Alt-" + name;
449 if (e.ctrlKey) name = "Ctrl-" + name;
450 if (e.metaKey) name = "Cmd-" + name;
451 if (e.shiftKey && (bound = lookupKey("Shift-" + name, options.extraKeys, options.keyMap))) {
452 dropShift = true;
453 } else {
454 bound = lookupKey(name, options.extraKeys, options.keyMap);
455 }
456 if (typeof bound == "string") {
457 if (commands.propertyIsEnumerable(bound)) bound = commands[bound];
458 else bound = null;
459 }
460 if (next && (bound || !isModifierKey(e))) options.keyMap = next;
461 if (!bound) return false;
462 if (dropShift) {
463 var prevShift = shiftSelecting;
464 shiftSelecting = null;
465 bound(instance);
466 shiftSelecting = prevShift;
467 } else bound(instance);
468 e_preventDefault(e);
469 return true;
470 }
471 var lastStoppedKey = null;
345 472 function onKeyDown(e) {
346 473 if (!focused) onFocus();
347
348 474 var code = e.keyCode;
349 475 // IE does strange things with escape.
350 476 if (ie && code == 27) { e.returnValue = false; }
351 // Tries to detect ctrl on non-mac, cmd on mac.
352 var mod = (mac ? e.metaKey : e.ctrlKey) && !e.altKey, anyMod = e.ctrlKey || e.altKey || e.metaKey;
353 if (code == 16 || e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
354 else shiftSelecting = null;
477 setShift(code == 16 || e.shiftKey);
355 478 // First give onKeyEvent option a chance to handle this.
356 479 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
357
358 if (code == 33 || code == 34) {scrollPage(code == 34); return e_preventDefault(e);} // page up/down
359 if (mod && ((code == 36 || code == 35) || // ctrl-home/end
360 mac && (code == 38 || code == 40))) { // cmd-up/down
361 scrollEnd(code == 36 || code == 38); return e_preventDefault(e);
362 }
363 if (mod && code == 65) {selectAll(); return e_preventDefault(e);} // ctrl-a
364 if (!options.readOnly) {
365 if (!anyMod && code == 13) {return;} // enter
366 if (!anyMod && code == 9 && handleTab(e.shiftKey)) return e_preventDefault(e); // tab
367 if (mod && code == 90) {undo(); return e_preventDefault(e);} // ctrl-z
368 if (mod && ((e.shiftKey && code == 90) || code == 89)) {redo(); return e_preventDefault(e);} // ctrl-shift-z, ctrl-y
369 }
370 if (code == 36) { if (options.smartHome) { smartHome(); return e_preventDefault(e); } }
371
372 // Key id to use in the movementKeys map. We also pass it to
373 // fastPoll in order to 'self learn'. We need this because
374 // reducedSelection, the hack where we collapse the selection to
375 // its start when it is inverted and a movement key is pressed
376 // (and later restore it again), shouldn't be used for
377 // non-movement keys.
378 curKeyId = (mod ? "c" : "") + (e.altKey ? "a" : "") + code;
379 if (sel.inverted && movementKeys[curKeyId] === true) {
380 var range = selRange(input);
381 if (range) {
382 reducedSelection = {anchor: range.start};
383 setSelRange(input, range.start, range.start);
384 }
385 }
386 // Don't save the key as a movementkey unless it had a modifier
387 if (!mod && !e.altKey) curKeyId = null;
388 fastPoll(curKeyId);
389 }
390 function onKeyUp(e) {
391 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
392 if (reducedSelection) {
393 reducedSelection = null;
394 updateInput = true;
480 var handled = handleKeyBinding(e);
481 if (window.opera) {
482 lastStoppedKey = handled ? e.keyCode : null;
483 // Opera has no cut event... we try to at least catch the key combo
484 if (!handled && (mac ? e.metaKey : e.ctrlKey) && e.keyCode == 88)
485 replaceSelection("");
395 486 }
396 if (e.keyCode == 16) shiftSelecting = null;
397 487 }
398 488 function onKeyPress(e) {
489 if (window.opera && e.keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
399 490 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
491 if (window.opera && !e.which && handleKeyBinding(e)) return;
400 492 if (options.electricChars && mode.electricChars) {
401 493 var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode);
402 494 if (mode.electricChars.indexOf(ch) > -1)
403 setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50);
495 setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
404 496 }
405 var code = e.keyCode;
406 // Re-stop tab and enter. Necessary on some browsers.
407 if (code == 13) {if (!options.readOnly) handleEnter(); e_preventDefault(e);}
408 else if (!e.ctrlKey && !e.altKey && !e.metaKey && code == 9 && options.tabMode != "default") e_preventDefault(e);
409 else fastPoll(curKeyId);
497 fastPoll();
498 }
499 function onKeyUp(e) {
500 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
501 if (e.keyCode == 16) shiftSelecting = null;
410 502 }
411 503
412 504 function onFocus() {
413 if (options.readOnly == "nocursor") return;
505 if (options.readOnly) return;
414 506 if (!focused) {
415 507 if (options.onFocus) options.onFocus(instance);
416 508 focused = true;
417 509 if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
418 510 wrapper.className += " CodeMirror-focused";
419 if (!leaveInputAlone) prepareInput();
511 if (!leaveInputAlone) resetInput(true);
420 512 }
421 513 slowPoll();
422 514 restartBlink();
423 515 }
424 516 function onBlur() {
425 517 if (focused) {
426 518 if (options.onBlur) options.onBlur(instance);
427 519 focused = false;
428 520 wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
429 521 }
430 522 clearInterval(blinker);
431 523 setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
432 524 }
433 525
434 526 // Replace the range from from to to by the strings in newText.
435 527 // Afterwards, set the selection to selFrom, selTo.
436 528 function updateLines(from, to, newText, selFrom, selTo) {
437 529 if (history) {
438 530 var old = [];
439 for (var i = from.line, e = to.line + 1; i < e; ++i) old.push(lines[i].text);
531 doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
440 532 history.addChange(from.line, newText.length, old);
441 533 while (history.done.length > options.undoDepth) history.done.shift();
442 534 }
443 535 updateLinesNoUndo(from, to, newText, selFrom, selTo);
444 536 }
445 537 function unredoHelper(from, to) {
446 538 var change = from.pop();
447 539 if (change) {
448 540 var replaced = [], end = change.start + change.added;
449 for (var i = change.start; i < end; ++i) replaced.push(lines[i].text);
541 doc.iter(change.start, end, function(line) { replaced.push(line.text); });
450 542 to.push({start: change.start, added: change.old.length, old: replaced});
451 543 var pos = clipPos({line: change.start + change.old.length - 1,
452 544 ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
453 updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos);
545 updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
454 546 updateInput = true;
455 547 }
456 548 }
457 549 function undo() {unredoHelper(history.done, history.undone);}
458 550 function redo() {unredoHelper(history.undone, history.done);}
459 551
460 552 function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
461 553 var recomputeMaxLength = false, maxLineLength = maxLine.length;
462 for (var i = from.line; i <= to.line; ++i) {
463 if (lines[i].text.length == maxLineLength) {recomputeMaxLength = true; break;}
464 }
554 if (!options.lineWrapping)
555 doc.iter(from.line, to.line, function(line) {
556 if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
557 });
558 if (from.line != to.line || newText.length > 1) gutterDirty = true;
465 559
466 var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line];
560 var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
467 561 // First adjust the line structure, taking some care to leave highlighting intact.
468 if (firstLine == lastLine) {
562 if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") {
563 // This is a whole-line replace. Treated specially to make
564 // sure line objects move the way they are supposed to.
565 var added = [], prevLine = null;
566 if (from.line) {
567 prevLine = getLine(from.line - 1);
568 prevLine.fixMarkEnds(lastLine);
569 } else lastLine.fixMarkStarts();
570 for (var i = 0, e = newText.length - 1; i < e; ++i)
571 added.push(Line.inheritMarks(newText[i], prevLine));
572 if (nlines) doc.remove(from.line, nlines, callbacks);
573 if (added.length) doc.insert(from.line, added);
574 } else if (firstLine == lastLine) {
469 575 if (newText.length == 1)
470 576 firstLine.replace(from.ch, to.ch, newText[0]);
471 577 else {
472 578 lastLine = firstLine.split(to.ch, newText[newText.length-1]);
473 var spliceargs = [from.line + 1, nlines];
474 firstLine.replace(from.ch, firstLine.text.length, newText[0]);
475 for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
476 spliceargs.push(lastLine);
477 lines.splice.apply(lines, spliceargs);
478 }
479 }
480 else if (newText.length == 1) {
481 firstLine.replace(from.ch, firstLine.text.length, newText[0] + lastLine.text.slice(to.ch));
482 lines.splice(from.line + 1, nlines);
483 }
484 else {
485 var spliceargs = [from.line + 1, nlines - 1];
486 firstLine.replace(from.ch, firstLine.text.length, newText[0]);
487 lastLine.replace(0, to.ch, newText[newText.length-1]);
488 for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
489 lines.splice.apply(lines, spliceargs);
490 }
491
492
493 for (var i = from.line, e = i + newText.length; i < e; ++i) {
494 var l = lines[i].text;
495 if (l.length > maxLineLength) {
496 maxLine = l; maxLineLength = l.length; maxWidth = null;
497 recomputeMaxLength = false;
579 firstLine.replace(from.ch, null, newText[0]);
580 firstLine.fixMarkEnds(lastLine);
581 var added = [];
582 for (var i = 1, e = newText.length - 1; i < e; ++i)
583 added.push(Line.inheritMarks(newText[i], firstLine));
584 added.push(lastLine);
585 doc.insert(from.line + 1, added);
498 586 }
499 }
500 if (recomputeMaxLength) {
501 maxLineLength = 0; maxLine = ""; maxWidth = null;
502 for (var i = 0, e = lines.length; i < e; ++i) {
503 var l = lines[i].text;
587 } else if (newText.length == 1) {
588 firstLine.replace(from.ch, null, newText[0]);
589 lastLine.replace(null, to.ch, "");
590 firstLine.append(lastLine);
591 doc.remove(from.line + 1, nlines, callbacks);
592 } else {
593 var added = [];
594 firstLine.replace(from.ch, null, newText[0]);
595 lastLine.replace(null, to.ch, newText[newText.length-1]);
596 firstLine.fixMarkEnds(lastLine);
597 for (var i = 1, e = newText.length - 1; i < e; ++i)
598 added.push(Line.inheritMarks(newText[i], firstLine));
599 if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
600 doc.insert(from.line + 1, added);
601 }
602 if (options.lineWrapping) {
603 var perLine = scroller.clientWidth / charWidth() - 3;
604 doc.iter(from.line, from.line + newText.length, function(line) {
605 if (line.hidden) return;
606 var guess = Math.ceil(line.text.length / perLine) || 1;
607 if (guess != line.height) updateLineHeight(line, guess);
608 });
609 } else {
610 doc.iter(from.line, i + newText.length, function(line) {
611 var l = line.text;
504 612 if (l.length > maxLineLength) {
505 maxLineLength = l.length; maxLine = l;
613 maxLine = l; maxLineLength = l.length; maxWidth = null;
614 recomputeMaxLength = false;
506 615 }
616 });
617 if (recomputeMaxLength) {
618 maxLineLength = 0; maxLine = ""; maxWidth = null;
619 doc.iter(0, doc.size, function(line) {
620 var l = line.text;
621 if (l.length > maxLineLength) {
622 maxLineLength = l.length; maxLine = l;
623 }
624 });
507 625 }
508 626 }
509 627
510 628 // Add these lines to the work array, so that they will be
511 629 // highlighted. Adjust work lines if lines were added/removed.
512 630 var newWork = [], lendiff = newText.length - nlines - 1;
513 631 for (var i = 0, l = work.length; i < l; ++i) {
514 632 var task = work[i];
515 633 if (task < from.line) newWork.push(task);
516 634 else if (task > to.line) newWork.push(task + lendiff);
517 635 }
518 if (newText.length < 5) {
519 highlightLines(from.line, from.line + newText.length);
520 newWork.push(from.line + newText.length);
521 } else {
522 newWork.push(from.line);
523 }
636 var hlEnd = from.line + Math.min(newText.length, 500);
637 highlightLines(from.line, hlEnd);
638 newWork.push(hlEnd);
524 639 work = newWork;
525 640 startWorker(100);
526 641 // Remember that these lines changed, for updating the display
527 642 changes.push({from: from.line, to: to.line + 1, diff: lendiff});
528 textChanged = {from: from, to: to, text: newText};
643 var changeObj = {from: from, to: to, text: newText};
644 if (textChanged) {
645 for (var cur = textChanged; cur.next; cur = cur.next) {}
646 cur.next = changeObj;
647 } else textChanged = changeObj;
529 648
530 649 // Update the selection
531 650 function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
532 651 setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
533 652
534 653 // Make sure the scroll-size div has the correct height.
535 code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
654 code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
536 655 }
537 656
538 657 function replaceRange(code, from, to) {
539 658 from = clipPos(from);
540 659 if (!to) to = from; else to = clipPos(to);
541 660 code = splitLines(code);
542 661 function adjustPos(pos) {
543 662 if (posLess(pos, from)) return pos;
544 663 if (!posLess(to, pos)) return end;
545 664 var line = pos.line + code.length - (to.line - from.line) - 1;
546 665 var ch = pos.ch;
547 666 if (pos.line == to.line)
548 667 ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0));
549 668 return {line: line, ch: ch};
550 669 }
551 670 var end;
552 671 replaceRange1(code, from, to, function(end1) {
553 672 end = end1;
554 673 return {from: adjustPos(sel.from), to: adjustPos(sel.to)};
555 674 });
556 675 return end;
557 676 }
558 677 function replaceSelection(code, collapse) {
559 678 replaceRange1(splitLines(code), sel.from, sel.to, function(end) {
560 679 if (collapse == "end") return {from: end, to: end};
561 680 else if (collapse == "start") return {from: sel.from, to: sel.from};
562 681 else return {from: sel.from, to: end};
563 682 });
564 683 }
565 684 function replaceRange1(code, from, to, computeSel) {
566 685 var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length;
567 686 var newSel = computeSel({line: from.line + code.length - 1, ch: endch});
568 687 updateLines(from, to, code, newSel.from, newSel.to);
569 688 }
570 689
571 690 function getRange(from, to) {
572 691 var l1 = from.line, l2 = to.line;
573 if (l1 == l2) return lines[l1].text.slice(from.ch, to.ch);
574 var code = [lines[l1].text.slice(from.ch)];
575 for (var i = l1 + 1; i < l2; ++i) code.push(lines[i].text);
576 code.push(lines[l2].text.slice(0, to.ch));
692 if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
693 var code = [getLine(l1).text.slice(from.ch)];
694 doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
695 code.push(getLine(l2).text.slice(0, to.ch));
577 696 return code.join("\n");
578 697 }
579 698 function getSelection() {
580 699 return getRange(sel.from, sel.to);
581 700 }
582 701
583 702 var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
584 703 function slowPoll() {
585 704 if (pollingFast) return;
586 poll.set(2000, function() {
705 poll.set(options.pollInterval, function() {
587 706 startOperation();
588 707 readInput();
589 708 if (focused) slowPoll();
590 709 endOperation();
591 710 });
592 711 }
593 function fastPoll(keyId) {
712 function fastPoll() {
594 713 var missed = false;
595 714 pollingFast = true;
596 715 function p() {
597 716 startOperation();
598 717 var changed = readInput();
599 if (changed && keyId) {
600 if (changed == "moved" && movementKeys[keyId] == null) movementKeys[keyId] = true;
601 if (changed == "changed") movementKeys[keyId] = false;
602 }
603 if (!changed && !missed) {missed = true; poll.set(80, p);}
718 if (!changed && !missed) {missed = true; poll.set(60, p);}
604 719 else {pollingFast = false; slowPoll();}
605 720 endOperation();
606 721 }
607 722 poll.set(20, p);
608 723 }
609 724
610 // Inspects the textarea, compares its state (content, selection)
611 // to the data in the editing variable, and updates the editor
612 // content or cursor if something changed.
725 // Previnput is a hack to work with IME. If we reset the textarea
726 // on every change, that breaks IME. So we look for changes
727 // compared to the previous content instead. (Modern browsers have
728 // events that indicate IME taking place, but these are not widely
729 // supported or compatible enough yet to rely on.)
730 var prevInput = "";
613 731 function readInput() {
614 if (leaveInputAlone || !focused) return;
615 var changed = false, text = input.value, sr = selRange(input);
616 if (!sr) return false;
617 var changed = editing.text != text, rs = reducedSelection;
618 var moved = changed || sr.start != editing.start || sr.end != (rs ? editing.start : editing.end);
619 if (!moved && !rs) return false;
620 if (changed) {
621 shiftSelecting = reducedSelection = null;
622 if (options.readOnly) {updateInput = true; return "changed";}
623 }
624
625 // Compute selection start and end based on start/end offsets in textarea
626 function computeOffset(n, startLine) {
627 var pos = 0;
628 for (;;) {
629 var found = text.indexOf("\n", pos);
630 if (found == -1 || (text.charAt(found-1) == "\r" ? found - 1 : found) >= n)
631 return {line: startLine, ch: n - pos};
632 ++startLine;
633 pos = found + 1;
634 }
635 }
636 var from = computeOffset(sr.start, editing.from),
637 to = computeOffset(sr.end, editing.from);
638 // Here we have to take the reducedSelection hack into account,
639 // so that you can, for example, press shift-up at the start of
640 // your selection and have the right thing happen.
641 if (rs) {
642 var head = sr.start == rs.anchor ? to : from;
643 var tail = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to;
644 if (sel.inverted = posLess(head, tail)) { from = head; to = tail; }
645 else { reducedSelection = null; from = tail; to = head; }
646 }
647
648 // In some cases (cursor on same line as before), we don't have
649 // to update the textarea content at all.
650 if (from.line == to.line && from.line == sel.from.line && from.line == sel.to.line && !shiftSelecting)
651 updateInput = false;
652
653 // Magic mess to extract precise edited range from the changed
654 // string.
655 if (changed) {
656 var start = 0, end = text.length, len = Math.min(end, editing.text.length);
657 var c, line = editing.from, nl = -1;
658 while (start < len && (c = text.charAt(start)) == editing.text.charAt(start)) {
659 ++start;
660 if (c == "\n") {line++; nl = start;}
661 }
662 var ch = nl > -1 ? start - nl : start, endline = editing.to - 1, edend = editing.text.length;
663 for (;;) {
664 c = editing.text.charAt(edend);
665 if (text.charAt(end) != c) {++end; ++edend; break;}
666 if (c == "\n") endline--;
667 if (edend <= start || end <= start) break;
668 --end; --edend;
669 }
670 var nl = editing.text.lastIndexOf("\n", edend - 1), endch = nl == -1 ? edend : edend - nl - 1;
671 updateLines({line: line, ch: ch}, {line: endline, ch: endch}, splitLines(text.slice(start, end)), from, to);
672 if (line != endline || from.line != line) updateInput = true;
673 }
674 else setSelection(from, to);
675
676 editing.text = text; editing.start = sr.start; editing.end = sr.end;
677 return changed ? "changed" : moved ? "moved" : false;
732 if (leaveInputAlone || !focused || hasSelection(input)) return false;
733 var text = input.value;
734 if (text == prevInput) return false;
735 shiftSelecting = null;
736 var same = 0, l = Math.min(prevInput.length, text.length);
737 while (same < l && prevInput[same] == text[same]) ++same;
738 if (same < prevInput.length)
739 sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};
740 else if (overwrite && posEq(sel.from, sel.to))
741 sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};
742 replaceSelection(text.slice(same), "end");
743 prevInput = text;
744 return true;
678 745 }
679
680 // Set the textarea content and selection range to match the
681 // editor state.
682 function prepareInput() {
683 var text = [];
684 var from = Math.max(0, sel.from.line - 1), to = Math.min(lines.length, sel.to.line + 2);
685 for (var i = from; i < to; ++i) text.push(lines[i].text);
686 text = input.value = text.join(lineSep);
687 var startch = sel.from.ch, endch = sel.to.ch;
688 for (var i = from; i < sel.from.line; ++i)
689 startch += lineSep.length + lines[i].text.length;
690 for (var i = from; i < sel.to.line; ++i)
691 endch += lineSep.length + lines[i].text.length;
692 editing = {text: text, from: from, to: to, start: startch, end: endch};
693 setSelRange(input, startch, reducedSelection ? startch : endch);
746 function resetInput(user) {
747 if (!posEq(sel.from, sel.to)) {
748 prevInput = "";
749 input.value = getSelection();
750 input.select();
751 } else if (user) prevInput = input.value = "";
694 752 }
753
695 754 function focusInput() {
696 if (options.readOnly != "nocursor") input.focus();
755 if (!options.readOnly) input.focus();
697 756 }
698 757
699 758 function scrollEditorIntoView() {
700 759 if (!cursor.getBoundingClientRect) return;
701 760 var rect = cursor.getBoundingClientRect();
702 var winH = window.innerHeight || document.body.offsetHeight || document.documentElement.offsetHeight;
761 // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden
762 if (ie && rect.top == rect.bottom) return;
763 var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
703 764 if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();
704 765 }
705 766 function scrollCursorIntoView() {
706 767 var cursor = localCoords(sel.inverted ? sel.from : sel.to);
707 return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot);
768 var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
769 return scrollIntoView(x, cursor.y, x, cursor.yBot);
708 770 }
709 771 function scrollIntoView(x1, y1, x2, y2) {
710 var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight();
772 var pl = paddingLeft(), pt = paddingTop(), lh = textHeight();
711 773 y1 += pt; y2 += pt; x1 += pl; x2 += pl;
712 774 var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
713 775 if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}
714 776 else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;}
715 777
716 778 var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
717 779 var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
718 780 if (x1 < screenleft + gutterw) {
719 781 if (x1 < 50) x1 = 0;
720 782 scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
721 783 scrolled = true;
722 784 }
723 else if (x2 > screenw + screenleft) {
785 else if (x2 > screenw + screenleft - 3) {
724 786 scroller.scrollLeft = x2 + 10 - screenw;
725 787 scrolled = true;
726 788 if (x2 > code.clientWidth) result = false;
727 789 }
728 790 if (scrolled && options.onScroll) options.onScroll(instance);
729 791 return result;
730 792 }
731 793
732 794 function visibleLines() {
733 var lh = lineHeight(), top = scroller.scrollTop - paddingTop();
734 return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))),
735 to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))};
795 var lh = textHeight(), top = scroller.scrollTop - paddingTop();
796 var from_height = Math.max(0, Math.floor(top / lh));
797 var to_height = Math.ceil((top + scroller.clientHeight) / lh);
798 return {from: lineAtHeight(doc, from_height),
799 to: lineAtHeight(doc, to_height)};
736 800 }
737 801 // Uses a set of changes plus the current scroll position to
738 802 // determine which DOM updates have to be made, and makes the
739 803 // updates.
740 function updateDisplay(changes) {
804 function updateDisplay(changes, suppressCallback) {
741 805 if (!scroller.clientWidth) {
742 showingFrom = showingTo = 0;
806 showingFrom = showingTo = displayOffset = 0;
743 807 return;
744 808 }
745 // First create a range of theoretically intact lines, and punch
746 // holes in that using the change info.
747 var intact = changes === true ? [] : [{from: showingFrom, to: showingTo, domStart: 0}];
809 // Compute the new visible window
810 var visible = visibleLines();
811 // Bail out if the visible area is already rendered and nothing changed.
812 if (changes !== true && changes.length == 0 && visible.from >= showingFrom && visible.to <= showingTo) return;
813 var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
814 if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
815 if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
816
817 // Create a range of theoretically intact lines, and punch holes
818 // in that using the change info.
819 var intact = changes === true ? [] :
820 computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
821 // Clip off the parts that won't be visible
822 var intactLines = 0;
823 for (var i = 0; i < intact.length; ++i) {
824 var range = intact[i];
825 if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
826 if (range.to > to) range.to = to;
827 if (range.from >= range.to) intact.splice(i--, 1);
828 else intactLines += range.to - range.from;
829 }
830 if (intactLines == to - from) return;
831 intact.sort(function(a, b) {return a.domStart - b.domStart;});
832
833 var th = textHeight(), gutterDisplay = gutter.style.display;
834 lineDiv.style.display = gutter.style.display = "none";
835 patchDisplay(from, to, intact);
836 lineDiv.style.display = "";
837
838 // Position the mover div to align with the lines it's supposed
839 // to be showing (which will cover the visible display)
840 var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;
841 // This is just a bogus formula that detects when the editor is
842 // resized or the font size changes.
843 if (different) lastSizeC = scroller.clientHeight + th;
844 showingFrom = from; showingTo = to;
845 displayOffset = heightAtLine(doc, from);
846 mover.style.top = (displayOffset * th) + "px";
847 code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
848
849 // Since this is all rather error prone, it is honoured with the
850 // only assertion in the whole file.
851 if (lineDiv.childNodes.length != showingTo - showingFrom)
852 throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
853 " nodes=" + lineDiv.childNodes.length);
854
855 if (options.lineWrapping) {
856 maxWidth = scroller.clientWidth;
857 var curNode = lineDiv.firstChild;
858 doc.iter(showingFrom, showingTo, function(line) {
859 if (!line.hidden) {
860 var height = Math.round(curNode.offsetHeight / th) || 1;
861 if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;}
862 }
863 curNode = curNode.nextSibling;
864 });
865 } else {
866 if (maxWidth == null) maxWidth = stringWidth(maxLine);
867 if (maxWidth > scroller.clientWidth) {
868 lineSpace.style.width = maxWidth + "px";
869 // Needed to prevent odd wrapping/hiding of widgets placed in here.
870 code.style.width = "";
871 code.style.width = scroller.scrollWidth + "px";
872 } else {
873 lineSpace.style.width = code.style.width = "";
874 }
875 }
876 gutter.style.display = gutterDisplay;
877 if (different || gutterDirty) updateGutter();
878 updateCursor();
879 if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
880 return true;
881 }
882
883 function computeIntact(intact, changes) {
748 884 for (var i = 0, l = changes.length || 0; i < l; ++i) {
749 885 var change = changes[i], intact2 = [], diff = change.diff || 0;
750 886 for (var j = 0, l2 = intact.length; j < l2; ++j) {
751 887 var range = intact[j];
752 if (change.to <= range.from)
753 intact2.push({from: range.from + diff, to: range.to + diff, domStart: range.domStart});
754 else if (range.to <= change.from)
888 if (change.to <= range.from && change.diff)
889 intact2.push({from: range.from + diff, to: range.to + diff,
890 domStart: range.domStart});
891 else if (change.to <= range.from || change.from >= range.to)
755 892 intact2.push(range);
756 893 else {
757 894 if (change.from > range.from)
758 intact2.push({from: range.from, to: change.from, domStart: range.domStart})
895 intact2.push({from: range.from, to: change.from, domStart: range.domStart});
759 896 if (change.to < range.to)
760 897 intact2.push({from: change.to + diff, to: range.to + diff,
761 898 domStart: range.domStart + (change.to - range.from)});
762 899 }
763 900 }
764 901 intact = intact2;
765 902 }
903 return intact;
904 }
766 905
767 // Then, determine which lines we'd want to see, and which
768 // updates have to be made to get there.
769 var visible = visibleLines();
770 var from = Math.min(showingFrom, Math.max(visible.from - 3, 0)),
771 to = Math.min(lines.length, Math.max(showingTo, visible.to + 3)),
772 updates = [], domPos = 0, domEnd = showingTo - showingFrom, pos = from, changedLines = 0;
773
774 for (var i = 0, l = intact.length; i < l; ++i) {
775 var range = intact[i];
776 if (range.to <= from) continue;
777 if (range.from >= to) break;
778 if (range.domStart > domPos || range.from > pos) {
779 updates.push({from: pos, to: range.from, domSize: range.domStart - domPos, domStart: domPos});
780 changedLines += range.from - pos;
906 function patchDisplay(from, to, intact) {
907 // The first pass removes the DOM nodes that aren't intact.
908 if (!intact.length) lineDiv.innerHTML = "";
909 else {
910 function killNode(node) {
911 var tmp = node.nextSibling;
912 node.parentNode.removeChild(node);
913 return tmp;
781 914 }
782 pos = range.to;
783 domPos = range.domStart + (range.to - range.from);
784 }
785 if (domPos != domEnd || pos != to) {
786 changedLines += Math.abs(to - pos);
787 updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos});
788 if (to - pos != domEnd - domPos) gutterDirty = true;
789 }
790
791 if (!updates.length) return;
792 lineDiv.style.display = "none";
793 // If more than 30% of the screen needs update, just do a full
794 // redraw (which is quicker than patching)
795 if (changedLines > (visible.to - visible.from) * .3)
796 refreshDisplay(from = Math.max(visible.from - 10, 0), to = Math.min(visible.to + 7, lines.length));
797 // Otherwise, only update the stuff that needs updating.
798 else
799 patchDisplay(updates);
800 lineDiv.style.display = "";
801
802 // Position the mover div to align with the lines it's supposed
803 // to be showing (which will cover the visible display)
804 var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
805 showingFrom = from; showingTo = to;
806 mover.style.top = (from * lineHeight()) + "px";
807 if (different) {
808 lastHeight = scroller.clientHeight;
809 code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
810 }
811 if (different || gutterDirty) updateGutter();
812
813 if (maxWidth == null) maxWidth = stringWidth(maxLine);
814 if (maxWidth > scroller.clientWidth) {
815 lineSpace.style.width = maxWidth + "px";
816 // Needed to prevent odd wrapping/hiding of widgets placed in here.
817 code.style.width = "";
818 code.style.width = scroller.scrollWidth + "px";
819 } else {
820 lineSpace.style.width = code.style.width = "";
915 var domPos = 0, curNode = lineDiv.firstChild, n;
916 for (var i = 0; i < intact.length; ++i) {
917 var cur = intact[i];
918 while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
919 for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
920 }
921 while (curNode) curNode = killNode(curNode);
821 922 }
822
823 // Since this is all rather error prone, it is honoured with the
824 // only assertion in the whole file.
825 if (lineDiv.childNodes.length != showingTo - showingFrom)
826 throw new Error("BAD PATCH! " + JSON.stringify(updates) + " size=" + (showingTo - showingFrom) +
827 " nodes=" + lineDiv.childNodes.length);
828 updateCursor();
829 }
830
831 function refreshDisplay(from, to) {
832 var html = [], start = {line: from, ch: 0}, inSel = posLess(sel.from, start) && !posLess(sel.to, start);
833 for (var i = from; i < to; ++i) {
923 // This pass fills in the lines that actually changed.
924 var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
925 var sfrom = sel.from.line, sto = sel.to.line, inSel = sfrom < from && sto >= from;
926 var scratch = targetDocument.createElement("div"), newElt;
927 doc.iter(from, to, function(line) {
834 928 var ch1 = null, ch2 = null;
835 929 if (inSel) {
836 930 ch1 = 0;
837 if (sel.to.line == i) {inSel = false; ch2 = sel.to.ch;}
838 }
839 else if (sel.from.line == i) {
840 if (sel.to.line == i) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
931 if (sto == j) {inSel = false; ch2 = sel.to.ch;}
932 } else if (sfrom == j) {
933 if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
841 934 else {inSel = true; ch1 = sel.from.ch;}
842 935 }
843 html.push(lines[i].getHTML(ch1, ch2, true));
844 }
845 lineDiv.innerHTML = html.join("");
846 }
847 function patchDisplay(updates) {
848 // Slightly different algorithm for IE (badInnerHTML), since
849 // there .innerHTML on PRE nodes is dumb, and discards
850 // whitespace.
851 var sfrom = sel.from.line, sto = sel.to.line, off = 0,
852 scratch = badInnerHTML && targetDocument.createElement("div");
853 for (var i = 0, e = updates.length; i < e; ++i) {
854 var rec = updates[i];
855 var extra = (rec.to - rec.from) - rec.domSize;
856 var nodeAfter = lineDiv.childNodes[rec.domStart + rec.domSize + off] || null;
857 if (badInnerHTML)
858 for (var j = Math.max(-extra, rec.domSize); j > 0; --j)
859 lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
860 else if (extra) {
861 for (var j = Math.max(0, extra); j > 0; --j)
862 lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter);
863 for (var j = Math.max(0, -extra); j > 0; --j)
864 lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
865 }
866 var node = lineDiv.childNodes[rec.domStart + off], inSel = sfrom < rec.from && sto >= rec.from;
867 for (var j = rec.from; j < rec.to; ++j) {
868 var ch1 = null, ch2 = null;
869 if (inSel) {
870 ch1 = 0;
871 if (sto == j) {inSel = false; ch2 = sel.to.ch;}
872 }
873 else if (sfrom == j) {
874 if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
875 else {inSel = true; ch1 = sel.from.ch;}
876 }
877 if (badInnerHTML) {
878 scratch.innerHTML = lines[j].getHTML(ch1, ch2, true);
879 lineDiv.insertBefore(scratch.firstChild, nodeAfter);
880 }
881 else {
882 node.innerHTML = lines[j].getHTML(ch1, ch2, false);
883 node.className = lines[j].className || "";
884 node = node.nextSibling;
885 }
936 if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
937 if (!nextIntact || nextIntact.from > j) {
938 if (line.hidden) scratch.innerHTML = "<pre></pre>";
939 else scratch.innerHTML = line.getHTML(ch1, ch2, true, tabText);
940 lineDiv.insertBefore(scratch.firstChild, curNode);
941 } else {
942 curNode = curNode.nextSibling;
886 943 }
887 off += extra;
888 }
944 ++j;
945 });
889 946 }
890 947
891 948 function updateGutter() {
892 949 if (!options.gutter && !options.lineNumbers) return;
893 950 var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
894 951 gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
895 var html = [];
896 for (var i = showingFrom; i < Math.max(showingTo, showingFrom + 1); ++i) {
897 var marker = lines[i].gutterMarker;
898 var text = options.lineNumbers ? i + options.firstLineNumber : null;
899 if (marker && marker.text)
900 text = marker.text.replace("%N%", text != null ? text : "");
901 else if (text == null)
902 text = "\u00a0";
903 html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text, "</pre>");
904 }
952 var html = [], i = showingFrom;
953 doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
954 if (line.hidden) {
955 html.push("<pre></pre>");
956 } else {
957 var marker = line.gutterMarker;
958 var text = options.lineNumbers ? i + options.firstLineNumber : null;
959 if (marker && marker.text)
960 text = marker.text.replace("%N%", text != null ? text : "");
961 else if (text == null)
962 text = "\u00a0";
963 html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
964 for (var j = 1; j < line.height; ++j) html.push("<br/>&#160;");
965 html.push("</pre>");
966 }
967 ++i;
968 });
905 969 gutter.style.display = "none";
906 970 gutterText.innerHTML = html.join("");
907 var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
971 var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
908 972 while (val.length + pad.length < minwidth) pad += "\u00a0";
909 973 if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);
910 974 gutter.style.display = "";
911 975 lineSpace.style.marginLeft = gutter.offsetWidth + "px";
912 976 gutterDirty = false;
913 977 }
914 978 function updateCursor() {
915 var head = sel.inverted ? sel.from : sel.to, lh = lineHeight();
916 var x = charX(head.line, head.ch);
917 var top = head.line * lh - scroller.scrollTop;
918 inputDiv.style.top = Math.max(Math.min(top, scroller.offsetHeight), 0) + "px";
919 inputDiv.style.left = (x - scroller.scrollLeft) + "px";
979 var head = sel.inverted ? sel.from : sel.to, lh = textHeight();
980 var pos = localCoords(head, true);
981 var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
982 inputDiv.style.top = (pos.y + lineOff.top - wrapOff.top) + "px";
983 inputDiv.style.left = (pos.x + lineOff.left - wrapOff.left) + "px";
920 984 if (posEq(sel.from, sel.to)) {
921 cursor.style.top = (head.line - showingFrom) * lh + "px";
922 cursor.style.left = x + "px";
985 cursor.style.top = pos.y + "px";
986 cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + "px";
923 987 cursor.style.display = "";
924 988 }
925 989 else cursor.style.display = "none";
926 990 }
927 991
992 function setShift(val) {
993 if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
994 else shiftSelecting = null;
995 }
928 996 function setSelectionUser(from, to) {
929 997 var sh = shiftSelecting && clipPos(shiftSelecting);
930 998 if (sh) {
931 999 if (posLess(sh, from)) from = sh;
932 1000 else if (posLess(to, sh)) to = sh;
933 1001 }
934 1002 setSelection(from, to);
1003 userSelChange = true;
935 1004 }
936 1005 // Update the selection. Last two args are only used by
937 1006 // updateLines, since they have to be expressed in the line
938 1007 // numbers before the update.
939 1008 function setSelection(from, to, oldFrom, oldTo) {
1009 goalColumn = null;
1010 if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
940 1011 if (posEq(sel.from, from) && posEq(sel.to, to)) return;
941 1012 if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
942 1013
1014 // Skip over hidden lines.
1015 if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch);
1016 if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
1017
943 1018 if (posEq(from, to)) sel.inverted = false;
944 1019 else if (posEq(from, sel.to)) sel.inverted = false;
945 1020 else if (posEq(to, sel.from)) sel.inverted = true;
946 1021
947 1022 // Some ugly logic used to only mark the lines that actually did
948 1023 // see a change in selection as changed, rather than the whole
949 1024 // selected range.
950 if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
951 1025 if (posEq(from, to)) {
952 1026 if (!posEq(sel.from, sel.to))
953 1027 changes.push({from: oldFrom, to: oldTo + 1});
954 1028 }
955 1029 else if (posEq(sel.from, sel.to)) {
956 1030 changes.push({from: from.line, to: to.line + 1});
957 1031 }
958 1032 else {
959 1033 if (!posEq(from, sel.from)) {
960 1034 if (from.line < oldFrom)
961 1035 changes.push({from: from.line, to: Math.min(to.line, oldFrom) + 1});
962 1036 else
963 1037 changes.push({from: oldFrom, to: Math.min(oldTo, from.line) + 1});
964 1038 }
965 1039 if (!posEq(to, sel.to)) {
966 1040 if (to.line < oldTo)
967 1041 changes.push({from: Math.max(oldFrom, from.line), to: oldTo + 1});
968 1042 else
969 1043 changes.push({from: Math.max(from.line, oldTo), to: to.line + 1});
970 1044 }
971 1045 }
972 1046 sel.from = from; sel.to = to;
973 1047 selectionChanged = true;
974 1048 }
1049 function skipHidden(pos, oldLine, oldCh) {
1050 function getNonHidden(dir) {
1051 var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
1052 while (lNo != end) {
1053 var line = getLine(lNo);
1054 if (!line.hidden) {
1055 var ch = pos.ch;
1056 if (ch > oldCh || ch > line.text.length) ch = line.text.length;
1057 return {line: lNo, ch: ch};
1058 }
1059 lNo += dir;
1060 }
1061 }
1062 var line = getLine(pos.line);
1063 if (!line.hidden) return pos;
1064 if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
1065 else return getNonHidden(-1) || getNonHidden(1);
1066 }
975 1067 function setCursor(line, ch, user) {
976 1068 var pos = clipPos({line: line, ch: ch || 0});
977 1069 (user ? setSelectionUser : setSelection)(pos, pos);
978 1070 }
979 1071
980 function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));}
1072 function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
981 1073 function clipPos(pos) {
982 1074 if (pos.line < 0) return {line: 0, ch: 0};
983 if (pos.line >= lines.length) return {line: lines.length-1, ch: lines[lines.length-1].text.length};
984 var ch = pos.ch, linelen = lines[pos.line].text.length;
1075 if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
1076 var ch = pos.ch, linelen = getLine(pos.line).text.length;
985 1077 if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
986 1078 else if (ch < 0) return {line: pos.line, ch: 0};
987 1079 else return pos;
988 1080 }
989 1081
990 function scrollPage(down) {
991 var linesPerPage = Math.floor(scroller.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
992 setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch, true);
1082 function findPosH(dir, unit) {
1083 var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
1084 var lineObj = getLine(line);
1085 function findNextLine() {
1086 for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {
1087 var lo = getLine(l);
1088 if (!lo.hidden) { line = l; lineObj = lo; return true; }
1089 }
1090 }
1091 function moveOnce(boundToLine) {
1092 if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
1093 if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
1094 else return false;
1095 } else ch += dir;
1096 return true;
1097 }
1098 if (unit == "char") moveOnce();
1099 else if (unit == "column") moveOnce(true);
1100 else if (unit == "word") {
1101 var sawWord = false;
1102 for (;;) {
1103 if (dir < 0) if (!moveOnce()) break;
1104 if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
1105 else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
1106 if (dir > 0) if (!moveOnce()) break;
1107 }
1108 }
1109 return {line: line, ch: ch};
993 1110 }
994 function scrollEnd(top) {
995 var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length};
996 setSelectionUser(pos, pos);
1111 function moveH(dir, unit) {
1112 var pos = dir < 0 ? sel.from : sel.to;
1113 if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
1114 setCursor(pos.line, pos.ch, true);
997 1115 }
998 function selectAll() {
999 var endLine = lines.length - 1;
1000 setSelection({line: 0, ch: 0}, {line: endLine, ch: lines[endLine].text.length});
1116 function deleteH(dir, unit) {
1117 if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to);
1118 else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to);
1119 else replaceRange("", sel.from, findPosH(dir, unit));
1120 userSelChange = true;
1001 1121 }
1122 var goalColumn = null;
1123 function moveV(dir, unit) {
1124 var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
1125 if (goalColumn != null) pos.x = goalColumn;
1126 if (unit == "page") dist = scroller.clientHeight;
1127 else if (unit == "line") dist = textHeight();
1128 var target = coordsChar(pos.x, pos.y + dist * dir + 2);
1129 setCursor(target.line, target.ch, true);
1130 goalColumn = pos.x;
1131 }
1132
1002 1133 function selectWordAt(pos) {
1003 var line = lines[pos.line].text;
1134 var line = getLine(pos.line).text;
1004 1135 var start = pos.ch, end = pos.ch;
1005 while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
1006 while (end < line.length && /\w/.test(line.charAt(end))) ++end;
1136 while (start > 0 && isWordChar(line.charAt(start - 1))) --start;
1137 while (end < line.length && isWordChar(line.charAt(end))) ++end;
1007 1138 setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
1008 1139 }
1009 1140 function selectLine(line) {
1010 setSelectionUser({line: line, ch: 0}, {line: line, ch: lines[line].text.length});
1141 setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
1011 1142 }
1012 function handleEnter() {
1013 replaceSelection("\n", "end");
1014 if (options.enterMode != "flat")
1015 indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart");
1016 }
1017 function handleTab(shift) {
1018 function indentSelected(mode) {
1019 if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
1020 var e = sel.to.line - (sel.to.ch ? 0 : 1);
1021 for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
1022 }
1023 shiftSelecting = null;
1024 switch (options.tabMode) {
1025 case "default":
1026 return false;
1027 case "indent":
1028 indentSelected("smart");
1029 break;
1030 case "classic":
1031 if (posEq(sel.from, sel.to)) {
1032 if (shift) indentLine(sel.from.line, "smart");
1033 else replaceSelection("\t", "end");
1034 break;
1035 }
1036 case "shift":
1037 indentSelected(shift ? "subtract" : "add");
1038 break;
1039 }
1040 return true;
1041 }
1042 function smartHome() {
1043 var firstNonWS = Math.max(0, lines[sel.from.line].text.search(/\S/));
1044 setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true);
1143 function indentSelected(mode) {
1144 if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
1145 var e = sel.to.line - (sel.to.ch ? 0 : 1);
1146 for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
1045 1147 }
1046 1148
1047 1149 function indentLine(n, how) {
1150 if (!how) how = "add";
1048 1151 if (how == "smart") {
1049 1152 if (!mode.indent) how = "prev";
1050 1153 else var state = getStateBefore(n);
1051 1154 }
1052 1155
1053 var line = lines[n], curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
1156 var line = getLine(n), curSpace = line.indentation(options.tabSize),
1157 curSpaceString = line.text.match(/^\s*/)[0], indentation;
1054 1158 if (how == "prev") {
1055 if (n) indentation = lines[n-1].indentation();
1159 if (n) indentation = getLine(n-1).indentation(options.tabSize);
1056 1160 else indentation = 0;
1057 1161 }
1058 else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length));
1162 else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);
1059 1163 else if (how == "add") indentation = curSpace + options.indentUnit;
1060 1164 else if (how == "subtract") indentation = curSpace - options.indentUnit;
1061 1165 indentation = Math.max(0, indentation);
1062 1166 var diff = indentation - curSpace;
1063 1167
1064 1168 if (!diff) {
1065 1169 if (sel.from.line != n && sel.to.line != n) return;
1066 1170 var indentString = curSpaceString;
1067 1171 }
1068 1172 else {
1069 1173 var indentString = "", pos = 0;
1070 1174 if (options.indentWithTabs)
1071 for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
1175 for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";}
1072 1176 while (pos < indentation) {++pos; indentString += " ";}
1073 1177 }
1074 1178
1075 1179 replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});
1076 1180 }
1077 1181
1078 1182 function loadMode() {
1079 1183 mode = CodeMirror.getMode(options, options.mode);
1080 for (var i = 0, l = lines.length; i < l; ++i)
1081 lines[i].stateAfter = null;
1184 doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
1082 1185 work = [0];
1083 1186 startWorker();
1084 1187 }
1085 1188 function gutterChanged() {
1086 1189 var visible = options.gutter || options.lineNumbers;
1087 1190 gutter.style.display = visible ? "" : "none";
1088 1191 if (visible) gutterDirty = true;
1089 1192 else lineDiv.parentNode.style.marginLeft = 0;
1090 1193 }
1194 function wrappingChanged(from, to) {
1195 if (options.lineWrapping) {
1196 wrapper.className += " CodeMirror-wrap";
1197 var perLine = scroller.clientWidth / charWidth() - 3;
1198 doc.iter(0, doc.size, function(line) {
1199 if (line.hidden) return;
1200 var guess = Math.ceil(line.text.length / perLine) || 1;
1201 if (guess != 1) updateLineHeight(line, guess);
1202 });
1203 lineSpace.style.width = code.style.width = "";
1204 } else {
1205 wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
1206 maxWidth = null; maxLine = "";
1207 doc.iter(0, doc.size, function(line) {
1208 if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
1209 if (line.text.length > maxLine.length) maxLine = line.text;
1210 });
1211 }
1212 changes.push({from: 0, to: doc.size});
1213 }
1214 function computeTabText() {
1215 for (var str = '<span class="cm-tab">', i = 0; i < options.tabSize; ++i) str += " ";
1216 return str + "</span>";
1217 }
1218 function tabsChanged() {
1219 tabText = computeTabText();
1220 updateDisplay(true);
1221 }
1222 function themeChanged() {
1223 scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") +
1224 options.theme.replace(/(^|\s)\s*/g, " cm-s-");
1225 }
1226
1227 function TextMarker() { this.set = []; }
1228 TextMarker.prototype.clear = operation(function() {
1229 var min = Infinity, max = -Infinity;
1230 for (var i = 0, e = this.set.length; i < e; ++i) {
1231 var line = this.set[i], mk = line.marked;
1232 if (!mk || !line.parent) continue;
1233 var lineN = lineNo(line);
1234 min = Math.min(min, lineN); max = Math.max(max, lineN);
1235 for (var j = 0; j < mk.length; ++j)
1236 if (mk[j].set == this.set) mk.splice(j--, 1);
1237 }
1238 if (min != Infinity)
1239 changes.push({from: min, to: max + 1});
1240 });
1241 TextMarker.prototype.find = function() {
1242 var from, to;
1243 for (var i = 0, e = this.set.length; i < e; ++i) {
1244 var line = this.set[i], mk = line.marked;
1245 for (var j = 0; j < mk.length; ++j) {
1246 var mark = mk[j];
1247 if (mark.set == this.set) {
1248 if (mark.from != null || mark.to != null) {
1249 var found = lineNo(line);
1250 if (found != null) {
1251 if (mark.from != null) from = {line: found, ch: mark.from};
1252 if (mark.to != null) to = {line: found, ch: mark.to};
1253 }
1254 }
1255 }
1256 }
1257 }
1258 return {from: from, to: to};
1259 };
1091 1260
1092 1261 function markText(from, to, className) {
1093 1262 from = clipPos(from); to = clipPos(to);
1094 var accum = [];
1263 var tm = new TextMarker();
1095 1264 function add(line, from, to, className) {
1096 var line = lines[line], mark = line.addMark(from, to, className);
1097 mark.line = line;
1098 accum.push(mark);
1265 getLine(line).addMark(new MarkedText(from, to, className, tm.set));
1099 1266 }
1100 1267 if (from.line == to.line) add(from.line, from.ch, to.ch, className);
1101 1268 else {
1102 1269 add(from.line, from.ch, null, className);
1103 1270 for (var i = from.line + 1, e = to.line; i < e; ++i)
1104 add(i, 0, null, className);
1105 add(to.line, 0, to.ch, className);
1271 add(i, null, null, className);
1272 add(to.line, null, to.ch, className);
1106 1273 }
1107 1274 changes.push({from: from.line, to: to.line + 1});
1108 return function() {
1109 var start, end;
1110 for (var i = 0; i < accum.length; ++i) {
1111 var mark = accum[i], found = indexOf(lines, mark.line);
1112 mark.line.removeMark(mark);
1113 if (found > -1) {
1114 if (start == null) start = found;
1115 end = found;
1116 }
1117 }
1118 if (start != null) changes.push({from: start, to: end + 1});
1119 };
1275 return tm;
1276 }
1277
1278 function setBookmark(pos) {
1279 pos = clipPos(pos);
1280 var bm = new Bookmark(pos.ch);
1281 getLine(pos.line).addMark(bm);
1282 return bm;
1120 1283 }
1121 1284
1122 1285 function addGutterMarker(line, text, className) {
1123 if (typeof line == "number") line = lines[clipLine(line)];
1286 if (typeof line == "number") line = getLine(clipLine(line));
1124 1287 line.gutterMarker = {text: text, style: className};
1125 1288 gutterDirty = true;
1126 1289 return line;
1127 1290 }
1128 1291 function removeGutterMarker(line) {
1129 if (typeof line == "number") line = lines[clipLine(line)];
1292 if (typeof line == "number") line = getLine(clipLine(line));
1130 1293 line.gutterMarker = null;
1131 1294 gutterDirty = true;
1132 1295 }
1133 function setLineClass(line, className) {
1134 if (typeof line == "number") {
1135 var no = line;
1136 line = lines[clipLine(line)];
1137 }
1138 else {
1139 var no = indexOf(lines, line);
1140 if (no == -1) return null;
1141 }
1142 if (line.className != className) {
1143 line.className = className;
1144 changes.push({from: no, to: no + 1});
1145 }
1296
1297 function changeLine(handle, op) {
1298 var no = handle, line = handle;
1299 if (typeof handle == "number") line = getLine(clipLine(handle));
1300 else no = lineNo(handle);
1301 if (no == null) return null;
1302 if (op(line, no)) changes.push({from: no, to: no + 1});
1303 else return null;
1146 1304 return line;
1147 1305 }
1306 function setLineClass(handle, className) {
1307 return changeLine(handle, function(line) {
1308 if (line.className != className) {
1309 line.className = className;
1310 return true;
1311 }
1312 });
1313 }
1314 function setLineHidden(handle, hidden) {
1315 return changeLine(handle, function(line, no) {
1316 if (line.hidden != hidden) {
1317 line.hidden = hidden;
1318 updateLineHeight(line, hidden ? 0 : 1);
1319 if (hidden && (sel.from.line == no || sel.to.line == no))
1320 setSelection(skipHidden(sel.from, sel.from.line, sel.from.ch),
1321 skipHidden(sel.to, sel.to.line, sel.to.ch));
1322 return (gutterDirty = true);
1323 }
1324 });
1325 }
1148 1326
1149 1327 function lineInfo(line) {
1150 1328 if (typeof line == "number") {
1329 if (!isLine(line)) return null;
1151 1330 var n = line;
1152 line = lines[line];
1331 line = getLine(line);
1153 1332 if (!line) return null;
1154 1333 }
1155 1334 else {
1156 var n = indexOf(lines, line);
1157 if (n == -1) return null;
1335 var n = lineNo(line);
1336 if (n == null) return null;
1158 1337 }
1159 1338 var marker = line.gutterMarker;
1160 return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style};
1339 return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
1340 markerClass: marker && marker.style, lineClass: line.className};
1161 1341 }
1162 1342
1163 1343 function stringWidth(str) {
1164 1344 measure.innerHTML = "<pre><span>x</span></pre>";
1165 1345 measure.firstChild.firstChild.firstChild.nodeValue = str;
1166 1346 return measure.firstChild.firstChild.offsetWidth || 10;
1167 1347 }
1168 1348 // These are used to go from pixel positions to character
1169 1349 // positions, taking varying character widths into account.
1170 function charX(line, pos) {
1171 if (pos == 0) return 0;
1172 measure.innerHTML = "<pre><span>" + lines[line].getHTML(null, null, false, pos) + "</span></pre>";
1173 return measure.firstChild.firstChild.offsetWidth;
1174 }
1175 1350 function charFromX(line, x) {
1176 1351 if (x <= 0) return 0;
1177 var lineObj = lines[line], text = lineObj.text;
1352 var lineObj = getLine(line), text = lineObj.text;
1178 1353 function getX(len) {
1179 measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>";
1354 measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, tabText, len) + "</span></pre>";
1180 1355 return measure.firstChild.firstChild.offsetWidth;
1181 1356 }
1182 1357 var from = 0, fromX = 0, to = text.length, toX;
1183 1358 // Guess a suitable upper bound for our search.
1184 var estimated = Math.min(to, Math.ceil(x / stringWidth("x")));
1359 var estimated = Math.min(to, Math.ceil(x / charWidth()));
1185 1360 for (;;) {
1186 1361 var estX = getX(estimated);
1187 1362 if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1188 1363 else {toX = estX; to = estimated; break;}
1189 1364 }
1190 1365 if (x > toX) return to;
1191 1366 // Try to guess a suitable lower bound as well.
1192 1367 estimated = Math.floor(to * 0.8); estX = getX(estimated);
1193 1368 if (estX < x) {from = estimated; fromX = estX;}
1194 1369 // Do a binary search between these bounds.
1195 1370 for (;;) {
1196 1371 if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
1197 1372 var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1198 1373 if (middleX > x) {to = middle; toX = middleX;}
1199 1374 else {from = middle; fromX = middleX;}
1200 1375 }
1201 1376 }
1202 1377
1378 var tempId = Math.floor(Math.random() * 0xffffff).toString(16);
1379 function measureLine(line, ch) {
1380 var extra = "";
1381 // Include extra text at the end to make sure the measured line is wrapped in the right way.
1382 if (options.lineWrapping) {
1383 var end = line.text.indexOf(" ", ch + 2);
1384 extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0)));
1385 }
1386 measure.innerHTML = "<pre>" + line.getHTML(null, null, false, tabText, ch) +
1387 '<span id="CodeMirror-temp-' + tempId + '">' + htmlEscape(line.text.charAt(ch) || " ") + "</span>" +
1388 extra + "</pre>";
1389 var elt = document.getElementById("CodeMirror-temp-" + tempId);
1390 var top = elt.offsetTop, left = elt.offsetLeft;
1391 // Older IEs report zero offsets for spans directly after a wrap
1392 if (ie && ch && top == 0 && left == 0) {
1393 var backup = document.createElement("span");
1394 backup.innerHTML = "x";
1395 elt.parentNode.insertBefore(backup, elt.nextSibling);
1396 top = backup.offsetTop;
1397 }
1398 return {top: top, left: left};
1399 }
1203 1400 function localCoords(pos, inLineWrap) {
1204 var lh = lineHeight(), line = pos.line - (inLineWrap ? showingFrom : 0);
1205 return {x: charX(pos.line, pos.ch), y: line * lh, yBot: (line + 1) * lh};
1401 var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
1402 if (pos.ch == 0) x = 0;
1403 else {
1404 var sp = measureLine(getLine(pos.line), pos.ch);
1405 x = sp.left;
1406 if (options.lineWrapping) y += Math.max(0, sp.top);
1407 }
1408 return {x: x, y: y, yBot: y + lh};
1409 }
1410 // Coords must be lineSpace-local
1411 function coordsChar(x, y) {
1412 if (y < 0) y = 0;
1413 var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
1414 var lineNo = lineAtHeight(doc, heightPos);
1415 if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};
1416 var lineObj = getLine(lineNo), text = lineObj.text;
1417 var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
1418 if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
1419 function getX(len) {
1420 var sp = measureLine(lineObj, len);
1421 if (tw) {
1422 var off = Math.round(sp.top / th);
1423 return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
1424 }
1425 return sp.left;
1426 }
1427 var from = 0, fromX = 0, to = text.length, toX;
1428 // Guess a suitable upper bound for our search.
1429 var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));
1430 for (;;) {
1431 var estX = getX(estimated);
1432 if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1433 else {toX = estX; to = estimated; break;}
1434 }
1435 if (x > toX) return {line: lineNo, ch: to};
1436 // Try to guess a suitable lower bound as well.
1437 estimated = Math.floor(to * 0.8); estX = getX(estimated);
1438 if (estX < x) {from = estimated; fromX = estX;}
1439 // Do a binary search between these bounds.
1440 for (;;) {
1441 if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to};
1442 var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1443 if (middleX > x) {to = middle; toX = middleX;}
1444 else {from = middle; fromX = middleX;}
1445 }
1206 1446 }
1207 1447 function pageCoords(pos) {
1208 1448 var local = localCoords(pos, true), off = eltOffset(lineSpace);
1209 1449 return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
1210 1450 }
1211 1451
1212 function lineHeight() {
1213 var nlines = lineDiv.childNodes.length;
1214 if (nlines) return (lineDiv.offsetHeight / nlines) || 1;
1215 measure.innerHTML = "<pre>x</pre>";
1216 return measure.firstChild.offsetHeight || 1;
1452 var cachedHeight, cachedHeightFor, measureText;
1453 function textHeight() {
1454 if (measureText == null) {
1455 measureText = "<pre>";
1456 for (var i = 0; i < 49; ++i) measureText += "x<br/>";
1457 measureText += "x</pre>";
1458 }
1459 var offsetHeight = lineDiv.clientHeight;
1460 if (offsetHeight == cachedHeightFor) return cachedHeight;
1461 cachedHeightFor = offsetHeight;
1462 measure.innerHTML = measureText;
1463 cachedHeight = measure.firstChild.offsetHeight / 50 || 1;
1464 measure.innerHTML = "";
1465 return cachedHeight;
1466 }
1467 var cachedWidth, cachedWidthFor = 0;
1468 function charWidth() {
1469 if (scroller.clientWidth == cachedWidthFor) return cachedWidth;
1470 cachedWidthFor = scroller.clientWidth;
1471 return (cachedWidth = stringWidth("x"));
1217 1472 }
1218 1473 function paddingTop() {return lineSpace.offsetTop;}
1219 1474 function paddingLeft() {return lineSpace.offsetLeft;}
1220 1475
1221 1476 function posFromMouse(e, liberal) {
1222 1477 var offW = eltOffset(scroller, true), x, y;
1223 1478 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1224 1479 try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1225 1480 // This is a mess of a heuristic to try and determine whether a
1226 1481 // scroll-bar was clicked or not, and to return null if one was
1227 1482 // (and !liberal).
1228 1483 if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
1229 1484 return null;
1230 1485 var offL = eltOffset(lineSpace, true);
1231 var line = showingFrom + Math.floor((y - offL.top) / lineHeight());
1232 return clipPos({line: line, ch: charFromX(clipLine(line), x - offL.left)});
1486 return coordsChar(x - offL.left, y - offL.top);
1233 1487 }
1234 1488 function onContextMenu(e) {
1235 1489 var pos = posFromMouse(e);
1236 1490 if (!pos || window.opera) return; // Opera is difficult.
1237 1491 if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1238 1492 operation(setCursor)(pos.line, pos.ch);
1239 1493
1240 1494 var oldCSS = input.style.cssText;
1241 1495 inputDiv.style.position = "absolute";
1242 1496 input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1243 1497 "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
1244 1498 "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1245 1499 leaveInputAlone = true;
1246 1500 var val = input.value = getSelection();
1247 1501 focusInput();
1248 setSelRange(input, 0, input.value.length);
1502 input.select();
1249 1503 function rehide() {
1250 1504 var newVal = splitLines(input.value).join("\n");
1251 1505 if (newVal != val) operation(replaceSelection)(newVal, "end");
1252 1506 inputDiv.style.position = "relative";
1253 1507 input.style.cssText = oldCSS;
1254 1508 leaveInputAlone = false;
1255 prepareInput();
1509 resetInput(true);
1256 1510 slowPoll();
1257 1511 }
1258 1512
1259 1513 if (gecko) {
1260 1514 e_stop(e);
1261 1515 var mouseup = connect(window, "mouseup", function() {
1262 1516 mouseup();
1263 1517 setTimeout(rehide, 20);
1264 1518 }, true);
1265 1519 }
1266 1520 else {
1267 1521 setTimeout(rehide, 50);
1268 1522 }
1269 1523 }
1270 1524
1271 1525 // Cursor-blinking
1272 1526 function restartBlink() {
1273 1527 clearInterval(blinker);
1274 1528 var on = true;
1275 1529 cursor.style.visibility = "";
1276 1530 blinker = setInterval(function() {
1277 1531 cursor.style.visibility = (on = !on) ? "" : "hidden";
1278 1532 }, 650);
1279 1533 }
1280 1534
1281 1535 var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
1282 1536 function matchBrackets(autoclear) {
1283 var head = sel.inverted ? sel.from : sel.to, line = lines[head.line], pos = head.ch - 1;
1537 var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
1284 1538 var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
1285 1539 if (!match) return;
1286 1540 var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
1287 1541 for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)
1288 1542 if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}
1289 1543
1290 1544 var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
1291 1545 function scan(line, from, to) {
1292 1546 if (!line.text) return;
1293 1547 var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;
1294 1548 for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) {
1295 1549 var text = st[i];
1296 1550 if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;}
1297 1551 for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) {
1298 1552 if (pos >= from && pos < to && re.test(cur = text.charAt(j))) {
1299 1553 var match = matching[cur];
1300 1554 if (match.charAt(1) == ">" == forward) stack.push(cur);
1301 1555 else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
1302 1556 else if (!stack.length) return {pos: pos, match: true};
1303 1557 }
1304 1558 }
1305 1559 }
1306 1560 }
1307 for (var i = head.line, e = forward ? Math.min(i + 100, lines.length) : Math.max(-1, i - 100); i != e; i+=d) {
1308 var line = lines[i], first = i == head.line;
1561 for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
1562 var line = getLine(i), first = i == head.line;
1309 1563 var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
1310 1564 if (found) break;
1311 1565 }
1312 1566 if (!found) found = {pos: null, match: false};
1313 1567 var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
1314 1568 var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
1315 two = found.pos != null
1316 ? markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style)
1317 : function() {};
1318 var clear = operation(function(){one(); two();});
1569 two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
1570 var clear = operation(function(){one.clear(); two && two.clear();});
1319 1571 if (autoclear) setTimeout(clear, 800);
1320 1572 else bracketHighlighted = clear;
1321 1573 }
1322 1574
1323 1575 // Finds the line to start with when starting a parse. Tries to
1324 1576 // find a line with a stateAfter, so that it can start with a
1325 1577 // valid state. If that fails, it returns the line with the
1326 1578 // smallest indentation, which tends to need the least context to
1327 1579 // parse correctly.
1328 1580 function findStartLine(n) {
1329 1581 var minindent, minline;
1330 1582 for (var search = n, lim = n - 40; search > lim; --search) {
1331 1583 if (search == 0) return 0;
1332 var line = lines[search-1];
1584 var line = getLine(search-1);
1333 1585 if (line.stateAfter) return search;
1334 var indented = line.indentation();
1586 var indented = line.indentation(options.tabSize);
1335 1587 if (minline == null || minindent > indented) {
1336 1588 minline = search - 1;
1337 1589 minindent = indented;
1338 1590 }
1339 1591 }
1340 1592 return minline;
1341 1593 }
1342 1594 function getStateBefore(n) {
1343 var start = findStartLine(n), state = start && lines[start-1].stateAfter;
1595 var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
1344 1596 if (!state) state = startState(mode);
1345 1597 else state = copyState(mode, state);
1346 for (var i = start; i < n; ++i) {
1347 var line = lines[i];
1348 line.highlight(mode, state);
1598 doc.iter(start, n, function(line) {
1599 line.highlight(mode, state, options.tabSize);
1349 1600 line.stateAfter = copyState(mode, state);
1350 }
1351 if (n < lines.length && !lines[n].stateAfter) work.push(n);
1601 });
1602 if (start < n) changes.push({from: start, to: n});
1603 if (n < doc.size && !getLine(n).stateAfter) work.push(n);
1352 1604 return state;
1353 1605 }
1354 1606 function highlightLines(start, end) {
1355 1607 var state = getStateBefore(start);
1356 for (var i = start; i < end; ++i) {
1357 var line = lines[i];
1358 line.highlight(mode, state);
1608 doc.iter(start, end, function(line) {
1609 line.highlight(mode, state, options.tabSize);
1359 1610 line.stateAfter = copyState(mode, state);
1360 }
1611 });
1361 1612 }
1362 1613 function highlightWorker() {
1363 1614 var end = +new Date + options.workTime;
1364 1615 var foundWork = work.length;
1365 1616 while (work.length) {
1366 if (!lines[showingFrom].stateAfter) var task = showingFrom;
1617 if (!getLine(showingFrom).stateAfter) var task = showingFrom;
1367 1618 else var task = work.pop();
1368 if (task >= lines.length) continue;
1369 var start = findStartLine(task), state = start && lines[start-1].stateAfter;
1619 if (task >= doc.size) continue;
1620 var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
1370 1621 if (state) state = copyState(mode, state);
1371 1622 else state = startState(mode);
1372 1623
1373 var unchanged = 0, compare = mode.compareStates, realChange = false;
1374 for (var i = start, l = lines.length; i < l; ++i) {
1375 var line = lines[i], hadState = line.stateAfter;
1624 var unchanged = 0, compare = mode.compareStates, realChange = false,
1625 i = start, bail = false;
1626 doc.iter(i, doc.size, function(line) {
1627 var hadState = line.stateAfter;
1376 1628 if (+new Date > end) {
1377 1629 work.push(i);
1378 1630 startWorker(options.workDelay);
1379 1631 if (realChange) changes.push({from: task, to: i + 1});
1380 return;
1632 return (bail = true);
1381 1633 }
1382 var changed = line.highlight(mode, state);
1634 var changed = line.highlight(mode, state, options.tabSize);
1383 1635 if (changed) realChange = true;
1384 1636 line.stateAfter = copyState(mode, state);
1385 1637 if (compare) {
1386 if (hadState && compare(hadState, state)) break;
1638 if (hadState && compare(hadState, state)) return true;
1387 1639 } else {
1388 1640 if (changed !== false || !hadState) unchanged = 0;
1389 else if (++unchanged > 3) break;
1641 else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, "")))
1642 return true;
1390 1643 }
1391 }
1644 ++i;
1645 });
1646 if (bail) return;
1392 1647 if (realChange) changes.push({from: task, to: i + 1});
1393 1648 }
1394 1649 if (foundWork && options.onHighlightComplete)
1395 1650 options.onHighlightComplete(instance);
1396 1651 }
1397 1652 function startWorker(time) {
1398 1653 if (!work.length) return;
1399 1654 highlight.set(time, operation(highlightWorker));
1400 1655 }
1401 1656
1402 1657 // Operations are used to wrap changes in such a way that each
1403 1658 // change won't have to update the cursor and display (which would
1404 1659 // be awkward, slow, and error-prone), but instead updates are
1405 1660 // batched and then all combined and executed at once.
1406 1661 function startOperation() {
1407 updateInput = null; changes = []; textChanged = selectionChanged = false;
1662 updateInput = userSelChange = textChanged = null;
1663 changes = []; selectionChanged = false; callbacks = [];
1408 1664 }
1409 1665 function endOperation() {
1410 var reScroll = false;
1666 var reScroll = false, updated;
1411 1667 if (selectionChanged) reScroll = !scrollCursorIntoView();
1412 if (changes.length) updateDisplay(changes);
1668 if (changes.length) updated = updateDisplay(changes, true);
1413 1669 else {
1414 1670 if (selectionChanged) updateCursor();
1415 1671 if (gutterDirty) updateGutter();
1416 1672 }
1417 1673 if (reScroll) scrollCursorIntoView();
1418 1674 if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
1419 1675
1420 // updateInput can be set to a boolean value to force/prevent an
1421 // update.
1422 1676 if (focused && !leaveInputAlone &&
1423 1677 (updateInput === true || (updateInput !== false && selectionChanged)))
1424 prepareInput();
1678 resetInput(userSelChange);
1425 1679
1426 1680 if (selectionChanged && options.matchBrackets)
1427 1681 setTimeout(operation(function() {
1428 1682 if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
1429 matchBrackets(false);
1683 if (posEq(sel.from, sel.to)) matchBrackets(false);
1430 1684 }), 20);
1431 var tc = textChanged; // textChanged can be reset by cursoractivity callback
1685 var tc = textChanged, cbs = callbacks; // these can be reset by callbacks
1432 1686 if (selectionChanged && options.onCursorActivity)
1433 1687 options.onCursorActivity(instance);
1434 1688 if (tc && options.onChange && instance)
1435 1689 options.onChange(instance, tc);
1690 for (var i = 0; i < cbs.length; ++i) cbs[i](instance);
1691 if (updated && options.onUpdate) options.onUpdate(instance);
1436 1692 }
1437 1693 var nestedOperation = 0;
1438 1694 function operation(f) {
1439 1695 return function() {
1440 1696 if (!nestedOperation++) startOperation();
1441 1697 try {var result = f.apply(this, arguments);}
1442 1698 finally {if (!--nestedOperation) endOperation();}
1443 1699 return result;
1444 1700 };
1445 1701 }
1446 1702
1447 function SearchCursor(query, pos, caseFold) {
1448 this.atOccurrence = false;
1449 if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase();
1450
1451 if (pos && typeof pos == "object") pos = clipPos(pos);
1452 else pos = {line: 0, ch: 0};
1453 this.pos = {from: pos, to: pos};
1454
1455 // The matches method is filled in based on the type of query.
1456 // It takes a position and a direction, and returns an object
1457 // describing the next occurrence of the query, or null if no
1458 // more matches were found.
1459 if (typeof query != "string") // Regexp match
1460 this.matches = function(reverse, pos) {
1461 if (reverse) {
1462 var line = lines[pos.line].text.slice(0, pos.ch), match = line.match(query), start = 0;
1463 while (match) {
1464 var ind = line.indexOf(match[0]);
1465 start += ind;
1466 line = line.slice(ind + 1);
1467 var newmatch = line.match(query);
1468 if (newmatch) match = newmatch;
1469 else break;
1470 start++;
1471 }
1472 }
1473 else {
1474 var line = lines[pos.line].text.slice(pos.ch), match = line.match(query),
1475 start = match && pos.ch + line.indexOf(match[0]);
1476 }
1477 if (match)
1478 return {from: {line: pos.line, ch: start},
1479 to: {line: pos.line, ch: start + match[0].length},
1480 match: match};
1481 };
1482 else { // String query
1483 if (caseFold) query = query.toLowerCase();
1484 var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
1485 var target = query.split("\n");
1486 // Different methods for single-line and multi-line queries
1487 if (target.length == 1)
1488 this.matches = function(reverse, pos) {
1489 var line = fold(lines[pos.line].text), len = query.length, match;
1490 if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
1491 : (match = line.indexOf(query, pos.ch)) != -1)
1492 return {from: {line: pos.line, ch: match},
1493 to: {line: pos.line, ch: match + len}};
1494 };
1495 else
1496 this.matches = function(reverse, pos) {
1497 var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(lines[ln].text);
1498 var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
1499 if (reverse ? offsetA >= pos.ch || offsetA != match.length
1500 : offsetA <= pos.ch || offsetA != line.length - match.length)
1501 return;
1502 for (;;) {
1503 if (reverse ? !ln : ln == lines.length - 1) return;
1504 line = fold(lines[ln += reverse ? -1 : 1].text);
1505 match = target[reverse ? --idx : ++idx];
1506 if (idx > 0 && idx < target.length - 1) {
1507 if (line != match) return;
1508 else continue;
1509 }
1510 var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
1511 if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
1512 return;
1513 var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB};
1514 return {from: reverse ? end : start, to: reverse ? start : end};
1515 }
1516 };
1517 }
1518 }
1519
1520 SearchCursor.prototype = {
1521 findNext: function() {return this.find(false);},
1522 findPrevious: function() {return this.find(true);},
1523
1524 find: function(reverse) {
1525 var self = this, pos = clipPos(reverse ? this.pos.from : this.pos.to);
1526 function savePosAndFail(line) {
1527 var pos = {line: line, ch: 0};
1528 self.pos = {from: pos, to: pos};
1529 self.atOccurrence = false;
1530 return false;
1531 }
1532
1533 for (;;) {
1534 if (this.pos = this.matches(reverse, pos)) {
1535 this.atOccurrence = true;
1536 return this.pos.match || true;
1537 }
1538 if (reverse) {
1539 if (!pos.line) return savePosAndFail(0);
1540 pos = {line: pos.line-1, ch: lines[pos.line-1].text.length};
1541 }
1542 else {
1543 if (pos.line == lines.length - 1) return savePosAndFail(lines.length);
1544 pos = {line: pos.line+1, ch: 0};
1545 }
1546 }
1547 },
1548
1549 from: function() {if (this.atOccurrence) return copyPos(this.pos.from);},
1550 to: function() {if (this.atOccurrence) return copyPos(this.pos.to);},
1551
1552 replace: function(newText) {
1553 var self = this;
1554 if (this.atOccurrence)
1555 operation(function() {
1556 self.pos.to = replaceRange(newText, self.pos.from, self.pos.to);
1557 })();
1558 }
1559 };
1560
1561 1703 for (var ext in extensions)
1562 1704 if (extensions.propertyIsEnumerable(ext) &&
1563 1705 !instance.propertyIsEnumerable(ext))
1564 1706 instance[ext] = extensions[ext];
1565 1707 return instance;
1566 1708 } // (end of function CodeMirror)
1567 1709
1568 1710 // The default configuration options.
1569 1711 CodeMirror.defaults = {
1570 1712 value: "",
1571 1713 mode: null,
1572 1714 theme: "default",
1573 1715 indentUnit: 2,
1574 1716 indentWithTabs: false,
1575 tabMode: "classic",
1576 enterMode: "indent",
1717 tabSize: 4,
1718 keyMap: "default",
1719 extraKeys: null,
1577 1720 electricChars: true,
1578 1721 onKeyEvent: null,
1722 lineWrapping: false,
1579 1723 lineNumbers: false,
1580 1724 gutter: false,
1581 1725 fixedGutter: false,
1582 1726 firstLineNumber: 1,
1583 1727 readOnly: false,
1584 smartHome: true,
1585 1728 onChange: null,
1586 1729 onCursorActivity: null,
1587 1730 onGutterClick: null,
1588 1731 onHighlightComplete: null,
1732 onUpdate: null,
1589 1733 onFocus: null, onBlur: null, onScroll: null,
1590 1734 matchBrackets: false,
1591 1735 workTime: 100,
1592 1736 workDelay: 200,
1737 pollInterval: 100,
1593 1738 undoDepth: 40,
1594 1739 tabindex: null,
1595 1740 document: window.document
1596 1741 };
1597 1742
1743 var mac = /Mac/.test(navigator.platform);
1744 var win = /Win/.test(navigator.platform);
1745
1598 1746 // Known modes, by name and by MIME
1599 1747 var modes = {}, mimeModes = {};
1600 1748 CodeMirror.defineMode = function(name, mode) {
1601 1749 if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
1602 1750 modes[name] = mode;
1603 1751 };
1604 1752 CodeMirror.defineMIME = function(mime, spec) {
1605 1753 mimeModes[mime] = spec;
1606 1754 };
1607 1755 CodeMirror.getMode = function(options, spec) {
1608 1756 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
1609 1757 spec = mimeModes[spec];
1610 1758 if (typeof spec == "string")
1611 1759 var mname = spec, config = {};
1612 1760 else if (spec != null)
1613 1761 var mname = spec.name, config = spec;
1614 1762 var mfactory = modes[mname];
1615 1763 if (!mfactory) {
1616 1764 if (window.console) console.warn("No mode " + mname + " found, falling back to plain text.");
1617 1765 return CodeMirror.getMode(options, "text/plain");
1618 1766 }
1619 1767 return mfactory(options, config || {});
1620 1768 };
1621 1769 CodeMirror.listModes = function() {
1622 1770 var list = [];
1623 1771 for (var m in modes)
1624 1772 if (modes.propertyIsEnumerable(m)) list.push(m);
1625 1773 return list;
1626 1774 };
1627 1775 CodeMirror.listMIMEs = function() {
1628 1776 var list = [];
1629 1777 for (var m in mimeModes)
1630 1778 if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
1631 1779 return list;
1632 1780 };
1633 1781
1634 var extensions = {};
1782 var extensions = CodeMirror.extensions = {};
1635 1783 CodeMirror.defineExtension = function(name, func) {
1636 1784 extensions[name] = func;
1637 1785 };
1638 1786
1787 var commands = CodeMirror.commands = {
1788 selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
1789 killLine: function(cm) {
1790 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
1791 if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0});
1792 else cm.replaceRange("", from, sel ? to : {line: from.line});
1793 },
1794 deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});},
1795 undo: function(cm) {cm.undo();},
1796 redo: function(cm) {cm.redo();},
1797 goDocStart: function(cm) {cm.setCursor(0, 0, true);},
1798 goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
1799 goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
1800 goLineStartSmart: function(cm) {
1801 var cur = cm.getCursor();
1802 var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
1803 cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
1804 },
1805 goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
1806 goLineUp: function(cm) {cm.moveV(-1, "line");},
1807 goLineDown: function(cm) {cm.moveV(1, "line");},
1808 goPageUp: function(cm) {cm.moveV(-1, "page");},
1809 goPageDown: function(cm) {cm.moveV(1, "page");},
1810 goCharLeft: function(cm) {cm.moveH(-1, "char");},
1811 goCharRight: function(cm) {cm.moveH(1, "char");},
1812 goColumnLeft: function(cm) {cm.moveH(-1, "column");},
1813 goColumnRight: function(cm) {cm.moveH(1, "column");},
1814 goWordLeft: function(cm) {cm.moveH(-1, "word");},
1815 goWordRight: function(cm) {cm.moveH(1, "word");},
1816 delCharLeft: function(cm) {cm.deleteH(-1, "char");},
1817 delCharRight: function(cm) {cm.deleteH(1, "char");},
1818 delWordLeft: function(cm) {cm.deleteH(-1, "word");},
1819 delWordRight: function(cm) {cm.deleteH(1, "word");},
1820 indentAuto: function(cm) {cm.indentSelection("smart");},
1821 indentMore: function(cm) {cm.indentSelection("add");},
1822 indentLess: function(cm) {cm.indentSelection("subtract");},
1823 insertTab: function(cm) {cm.replaceSelection("\t", "end");},
1824 transposeChars: function(cm) {
1825 var cur = cm.getCursor(), line = cm.getLine(cur.line);
1826 if (cur.ch > 0 && cur.ch < line.length - 1)
1827 cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
1828 {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
1829 },
1830 newlineAndIndent: function(cm) {
1831 cm.replaceSelection("\n", "end");
1832 cm.indentLine(cm.getCursor().line);
1833 },
1834 toggleOverwrite: function(cm) {cm.toggleOverwrite();}
1835 };
1836
1837 var keyMap = CodeMirror.keyMap = {};
1838 keyMap.basic = {
1839 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
1840 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
1841 "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "indentMore", "Shift-Tab": "indentLess",
1842 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
1843 };
1844 // Note that the save and find-related commands aren't defined by
1845 // default. Unknown commands are simply ignored.
1846 keyMap.pcDefault = {
1847 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
1848 "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
1849 "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
1850 "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find",
1851 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
1852 fallthrough: "basic"
1853 };
1854 keyMap.macDefault = {
1855 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
1856 "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
1857 "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft",
1858 "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find",
1859 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
1860 fallthrough: ["basic", "emacsy"]
1861 };
1862 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
1863 keyMap.emacsy = {
1864 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
1865 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
1866 "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
1867 "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
1868 };
1869
1870 function lookupKey(name, extraMap, map) {
1871 function lookup(name, map, ft) {
1872 var found = map[name];
1873 if (found != null) return found;
1874 if (ft == null) ft = map.fallthrough;
1875 if (ft == null) return map.catchall;
1876 if (typeof ft == "string") return lookup(name, keyMap[ft]);
1877 for (var i = 0, e = ft.length; i < e; ++i) {
1878 found = lookup(name, keyMap[ft[i]]);
1879 if (found != null) return found;
1880 }
1881 return null;
1882 }
1883 return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]);
1884 }
1885 function isModifierKey(event) {
1886 var name = keyNames[event.keyCode];
1887 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
1888 }
1889
1639 1890 CodeMirror.fromTextArea = function(textarea, options) {
1640 1891 if (!options) options = {};
1641 1892 options.value = textarea.value;
1642 1893 if (!options.tabindex && textarea.tabindex)
1643 1894 options.tabindex = textarea.tabindex;
1644 1895
1645 1896 function save() {textarea.value = instance.getValue();}
1646 1897 if (textarea.form) {
1647 1898 // Deplorable hack to make the submit method do the right thing.
1648 1899 var rmSubmit = connect(textarea.form, "submit", save, true);
1649 1900 if (typeof textarea.form.submit == "function") {
1650 1901 var realSubmit = textarea.form.submit;
1651 1902 function wrappedSubmit() {
1652 1903 save();
1653 1904 textarea.form.submit = realSubmit;
1654 1905 textarea.form.submit();
1655 1906 textarea.form.submit = wrappedSubmit;
1656 1907 }
1657 1908 textarea.form.submit = wrappedSubmit;
1658 1909 }
1659 1910 }
1660 1911
1661 1912 textarea.style.display = "none";
1662 1913 var instance = CodeMirror(function(node) {
1663 1914 textarea.parentNode.insertBefore(node, textarea.nextSibling);
1664 1915 }, options);
1665 1916 instance.save = save;
1917 instance.getTextArea = function() { return textarea; };
1666 1918 instance.toTextArea = function() {
1667 1919 save();
1668 1920 textarea.parentNode.removeChild(instance.getWrapperElement());
1669 1921 textarea.style.display = "";
1670 1922 if (textarea.form) {
1671 1923 rmSubmit();
1672 1924 if (typeof textarea.form.submit == "function")
1673 1925 textarea.form.submit = realSubmit;
1674 1926 }
1675 1927 };
1676 1928 return instance;
1677 1929 };
1678 1930
1679 1931 // Utility functions for working with state. Exported because modes
1680 1932 // sometimes need to do this.
1681 1933 function copyState(mode, state) {
1682 1934 if (state === true) return state;
1683 1935 if (mode.copyState) return mode.copyState(state);
1684 1936 var nstate = {};
1685 1937 for (var n in state) {
1686 1938 var val = state[n];
1687 1939 if (val instanceof Array) val = val.concat([]);
1688 1940 nstate[n] = val;
1689 1941 }
1690 1942 return nstate;
1691 1943 }
1692 CodeMirror.startState = startState;
1944 CodeMirror.copyState = copyState;
1693 1945 function startState(mode, a1, a2) {
1694 1946 return mode.startState ? mode.startState(a1, a2) : true;
1695 1947 }
1696 CodeMirror.copyState = copyState;
1948 CodeMirror.startState = startState;
1697 1949
1698 1950 // The character stream used by a mode's parser.
1699 function StringStream(string) {
1951 function StringStream(string, tabSize) {
1700 1952 this.pos = this.start = 0;
1701 1953 this.string = string;
1954 this.tabSize = tabSize || 8;
1702 1955 }
1703 1956 StringStream.prototype = {
1704 1957 eol: function() {return this.pos >= this.string.length;},
1705 1958 sol: function() {return this.pos == 0;},
1706 1959 peek: function() {return this.string.charAt(this.pos);},
1707 1960 next: function() {
1708 1961 if (this.pos < this.string.length)
1709 1962 return this.string.charAt(this.pos++);
1710 1963 },
1711 1964 eat: function(match) {
1712 1965 var ch = this.string.charAt(this.pos);
1713 1966 if (typeof match == "string") var ok = ch == match;
1714 1967 else var ok = ch && (match.test ? match.test(ch) : match(ch));
1715 1968 if (ok) {++this.pos; return ch;}
1716 1969 },
1717 1970 eatWhile: function(match) {
1718 1971 var start = this.pos;
1719 1972 while (this.eat(match)){}
1720 1973 return this.pos > start;
1721 1974 },
1722 1975 eatSpace: function() {
1723 1976 var start = this.pos;
1724 1977 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
1725 1978 return this.pos > start;
1726 1979 },
1727 1980 skipToEnd: function() {this.pos = this.string.length;},
1728 1981 skipTo: function(ch) {
1729 1982 var found = this.string.indexOf(ch, this.pos);
1730 1983 if (found > -1) {this.pos = found; return true;}
1731 1984 },
1732 1985 backUp: function(n) {this.pos -= n;},
1733 column: function() {return countColumn(this.string, this.start);},
1734 indentation: function() {return countColumn(this.string);},
1986 column: function() {return countColumn(this.string, this.start, this.tabSize);},
1987 indentation: function() {return countColumn(this.string, null, this.tabSize);},
1735 1988 match: function(pattern, consume, caseInsensitive) {
1736 1989 if (typeof pattern == "string") {
1737 1990 function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
1738 1991 if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
1739 1992 if (consume !== false) this.pos += pattern.length;
1740 1993 return true;
1741 1994 }
1742 1995 }
1743 1996 else {
1744 1997 var match = this.string.slice(this.pos).match(pattern);
1745 1998 if (match && consume !== false) this.pos += match[0].length;
1746 1999 return match;
1747 2000 }
1748 2001 },
1749 2002 current: function(){return this.string.slice(this.start, this.pos);}
1750 2003 };
1751 2004 CodeMirror.StringStream = StringStream;
1752 2005
2006 function MarkedText(from, to, className, set) {
2007 this.from = from; this.to = to; this.style = className; this.set = set;
2008 }
2009 MarkedText.prototype = {
2010 attach: function(line) { this.set.push(line); },
2011 detach: function(line) {
2012 var ix = indexOf(this.set, line);
2013 if (ix > -1) this.set.splice(ix, 1);
2014 },
2015 split: function(pos, lenBefore) {
2016 if (this.to <= pos && this.to != null) return null;
2017 var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
2018 var to = this.to == null ? null : this.to - pos + lenBefore;
2019 return new MarkedText(from, to, this.style, this.set);
2020 },
2021 dup: function() { return new MarkedText(null, null, this.style, this.set); },
2022 clipTo: function(fromOpen, from, toOpen, to, diff) {
2023 if (this.from != null && this.from >= from)
2024 this.from = Math.max(to, this.from) + diff;
2025 if (this.to != null && this.to > from)
2026 this.to = to < this.to ? this.to + diff : from;
2027 if (fromOpen && to > this.from && (to < this.to || this.to == null))
2028 this.from = null;
2029 if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
2030 this.to = null;
2031 },
2032 isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
2033 sameSet: function(x) { return this.set == x.set; }
2034 };
2035
2036 function Bookmark(pos) {
2037 this.from = pos; this.to = pos; this.line = null;
2038 }
2039 Bookmark.prototype = {
2040 attach: function(line) { this.line = line; },
2041 detach: function(line) { if (this.line == line) this.line = null; },
2042 split: function(pos, lenBefore) {
2043 if (pos < this.from) {
2044 this.from = this.to = (this.from - pos) + lenBefore;
2045 return this;
2046 }
2047 },
2048 isDead: function() { return this.from > this.to; },
2049 clipTo: function(fromOpen, from, toOpen, to, diff) {
2050 if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
2051 this.from = 0; this.to = -1;
2052 } else if (this.from > from) {
2053 this.from = this.to = Math.max(to, this.from) + diff;
2054 }
2055 },
2056 sameSet: function(x) { return false; },
2057 find: function() {
2058 if (!this.line || !this.line.parent) return null;
2059 return {line: lineNo(this.line), ch: this.from};
2060 },
2061 clear: function() {
2062 if (this.line) {
2063 var found = indexOf(this.line.marked, this);
2064 if (found != -1) this.line.marked.splice(found, 1);
2065 this.line = null;
2066 }
2067 }
2068 };
2069
1753 2070 // Line objects. These hold state related to a line, including
1754 2071 // highlighting info (the styles array).
1755 2072 function Line(text, styles) {
1756 2073 this.styles = styles || [text, null];
1757 this.stateAfter = null;
1758 2074 this.text = text;
1759 this.marked = this.gutterMarker = this.className = null;
2075 this.height = 1;
2076 this.marked = this.gutterMarker = this.className = this.handlers = null;
2077 this.stateAfter = this.parent = this.hidden = null;
2078 }
2079 Line.inheritMarks = function(text, orig) {
2080 var ln = new Line(text), mk = orig && orig.marked;
2081 if (mk) {
2082 for (var i = 0; i < mk.length; ++i) {
2083 if (mk[i].to == null && mk[i].style) {
2084 var newmk = ln.marked || (ln.marked = []), mark = mk[i];
2085 var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
2086 }
2087 }
2088 }
2089 return ln;
1760 2090 }
1761 2091 Line.prototype = {
1762 2092 // Replace a piece of a line, keeping the styles around it intact.
1763 replace: function(from, to, text) {
1764 var st = [], mk = this.marked;
2093 replace: function(from, to_, text) {
2094 var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
1765 2095 copyStyles(0, from, this.styles, st);
1766 2096 if (text) st.push(text, null);
1767 2097 copyStyles(to, this.text.length, this.styles, st);
1768 2098 this.styles = st;
1769 2099 this.text = this.text.slice(0, from) + text + this.text.slice(to);
1770 2100 this.stateAfter = null;
1771 2101 if (mk) {
1772 var diff = text.length - (to - from), end = this.text.length;
1773 function fix(n) {return n <= Math.min(to, to + diff) ? n : n + diff;}
1774 for (var i = 0; i < mk.length; ++i) {
1775 var mark = mk[i], del = false;
1776 if (mark.from >= end) del = true;
1777 else {mark.from = fix(mark.from); if (mark.to != null) mark.to = fix(mark.to);}
1778 if (del || mark.from >= mark.to) {mk.splice(i, 1); i--;}
2102 var diff = text.length - (to - from);
2103 for (var i = 0, mark = mk[i]; i < mk.length; ++i) {
2104 mark.clipTo(from == null, from || 0, to_ == null, to, diff);
2105 if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
1779 2106 }
1780 2107 }
1781 2108 },
1782 // Split a line in two, again keeping styles intact.
2109 // Split a part off a line, keeping styles and markers intact.
1783 2110 split: function(pos, textBefore) {
1784 var st = [textBefore, null];
2111 var st = [textBefore, null], mk = this.marked;
1785 2112 copyStyles(pos, this.text.length, this.styles, st);
1786 return new Line(textBefore + this.text.slice(pos), st);
2113 var taken = new Line(textBefore + this.text.slice(pos), st);
2114 if (mk) {
2115 for (var i = 0; i < mk.length; ++i) {
2116 var mark = mk[i];
2117 var newmark = mark.split(pos, textBefore.length);
2118 if (newmark) {
2119 if (!taken.marked) taken.marked = [];
2120 taken.marked.push(newmark); newmark.attach(taken);
2121 }
2122 }
2123 }
2124 return taken;
1787 2125 },
1788 addMark: function(from, to, style) {
1789 var mk = this.marked, mark = {from: from, to: to, style: style};
1790 if (this.marked == null) this.marked = [];
1791 this.marked.push(mark);
1792 this.marked.sort(function(a, b){return a.from - b.from;});
1793 return mark;
2126 append: function(line) {
2127 var mylen = this.text.length, mk = line.marked, mymk = this.marked;
2128 this.text += line.text;
2129 copyStyles(0, line.text.length, line.styles, this.styles);
2130 if (mymk) {
2131 for (var i = 0; i < mymk.length; ++i)
2132 if (mymk[i].to == null) mymk[i].to = mylen;
2133 }
2134 if (mk && mk.length) {
2135 if (!mymk) this.marked = mymk = [];
2136 outer: for (var i = 0; i < mk.length; ++i) {
2137 var mark = mk[i];
2138 if (!mark.from) {
2139 for (var j = 0; j < mymk.length; ++j) {
2140 var mymark = mymk[j];
2141 if (mymark.to == mylen && mymark.sameSet(mark)) {
2142 mymark.to = mark.to == null ? null : mark.to + mylen;
2143 if (mymark.isDead()) {
2144 mymark.detach(this);
2145 mk.splice(i--, 1);
2146 }
2147 continue outer;
2148 }
2149 }
2150 }
2151 mymk.push(mark);
2152 mark.attach(this);
2153 mark.from += mylen;
2154 if (mark.to != null) mark.to += mylen;
2155 }
2156 }
2157 },
2158 fixMarkEnds: function(other) {
2159 var mk = this.marked, omk = other.marked;
2160 if (!mk) return;
2161 for (var i = 0; i < mk.length; ++i) {
2162 var mark = mk[i], close = mark.to == null;
2163 if (close && omk) {
2164 for (var j = 0; j < omk.length; ++j)
2165 if (omk[j].sameSet(mark)) {close = false; break;}
2166 }
2167 if (close) mark.to = this.text.length;
2168 }
1794 2169 },
1795 removeMark: function(mark) {
2170 fixMarkStarts: function() {
1796 2171 var mk = this.marked;
1797 2172 if (!mk) return;
1798 2173 for (var i = 0; i < mk.length; ++i)
1799 if (mk[i] == mark) {mk.splice(i, 1); break;}
2174 if (mk[i].from == null) mk[i].from = 0;
2175 },
2176 addMark: function(mark) {
2177 mark.attach(this);
2178 if (this.marked == null) this.marked = [];
2179 this.marked.push(mark);
2180 this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
1800 2181 },
1801 2182 // Run the given mode's parser over a line, update the styles
1802 2183 // array, which contains alternating fragments of text and CSS
1803 2184 // classes.
1804 highlight: function(mode, state) {
1805 var stream = new StringStream(this.text), st = this.styles, pos = 0;
2185 highlight: function(mode, state, tabSize) {
2186 var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;
1806 2187 var changed = false, curWord = st[0], prevWord;
1807 2188 if (this.text == "" && mode.blankLine) mode.blankLine(state);
1808 2189 while (!stream.eol()) {
1809 2190 var style = mode.token(stream, state);
1810 2191 var substr = this.text.slice(stream.start, stream.pos);
1811 2192 stream.start = stream.pos;
1812 2193 if (pos && st[pos-1] == style)
1813 2194 st[pos-2] += substr;
1814 2195 else if (substr) {
1815 2196 if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;
1816 2197 st[pos++] = substr; st[pos++] = style;
1817 2198 prevWord = curWord; curWord = st[pos];
1818 2199 }
1819 2200 // Give up when line is ridiculously long
1820 2201 if (stream.pos > 5000) {
1821 2202 st[pos++] = this.text.slice(stream.pos); st[pos++] = null;
1822 2203 break;
1823 2204 }
1824 2205 }
1825 2206 if (st.length != pos) {st.length = pos; changed = true;}
1826 2207 if (pos && st[pos-2] != prevWord) changed = true;
1827 2208 // Short lines with simple highlights return null, and are
1828 2209 // counted as changed by the driver because they are likely to
1829 2210 // highlight the same way in various contexts.
1830 2211 return changed || (st.length < 5 && this.text.length < 10 ? null : false);
1831 2212 },
1832 2213 // Fetch the parser token for a given character. Useful for hacks
1833 2214 // that want to inspect the mode state (say, for completion).
1834 2215 getTokenAt: function(mode, state, ch) {
1835 2216 var txt = this.text, stream = new StringStream(txt);
1836 2217 while (stream.pos < ch && !stream.eol()) {
1837 2218 stream.start = stream.pos;
1838 2219 var style = mode.token(stream, state);
1839 2220 }
1840 2221 return {start: stream.start,
1841 2222 end: stream.pos,
1842 2223 string: stream.current(),
1843 2224 className: style || null,
1844 2225 state: state};
1845 2226 },
1846 indentation: function() {return countColumn(this.text);},
2227 indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
1847 2228 // Produces an HTML fragment for the line, taking selection,
1848 2229 // marking, and highlighting into account.
1849 getHTML: function(sfrom, sto, includePre, endAt) {
1850 var html = [];
2230 getHTML: function(sfrom, sto, includePre, tabText, endAt) {
2231 var html = [], first = true;
1851 2232 if (includePre)
1852 2233 html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
1853 2234 function span(text, style) {
1854 2235 if (!text) return;
1855 if (style) html.push('<span class="', style, '">', htmlEscape(text), "</span>");
1856 else html.push(htmlEscape(text));
2236 // Work around a bug where, in some compat modes, IE ignores leading spaces
2237 if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
2238 first = false;
2239 if (style) html.push('<span class="', style, '">', htmlEscape(text).replace(/\t/g, tabText), "</span>");
2240 else html.push(htmlEscape(text).replace(/\t/g, tabText));
1857 2241 }
1858 2242 var st = this.styles, allText = this.text, marked = this.marked;
1859 2243 if (sfrom == sto) sfrom = null;
1860 2244 var len = allText.length;
1861 2245 if (endAt != null) len = Math.min(endAt, len);
1862 2246
1863 2247 if (!allText && endAt == null)
1864 2248 span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
1865 2249 else if (!marked && sfrom == null)
1866 2250 for (var i = 0, ch = 0; ch < len; i+=2) {
1867 var str = st[i], l = str.length;
2251 var str = st[i], style = st[i+1], l = str.length;
1868 2252 if (ch + l > len) str = str.slice(0, len - ch);
1869 2253 ch += l;
1870 span(str, "cm-" + st[i+1]);
2254 span(str, style && "cm-" + style);
1871 2255 }
1872 2256 else {
1873 2257 var pos = 0, i = 0, text = "", style, sg = 0;
1874 2258 var markpos = -1, mark = null;
1875 2259 function nextMark() {
1876 2260 if (marked) {
1877 2261 markpos += 1;
1878 2262 mark = (markpos < marked.length) ? marked[markpos] : null;
1879 2263 }
1880 2264 }
1881 2265 nextMark();
1882 2266 while (pos < len) {
1883 2267 var upto = len;
1884 2268 var extraStyle = "";
1885 2269 if (sfrom != null) {
1886 2270 if (sfrom > pos) upto = sfrom;
1887 2271 else if (sto == null || sto > pos) {
1888 2272 extraStyle = " CodeMirror-selected";
1889 2273 if (sto != null) upto = Math.min(upto, sto);
1890 2274 }
1891 2275 }
1892 2276 while (mark && mark.to != null && mark.to <= pos) nextMark();
1893 2277 if (mark) {
1894 2278 if (mark.from > pos) upto = Math.min(upto, mark.from);
1895 2279 else {
1896 2280 extraStyle += " " + mark.style;
1897 2281 if (mark.to != null) upto = Math.min(upto, mark.to);
1898 2282 }
1899 2283 }
1900 2284 for (;;) {
1901 2285 var end = pos + text.length;
1902 2286 var appliedStyle = style;
1903 2287 if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle;
1904 2288 span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
1905 2289 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
1906 2290 pos = end;
1907 2291 text = st[i++]; style = "cm-" + st[i++];
1908 2292 }
1909 2293 }
1910 2294 if (sfrom != null && sto == null) span(" ", "CodeMirror-selected");
1911 2295 }
1912 2296 if (includePre) html.push("</pre>");
1913 2297 return html.join("");
2298 },
2299 cleanUp: function() {
2300 this.parent = null;
2301 if (this.marked)
2302 for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
1914 2303 }
1915 2304 };
1916 2305 // Utility used by replace and split above
1917 2306 function copyStyles(from, to, source, dest) {
1918 2307 for (var i = 0, pos = 0, state = 0; pos < to; i+=2) {
1919 2308 var part = source[i], end = pos + part.length;
1920 2309 if (state == 0) {
1921 2310 if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]);
1922 2311 if (end >= from) state = 1;
1923 2312 }
1924 2313 else if (state == 1) {
1925 2314 if (end > to) dest.push(part.slice(0, to - pos), source[i+1]);
1926 2315 else dest.push(part, source[i+1]);
1927 2316 }
1928 2317 pos = end;
1929 2318 }
1930 2319 }
1931 2320
2321 // Data structure that holds the sequence of lines.
2322 function LeafChunk(lines) {
2323 this.lines = lines;
2324 this.parent = null;
2325 for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
2326 lines[i].parent = this;
2327 height += lines[i].height;
2328 }
2329 this.height = height;
2330 }
2331 LeafChunk.prototype = {
2332 chunkSize: function() { return this.lines.length; },
2333 remove: function(at, n, callbacks) {
2334 for (var i = at, e = at + n; i < e; ++i) {
2335 var line = this.lines[i];
2336 this.height -= line.height;
2337 line.cleanUp();
2338 if (line.handlers)
2339 for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);
2340 }
2341 this.lines.splice(at, n);
2342 },
2343 collapse: function(lines) {
2344 lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
2345 },
2346 insertHeight: function(at, lines, height) {
2347 this.height += height;
2348 this.lines.splice.apply(this.lines, [at, 0].concat(lines));
2349 for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
2350 },
2351 iterN: function(at, n, op) {
2352 for (var e = at + n; at < e; ++at)
2353 if (op(this.lines[at])) return true;
2354 }
2355 };
2356 function BranchChunk(children) {
2357 this.children = children;
2358 var size = 0, height = 0;
2359 for (var i = 0, e = children.length; i < e; ++i) {
2360 var ch = children[i];
2361 size += ch.chunkSize(); height += ch.height;
2362 ch.parent = this;
2363 }
2364 this.size = size;
2365 this.height = height;
2366 this.parent = null;
2367 }
2368 BranchChunk.prototype = {
2369 chunkSize: function() { return this.size; },
2370 remove: function(at, n, callbacks) {
2371 this.size -= n;
2372 for (var i = 0; i < this.children.length; ++i) {
2373 var child = this.children[i], sz = child.chunkSize();
2374 if (at < sz) {
2375 var rm = Math.min(n, sz - at), oldHeight = child.height;
2376 child.remove(at, rm, callbacks);
2377 this.height -= oldHeight - child.height;
2378 if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
2379 if ((n -= rm) == 0) break;
2380 at = 0;
2381 } else at -= sz;
2382 }
2383 if (this.size - n < 25) {
2384 var lines = [];
2385 this.collapse(lines);
2386 this.children = [new LeafChunk(lines)];
2387 }
2388 },
2389 collapse: function(lines) {
2390 for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
2391 },
2392 insert: function(at, lines) {
2393 var height = 0;
2394 for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
2395 this.insertHeight(at, lines, height);
2396 },
2397 insertHeight: function(at, lines, height) {
2398 this.size += lines.length;
2399 this.height += height;
2400 for (var i = 0, e = this.children.length; i < e; ++i) {
2401 var child = this.children[i], sz = child.chunkSize();
2402 if (at <= sz) {
2403 child.insertHeight(at, lines, height);
2404 if (child.lines && child.lines.length > 50) {
2405 while (child.lines.length > 50) {
2406 var spilled = child.lines.splice(child.lines.length - 25, 25);
2407 var newleaf = new LeafChunk(spilled);
2408 child.height -= newleaf.height;
2409 this.children.splice(i + 1, 0, newleaf);
2410 newleaf.parent = this;
2411 }
2412 this.maybeSpill();
2413 }
2414 break;
2415 }
2416 at -= sz;
2417 }
2418 },
2419 maybeSpill: function() {
2420 if (this.children.length <= 10) return;
2421 var me = this;
2422 do {
2423 var spilled = me.children.splice(me.children.length - 5, 5);
2424 var sibling = new BranchChunk(spilled);
2425 if (!me.parent) { // Become the parent node
2426 var copy = new BranchChunk(me.children);
2427 copy.parent = me;
2428 me.children = [copy, sibling];
2429 me = copy;
2430 } else {
2431 me.size -= sibling.size;
2432 me.height -= sibling.height;
2433 var myIndex = indexOf(me.parent.children, me);
2434 me.parent.children.splice(myIndex + 1, 0, sibling);
2435 }
2436 sibling.parent = me.parent;
2437 } while (me.children.length > 10);
2438 me.parent.maybeSpill();
2439 },
2440 iter: function(from, to, op) { this.iterN(from, to - from, op); },
2441 iterN: function(at, n, op) {
2442 for (var i = 0, e = this.children.length; i < e; ++i) {
2443 var child = this.children[i], sz = child.chunkSize();
2444 if (at < sz) {
2445 var used = Math.min(n, sz - at);
2446 if (child.iterN(at, used, op)) return true;
2447 if ((n -= used) == 0) break;
2448 at = 0;
2449 } else at -= sz;
2450 }
2451 }
2452 };
2453
2454 function getLineAt(chunk, n) {
2455 while (!chunk.lines) {
2456 for (var i = 0;; ++i) {
2457 var child = chunk.children[i], sz = child.chunkSize();
2458 if (n < sz) { chunk = child; break; }
2459 n -= sz;
2460 }
2461 }
2462 return chunk.lines[n];
2463 }
2464 function lineNo(line) {
2465 if (line.parent == null) return null;
2466 var cur = line.parent, no = indexOf(cur.lines, line);
2467 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
2468 for (var i = 0, e = chunk.children.length; ; ++i) {
2469 if (chunk.children[i] == cur) break;
2470 no += chunk.children[i].chunkSize();
2471 }
2472 }
2473 return no;
2474 }
2475 function lineAtHeight(chunk, h) {
2476 var n = 0;
2477 outer: do {
2478 for (var i = 0, e = chunk.children.length; i < e; ++i) {
2479 var child = chunk.children[i], ch = child.height;
2480 if (h < ch) { chunk = child; continue outer; }
2481 h -= ch;
2482 n += child.chunkSize();
2483 }
2484 return n;
2485 } while (!chunk.lines);
2486 for (var i = 0, e = chunk.lines.length; i < e; ++i) {
2487 var line = chunk.lines[i], lh = line.height;
2488 if (h < lh) break;
2489 h -= lh;
2490 }
2491 return n + i;
2492 }
2493 function heightAtLine(chunk, n) {
2494 var h = 0;
2495 outer: do {
2496 for (var i = 0, e = chunk.children.length; i < e; ++i) {
2497 var child = chunk.children[i], sz = child.chunkSize();
2498 if (n < sz) { chunk = child; continue outer; }
2499 n -= sz;
2500 h += child.height;
2501 }
2502 return h;
2503 } while (!chunk.lines);
2504 for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
2505 return h;
2506 }
2507
1932 2508 // The history object 'chunks' changes that are made close together
1933 2509 // and at almost the same time into bigger undoable units.
1934 2510 function History() {
1935 2511 this.time = 0;
1936 2512 this.done = []; this.undone = [];
1937 2513 }
1938 2514 History.prototype = {
1939 2515 addChange: function(start, added, old) {
1940 2516 this.undone.length = 0;
1941 2517 var time = +new Date, last = this.done[this.done.length - 1];
1942 2518 if (time - this.time > 400 || !last ||
1943 2519 last.start > start + added || last.start + last.added < start - last.added + last.old.length)
1944 2520 this.done.push({start: start, added: added, old: old});
1945 2521 else {
1946 2522 var oldoff = 0;
1947 2523 if (start < last.start) {
1948 2524 for (var i = last.start - start - 1; i >= 0; --i)
1949 2525 last.old.unshift(old[i]);
1950 2526 last.added += last.start - start;
1951 2527 last.start = start;
1952 2528 }
1953 2529 else if (last.start < start) {
1954 2530 oldoff = start - last.start;
1955 2531 added += oldoff;
1956 2532 }
1957 2533 for (var i = last.added - oldoff, e = old.length; i < e; ++i)
1958 2534 last.old.push(old[i]);
1959 2535 if (last.added < added) last.added = added;
1960 2536 }
1961 2537 this.time = time;
1962 2538 }
1963 2539 };
1964 2540
1965 2541 function stopMethod() {e_stop(this);}
1966 2542 // Ensure an event has a stop method.
1967 2543 function addStop(event) {
1968 2544 if (!event.stop) event.stop = stopMethod;
1969 2545 return event;
1970 2546 }
1971 2547
1972 2548 function e_preventDefault(e) {
1973 2549 if (e.preventDefault) e.preventDefault();
1974 2550 else e.returnValue = false;
1975 2551 }
1976 2552 function e_stopPropagation(e) {
1977 2553 if (e.stopPropagation) e.stopPropagation();
1978 2554 else e.cancelBubble = true;
1979 2555 }
1980 2556 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
2557 CodeMirror.e_stop = e_stop;
2558 CodeMirror.e_preventDefault = e_preventDefault;
2559 CodeMirror.e_stopPropagation = e_stopPropagation;
2560
1981 2561 function e_target(e) {return e.target || e.srcElement;}
1982 2562 function e_button(e) {
1983 2563 if (e.which) return e.which;
1984 2564 else if (e.button & 1) return 1;
1985 2565 else if (e.button & 2) return 3;
1986 2566 else if (e.button & 4) return 2;
1987 2567 }
1988 2568
1989 2569 // Event handler registration. If disconnect is true, it'll return a
1990 2570 // function that unregisters the handler.
1991 2571 function connect(node, type, handler, disconnect) {
1992 function wrapHandler(event) {handler(event || window.event);}
1993 2572 if (typeof node.addEventListener == "function") {
1994 node.addEventListener(type, wrapHandler, false);
1995 if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);};
2573 node.addEventListener(type, handler, false);
2574 if (disconnect) return function() {node.removeEventListener(type, handler, false);};
1996 2575 }
1997 2576 else {
2577 var wrapHandler = function(event) {handler(event || window.event);};
1998 2578 node.attachEvent("on" + type, wrapHandler);
1999 2579 if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
2000 2580 }
2001 2581 }
2582 CodeMirror.connect = connect;
2002 2583
2003 2584 function Delayed() {this.id = null;}
2004 2585 Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
2005 2586
2006 // Some IE versions don't preserve whitespace when setting the
2007 // innerHTML of a PRE tag.
2008 var badInnerHTML = (function() {
2009 var pre = document.createElement("pre");
2010 pre.innerHTML = " "; return !pre.innerHTML;
2011 })();
2012
2013 2587 // Detect drag-and-drop
2014 var dragAndDrop = (function() {
2588 var dragAndDrop = function() {
2015 2589 // IE8 has ondragstart and ondrop properties, but doesn't seem to
2016 2590 // actually support ondragstart the way it's supposed to work.
2017 2591 if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false;
2018 2592 var div = document.createElement('div');
2019 return "ondragstart" in div && "ondrop" in div;
2020 })();
2593 return "draggable" in div;
2594 }();
2021 2595
2022 2596 var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
2023 2597 var ie = /MSIE \d/.test(navigator.userAgent);
2024 var safari = /Apple Computer/.test(navigator.vendor);
2598 var webkit = /WebKit\//.test(navigator.userAgent);
2025 2599
2026 2600 var lineSep = "\n";
2027 2601 // Feature-detect whether newlines in textareas are converted to \r\n
2028 2602 (function () {
2029 2603 var te = document.createElement("textarea");
2030 2604 te.value = "foo\nbar";
2031 2605 if (te.value.indexOf("\r") > -1) lineSep = "\r\n";
2032 2606 }());
2033 2607
2034 var tabSize = 8;
2035 var mac = /Mac/.test(navigator.platform);
2036 var movementKeys = {};
2037 for (var i = 35; i <= 40; ++i)
2038 movementKeys[i] = movementKeys["c" + i] = true;
2039
2040 2608 // Counts the column offset in a string, taking tabs into account.
2041 2609 // Used mostly to find indentation.
2042 function countColumn(string, end) {
2610 function countColumn(string, end, tabSize) {
2043 2611 if (end == null) {
2044 2612 end = string.search(/[^\s\u00a0]/);
2045 2613 if (end == -1) end = string.length;
2046 2614 }
2047 2615 for (var i = 0, n = 0; i < end; ++i) {
2048 2616 if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
2049 2617 else ++n;
2050 2618 }
2051 2619 return n;
2052 2620 }
2053 2621
2054 2622 function computedStyle(elt) {
2055 2623 if (elt.currentStyle) return elt.currentStyle;
2056 2624 return window.getComputedStyle(elt, null);
2057 2625 }
2626
2058 2627 // Find the position of an element by following the offsetParent chain.
2059 2628 // If screen==true, it returns screen (rather than page) coordinates.
2060 2629 function eltOffset(node, screen) {
2061 var doc = node.ownerDocument.body;
2062 var x = 0, y = 0, skipDoc = false;
2630 var bod = node.ownerDocument.body;
2631 var x = 0, y = 0, skipBody = false;
2063 2632 for (var n = node; n; n = n.offsetParent) {
2064 x += n.offsetLeft; y += n.offsetTop;
2633 var ol = n.offsetLeft, ot = n.offsetTop;
2634 // Firefox reports weird inverted offsets when the body has a border.
2635 if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); }
2636 else { x += ol, y += ot; }
2065 2637 if (screen && computedStyle(n).position == "fixed")
2066 skipDoc = true;
2638 skipBody = true;
2067 2639 }
2068 var e = screen && !skipDoc ? null : doc;
2640 var e = screen && !skipBody ? null : bod;
2069 2641 for (var n = node.parentNode; n != e; n = n.parentNode)
2070 2642 if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}
2071 2643 return {left: x, top: y};
2072 2644 }
2645 // Use the faster and saner getBoundingClientRect method when possible.
2646 if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) {
2647 // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
2648 // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
2649 try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
2650 catch(e) { box = {top: 0, left: 0}; }
2651 if (!screen) {
2652 // Get the toplevel scroll, working around browser differences.
2653 if (window.pageYOffset == null) {
2654 var t = document.documentElement || document.body.parentNode;
2655 if (t.scrollTop == null) t = document.body;
2656 box.top += t.scrollTop; box.left += t.scrollLeft;
2657 } else {
2658 box.top += window.pageYOffset; box.left += window.pageXOffset;
2659 }
2660 }
2661 return box;
2662 };
2663
2073 2664 // Get a node's text content.
2074 2665 function eltText(node) {
2075 2666 return node.textContent || node.innerText || node.nodeValue || "";
2076 2667 }
2077 2668
2078 2669 // Operations on {line, ch} objects.
2079 2670 function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
2080 2671 function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2081 2672 function copyPos(x) {return {line: x.line, ch: x.ch};}
2082 2673
2083 var escapeElement = document.createElement("div");
2674 var escapeElement = document.createElement("pre");
2084 2675 function htmlEscape(str) {
2085 escapeElement.innerText = escapeElement.textContent = str;
2676 escapeElement.textContent = str;
2086 2677 return escapeElement.innerHTML;
2087 2678 }
2679 // Recent (late 2011) Opera betas insert bogus newlines at the start
2680 // of the textContent, so we strip those.
2681 if (htmlEscape("a") == "\na")
2682 htmlEscape = function(str) {
2683 escapeElement.textContent = str;
2684 return escapeElement.innerHTML.slice(1);
2685 };
2686 // Some IEs don't preserve tabs through innerHTML
2687 else if (htmlEscape("\t") != "\t")
2688 htmlEscape = function(str) {
2689 escapeElement.innerHTML = "";
2690 escapeElement.appendChild(document.createTextNode(str));
2691 return escapeElement.innerHTML;
2692 };
2088 2693 CodeMirror.htmlEscape = htmlEscape;
2089 2694
2090 2695 // Used to position the cursor after an undo/redo by finding the
2091 2696 // last edited character.
2092 2697 function editEnd(from, to) {
2093 2698 if (!to) return from ? from.length : 0;
2094 2699 if (!from) return to.length;
2095 2700 for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
2096 2701 if (from.charAt(i) != to.charAt(j)) break;
2097 2702 return j + 1;
2098 2703 }
2099 2704
2100 2705 function indexOf(collection, elt) {
2101 2706 if (collection.indexOf) return collection.indexOf(elt);
2102 2707 for (var i = 0, e = collection.length; i < e; ++i)
2103 2708 if (collection[i] == elt) return i;
2104 2709 return -1;
2105 2710 }
2711 function isWordChar(ch) {
2712 return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();
2713 }
2106 2714
2107 2715 // See if "".split is the broken IE version, if so, provide an
2108 2716 // alternative way to split lines.
2109 var splitLines, selRange, setSelRange;
2110 if ("\n\nb".split(/\n/).length != 3)
2111 splitLines = function(string) {
2112 var pos = 0, nl, result = [];
2113 while ((nl = string.indexOf("\n", pos)) > -1) {
2114 result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
2115 pos = nl + 1;
2116 }
2117 result.push(string.slice(pos));
2118 return result;
2119 };
2120 else
2121 splitLines = function(string){return string.split(/\r?\n/);};
2717 var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
2718 var pos = 0, nl, result = [];
2719 while ((nl = string.indexOf("\n", pos)) > -1) {
2720 result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
2721 pos = nl + 1;
2722 }
2723 result.push(string.slice(pos));
2724 return result;
2725 } : function(string){return string.split(/\r?\n/);};
2122 2726 CodeMirror.splitLines = splitLines;
2123 2727
2124 // Sane model of finding and setting the selection in a textarea
2125 if (window.getSelection) {
2126 selRange = function(te) {
2127 try {return {start: te.selectionStart, end: te.selectionEnd};}
2128 catch(e) {return null;}
2129 };
2130 if (safari)
2131 // On Safari, selection set with setSelectionRange are in a sort
2132 // of limbo wrt their anchor. If you press shift-left in them,
2133 // the anchor is put at the end, and the selection expanded to
2134 // the left. If you press shift-right, the anchor ends up at the
2135 // front. This is not what CodeMirror wants, so it does a
2136 // spurious modify() call to get out of limbo.
2137 setSelRange = function(te, start, end) {
2138 if (start == end)
2139 te.setSelectionRange(start, end);
2140 else {
2141 te.setSelectionRange(start, end - 1);
2142 window.getSelection().modify("extend", "forward", "character");
2143 }
2144 };
2145 else
2146 setSelRange = function(te, start, end) {
2147 try {te.setSelectionRange(start, end);}
2148 catch(e) {} // Fails on Firefox when textarea isn't part of the document
2149 };
2150 }
2151 // IE model. Don't ask.
2152 else {
2153 selRange = function(te) {
2154 try {var range = te.ownerDocument.selection.createRange();}
2155 catch(e) {return null;}
2156 if (!range || range.parentElement() != te) return null;
2157 var val = te.value, len = val.length, localRange = te.createTextRange();
2158 localRange.moveToBookmark(range.getBookmark());
2159 var endRange = te.createTextRange();
2160 endRange.collapse(false);
2161
2162 if (localRange.compareEndPoints("StartToEnd", endRange) > -1)
2163 return {start: len, end: len};
2164
2165 var start = -localRange.moveStart("character", -len);
2166 for (var i = val.indexOf("\r"); i > -1 && i < start; i = val.indexOf("\r", i+1), start++) {}
2167
2168 if (localRange.compareEndPoints("EndToEnd", endRange) > -1)
2169 return {start: start, end: len};
2170
2171 var end = -localRange.moveEnd("character", -len);
2172 for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {}
2173 return {start: start, end: end};
2174 };
2175 setSelRange = function(te, start, end) {
2176 var range = te.createTextRange();
2177 range.collapse(true);
2178 var endrange = range.duplicate();
2179 var newlines = 0, txt = te.value;
2180 for (var pos = txt.indexOf("\n"); pos > -1 && pos < start; pos = txt.indexOf("\n", pos + 1))
2181 ++newlines;
2182 range.move("character", start - newlines);
2183 for (; pos > -1 && pos < end; pos = txt.indexOf("\n", pos + 1))
2184 ++newlines;
2185 endrange.move("character", end - newlines);
2186 range.setEndPoint("EndToEnd", endrange);
2187 range.select();
2188 };
2189 }
2728 var hasSelection = window.getSelection ? function(te) {
2729 try { return te.selectionStart != te.selectionEnd; }
2730 catch(e) { return false; }
2731 } : function(te) {
2732 try {var range = te.ownerDocument.selection.createRange();}
2733 catch(e) {}
2734 if (!range || range.parentElement() != te) return false;
2735 return range.compareEndPoints("StartToEnd", range) != 0;
2736 };
2190 2737
2191 2738 CodeMirror.defineMode("null", function() {
2192 2739 return {token: function(stream) {stream.skipToEnd();}};
2193 2740 });
2194 2741 CodeMirror.defineMIME("text/plain", "null");
2195 2742
2743 var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
2744 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
2745 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
2746 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 186: ";", 187: "=", 188: ",",
2747 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63276: "PageUp",
2748 63277: "PageDown", 63275: "End", 63273: "Home", 63234: "Left", 63232: "Up", 63235: "Right",
2749 63233: "Down", 63302: "Insert", 63272: "Delete"};
2750 CodeMirror.keyNames = keyNames;
2751 (function() {
2752 // Number keys
2753 for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
2754 // Alphabetic keys
2755 for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
2756 // Function keys
2757 for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
2758 })();
2759
2196 2760 return CodeMirror;
2197 2761 })();
@@ -1,51 +1,51 b''
1 1 // Utility function that allows modes to be combined. The mode given
2 2 // as the base argument takes care of most of the normal mode
3 3 // functionality, but a second (typically simple) mode is used, which
4 4 // can override the style of text. Both modes get to parse all of the
5 5 // text, but when both assign a non-null style to a piece of code, the
6 6 // overlay wins, unless the combine argument was true, in which case
7 7 // the styles are combined.
8 8
9 9 CodeMirror.overlayParser = function(base, overlay, combine) {
10 10 return {
11 11 startState: function() {
12 12 return {
13 13 base: CodeMirror.startState(base),
14 14 overlay: CodeMirror.startState(overlay),
15 15 basePos: 0, baseCur: null,
16 16 overlayPos: 0, overlayCur: null
17 17 };
18 18 },
19 19 copyState: function(state) {
20 20 return {
21 21 base: CodeMirror.copyState(base, state.base),
22 22 overlay: CodeMirror.copyState(overlay, state.overlay),
23 23 basePos: state.basePos, baseCur: null,
24 24 overlayPos: state.overlayPos, overlayCur: null
25 25 };
26 26 },
27 27
28 28 token: function(stream, state) {
29 29 if (stream.start == state.basePos) {
30 30 state.baseCur = base.token(stream, state.base);
31 31 state.basePos = stream.pos;
32 32 }
33 33 if (stream.start == state.overlayPos) {
34 34 stream.pos = stream.start;
35 35 state.overlayCur = overlay.token(stream, state.overlay);
36 36 state.overlayPos = stream.pos;
37 37 }
38 38 stream.pos = Math.min(state.basePos, state.overlayPos);
39 39 if (stream.eol()) state.basePos = state.overlayPos = 0;
40 40
41 41 if (state.overlayCur == null) return state.baseCur;
42 42 if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur;
43 43 else return state.overlayCur;
44 44 },
45
45
46 46 indent: function(state, textAfter) {
47 47 return base.indent(state.base, textAfter);
48 48 },
49 49 electricChars: base.electricChars
50 50 };
51 51 };
@@ -1,27 +1,27 b''
1 1 CodeMirror.runMode = function(string, modespec, callback) {
2 2 var mode = CodeMirror.getMode({indentUnit: 2}, modespec);
3 3 var isNode = callback.nodeType == 1;
4 4 if (isNode) {
5 5 var node = callback, accum = [];
6 6 callback = function(string, style) {
7 7 if (string == "\n")
8 8 accum.push("<br>");
9 9 else if (style)
10 10 accum.push("<span class=\"cm-" + CodeMirror.htmlEscape(style) + "\">" + CodeMirror.htmlEscape(string) + "</span>");
11 11 else
12 12 accum.push(CodeMirror.htmlEscape(string));
13 13 }
14 14 }
15 15 var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode);
16 16 for (var i = 0, e = lines.length; i < e; ++i) {
17 17 if (i) callback("\n");
18 18 var stream = new CodeMirror.StringStream(lines[i]);
19 19 while (!stream.eol()) {
20 20 var style = mode.token(stream, state);
21 callback(stream.current(), style);
21 callback(stream.current(), style, i, stream.start);
22 22 stream.start = stream.pos;
23 23 }
24 24 }
25 25 if (isNode)
26 26 node.innerHTML = accum.join("");
27 27 };
@@ -1,56 +1,55 b''
1 1 <!doctype html>
2 2 <html>
3 3 <head>
4 <title>CodeMirror 2: CSS mode</title>
4 <title>CodeMirror: CSS mode</title>
5 5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 6 <script src="../../lib/codemirror.js"></script>
7 7 <script src="css.js"></script>
8 <link rel="stylesheet" href="../../theme/default.css">
9 8 <style>.CodeMirror {background: #f8f8f8;}</style>
10 <link rel="stylesheet" href="../../css/docs.css">
9 <link rel="stylesheet" href="../../doc/docs.css">
11 10 </head>
12 11 <body>
13 <h1>CodeMirror 2: CSS mode</h1>
12 <h1>CodeMirror: CSS mode</h1>
14 13 <form><textarea id="code" name="code">
15 14 /* Some example CSS */
16 15
17 16 @import url("something.css");
18 17
19 18 body {
20 19 margin: 0;
21 20 padding: 3em 6em;
22 21 font-family: tahoma, arial, sans-serif;
23 22 color: #000;
24 23 }
25 24
26 25 #navigation a {
27 26 font-weight: bold;
28 27 text-decoration: none !important;
29 28 }
30 29
31 30 h1 {
32 31 font-size: 2.5em;
33 32 }
34 33
35 34 h2 {
36 35 font-size: 1.7em;
37 36 }
38 37
39 38 h1:before, h2:before {
40 39 content: "::";
41 40 }
42 41
43 42 code {
44 43 font-family: courier, monospace;
45 44 font-size: 80%;
46 45 color: #418A8A;
47 46 }
48 47 </textarea></form>
49 48 <script>
50 49 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
51 50 </script>
52 51
53 52 <p><strong>MIME types defined:</strong> <code>text/css</code>.</p>
54 53
55 54 </body>
56 55 </html>
@@ -1,79 +1,83 b''
1 1 CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
2 2 var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
3 3 var jsMode = CodeMirror.getMode(config, "javascript");
4 4 var cssMode = CodeMirror.getMode(config, "css");
5 5
6 6 function html(stream, state) {
7 7 var style = htmlMode.token(stream, state.htmlState);
8 8 if (style == "tag" && stream.current() == ">" && state.htmlState.context) {
9 9 if (/^script$/i.test(state.htmlState.context.tagName)) {
10 10 state.token = javascript;
11 11 state.localState = jsMode.startState(htmlMode.indent(state.htmlState, ""));
12 12 state.mode = "javascript";
13 13 }
14 14 else if (/^style$/i.test(state.htmlState.context.tagName)) {
15 15 state.token = css;
16 16 state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
17 17 state.mode = "css";
18 18 }
19 19 }
20 20 return style;
21 21 }
22 22 function maybeBackup(stream, pat, style) {
23 23 var cur = stream.current();
24 24 var close = cur.search(pat);
25 25 if (close > -1) stream.backUp(cur.length - close);
26 26 return style;
27 27 }
28 28 function javascript(stream, state) {
29 29 if (stream.match(/^<\/\s*script\s*>/i, false)) {
30 30 state.token = html;
31 31 state.curState = null;
32 32 state.mode = "html";
33 33 return html(stream, state);
34 34 }
35 35 return maybeBackup(stream, /<\/\s*script\s*>/,
36 36 jsMode.token(stream, state.localState));
37 37 }
38 38 function css(stream, state) {
39 39 if (stream.match(/^<\/\s*style\s*>/i, false)) {
40 40 state.token = html;
41 41 state.localState = null;
42 42 state.mode = "html";
43 43 return html(stream, state);
44 44 }
45 45 return maybeBackup(stream, /<\/\s*style\s*>/,
46 46 cssMode.token(stream, state.localState));
47 47 }
48 48
49 49 return {
50 50 startState: function() {
51 51 var state = htmlMode.startState();
52 52 return {token: html, localState: null, mode: "html", htmlState: state};
53 53 },
54 54
55 55 copyState: function(state) {
56 56 if (state.localState)
57 57 var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState);
58 58 return {token: state.token, localState: local, mode: state.mode,
59 59 htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
60 60 },
61 61
62 62 token: function(stream, state) {
63 63 return state.token(stream, state);
64 64 },
65 65
66 66 indent: function(state, textAfter) {
67 67 if (state.token == html || /^\s*<\//.test(textAfter))
68 68 return htmlMode.indent(state.htmlState, textAfter);
69 69 else if (state.token == javascript)
70 70 return jsMode.indent(state.localState, textAfter);
71 71 else
72 72 return cssMode.indent(state.localState, textAfter);
73 73 },
74 74
75 compareStates: function(a, b) {
76 return htmlMode.compareStates(a.htmlState, b.htmlState);
77 },
78
75 79 electricChars: "/{}:"
76 80 }
77 81 });
78 82
79 83 CodeMirror.defineMIME("text/html", "htmlmixed");
@@ -1,52 +1,51 b''
1 1 <!doctype html>
2 2 <html>
3 3 <head>
4 <title>CodeMirror 2: HTML mixed mode</title>
4 <title>CodeMirror: HTML mixed mode</title>
5 5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 6 <script src="../../lib/codemirror.js"></script>
7 7 <script src="../xml/xml.js"></script>
8 8 <script src="../javascript/javascript.js"></script>
9 9 <script src="../css/css.js"></script>
10 <link rel="stylesheet" href="../../theme/default.css">
11 10 <script src="htmlmixed.js"></script>
12 <link rel="stylesheet" href="../../css/docs.css">
11 <link rel="stylesheet" href="../../doc/docs.css">
13 12 <style>.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
14 13 </head>
15 14 <body>
16 <h1>CodeMirror 2: HTML mixed mode</h1>
15 <h1>CodeMirror: HTML mixed mode</h1>
17 16 <form><textarea id="code" name="code">
18 17 <html style="color: green">
19 18 <!-- this is a comment -->
20 19 <head>
21 20 <title>Mixed HTML Example</title>
22 21 <style type="text/css">
23 22 h1 {font-family: comic sans; color: #f0f;}
24 23 div {background: yellow !important;}
25 24 body {
26 25 max-width: 50em;
27 26 margin: 1em 2em 1em 5em;
28 27 }
29 28 </style>
30 29 </head>
31 30 <body>
32 31 <h1>Mixed HTML Example</h1>
33 32 <script>
34 33 function jsFunc(arg1, arg2) {
35 34 if (arg1 && arg2) document.body.innerHTML = "achoo";
36 35 }
37 36 </script>
38 37 </body>
39 38 </html>
40 39 </textarea></form>
41 40 <script>
42 41 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "text/html", tabMode: "indent"});
43 42 </script>
44 43
45 44 <p>The HTML mixed mode depends on the XML, JavaScript, and CSS modes.</p>
46 45
47 46 <p><strong>MIME types defined:</strong> <code>text/html</code>
48 47 (redefined, only takes effect if you load this parser after the
49 48 XML parser).</p>
50 49
51 50 </body>
52 51 </html>
@@ -1,78 +1,77 b''
1 1 <!doctype html>
2 2 <html>
3 3 <head>
4 <title>CodeMirror 2: JavaScript mode</title>
4 <title>CodeMirror: JavaScript mode</title>
5 5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 6 <script src="../../lib/codemirror.js"></script>
7 7 <script src="javascript.js"></script>
8 <link rel="stylesheet" href="../../theme/default.css">
9 <link rel="stylesheet" href="../../css/docs.css">
8 <link rel="stylesheet" href="../../doc/docs.css">
10 9 <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
11 10 </head>
12 11 <body>
13 <h1>CodeMirror 2: JavaScript mode</h1>
12 <h1>CodeMirror: JavaScript mode</h1>
14 13
15 14 <div><textarea id="code" name="code">
16 15 // Demo code (the actual new parser character stream implementation)
17 16
18 17 function StringStream(string) {
19 18 this.pos = 0;
20 19 this.string = string;
21 20 }
22 21
23 22 StringStream.prototype = {
24 23 done: function() {return this.pos >= this.string.length;},
25 24 peek: function() {return this.string.charAt(this.pos);},
26 25 next: function() {
27 26 if (this.pos &lt; this.string.length)
28 27 return this.string.charAt(this.pos++);
29 28 },
30 29 eat: function(match) {
31 30 var ch = this.string.charAt(this.pos);
32 31 if (typeof match == "string") var ok = ch == match;
33 32 else var ok = ch &amp;&amp; match.test ? match.test(ch) : match(ch);
34 33 if (ok) {this.pos++; return ch;}
35 34 },
36 35 eatWhile: function(match) {
37 36 var start = this.pos;
38 37 while (this.eat(match));
39 38 if (this.pos > start) return this.string.slice(start, this.pos);
40 39 },
41 40 backUp: function(n) {this.pos -= n;},
42 41 column: function() {return this.pos;},
43 42 eatSpace: function() {
44 43 var start = this.pos;
45 44 while (/\s/.test(this.string.charAt(this.pos))) this.pos++;
46 45 return this.pos - start;
47 46 },
48 47 match: function(pattern, consume, caseInsensitive) {
49 48 if (typeof pattern == "string") {
50 49 function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
51 50 if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
52 51 if (consume !== false) this.pos += str.length;
53 52 return true;
54 53 }
55 54 }
56 55 else {
57 56 var match = this.string.slice(this.pos).match(pattern);
58 57 if (match &amp;&amp; consume !== false) this.pos += match[0].length;
59 58 return match;
60 59 }
61 60 }
62 61 };
63 62 </textarea></div>
64 63
65 64 <script>
66 65 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
67 66 lineNumbers: true,
68 67 matchBrackets: true
69 68 });
70 69 </script>
71 70
72 71 <p>JavaScript mode supports a single configuration
73 72 option, <code>json</code>, which will set the mode to expect JSON
74 73 data rather than a JavaScript program.</p>
75 74
76 75 <p><strong>MIME types defined:</strong> <code>text/javascript</code>, <code>application/json</code>.</p>
77 76 </body>
78 77 </html>
@@ -1,352 +1,360 b''
1 1 CodeMirror.defineMode("javascript", function(config, parserConfig) {
2 2 var indentUnit = config.indentUnit;
3 3 var jsonMode = parserConfig.json;
4 4
5 5 // Tokenizer
6 6
7 7 var keywords = function(){
8 8 function kw(type) {return {type: type, style: "keyword"};}
9 9 var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
10 10 var operator = kw("operator"), atom = {type: "atom", style: "atom"};
11 11 return {
12 12 "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
13 13 "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
14 "var": kw("var"), "function": kw("function"), "catch": kw("catch"),
14 "var": kw("var"), "const": kw("var"), "let": kw("var"),
15 "function": kw("function"), "catch": kw("catch"),
15 16 "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
16 17 "in": operator, "typeof": operator, "instanceof": operator,
17 18 "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
18 19 };
19 20 }();
20 21
21 22 var isOperatorChar = /[+\-*&%=<>!?|]/;
22 23
23 24 function chain(stream, state, f) {
24 25 state.tokenize = f;
25 26 return f(stream, state);
26 27 }
27 28
28 29 function nextUntilUnescaped(stream, end) {
29 30 var escaped = false, next;
30 31 while ((next = stream.next()) != null) {
31 32 if (next == end && !escaped)
32 33 return false;
33 34 escaped = !escaped && next == "\\";
34 35 }
35 36 return escaped;
36 37 }
37 38
38 39 // Used as scratch variables to communicate multiple values without
39 40 // consing up tons of objects.
40 41 var type, content;
41 42 function ret(tp, style, cont) {
42 43 type = tp; content = cont;
43 44 return style;
44 45 }
45 46
46 47 function jsTokenBase(stream, state) {
47 48 var ch = stream.next();
48 49 if (ch == '"' || ch == "'")
49 50 return chain(stream, state, jsTokenString(ch));
50 51 else if (/[\[\]{}\(\),;\:\.]/.test(ch))
51 52 return ret(ch);
52 53 else if (ch == "0" && stream.eat(/x/i)) {
53 54 stream.eatWhile(/[\da-f]/i);
54 55 return ret("number", "number");
55 }
56 }
56 57 else if (/\d/.test(ch)) {
57 stream.match(/^\d*(?:\.\d*)?(?:e[+\-]?\d+)?/);
58 stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
58 59 return ret("number", "number");
59 60 }
60 61 else if (ch == "/") {
61 62 if (stream.eat("*")) {
62 63 return chain(stream, state, jsTokenComment);
63 64 }
64 65 else if (stream.eat("/")) {
65 66 stream.skipToEnd();
66 67 return ret("comment", "comment");
67 68 }
68 69 else if (state.reAllowed) {
69 70 nextUntilUnescaped(stream, "/");
70 71 stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
71 72 return ret("regexp", "string");
72 73 }
73 74 else {
74 75 stream.eatWhile(isOperatorChar);
75 76 return ret("operator", null, stream.current());
76 77 }
77 78 }
78 79 else if (ch == "#") {
79 80 stream.skipToEnd();
80 81 return ret("error", "error");
81 82 }
82 83 else if (isOperatorChar.test(ch)) {
83 84 stream.eatWhile(isOperatorChar);
84 85 return ret("operator", null, stream.current());
85 86 }
86 87 else {
87 88 stream.eatWhile(/[\w\$_]/);
88 89 var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
89 return known ? ret(known.type, known.style, word) :
90 return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
90 91 ret("variable", "variable", word);
91 92 }
92 93 }
93 94
94 95 function jsTokenString(quote) {
95 96 return function(stream, state) {
96 97 if (!nextUntilUnescaped(stream, quote))
97 98 state.tokenize = jsTokenBase;
98 99 return ret("string", "string");
99 100 };
100 101 }
101 102
102 103 function jsTokenComment(stream, state) {
103 104 var maybeEnd = false, ch;
104 105 while (ch = stream.next()) {
105 106 if (ch == "/" && maybeEnd) {
106 107 state.tokenize = jsTokenBase;
107 108 break;
108 109 }
109 110 maybeEnd = (ch == "*");
110 111 }
111 112 return ret("comment", "comment");
112 113 }
113 114
114 115 // Parser
115 116
116 117 var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
117 118
118 119 function JSLexical(indented, column, type, align, prev, info) {
119 120 this.indented = indented;
120 121 this.column = column;
121 122 this.type = type;
122 123 this.prev = prev;
123 124 this.info = info;
124 125 if (align != null) this.align = align;
125 126 }
126 127
127 128 function inScope(state, varname) {
128 129 for (var v = state.localVars; v; v = v.next)
129 130 if (v.name == varname) return true;
130 131 }
131 132
132 133 function parseJS(state, style, type, content, stream) {
133 134 var cc = state.cc;
134 135 // Communicate our context to the combinators.
135 136 // (Less wasteful than consing up a hundred closures on every call.)
136 137 cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
137
138
138 139 if (!state.lexical.hasOwnProperty("align"))
139 140 state.lexical.align = true;
140 141
141 142 while(true) {
142 143 var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
143 144 if (combinator(type, content)) {
144 145 while(cc.length && cc[cc.length - 1].lex)
145 146 cc.pop()();
146 147 if (cx.marked) return cx.marked;
147 148 if (type == "variable" && inScope(state, content)) return "variable-2";
148 149 return style;
149 150 }
150 151 }
151 152 }
152 153
153 154 // Combinator utils
154 155
155 156 var cx = {state: null, column: null, marked: null, cc: null};
156 157 function pass() {
157 158 for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
158 159 }
159 160 function cont() {
160 161 pass.apply(null, arguments);
161 162 return true;
162 163 }
163 164 function register(varname) {
164 165 var state = cx.state;
165 166 if (state.context) {
166 167 cx.marked = "def";
167 168 for (var v = state.localVars; v; v = v.next)
168 169 if (v.name == varname) return;
169 170 state.localVars = {name: varname, next: state.localVars};
170 171 }
171 172 }
172 173
173 174 // Combinators
174 175
175 176 var defaultVars = {name: "this", next: {name: "arguments"}};
176 177 function pushcontext() {
177 178 if (!cx.state.context) cx.state.localVars = defaultVars;
178 179 cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
179 180 }
180 181 function popcontext() {
181 182 cx.state.localVars = cx.state.context.vars;
182 183 cx.state.context = cx.state.context.prev;
183 184 }
184 185 function pushlex(type, info) {
185 186 var result = function() {
186 187 var state = cx.state;
187 188 state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
188 189 };
189 190 result.lex = true;
190 191 return result;
191 192 }
192 193 function poplex() {
193 194 var state = cx.state;
194 195 if (state.lexical.prev) {
195 196 if (state.lexical.type == ")")
196 197 state.indented = state.lexical.indented;
197 198 state.lexical = state.lexical.prev;
198 199 }
199 200 }
200 201 poplex.lex = true;
201 202
202 203 function expect(wanted) {
203 204 return function expecting(type) {
204 205 if (type == wanted) return cont();
205 206 else if (wanted == ";") return pass();
206 207 else return cont(arguments.callee);
207 208 };
208 209 }
209 210
210 211 function statement(type) {
211 212 if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
212 213 if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
213 214 if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
214 215 if (type == "{") return cont(pushlex("}"), block, poplex);
215 216 if (type == ";") return cont();
216 217 if (type == "function") return cont(functiondef);
217 218 if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
218 219 poplex, statement, poplex);
219 220 if (type == "variable") return cont(pushlex("stat"), maybelabel);
220 221 if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
221 222 block, poplex, poplex);
222 223 if (type == "case") return cont(expression, expect(":"));
223 224 if (type == "default") return cont(expect(":"));
224 225 if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
225 226 statement, poplex, popcontext);
226 227 return pass(pushlex("stat"), expression, expect(";"), poplex);
227 228 }
228 229 function expression(type) {
229 230 if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
230 231 if (type == "function") return cont(functiondef);
231 if (type == "keyword c") return cont(expression);
232 if (type == "keyword c") return cont(maybeexpression);
232 233 if (type == "(") return cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator);
233 234 if (type == "operator") return cont(expression);
234 235 if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
235 236 if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
236 237 return cont();
237 238 }
239 function maybeexpression(type) {
240 if (type.match(/[;\}\)\],]/)) return pass();
241 return pass(expression);
242 }
243
238 244 function maybeoperator(type, value) {
239 245 if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
240 246 if (type == "operator") return cont(expression);
241 247 if (type == ";") return;
242 248 if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
243 249 if (type == ".") return cont(property, maybeoperator);
244 250 if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
245 251 }
246 252 function maybelabel(type) {
247 253 if (type == ":") return cont(poplex, statement);
248 254 return pass(maybeoperator, expect(";"), poplex);
249 255 }
250 256 function property(type) {
251 257 if (type == "variable") {cx.marked = "property"; return cont();}
252 258 }
253 259 function objprop(type) {
254 260 if (type == "variable") cx.marked = "property";
255 261 if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
256 262 }
257 263 function commasep(what, end) {
258 264 function proceed(type) {
259 265 if (type == ",") return cont(what, proceed);
260 266 if (type == end) return cont();
261 267 return cont(expect(end));
262 268 }
263 269 return function commaSeparated(type) {
264 270 if (type == end) return cont();
265 271 else return pass(what, proceed);
266 272 };
267 273 }
268 274 function block(type) {
269 275 if (type == "}") return cont();
270 276 return pass(statement, block);
271 277 }
272 278 function vardef1(type, value) {
273 279 if (type == "variable"){register(value); return cont(vardef2);}
274 280 return cont();
275 281 }
276 282 function vardef2(type, value) {
277 283 if (value == "=") return cont(expression, vardef2);
278 284 if (type == ",") return cont(vardef1);
279 285 }
280 286 function forspec1(type) {
281 287 if (type == "var") return cont(vardef1, forspec2);
282 288 if (type == ";") return pass(forspec2);
283 289 if (type == "variable") return cont(formaybein);
284 290 return pass(forspec2);
285 291 }
286 292 function formaybein(type, value) {
287 293 if (value == "in") return cont(expression);
288 294 return cont(maybeoperator, forspec2);
289 295 }
290 296 function forspec2(type, value) {
291 297 if (type == ";") return cont(forspec3);
292 298 if (value == "in") return cont(expression);
293 299 return cont(expression, expect(";"), forspec3);
294 300 }
295 301 function forspec3(type) {
296 302 if (type != ")") cont(expression);
297 303 }
298 304 function functiondef(type, value) {
299 305 if (type == "variable") {register(value); return cont(functiondef);}
300 306 if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
301 307 }
302 308 function funarg(type, value) {
303 309 if (type == "variable") {register(value); return cont();}
304 310 }
305 311
306 312 // Interface
307 313
308 314 return {
309 315 startState: function(basecolumn) {
310 316 return {
311 317 tokenize: jsTokenBase,
312 318 reAllowed: true,
319 kwAllowed: true,
313 320 cc: [],
314 321 lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
315 322 localVars: null,
316 323 context: null,
317 324 indented: 0
318 325 };
319 326 },
320 327
321 328 token: function(stream, state) {
322 329 if (stream.sol()) {
323 330 if (!state.lexical.hasOwnProperty("align"))
324 331 state.lexical.align = false;
325 332 state.indented = stream.indentation();
326 333 }
327 334 if (stream.eatSpace()) return null;
328 335 var style = state.tokenize(stream, state);
329 336 if (type == "comment") return style;
330 337 state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
338 state.kwAllowed = type != '.';
331 339 return parseJS(state, style, type, content, stream);
332 340 },
333 341
334 342 indent: function(state, textAfter) {
335 343 if (state.tokenize != jsTokenBase) return 0;
336 344 var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
337 345 type = lexical.type, closing = firstChar == type;
338 346 if (type == "vardef") return lexical.indented + 4;
339 347 else if (type == "form" && firstChar == "{") return lexical.indented;
340 348 else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
341 349 else if (lexical.info == "switch" && !closing)
342 350 return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
343 351 else if (lexical.align) return lexical.column + (closing ? 0 : 1);
344 352 else return lexical.indented + (closing ? 0 : indentUnit);
345 353 },
346 354
347 355 electricChars: ":{}"
348 356 };
349 357 });
350 358
351 359 CodeMirror.defineMIME("text/javascript", "javascript");
352 360 CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
@@ -1,340 +1,339 b''
1 1 <!doctype html>
2 2 <html>
3 3 <head>
4 <title>CodeMirror 2: Markdown mode</title>
4 <title>CodeMirror: Markdown mode</title>
5 5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 6 <script src="../../lib/codemirror.js"></script>
7 7 <script src="../xml/xml.js"></script>
8 8 <script src="markdown.js"></script>
9 <link rel="stylesheet" href="../../theme/default.css">
10 9 <link rel="stylesheet" href="markdown.css">
11 10 <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
12 <link rel="stylesheet" href="../../css/docs.css">
11 <link rel="stylesheet" href="../../doc/docs.css">
13 12 </head>
14 13 <body>
15 <h1>CodeMirror 2: Markdown mode</h1>
14 <h1>CodeMirror: Markdown mode</h1>
16 15
17 16 <!-- source: http://daringfireball.net/projects/markdown/basics.text -->
18 17 <form><textarea id="code" name="code">
19 18 Markdown: Basics
20 19 ================
21 20
22 21 &lt;ul id="ProjectSubmenu"&gt;
23 22 &lt;li&gt;&lt;a href="/projects/markdown/" title="Markdown Project Page"&gt;Main&lt;/a&gt;&lt;/li&gt;
24 23 &lt;li&gt;&lt;a class="selected" title="Markdown Basics"&gt;Basics&lt;/a&gt;&lt;/li&gt;
25 24 &lt;li&gt;&lt;a href="/projects/markdown/syntax" title="Markdown Syntax Documentation"&gt;Syntax&lt;/a&gt;&lt;/li&gt;
26 25 &lt;li&gt;&lt;a href="/projects/markdown/license" title="Pricing and License Information"&gt;License&lt;/a&gt;&lt;/li&gt;
27 26 &lt;li&gt;&lt;a href="/projects/markdown/dingus" title="Online Markdown Web Form"&gt;Dingus&lt;/a&gt;&lt;/li&gt;
28 27 &lt;/ul&gt;
29 28
30 29
31 30 Getting the Gist of Markdown's Formatting Syntax
32 31 ------------------------------------------------
33 32
34 33 This page offers a brief overview of what it's like to use Markdown.
35 34 The [syntax page] [s] provides complete, detailed documentation for
36 35 every feature, but Markdown should be very easy to pick up simply by
37 36 looking at a few examples of it in action. The examples on this page
38 37 are written in a before/after style, showing example syntax and the
39 38 HTML output produced by Markdown.
40 39
41 40 It's also helpful to simply try Markdown out; the [Dingus] [d] is a
42 41 web application that allows you type your own Markdown-formatted text
43 42 and translate it to XHTML.
44 43
45 44 **Note:** This document is itself written using Markdown; you
46 45 can [see the source for it by adding '.text' to the URL] [src].
47 46
48 47 [s]: /projects/markdown/syntax "Markdown Syntax"
49 48 [d]: /projects/markdown/dingus "Markdown Dingus"
50 49 [src]: /projects/markdown/basics.text
51 50
52 51
53 52 ## Paragraphs, Headers, Blockquotes ##
54 53
55 54 A paragraph is simply one or more consecutive lines of text, separated
56 55 by one or more blank lines. (A blank line is any line that looks like
57 56 a blank line -- a line containing nothing but spaces or tabs is
58 57 considered blank.) Normal paragraphs should not be indented with
59 58 spaces or tabs.
60 59
61 60 Markdown offers two styles of headers: *Setext* and *atx*.
62 61 Setext-style headers for `&lt;h1&gt;` and `&lt;h2&gt;` are created by
63 62 "underlining" with equal signs (`=`) and hyphens (`-`), respectively.
64 63 To create an atx-style header, you put 1-6 hash marks (`#`) at the
65 64 beginning of the line -- the number of hashes equals the resulting
66 65 HTML header level.
67 66
68 67 Blockquotes are indicated using email-style '`&gt;`' angle brackets.
69 68
70 69 Markdown:
71 70
72 71 A First Level Header
73 72 ====================
74
73
75 74 A Second Level Header
76 75 ---------------------
77 76
78 77 Now is the time for all good men to come to
79 78 the aid of their country. This is just a
80 79 regular paragraph.
81 80
82 81 The quick brown fox jumped over the lazy
83 82 dog's back.
84
83
85 84 ### Header 3
86 85
87 86 &gt; This is a blockquote.
88 &gt;
87 &gt;
89 88 &gt; This is the second paragraph in the blockquote.
90 89 &gt;
91 90 &gt; ## This is an H2 in a blockquote
92 91
93 92
94 93 Output:
95 94
96 95 &lt;h1&gt;A First Level Header&lt;/h1&gt;
97
96
98 97 &lt;h2&gt;A Second Level Header&lt;/h2&gt;
99
98
100 99 &lt;p&gt;Now is the time for all good men to come to
101 100 the aid of their country. This is just a
102 101 regular paragraph.&lt;/p&gt;
103
102
104 103 &lt;p&gt;The quick brown fox jumped over the lazy
105 104 dog's back.&lt;/p&gt;
106
105
107 106 &lt;h3&gt;Header 3&lt;/h3&gt;
108
107
109 108 &lt;blockquote&gt;
110 109 &lt;p&gt;This is a blockquote.&lt;/p&gt;
111
110
112 111 &lt;p&gt;This is the second paragraph in the blockquote.&lt;/p&gt;
113
112
114 113 &lt;h2&gt;This is an H2 in a blockquote&lt;/h2&gt;
115 114 &lt;/blockquote&gt;
116 115
117 116
118 117
119 118 ### Phrase Emphasis ###
120 119
121 120 Markdown uses asterisks and underscores to indicate spans of emphasis.
122 121
123 122 Markdown:
124 123
125 124 Some of these words *are emphasized*.
126 125 Some of these words _are emphasized also_.
127
126
128 127 Use two asterisks for **strong emphasis**.
129 128 Or, if you prefer, __use two underscores instead__.
130 129
131 130 Output:
132 131
133 132 &lt;p&gt;Some of these words &lt;em&gt;are emphasized&lt;/em&gt;.
134 133 Some of these words &lt;em&gt;are emphasized also&lt;/em&gt;.&lt;/p&gt;
135
134
136 135 &lt;p&gt;Use two asterisks for &lt;strong&gt;strong emphasis&lt;/strong&gt;.
137 136 Or, if you prefer, &lt;strong&gt;use two underscores instead&lt;/strong&gt;.&lt;/p&gt;
138
137
139 138
140 139
141 140 ## Lists ##
142 141
143 142 Unordered (bulleted) lists use asterisks, pluses, and hyphens (`*`,
144 143 `+`, and `-`) as list markers. These three markers are
145 144 interchangable; this:
146 145
147 146 * Candy.
148 147 * Gum.
149 148 * Booze.
150 149
151 150 this:
152 151
153 152 + Candy.
154 153 + Gum.
155 154 + Booze.
156 155
157 156 and this:
158 157
159 158 - Candy.
160 159 - Gum.
161 160 - Booze.
162 161
163 162 all produce the same output:
164 163
165 164 &lt;ul&gt;
166 165 &lt;li&gt;Candy.&lt;/li&gt;
167 166 &lt;li&gt;Gum.&lt;/li&gt;
168 167 &lt;li&gt;Booze.&lt;/li&gt;
169 168 &lt;/ul&gt;
170 169
171 170 Ordered (numbered) lists use regular numbers, followed by periods, as
172 171 list markers:
173 172
174 173 1. Red
175 174 2. Green
176 175 3. Blue
177 176
178 177 Output:
179 178
180 179 &lt;ol&gt;
181 180 &lt;li&gt;Red&lt;/li&gt;
182 181 &lt;li&gt;Green&lt;/li&gt;
183 182 &lt;li&gt;Blue&lt;/li&gt;
184 183 &lt;/ol&gt;
185 184
186 185 If you put blank lines between items, you'll get `&lt;p&gt;` tags for the
187 186 list item text. You can create multi-paragraph list items by indenting
188 187 the paragraphs by 4 spaces or 1 tab:
189 188
190 189 * A list item.
191
190
192 191 With multiple paragraphs.
193 192
194 193 * Another item in the list.
195 194
196 195 Output:
197 196
198 197 &lt;ul&gt;
199 198 &lt;li&gt;&lt;p&gt;A list item.&lt;/p&gt;
200 199 &lt;p&gt;With multiple paragraphs.&lt;/p&gt;&lt;/li&gt;
201 200 &lt;li&gt;&lt;p&gt;Another item in the list.&lt;/p&gt;&lt;/li&gt;
202 201 &lt;/ul&gt;
203
202
204 203
205 204
206 205 ### Links ###
207 206
208 207 Markdown supports two styles for creating links: *inline* and
209 208 *reference*. With both styles, you use square brackets to delimit the
210 209 text you want to turn into a link.
211 210
212 211 Inline-style links use parentheses immediately after the link text.
213 212 For example:
214 213
215 214 This is an [example link](http://example.com/).
216 215
217 216 Output:
218 217
219 218 &lt;p&gt;This is an &lt;a href="http://example.com/"&gt;
220 219 example link&lt;/a&gt;.&lt;/p&gt;
221 220
222 221 Optionally, you may include a title attribute in the parentheses:
223 222
224 223 This is an [example link](http://example.com/ "With a Title").
225 224
226 225 Output:
227 226
228 227 &lt;p&gt;This is an &lt;a href="http://example.com/" title="With a Title"&gt;
229 228 example link&lt;/a&gt;.&lt;/p&gt;
230 229
231 230 Reference-style links allow you to refer to your links by names, which
232 231 you define elsewhere in your document:
233 232
234 233 I get 10 times more traffic from [Google][1] than from
235 234 [Yahoo][2] or [MSN][3].
236 235
237 236 [1]: http://google.com/ "Google"
238 237 [2]: http://search.yahoo.com/ "Yahoo Search"
239 238 [3]: http://search.msn.com/ "MSN Search"
240 239
241 240 Output:
242 241
243 242 &lt;p&gt;I get 10 times more traffic from &lt;a href="http://google.com/"
244 243 title="Google"&gt;Google&lt;/a&gt; than from &lt;a href="http://search.yahoo.com/"
245 244 title="Yahoo Search"&gt;Yahoo&lt;/a&gt; or &lt;a href="http://search.msn.com/"
246 245 title="MSN Search"&gt;MSN&lt;/a&gt;.&lt;/p&gt;
247 246
248 247 The title attribute is optional. Link names may contain letters,
249 248 numbers and spaces, but are *not* case sensitive:
250 249
251 250 I start my morning with a cup of coffee and
252 251 [The New York Times][NY Times].
253 252
254 253 [ny times]: http://www.nytimes.com/
255 254
256 255 Output:
257 256
258 257 &lt;p&gt;I start my morning with a cup of coffee and
259 258 &lt;a href="http://www.nytimes.com/"&gt;The New York Times&lt;/a&gt;.&lt;/p&gt;
260 259
261 260
262 261 ### Images ###
263 262
264 263 Image syntax is very much like link syntax.
265 264
266 265 Inline (titles are optional):
267 266
268 267 ![alt text](/path/to/img.jpg "Title")
269 268
270 269 Reference-style:
271 270
272 271 ![alt text][id]
273 272
274 273 [id]: /path/to/img.jpg "Title"
275 274
276 275 Both of the above examples produce the same output:
277 276
278 277 &lt;img src="/path/to/img.jpg" alt="alt text" title="Title" /&gt;
279 278
280 279
281 280
282 281 ### Code ###
283 282
284 283 In a regular paragraph, you can create code span by wrapping text in
285 284 backtick quotes. Any ampersands (`&amp;`) and angle brackets (`&lt;` or
286 285 `&gt;`) will automatically be translated into HTML entities. This makes
287 286 it easy to use Markdown to write about HTML example code:
288 287
289 288 I strongly recommend against using any `&lt;blink&gt;` tags.
290 289
291 290 I wish SmartyPants used named entities like `&amp;mdash;`
292 291 instead of decimal-encoded entites like `&amp;#8212;`.
293 292
294 293 Output:
295 294
296 295 &lt;p&gt;I strongly recommend against using any
297 296 &lt;code&gt;&amp;lt;blink&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;
298
297
299 298 &lt;p&gt;I wish SmartyPants used named entities like
300 299 &lt;code&gt;&amp;amp;mdash;&lt;/code&gt; instead of decimal-encoded
301 300 entites like &lt;code&gt;&amp;amp;#8212;&lt;/code&gt;.&lt;/p&gt;
302 301
303 302
304 303 To specify an entire block of pre-formatted code, indent every line of
305 304 the block by 4 spaces or 1 tab. Just like with code spans, `&amp;`, `&lt;`,
306 305 and `&gt;` characters will be escaped automatically.
307 306
308 307 Markdown:
309 308
310 309 If you want your page to validate under XHTML 1.0 Strict,
311 310 you've got to put paragraph tags in your blockquotes:
312 311
313 312 &lt;blockquote&gt;
314 313 &lt;p&gt;For example.&lt;/p&gt;
315 314 &lt;/blockquote&gt;
316 315
317 316 Output:
318 317
319 318 &lt;p&gt;If you want your page to validate under XHTML 1.0 Strict,
320 319 you've got to put paragraph tags in your blockquotes:&lt;/p&gt;
321
320
322 321 &lt;pre&gt;&lt;code&gt;&amp;lt;blockquote&amp;gt;
323 322 &amp;lt;p&amp;gt;For example.&amp;lt;/p&amp;gt;
324 323 &amp;lt;/blockquote&amp;gt;
325 324 &lt;/code&gt;&lt;/pre&gt;
326 325 </textarea></form>
327 326
328 327 <script>
329 328 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
330 329 mode: 'markdown',
331 330 lineNumbers: true,
332 331 matchBrackets: true,
333 332 theme: "default"
334 333 });
335 334 </script>
336 335
337 336 <p><strong>MIME types defined:</strong> <code>text/x-markdown</code>.</p>
338 337
339 338 </body>
340 339 </html>
@@ -1,230 +1,242 b''
1 1 CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
2 2
3 3 var htmlMode = CodeMirror.getMode(cmCfg, { name: 'xml', htmlMode: true });
4 4
5 5 var header = 'header'
6 , code = 'code'
6 , code = 'comment'
7 7 , quote = 'quote'
8 , list = 'list'
8 , list = 'string'
9 9 , hr = 'hr'
10 , linktext = 'linktext'
11 , linkhref = 'linkhref'
10 , linktext = 'link'
11 , linkhref = 'string'
12 12 , em = 'em'
13 13 , strong = 'strong'
14 14 , emstrong = 'emstrong';
15 15
16 16 var hrRE = /^[*-=_]/
17 17 , ulRE = /^[*-+]\s+/
18 18 , olRE = /^[0-9]\.\s+/
19 19 , headerRE = /^(?:\={3,}|-{3,})$/
20 20 , codeRE = /^(k:\t|\s{4,})/
21 21 , textRE = /^[^\[*_\\<>`]+/;
22 22
23 23 function switchInline(stream, state, f) {
24 24 state.f = state.inline = f;
25 25 return f(stream, state);
26 26 }
27 27
28 28 function switchBlock(stream, state, f) {
29 29 state.f = state.block = f;
30 30 return f(stream, state);
31 31 }
32 32
33 33
34 34 // Blocks
35 35
36 36 function blockNormal(stream, state) {
37 37 if (stream.match(codeRE)) {
38 38 stream.skipToEnd();
39 39 return code;
40 40 }
41
41
42 42 if (stream.eatSpace()) {
43 43 return null;
44 44 }
45
45
46 46 if (stream.peek() === '#' || stream.match(headerRE)) {
47 47 stream.skipToEnd();
48 48 return header;
49 49 }
50 50 if (stream.eat('>')) {
51 51 state.indentation++;
52 52 return quote;
53 53 }
54 if (stream.peek() === '<') {
55 return switchBlock(stream, state, htmlBlock);
56 }
57 54 if (stream.peek() === '[') {
58 55 return switchInline(stream, state, footnoteLink);
59 56 }
60 57 if (hrRE.test(stream.peek())) {
61 58 var re = new RegExp('(?:\s*['+stream.peek()+']){3,}$');
62 59 if (stream.match(re, true)) {
63 60 return hr;
64 61 }
65 62 }
66
63
67 64 var match;
68 65 if (match = stream.match(ulRE, true) || stream.match(olRE, true)) {
69 66 state.indentation += match[0].length;
70 67 return list;
71 68 }
72
69
73 70 return switchInline(stream, state, state.inline);
74 71 }
75 72
76 73 function htmlBlock(stream, state) {
77 var type = htmlMode.token(stream, state.htmlState);
78 if (stream.eol() && !state.htmlState.context) {
74 var style = htmlMode.token(stream, state.htmlState);
75 if (style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) {
76 state.f = inlineNormal;
79 77 state.block = blockNormal;
80 78 }
81 return type;
79 return style;
82 80 }
83 81
84 82
85 83 // Inline
84 function getType(state) {
85 return state.strong ? (state.em ? emstrong : strong)
86 : (state.em ? em : null);
87 }
86 88
87 function inlineNormal(stream, state) {
88 function getType() {
89 return state.strong ? (state.em ? emstrong : strong)
90 : (state.em ? em : null);
91 }
92
89 function handleText(stream, state) {
93 90 if (stream.match(textRE, true)) {
94 return getType();
91 return getType(state);
95 92 }
93 return undefined;
94 }
96 95
96 function inlineNormal(stream, state) {
97 var style = state.text(stream, state)
98 if (typeof style !== 'undefined')
99 return style;
100
97 101 var ch = stream.next();
98
102
99 103 if (ch === '\\') {
100 104 stream.next();
101 return getType();
105 return getType(state);
102 106 }
103 107 if (ch === '`') {
104 108 return switchInline(stream, state, inlineElement(code, '`'));
105 109 }
106 if (ch === '<') {
107 return switchInline(stream, state, inlineElement(linktext, '>'));
108 }
109 110 if (ch === '[') {
110 111 return switchInline(stream, state, linkText);
111 112 }
113 if (ch === '<' && stream.match(/^\w/, false)) {
114 stream.backUp(1);
115 return switchBlock(stream, state, htmlBlock);
116 }
112 117
113 var t = getType();
118 var t = getType(state);
114 119 if (ch === '*' || ch === '_') {
115 120 if (stream.eat(ch)) {
116 return (state.strong = !state.strong) ? getType() : t;
121 return (state.strong = !state.strong) ? getType(state) : t;
117 122 }
118 return (state.em = !state.em) ? getType() : t;
123 return (state.em = !state.em) ? getType(state) : t;
119 124 }
120
121 return getType();
125
126 return getType(state);
122 127 }
123 128
124 129 function linkText(stream, state) {
125 130 while (!stream.eol()) {
126 131 var ch = stream.next();
127 132 if (ch === '\\') stream.next();
128 133 if (ch === ']') {
129 134 state.inline = state.f = linkHref;
130 135 return linktext;
131 136 }
132 137 }
133 138 return linktext;
134 139 }
135 140
136 141 function linkHref(stream, state) {
137 142 stream.eatSpace();
138 143 var ch = stream.next();
139 144 if (ch === '(' || ch === '[') {
140 145 return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']'));
141 146 }
142 147 return 'error';
143 148 }
144 149
145 150 function footnoteLink(stream, state) {
146 151 if (stream.match(/^[^\]]*\]:/, true)) {
147 152 state.f = footnoteUrl;
148 153 return linktext;
149 154 }
150 155 return switchInline(stream, state, inlineNormal);
151 156 }
152 157
153 158 function footnoteUrl(stream, state) {
154 159 stream.eatSpace();
155 160 stream.match(/^[^\s]+/, true);
156 161 state.f = state.inline = inlineNormal;
157 162 return linkhref;
158 163 }
159 164
165 function inlineRE(endChar) {
166 if (!inlineRE[endChar]) {
167 // match any not-escaped-non-endChar and any escaped char
168 // then match endChar or eol
169 inlineRE[endChar] = new RegExp('^(?:[^\\\\\\' + endChar + ']|\\\\.)*(?:\\' + endChar + '|$)');
170 }
171 return inlineRE[endChar];
172 }
173
160 174 function inlineElement(type, endChar, next) {
161 175 next = next || inlineNormal;
162 176 return function(stream, state) {
163 while (!stream.eol()) {
164 var ch = stream.next();
165 if (ch === '\\') stream.next();
166 if (ch === endChar) {
167 state.inline = state.f = next;
168 return type;
169 }
170 }
177 stream.match(inlineRE(endChar));
178 state.inline = state.f = next;
171 179 return type;
172 180 };
173 181 }
174 182
175 183 return {
176 184 startState: function() {
177 185 return {
178 186 f: blockNormal,
179
187
180 188 block: blockNormal,
181 189 htmlState: htmlMode.startState(),
182 190 indentation: 0,
183
191
184 192 inline: inlineNormal,
193 text: handleText,
185 194 em: false,
186 195 strong: false
187 196 };
188 197 },
189 198
190 199 copyState: function(s) {
191 200 return {
192 201 f: s.f,
193
202
194 203 block: s.block,
195 204 htmlState: CodeMirror.copyState(htmlMode, s.htmlState),
196 205 indentation: s.indentation,
197
206
198 207 inline: s.inline,
208 text: s.text,
199 209 em: s.em,
200 210 strong: s.strong
201 211 };
202 212 },
203 213
204 214 token: function(stream, state) {
205 215 if (stream.sol()) {
206 216 state.f = state.block;
207 217 var previousIndentation = state.indentation
208 218 , currentIndentation = 0;
209 219 while (previousIndentation > 0) {
210 220 if (stream.eat(' ')) {
211 221 previousIndentation--;
212 222 currentIndentation++;
213 223 } else if (previousIndentation >= 4 && stream.eat('\t')) {
214 224 previousIndentation -= 4;
215 225 currentIndentation += 4;
216 226 } else {
217 227 break;
218 228 }
219 229 }
220 230 state.indentation = currentIndentation;
221
231
222 232 if (currentIndentation > 0) return null;
223 233 }
224 234 return state.f(stream, state);
225 }
235 },
236
237 getType: getType
226 238 };
227 239
228 240 });
229 241
230 242 CodeMirror.defineMIME("text/x-markdown", "markdown");
@@ -1,123 +1,122 b''
1 1 <!doctype html>
2 2 <html>
3 3 <head>
4 <title>CodeMirror 2: Python mode</title>
4 <title>CodeMirror: Python mode</title>
5 5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 6 <script src="../../lib/codemirror.js"></script>
7 7 <script src="python.js"></script>
8 <link rel="stylesheet" href="../../theme/default.css">
9 <link rel="stylesheet" href="../../css/docs.css">
8 <link rel="stylesheet" href="../../doc/docs.css">
10 9 <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
11 10 </head>
12 11 <body>
13 <h1>CodeMirror 2: Python mode</h1>
14
12 <h1>CodeMirror: Python mode</h1>
13
15 14 <div><textarea id="code" name="code">
16 15 # Literals
17 16 1234
18 17 0.0e101
19 18 .123
20 19 0b01010011100
21 20 0o01234567
22 21 0x0987654321abcdef
23 22 7
24 23 2147483647
25 24 3L
26 25 79228162514264337593543950336L
27 26 0x100000000L
28 27 79228162514264337593543950336
29 28 0xdeadbeef
30 29 3.14j
31 30 10.j
32 31 10j
33 32 .001j
34 33 1e100j
35 34 3.14e-10j
36 35
37 36
38 37 # String Literals
39 38 'For\''
40 39 "God\""
41 40 """so loved
42 41 the world"""
43 42 '''that he gave
44 43 his only begotten\' '''
45 44 'that whosoever believeth \
46 45 in him'
47 46 ''
48 47
49 48 # Identifiers
50 49 __a__
51 50 a.b
52 51 a.b.c
53 52
54 53 # Operators
55 54 + - * / % & | ^ ~ < >
56 55 == != <= >= <> << >> // **
57 56 and or not in is
58 57
59 58 # Delimiters
60 59 () [] {} , : ` = ; @ . # Note that @ and . require the proper context.
61 60 += -= *= /= %= &= |= ^=
62 61 //= >>= <<= **=
63 62
64 63 # Keywords
65 64 as assert break class continue def del elif else except
66 65 finally for from global if import lambda pass raise
67 66 return try while with yield
68 67
69 68 # Python 2 Keywords (otherwise Identifiers)
70 69 exec print
71 70
72 71 # Python 3 Keywords (otherwise Identifiers)
73 72 nonlocal
74 73
75 74 # Types
76 75 bool classmethod complex dict enumerate float frozenset int list object
77 76 property reversed set slice staticmethod str super tuple type
78 77
79 78 # Python 2 Types (otherwise Identifiers)
80 79 basestring buffer file long unicode xrange
81 80
82 81 # Python 3 Types (otherwise Identifiers)
83 82 bytearray bytes filter map memoryview open range zip
84 83
85 84 # Some Example code
86 85 import os
87 86 from package import ParentClass
88 87
89 88 @nonsenseDecorator
90 89 def doesNothing():
91 90 pass
92 91
93 92 class ExampleClass(ParentClass):
94 93 @staticmethod
95 94 def example(inputStr):
96 95 a = list(inputStr)
97 96 a.reverse()
98 97 return ''.join(a)
99 98
100 99 def __init__(self, mixin = 'Hello'):
101 100 self.mixin = mixin
102 101
103 102 </textarea></div>
104 103 <script>
105 104 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
106 105 mode: {name: "python",
107 106 version: 2,
108 107 singleLineStringErrors: false},
109 108 lineNumbers: true,
110 109 indentUnit: 4,
111 110 tabMode: "shift",
112 111 matchBrackets: true
113 112 });
114 113 </script>
115 114 <h2>Configuration Options:</h2>
116 115 <ul>
117 116 <li>version - 2/3 - The version of Python to recognize. Default is 2.</li>
118 117 <li>singleLineStringErrors - true/false - If you have a single-line string that is not terminated at the end of the line, this will show subsequent lines as errors if true, otherwise it will consider the newline as the end of the string. Default is false.</li>
119 118 </ul>
120 119
121 120 <p><strong>MIME types defined:</strong> <code>text/x-python</code>.</p>
122 121 </body>
123 122 </html>
@@ -1,324 +1,333 b''
1 1 CodeMirror.defineMode("python", function(conf, parserConf) {
2 2 var ERRORCLASS = 'error';
3
3
4 4 function wordRegexp(words) {
5 5 return new RegExp("^((" + words.join(")|(") + "))\\b");
6 6 }
7
8 // IPython-specific changes: add '?' as recognized character.
9 //var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
10 var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
11 // End IPython changes.
12 7
8 var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
13 9 var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
14 10 var doubleOperators = new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
15 11 var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
16 12 var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
17 13 var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
18 14
19 15 var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']);
20 16 var commonkeywords = ['as', 'assert', 'break', 'class', 'continue',
21 17 'def', 'del', 'elif', 'else', 'except', 'finally',
22 18 'for', 'from', 'global', 'if', 'import',
23 19 'lambda', 'pass', 'raise', 'return',
24 20 'try', 'while', 'with', 'yield'];
25 21 var commontypes = ['bool', 'classmethod', 'complex', 'dict', 'enumerate',
26 22 'float', 'frozenset', 'int', 'list', 'object',
27 23 'property', 'reversed', 'set', 'slice', 'staticmethod',
28 24 'str', 'super', 'tuple', 'type'];
29 25 var py2 = {'types': ['basestring', 'buffer', 'file', 'long', 'unicode',
30 26 'xrange'],
31 27 'keywords': ['exec', 'print']};
32 28 var py3 = {'types': ['bytearray', 'bytes', 'filter', 'map', 'memoryview',
33 29 'open', 'range', 'zip'],
34 30 'keywords': ['nonlocal']};
35 31
36 32 if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) {
37 33 commonkeywords = commonkeywords.concat(py3.keywords);
38 34 commontypes = commontypes.concat(py3.types);
39 35 var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
40 36 } else {
41 37 commonkeywords = commonkeywords.concat(py2.keywords);
42 38 commontypes = commontypes.concat(py2.types);
43 39 var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
44 40 }
45 41 var keywords = wordRegexp(commonkeywords);
46 42 var types = wordRegexp(commontypes);
47 43
48 44 var indentInfo = null;
49 45
50 46 // tokenizers
51 47 function tokenBase(stream, state) {
52 48 // Handle scope changes
53 49 if (stream.sol()) {
54 50 var scopeOffset = state.scopes[0].offset;
55 51 if (stream.eatSpace()) {
56 52 var lineOffset = stream.indentation();
57 53 if (lineOffset > scopeOffset) {
58 54 indentInfo = 'indent';
59 55 } else if (lineOffset < scopeOffset) {
60 56 indentInfo = 'dedent';
61 57 }
62 58 return null;
63 59 } else {
64 60 if (scopeOffset > 0) {
65 61 dedent(stream, state);
66 62 }
67 63 }
68 64 }
69 65 if (stream.eatSpace()) {
70 66 return null;
71 67 }
72
68
73 69 var ch = stream.peek();
74
70
75 71 // Handle Comments
76 72 if (ch === '#') {
77 73 stream.skipToEnd();
78 74 return 'comment';
79 75 }
80
76
81 77 // Handle Number Literals
82 78 if (stream.match(/^[0-9\.]/, false)) {
83 79 var floatLiteral = false;
84 80 // Floats
85 81 if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
86 82 if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
87 83 if (stream.match(/^\.\d+/)) { floatLiteral = true; }
88 84 if (floatLiteral) {
89 85 // Float literals may be "imaginary"
90 86 stream.eat(/J/i);
91 87 return 'number';
92 88 }
93 89 // Integers
94 90 var intLiteral = false;
95 91 // Hex
96 92 if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; }
97 93 // Binary
98 94 if (stream.match(/^0b[01]+/i)) { intLiteral = true; }
99 95 // Octal
100 96 if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; }
101 97 // Decimal
102 98 if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
103 99 // Decimal literals may be "imaginary"
104 100 stream.eat(/J/i);
105 101 // TODO - Can you have imaginary longs?
106 102 intLiteral = true;
107 103 }
108 104 // Zero by itself with no other piece of number.
109 105 if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
110 106 if (intLiteral) {
111 107 // Integer literals may be "long"
112 108 stream.eat(/L/i);
113 109 return 'number';
114 110 }
115 111 }
116
112
117 113 // Handle Strings
118 114 if (stream.match(stringPrefixes)) {
119 115 state.tokenize = tokenStringFactory(stream.current());
120 116 return state.tokenize(stream, state);
121 117 }
122
118
123 119 // Handle operators and Delimiters
124 120 if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
125 121 return null;
126 122 }
127 123 if (stream.match(doubleOperators)
128 124 || stream.match(singleOperators)
129 125 || stream.match(wordOperators)) {
130 126 return 'operator';
131 127 }
132 128 if (stream.match(singleDelimiters)) {
133 129 return null;
134 130 }
135
131
136 132 if (stream.match(types)) {
137 133 return 'builtin';
138 134 }
139
135
140 136 if (stream.match(keywords)) {
141 137 return 'keyword';
142 138 }
143
139
144 140 if (stream.match(identifiers)) {
145 141 return 'variable';
146 142 }
147
143
148 144 // Handle non-detected items
149 145 stream.next();
150 146 return ERRORCLASS;
151 147 }
152
148
153 149 function tokenStringFactory(delimiter) {
154 150 while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) {
155 151 delimiter = delimiter.substr(1);
156 152 }
157 153 var singleline = delimiter.length == 1;
158 154 var OUTCLASS = 'string';
159
155
160 156 return function tokenString(stream, state) {
161 157 while (!stream.eol()) {
162 158 stream.eatWhile(/[^'"\\]/);
163 159 if (stream.eat('\\')) {
164 160 stream.next();
165 161 if (singleline && stream.eol()) {
166 162 return OUTCLASS;
167 163 }
168 164 } else if (stream.match(delimiter)) {
169 165 state.tokenize = tokenBase;
170 166 return OUTCLASS;
171 167 } else {
172 168 stream.eat(/['"]/);
173 169 }
174 170 }
175 171 if (singleline) {
176 172 if (parserConf.singleLineStringErrors) {
177 173 return ERRORCLASS;
178 174 } else {
179 175 state.tokenize = tokenBase;
180 176 }
181 177 }
182 178 return OUTCLASS;
183 179 };
184 180 }
185
181
186 182 function indent(stream, state, type) {
187 183 type = type || 'py';
188 184 var indentUnit = 0;
189 185 if (type === 'py') {
186 if (state.scopes[0].type !== 'py') {
187 state.scopes[0].offset = stream.indentation();
188 return;
189 }
190 190 for (var i = 0; i < state.scopes.length; ++i) {
191 191 if (state.scopes[i].type === 'py') {
192 192 indentUnit = state.scopes[i].offset + conf.indentUnit;
193 193 break;
194 194 }
195 195 }
196 196 } else {
197 197 indentUnit = stream.column() + stream.current().length;
198 198 }
199 199 state.scopes.unshift({
200 200 offset: indentUnit,
201 201 type: type
202 202 });
203 203 }
204
205 function dedent(stream, state) {
204
205 function dedent(stream, state, type) {
206 type = type || 'py';
206 207 if (state.scopes.length == 1) return;
207 208 if (state.scopes[0].type === 'py') {
208 209 var _indent = stream.indentation();
209 210 var _indent_index = -1;
210 211 for (var i = 0; i < state.scopes.length; ++i) {
211 212 if (_indent === state.scopes[i].offset) {
212 213 _indent_index = i;
213 214 break;
214 215 }
215 216 }
216 217 if (_indent_index === -1) {
217 218 return true;
218 219 }
219 220 while (state.scopes[0].offset !== _indent) {
220 221 state.scopes.shift();
221 222 }
222 223 return false
223 224 } else {
224 state.scopes.shift();
225 return false;
225 if (type === 'py') {
226 state.scopes[0].offset = stream.indentation();
227 return false;
228 } else {
229 if (state.scopes[0].type != type) {
230 return true;
231 }
232 state.scopes.shift();
233 return false;
234 }
226 235 }
227 236 }
228 237
229 238 function tokenLexer(stream, state) {
230 239 indentInfo = null;
231 240 var style = state.tokenize(stream, state);
232 241 var current = stream.current();
233 242
234 243 // Handle '.' connected identifiers
235 244 if (current === '.') {
236 245 style = state.tokenize(stream, state);
237 246 current = stream.current();
238 247 if (style === 'variable') {
239 248 return 'variable';
240 249 } else {
241 250 return ERRORCLASS;
242 251 }
243 252 }
244
253
245 254 // Handle decorators
246 255 if (current === '@') {
247 256 style = state.tokenize(stream, state);
248 257 current = stream.current();
249 258 if (style === 'variable'
250 259 || current === '@staticmethod'
251 260 || current === '@classmethod') {
252 261 return 'meta';
253 262 } else {
254 263 return ERRORCLASS;
255 264 }
256 265 }
257
266
258 267 // Handle scope changes.
259 268 if (current === 'pass' || current === 'return') {
260 269 state.dedent += 1;
261 270 }
262 271 if ((current === ':' && !state.lambda && state.scopes[0].type == 'py')
263 272 || indentInfo === 'indent') {
264 273 indent(stream, state);
265 274 }
266 275 var delimiter_index = '[({'.indexOf(current);
267 276 if (delimiter_index !== -1) {
268 277 indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
269 278 }
270 279 if (indentInfo === 'dedent') {
271 280 if (dedent(stream, state)) {
272 281 return ERRORCLASS;
273 282 }
274 283 }
275 284 delimiter_index = '])}'.indexOf(current);
276 285 if (delimiter_index !== -1) {
277 if (dedent(stream, state)) {
286 if (dedent(stream, state, current)) {
278 287 return ERRORCLASS;
279 288 }
280 289 }
281 290 if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') {
282 291 if (state.scopes.length > 1) state.scopes.shift();
283 292 state.dedent -= 1;
284 293 }
285
294
286 295 return style;
287 296 }
288 297
289 298 var external = {
290 299 startState: function(basecolumn) {
291 300 return {
292 301 tokenize: tokenBase,
293 302 scopes: [{offset:basecolumn || 0, type:'py'}],
294 303 lastToken: null,
295 304 lambda: false,
296 305 dedent: 0
297 306 };
298 307 },
299
308
300 309 token: function(stream, state) {
301 310 var style = tokenLexer(stream, state);
302
311
303 312 state.lastToken = {style:style, content: stream.current()};
304
313
305 314 if (stream.eol() && stream.lambda) {
306 315 state.lambda = false;
307 316 }
308
317
309 318 return style;
310 319 },
311
320
312 321 indent: function(state, textAfter) {
313 322 if (state.tokenize != tokenBase) {
314 323 return 0;
315 324 }
316
325
317 326 return state.scopes[0].offset;
318 327 }
319
328
320 329 };
321 330 return external;
322 331 });
323 332
324 333 CodeMirror.defineMIME("text/x-python", "python");
@@ -1,525 +1,525 b''
1 1 <!doctype html>
2 2 <html>
3 3 <head>
4 <title>CodeMirror 2: reStructuredText mode</title>
4 <title>CodeMirror: reStructuredText mode</title>
5 5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 6 <script src="../../lib/codemirror.js"></script>
7 7 <script src="rst.js"></script>
8 <link rel="stylesheet" href="rst.css">
9 8 <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
10 <link rel="stylesheet" href="../../css/docs.css">
9 <link rel="stylesheet" href="../../doc/docs.css">
11 10 </head>
12 11 <body>
13 <h1>CodeMirror 2: reStructuredText mode</h1>
12 <h1>CodeMirror: reStructuredText mode</h1>
14 13
15 14 <form><textarea id="code" name="code">
16 15 .. This is an excerpt from Sphinx documentation: http://sphinx.pocoo.org/_sources/rest.txt
17 16
18 17 .. highlightlang:: rest
19 18
20 19 .. _rst-primer:
21 20
22 21 reStructuredText Primer
23 22 =======================
24 23
25 24 This section is a brief introduction to reStructuredText (reST) concepts and
26 25 syntax, intended to provide authors with enough information to author documents
27 26 productively. Since reST was designed to be a simple, unobtrusive markup
28 27 language, this will not take too long.
29 28
30 29 .. seealso::
31 30
32 31 The authoritative `reStructuredText User Documentation
33 32 &lt;http://docutils.sourceforge.net/rst.html&gt;`_. The "ref" links in this
34 33 document link to the description of the individual constructs in the reST
35 34 reference.
36 35
37 36
38 37 Paragraphs
39 38 ----------
40 39
41 40 The paragraph (:duref:`ref &lt;paragraphs&gt;`) is the most basic block in a reST
42 41 document. Paragraphs are simply chunks of text separated by one or more blank
43 42 lines. As in Python, indentation is significant in reST, so all lines of the
44 43 same paragraph must be left-aligned to the same level of indentation.
45 44
46 45
47 46 .. _inlinemarkup:
48 47
49 48 Inline markup
50 49 -------------
51 50
52 51 The standard reST inline markup is quite simple: use
53 52
54 53 * one asterisk: ``*text*`` for emphasis (italics),
55 54 * two asterisks: ``**text**`` for strong emphasis (boldface), and
56 55 * backquotes: ````text```` for code samples.
57 56
58 57 If asterisks or backquotes appear in running text and could be confused with
59 58 inline markup delimiters, they have to be escaped with a backslash.
60 59
61 60 Be aware of some restrictions of this markup:
62 61
63 62 * it may not be nested,
64 63 * content may not start or end with whitespace: ``* text*`` is wrong,
65 64 * it must be separated from surrounding text by non-word characters. Use a
66 65 backslash escaped space to work around that: ``thisis\ *one*\ word``.
67 66
68 67 These restrictions may be lifted in future versions of the docutils.
69 68
70 69 reST also allows for custom "interpreted text roles"', which signify that the
71 70 enclosed text should be interpreted in a specific way. Sphinx uses this to
72 71 provide semantic markup and cross-referencing of identifiers, as described in
73 72 the appropriate section. The general syntax is ``:rolename:`content```.
74 73
75 74 Standard reST provides the following roles:
76 75
77 76 * :durole:`emphasis` -- alternate spelling for ``*emphasis*``
78 77 * :durole:`strong` -- alternate spelling for ``**strong**``
79 78 * :durole:`literal` -- alternate spelling for ````literal````
80 79 * :durole:`subscript` -- subscript text
81 80 * :durole:`superscript` -- superscript text
82 81 * :durole:`title-reference` -- for titles of books, periodicals, and other
83 82 materials
84 83
85 84 See :ref:`inline-markup` for roles added by Sphinx.
86 85
87 86
88 87 Lists and Quote-like blocks
89 88 ---------------------------
90 89
91 90 List markup (:duref:`ref &lt;bullet-lists&gt;`) is natural: just place an asterisk at
92 91 the start of a paragraph and indent properly. The same goes for numbered lists;
93 92 they can also be autonumbered using a ``#`` sign::
94 93
95 94 * This is a bulleted list.
96 95 * It has two items, the second
97 96 item uses two lines.
98 97
99 98 1. This is a numbered list.
100 99 2. It has two items too.
101 100
102 101 #. This is a numbered list.
103 102 #. It has two items too.
104 103
105 104
106 105 Nested lists are possible, but be aware that they must be separated from the
107 106 parent list items by blank lines::
108 107
109 108 * this is
110 109 * a list
111 110
112 111 * with a nested list
113 112 * and some subitems
114 113
115 114 * and here the parent list continues
116 115
117 116 Definition lists (:duref:`ref &lt;definition-lists&gt;`) are created as follows::
118 117
119 118 term (up to a line of text)
120 119 Definition of the term, which must be indented
121 120
122 121 and can even consist of multiple paragraphs
123 122
124 123 next term
125 124 Description.
126 125
127 126 Note that the term cannot have more than one line of text.
128 127
129 128 Quoted paragraphs (:duref:`ref &lt;block-quotes&gt;`) are created by just indenting
130 129 them more than the surrounding paragraphs.
131 130
132 131 Line blocks (:duref:`ref &lt;line-blocks&gt;`) are a way of preserving line breaks::
133 132
134 133 | These lines are
135 134 | broken exactly like in
136 135 | the source file.
137 136
138 137 There are also several more special blocks available:
139 138
140 139 * field lists (:duref:`ref &lt;field-lists&gt;`)
141 140 * option lists (:duref:`ref &lt;option-lists&gt;`)
142 141 * quoted literal blocks (:duref:`ref &lt;quoted-literal-blocks&gt;`)
143 142 * doctest blocks (:duref:`ref &lt;doctest-blocks&gt;`)
144 143
145 144
146 145 Source Code
147 146 -----------
148 147
149 148 Literal code blocks (:duref:`ref &lt;literal-blocks&gt;`) are introduced by ending a
150 149 paragraph with the special marker ``::``. The literal block must be indented
151 150 (and, like all paragraphs, separated from the surrounding ones by blank lines)::
152 151
153 152 This is a normal text paragraph. The next paragraph is a code sample::
154 153
155 154 It is not processed in any way, except
156 155 that the indentation is removed.
157 156
158 157 It can span multiple lines.
159 158
160 159 This is a normal text paragraph again.
161 160
162 161 The handling of the ``::`` marker is smart:
163 162
164 163 * If it occurs as a paragraph of its own, that paragraph is completely left
165 164 out of the document.
166 165 * If it is preceded by whitespace, the marker is removed.
167 166 * If it is preceded by non-whitespace, the marker is replaced by a single
168 167 colon.
169 168
170 169 That way, the second sentence in the above example's first paragraph would be
171 170 rendered as "The next paragraph is a code sample:".
172 171
173 172
174 173 .. _rst-tables:
175 174
176 175 Tables
177 176 ------
178 177
179 178 Two forms of tables are supported. For *grid tables* (:duref:`ref
180 179 &lt;grid-tables&gt;`), you have to "paint" the cell grid yourself. They look like
181 180 this::
182 181
183 182 +------------------------+------------+----------+----------+
184 183 | Header row, column 1 | Header 2 | Header 3 | Header 4 |
185 184 | (header rows optional) | | | |
186 185 +========================+============+==========+==========+
187 186 | body row 1, column 1 | column 2 | column 3 | column 4 |
188 187 +------------------------+------------+----------+----------+
189 188 | body row 2 | ... | ... | |
190 189 +------------------------+------------+----------+----------+
191 190
192 191 *Simple tables* (:duref:`ref &lt;simple-tables&gt;`) are easier to write, but
193 192 limited: they must contain more than one row, and the first column cannot
194 193 contain multiple lines. They look like this::
195 194
196 195 ===== ===== =======
197 196 A B A and B
198 197 ===== ===== =======
199 198 False False False
200 199 True False False
201 200 False True False
202 201 True True True
203 202 ===== ===== =======
204 203
205 204
206 205 Hyperlinks
207 206 ----------
208 207
209 208 External links
210 209 ^^^^^^^^^^^^^^
211 210
212 211 Use ```Link text &lt;http://example.com/&gt;`_`` for inline web links. If the link
213 212 text should be the web address, you don't need special markup at all, the parser
214 213 finds links and mail addresses in ordinary text.
215 214
216 215 You can also separate the link and the target definition (:duref:`ref
217 216 &lt;hyperlink-targets&gt;`), like this::
218 217
219 218 This is a paragraph that contains `a link`_.
220 219
221 220 .. _a link: http://example.com/
222 221
223 222
224 223 Internal links
225 224 ^^^^^^^^^^^^^^
226 225
227 226 Internal linking is done via a special reST role provided by Sphinx, see the
228 227 section on specific markup, :ref:`ref-role`.
229 228
230 229
231 230 Sections
232 231 --------
233 232
234 233 Section headers (:duref:`ref &lt;sections&gt;`) are created by underlining (and
235 234 optionally overlining) the section title with a punctuation character, at least
236 235 as long as the text::
237 236
238 237 =================
239 238 This is a heading
240 239 =================
241 240
242 241 Normally, there are no heading levels assigned to certain characters as the
243 242 structure is determined from the succession of headings. However, for the
244 243 Python documentation, this convention is used which you may follow:
245 244
246 245 * ``#`` with overline, for parts
247 246 * ``*`` with overline, for chapters
248 247 * ``=``, for sections
249 248 * ``-``, for subsections
250 249 * ``^``, for subsubsections
251 250 * ``"``, for paragraphs
252 251
253 252 Of course, you are free to use your own marker characters (see the reST
254 253 documentation), and use a deeper nesting level, but keep in mind that most
255 254 target formats (HTML, LaTeX) have a limited supported nesting depth.
256 255
257 256
258 257 Explicit Markup
259 258 ---------------
260 259
261 260 "Explicit markup" (:duref:`ref &lt;explicit-markup-blocks&gt;`) is used in reST for
262 261 most constructs that need special handling, such as footnotes,
263 262 specially-highlighted paragraphs, comments, and generic directives.
264 263
265 264 An explicit markup block begins with a line starting with ``..`` followed by
266 265 whitespace and is terminated by the next paragraph at the same level of
267 266 indentation. (There needs to be a blank line between explicit markup and normal
268 267 paragraphs. This may all sound a bit complicated, but it is intuitive enough
269 268 when you write it.)
270 269
271 270
272 271 .. _directives:
273 272
274 273 Directives
275 274 ----------
276 275
277 276 A directive (:duref:`ref &lt;directives&gt;`) is a generic block of explicit markup.
278 277 Besides roles, it is one of the extension mechanisms of reST, and Sphinx makes
279 278 heavy use of it.
280 279
281 280 Docutils supports the following directives:
282 281
283 282 * Admonitions: :dudir:`attention`, :dudir:`caution`, :dudir:`danger`,
284 283 :dudir:`error`, :dudir:`hint`, :dudir:`important`, :dudir:`note`,
285 284 :dudir:`tip`, :dudir:`warning` and the generic :dudir:`admonition`.
286 285 (Most themes style only "note" and "warning" specially.)
287 286
288 287 * Images:
289 288
290 289 - :dudir:`image` (see also Images_ below)
291 290 - :dudir:`figure` (an image with caption and optional legend)
292 291
293 292 * Additional body elements:
294 293
295 294 - :dudir:`contents` (a local, i.e. for the current file only, table of
296 295 contents)
297 296 - :dudir:`container` (a container with a custom class, useful to generate an
298 297 outer ``&lt;div&gt;`` in HTML)
299 298 - :dudir:`rubric` (a heading without relation to the document sectioning)
300 299 - :dudir:`topic`, :dudir:`sidebar` (special highlighted body elements)
301 300 - :dudir:`parsed-literal` (literal block that supports inline markup)
302 301 - :dudir:`epigraph` (a block quote with optional attribution line)
303 302 - :dudir:`highlights`, :dudir:`pull-quote` (block quotes with their own
304 303 class attribute)
305 304 - :dudir:`compound` (a compound paragraph)
306 305
307 306 * Special tables:
308 307
309 308 - :dudir:`table` (a table with title)
310 309 - :dudir:`csv-table` (a table generated from comma-separated values)
311 310 - :dudir:`list-table` (a table generated from a list of lists)
312 311
313 312 * Special directives:
314 313
315 314 - :dudir:`raw` (include raw target-format markup)
316 315 - :dudir:`include` (include reStructuredText from another file)
317 316 -- in Sphinx, when given an absolute include file path, this directive takes
318 317 it as relative to the source directory
319 318 - :dudir:`class` (assign a class attribute to the next element) [1]_
320 319
321 320 * HTML specifics:
322 321
323 322 - :dudir:`meta` (generation of HTML ``&lt;meta&gt;`` tags)
324 323 - :dudir:`title` (override document title)
325 324
326 325 * Influencing markup:
327 326
328 327 - :dudir:`default-role` (set a new default role)
329 328 - :dudir:`role` (create a new role)
330 329
331 330 Since these are only per-file, better use Sphinx' facilities for setting the
332 331 :confval:`default_role`.
333 332
334 333 Do *not* use the directives :dudir:`sectnum`, :dudir:`header` and
335 334 :dudir:`footer`.
336 335
337 336 Directives added by Sphinx are described in :ref:`sphinxmarkup`.
338 337
339 338 Basically, a directive consists of a name, arguments, options and content. (Keep
340 339 this terminology in mind, it is used in the next chapter describing custom
341 340 directives.) Looking at this example, ::
342 341
343 342 .. function:: foo(x)
344 343 foo(y, z)
345 344 :module: some.module.name
346 345
347 346 Return a line of text input from the user.
348 347
349 348 ``function`` is the directive name. It is given two arguments here, the
350 349 remainder of the first line and the second line, as well as one option
351 350 ``module`` (as you can see, options are given in the lines immediately following
352 351 the arguments and indicated by the colons). Options must be indented to the
353 352 same level as the directive content.
354 353
355 354 The directive content follows after a blank line and is indented relative to the
356 355 directive start.
357 356
358 357
359 358 Images
360 359 ------
361 360
362 361 reST supports an image directive (:dudir:`ref &lt;image&gt;`), used like so::
363 362
364 363 .. image:: gnu.png
365 364 (options)
366 365
367 366 When used within Sphinx, the file name given (here ``gnu.png``) must either be
368 367 relative to the source file, or absolute which means that they are relative to
369 368 the top source directory. For example, the file ``sketch/spam.rst`` could refer
370 369 to the image ``images/spam.png`` as ``../images/spam.png`` or
371 370 ``/images/spam.png``.
372 371
373 372 Sphinx will automatically copy image files over to a subdirectory of the output
374 373 directory on building (e.g. the ``_static`` directory for HTML output.)
375 374
376 375 Interpretation of image size options (``width`` and ``height``) is as follows:
377 376 if the size has no unit or the unit is pixels, the given size will only be
378 377 respected for output channels that support pixels (i.e. not in LaTeX output).
379 378 Other units (like ``pt`` for points) will be used for HTML and LaTeX output.
380 379
381 380 Sphinx extends the standard docutils behavior by allowing an asterisk for the
382 381 extension::
383 382
384 383 .. image:: gnu.*
385 384
386 385 Sphinx then searches for all images matching the provided pattern and determines
387 386 their type. Each builder then chooses the best image out of these candidates.
388 387 For instance, if the file name ``gnu.*`` was given and two files :file:`gnu.pdf`
389 388 and :file:`gnu.png` existed in the source tree, the LaTeX builder would choose
390 389 the former, while the HTML builder would prefer the latter.
391 390
392 391 .. versionchanged:: 0.4
393 392 Added the support for file names ending in an asterisk.
394 393
395 394 .. versionchanged:: 0.6
396 395 Image paths can now be absolute.
397 396
398 397
399 398 Footnotes
400 399 ---------
401 400
402 401 For footnotes (:duref:`ref &lt;footnotes&gt;`), use ``[#name]_`` to mark the footnote
403 402 location, and add the footnote body at the bottom of the document after a
404 403 "Footnotes" rubric heading, like so::
405 404
406 405 Lorem ipsum [#f1]_ dolor sit amet ... [#f2]_
407 406
408 407 .. rubric:: Footnotes
409 408
410 409 .. [#f1] Text of the first footnote.
411 410 .. [#f2] Text of the second footnote.
412 411
413 412 You can also explicitly number the footnotes (``[1]_``) or use auto-numbered
414 413 footnotes without names (``[#]_``).
415 414
416 415
417 416 Citations
418 417 ---------
419 418
420 419 Standard reST citations (:duref:`ref &lt;citations&gt;`) are supported, with the
421 420 additional feature that they are "global", i.e. all citations can be referenced
422 421 from all files. Use them like so::
423 422
424 423 Lorem ipsum [Ref]_ dolor sit amet.
425 424
426 425 .. [Ref] Book or article reference, URL or whatever.
427 426
428 427 Citation usage is similar to footnote usage, but with a label that is not
429 428 numeric or begins with ``#``.
430 429
431 430
432 431 Substitutions
433 432 -------------
434 433
435 434 reST supports "substitutions" (:duref:`ref &lt;substitution-definitions&gt;`), which
436 435 are pieces of text and/or markup referred to in the text by ``|name|``. They
437 436 are defined like footnotes with explicit markup blocks, like this::
438 437
439 438 .. |name| replace:: replacement *text*
440 439
441 440 or this::
442 441
443 442 .. |caution| image:: warning.png
444 443 :alt: Warning!
445 444
446 445 See the :duref:`reST reference for substitutions &lt;substitution-definitions&gt;`
447 446 for details.
448 447
449 448 If you want to use some substitutions for all documents, put them into
450 449 :confval:`rst_prolog` or put them into a separate file and include it into all
451 450 documents you want to use them in, using the :rst:dir:`include` directive. (Be
452 451 sure to give the include file a file name extension differing from that of other
453 452 source files, to avoid Sphinx finding it as a standalone document.)
454 453
455 454 Sphinx defines some default substitutions, see :ref:`default-substitutions`.
456 455
457 456
458 457 Comments
459 458 --------
460 459
461 460 Every explicit markup block which isn't a valid markup construct (like the
462 461 footnotes above) is regarded as a comment (:duref:`ref &lt;comments&gt;`). For
463 462 example::
464 463
465 464 .. This is a comment.
466 465
467 466 You can indent text after a comment start to form multiline comments::
468 467
469 468 ..
470 469 This whole indented block
471 470 is a comment.
472 471
473 472 Still in the comment.
474 473
475 474
476 475 Source encoding
477 476 ---------------
478 477
479 478 Since the easiest way to include special characters like em dashes or copyright
480 479 signs in reST is to directly write them as Unicode characters, one has to
481 480 specify an encoding. Sphinx assumes source files to be encoded in UTF-8 by
482 481 default; you can change this with the :confval:`source_encoding` config value.
483 482
484 483
485 484 Gotchas
486 485 -------
487 486
488 487 There are some problems one commonly runs into while authoring reST documents:
489 488
490 489 * **Separation of inline markup:** As said above, inline markup spans must be
491 490 separated from the surrounding text by non-word characters, you have to use a
492 491 backslash-escaped space to get around that. See `the reference
493 492 &lt;http://docutils.sf.net/docs/ref/rst/restructuredtext.html#inline-markup&gt;`_
494 493 for the details.
495 494
496 495 * **No nested inline markup:** Something like ``*see :func:`foo`*`` is not
497 496 possible.
498 497
499 498
500 499 .. rubric:: Footnotes
501 500
502 501 .. [1] When the default domain contains a :rst:dir:`class` directive, this directive
503 502 will be shadowed. Therefore, Sphinx re-exports it as :rst:dir:`rst-class`.
504 503 </textarea></form>
505 504
506 505 <script>
507 506 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
508 507 lineNumbers: true,
509 508 });
510 509 </script>
511 510 <p>The reStructuredText mode supports one configuration parameter:</p>
512 511 <dl>
513 512 <dt><code>verbatim (string)</code></dt>
514 513 <dd>A name or MIME type of a mode that will be used for highlighting
515 514 verbatim blocks. By default, reStructuredText mode uses uniform color
516 515 for whole block of verbatim text if no mode is given.</dd>
517 516 </dl>
518 <p>If <code>python</code> mode is available (not a part of CodeMirror 2 yet),
517 <p>If <code>python</code> mode is available,
519 518 it will be used for highlighting blocks containing Python/IPython terminal
520 519 sessions (blocks starting with <code>&gt;&gt;&gt;</code> (for Python) or
521 520 <code>In [num]:</code> (for IPython).
522 521
523 522 <p><strong>MIME types defined:</strong> <code>text/x-rst</code>.</p>
524 523 </body>
525 524 </html>
525
@@ -1,333 +1,326 b''
1 1 CodeMirror.defineMode('rst', function(config, options) {
2 2 function setState(state, fn, ctx) {
3 3 state.fn = fn;
4 4 setCtx(state, ctx);
5 5 }
6 6
7 7 function setCtx(state, ctx) {
8 8 state.ctx = ctx || {};
9 9 }
10 10
11 11 function setNormal(state, ch) {
12 12 if (ch && (typeof ch !== 'string')) {
13 13 var str = ch.current();
14 14 ch = str[str.length-1];
15 15 }
16 16
17 17 setState(state, normal, {back: ch});
18 18 }
19 19
20 20 function hasMode(mode) {
21 21 if (mode) {
22 22 var modes = CodeMirror.listModes();
23 23
24 24 for (var i in modes) {
25 25 if (modes[i] == mode) {
26 26 return true;
27 27 }
28 28 }
29 29 }
30 30
31 31 return false;
32 32 }
33 33
34 34 function getMode(mode) {
35 35 if (hasMode(mode)) {
36 36 return CodeMirror.getMode(config, mode);
37 37 } else {
38 38 return null;
39 39 }
40 40 }
41 41
42 42 var verbatimMode = getMode(options.verbatim);
43 43 var pythonMode = getMode('python');
44 44
45 45 var reSection = /^[!"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]/;
46 46 var reDirective = /^\s*\w([-:.\w]*\w)?::(\s|$)/;
47 47 var reHyperlink = /^\s*_[\w-]+:(\s|$)/;
48 48 var reFootnote = /^\s*\[(\d+|#)\](\s|$)/;
49 49 var reCitation = /^\s*\[[A-Za-z][\w-]*\](\s|$)/;
50 50 var reFootnoteRef = /^\[(\d+|#)\]_/;
51 51 var reCitationRef = /^\[[A-Za-z][\w-]*\]_/;
52 52 var reDirectiveMarker = /^\.\.(\s|$)/;
53 53 var reVerbatimMarker = /^::\s*$/;
54 54 var rePreInline = /^[-\s"([{</:]/;
55 55 var rePostInline = /^[-\s`'")\]}>/:.,;!?\\_]/;
56 56 var reEnumeratedList = /^\s*((\d+|[A-Za-z#])[.)]|\((\d+|[A-Z-a-z#])\))\s/;
57 57 var reBulletedList = /^\s*[-\+\*]\s/;
58 58 var reExamples = /^\s+(>>>|In \[\d+\]:)\s/;
59 59
60 60 function normal(stream, state) {
61 61 var ch, sol, i;
62 62
63 63 if (stream.eat(/\\/)) {
64 64 ch = stream.next();
65 65 setNormal(state, ch);
66 66 return null;
67 67 }
68 68
69 69 sol = stream.sol();
70 70
71 71 if (sol && (ch = stream.eat(reSection))) {
72 72 for (i = 0; stream.eat(ch); i++);
73 73
74 74 if (i >= 3 && stream.match(/^\s*$/)) {
75 75 setNormal(state, null);
76 return 'section';
76 return 'header';
77 77 } else {
78 78 stream.backUp(i + 1);
79 79 }
80 80 }
81 81
82 82 if (sol && stream.match(reDirectiveMarker)) {
83 83 if (!stream.eol()) {
84 84 setState(state, directive);
85 85 }
86
87 return 'directive-marker';
86 return 'meta';
88 87 }
89 88
90 89 if (stream.match(reVerbatimMarker)) {
91 90 if (!verbatimMode) {
92 91 setState(state, verbatim);
93 92 } else {
94 93 var mode = verbatimMode;
95 94
96 95 setState(state, verbatim, {
97 96 mode: mode,
98 97 local: mode.startState()
99 98 });
100 99 }
101
102 return 'verbatim-marker';
100 return 'meta';
103 101 }
104 102
105 103 if (sol && stream.match(reExamples, false)) {
106 104 if (!pythonMode) {
107 105 setState(state, verbatim);
108 return 'verbatim-marker';
106 return 'meta';
109 107 } else {
110 108 var mode = pythonMode;
111 109
112 110 setState(state, verbatim, {
113 111 mode: mode,
114 112 local: mode.startState()
115 113 });
116 114
117 115 return null;
118 116 }
119 117 }
120 118
121 if (sol && (stream.match(reEnumeratedList) ||
122 stream.match(reBulletedList))) {
123 setNormal(state, stream);
124 return 'list';
125 }
126
127 119 function testBackward(re) {
128 120 return sol || !state.ctx.back || re.test(state.ctx.back);
129 121 }
130 122
131 123 function testForward(re) {
132 124 return stream.eol() || stream.match(re, false);
133 125 }
134 126
135 127 function testInline(re) {
136 128 return stream.match(re) && testBackward(/\W/) && testForward(/\W/);
137 129 }
138 130
139 131 if (testInline(reFootnoteRef)) {
140 132 setNormal(state, stream);
141 133 return 'footnote';
142 134 }
143 135
144 136 if (testInline(reCitationRef)) {
145 137 setNormal(state, stream);
146 138 return 'citation';
147 139 }
148 140
149 141 ch = stream.next();
150 142
151 143 if (testBackward(rePreInline)) {
152 144 if ((ch === ':' || ch === '|') && stream.eat(/\S/)) {
153 145 var token;
154 146
155 147 if (ch === ':') {
156 token = 'role';
148 token = 'builtin';
157 149 } else {
158 token = 'replacement';
150 token = 'atom';
159 151 }
160 152
161 153 setState(state, inline, {
162 154 ch: ch,
163 155 wide: false,
164 156 prev: null,
165 157 token: token
166 158 });
167 159
168 160 return token;
169 161 }
170 162
171 163 if (ch === '*' || ch === '`') {
172 164 var orig = ch,
173 165 wide = false;
174 166
175 167 ch = stream.next();
176 168
177 169 if (ch == orig) {
178 170 wide = true;
179 171 ch = stream.next();
180 172 }
181 173
182 174 if (ch && !/\s/.test(ch)) {
183 175 var token;
184 176
185 177 if (orig === '*') {
186 token = wide ? 'strong' : 'emphasis';
178 token = wide ? 'strong' : 'em';
187 179 } else {
188 token = wide ? 'inline' : 'interpreted';
180 token = wide ? 'string' : 'string-2';
189 181 }
190 182
191 183 setState(state, inline, {
192 184 ch: orig, // inline() has to know what to search for
193 185 wide: wide, // are we looking for `ch` or `chch`
194 186 prev: null, // terminator must not be preceeded with whitespace
195 187 token: token // I don't want to recompute this all the time
196 188 });
197 189
198 190 return token;
199 191 }
200 192 }
201 193 }
202 194
203 195 setNormal(state, ch);
204 196 return null;
205 197 }
206 198
207 199 function inline(stream, state) {
208 200 var ch = stream.next(),
209 201 token = state.ctx.token;
210 202
211 203 function finish(ch) {
212 204 state.ctx.prev = ch;
213 205 return token;
214 206 }
215 207
216 208 if (ch != state.ctx.ch) {
217 209 return finish(ch);
218 210 }
219 211
220 212 if (/\s/.test(state.ctx.prev)) {
221 213 return finish(ch);
222 214 }
223 215
224 216 if (state.ctx.wide) {
225 217 ch = stream.next();
226 218
227 219 if (ch != state.ctx.ch) {
228 220 return finish(ch);
229 221 }
230 222 }
231 223
232 224 if (!stream.eol() && !rePostInline.test(stream.peek())) {
233 225 if (state.ctx.wide) {
234 226 stream.backUp(1);
235 227 }
236 228
237 229 return finish(ch);
238 230 }
239 231
240 232 setState(state, normal);
241 233 setNormal(state, ch);
242 234
243 235 return token;
244 236 }
245 237
246 238 function directive(stream, state) {
247 239 var token = null;
248 240
249 241 if (stream.match(reDirective)) {
250 token = 'directive';
242 token = 'attribute';
251 243 } else if (stream.match(reHyperlink)) {
252 token = 'hyperlink';
244 token = 'link';
253 245 } else if (stream.match(reFootnote)) {
254 token = 'footnote';
246 token = 'quote';
255 247 } else if (stream.match(reCitation)) {
256 token = 'citation';
248 token = 'quote';
257 249 } else {
258 250 stream.eatSpace();
259 251
260 252 if (stream.eol()) {
261 253 setNormal(state, stream);
262 254 return null;
263 255 } else {
264 256 stream.skipToEnd();
265 257 setState(state, comment);
266 258 return 'comment';
267 259 }
268 260 }
269 261
262 // FIXME this is unreachable
270 263 setState(state, body, {start: true});
271 264 return token;
272 265 }
273 266
274 267 function body(stream, state) {
275 268 var token = 'body';
276 269
277 270 if (!state.ctx.start || stream.sol()) {
278 271 return block(stream, state, token);
279 272 }
280 273
281 274 stream.skipToEnd();
282 275 setCtx(state);
283 276
284 277 return token;
285 278 }
286 279
287 280 function comment(stream, state) {
288 281 return block(stream, state, 'comment');
289 282 }
290 283
291 284 function verbatim(stream, state) {
292 285 if (!verbatimMode) {
293 return block(stream, state, 'verbatim');
286 return block(stream, state, 'meta');
294 287 } else {
295 288 if (stream.sol()) {
296 289 if (!stream.eatSpace()) {
297 290 setNormal(state, stream);
298 291 }
299 292
300 293 return null;
301 294 }
302 295
303 296 return verbatimMode.token(stream, state.ctx.local);
304 297 }
305 298 }
306 299
307 300 function block(stream, state, token) {
308 301 if (stream.eol() || stream.eatSpace()) {
309 302 stream.skipToEnd();
310 303 return token;
311 304 } else {
312 305 setNormal(state, stream);
313 306 return null;
314 307 }
315 308 }
316 309
317 310 return {
318 311 startState: function() {
319 312 return {fn: normal, ctx: {}};
320 313 },
321 314
322 315 copyState: function(state) {
323 316 return {fn: state.fn, ctx: state.ctx};
324 317 },
325 318
326 319 token: function(stream, state) {
327 320 var token = state.fn(stream, state);
328 321 return token;
329 322 }
330 323 };
331 324 });
332 325
333 326 CodeMirror.defineMIME("text/x-rst", "rst");
@@ -1,42 +1,44 b''
1 1 <!doctype html>
2 2 <html>
3 3 <head>
4 <title>CodeMirror 2: XML mode</title>
4 <title>CodeMirror: XML mode</title>
5 5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 6 <script src="../../lib/codemirror.js"></script>
7 7 <script src="xml.js"></script>
8 <link rel="stylesheet" href="../../theme/default.css">
9 8 <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
10 <link rel="stylesheet" href="../../css/docs.css">
9 <link rel="stylesheet" href="../../doc/docs.css">
11 10 </head>
12 11 <body>
13 <h1>CodeMirror 2: XML mode</h1>
12 <h1>CodeMirror: XML mode</h1>
14 13 <form><textarea id="code" name="code">
15 14 &lt;html style="color: green"&gt;
16 15 &lt;!-- this is a comment --&gt;
17 16 &lt;head&gt;
18 17 &lt;title&gt;HTML Example&lt;/title&gt;
19 18 &lt;/head&gt;
20 19 &lt;body&gt;
21 20 The indentation tries to be &lt;em&gt;somewhat &amp;quot;do what
22 21 I mean&amp;quot;&lt;/em&gt;... but might not match your style.
23 22 &lt;/body&gt;
24 23 &lt;/html&gt;
25 24 </textarea></form>
26 25 <script>
27 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: {name: "xml", htmlMode: true}});
26 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
27 mode: {name: "xml", alignCDATA: true},
28 lineNumbers: true
29 });
28 30 </script>
29 31 <p>The XML mode supports two configuration parameters:</p>
30 32 <dl>
31 33 <dt><code>htmlMode (boolean)</code></dt>
32 34 <dd>This switches the mode to parse HTML instead of XML. This
33 35 means attributes do not have to be quoted, and some elements
34 36 (such as <code>br</code>) do not require a closing tag.</dd>
35 37 <dt><code>alignCDATA (boolean)</code></dt>
36 38 <dd>Setting this to true will force the opening tag of CDATA
37 39 blocks to not be indented.</dd>
38 40 </dl>
39 41
40 42 <p><strong>MIME types defined:</strong> <code>application/xml</code>, <code>text/html</code>.</p>
41 43 </body>
42 44 </html>
@@ -1,231 +1,252 b''
1 1 CodeMirror.defineMode("xml", function(config, parserConfig) {
2 2 var indentUnit = config.indentUnit;
3 3 var Kludges = parserConfig.htmlMode ? {
4 4 autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
5 5 "meta": true, "col": true, "frame": true, "base": true, "area": true},
6 doNotIndent: {"pre": true, "!cdata": true},
6 doNotIndent: {"pre": true},
7 7 allowUnquoted: true
8 } : {autoSelfClosers: {}, doNotIndent: {"!cdata": true}, allowUnquoted: false};
8 } : {autoSelfClosers: {}, doNotIndent: {}, allowUnquoted: false};
9 9 var alignCDATA = parserConfig.alignCDATA;
10 10
11 11 // Return variables for tokenizers
12 12 var tagName, type;
13 13
14 14 function inText(stream, state) {
15 15 function chain(parser) {
16 16 state.tokenize = parser;
17 17 return parser(stream, state);
18 18 }
19 19
20 20 var ch = stream.next();
21 21 if (ch == "<") {
22 22 if (stream.eat("!")) {
23 23 if (stream.eat("[")) {
24 24 if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
25 25 else return null;
26 26 }
27 27 else if (stream.match("--")) return chain(inBlock("comment", "-->"));
28 28 else if (stream.match("DOCTYPE", true, true)) {
29 29 stream.eatWhile(/[\w\._\-]/);
30 return chain(inBlock("meta", ">"));
30 return chain(doctype(1));
31 31 }
32 32 else return null;
33 33 }
34 34 else if (stream.eat("?")) {
35 35 stream.eatWhile(/[\w\._\-]/);
36 36 state.tokenize = inBlock("meta", "?>");
37 37 return "meta";
38 38 }
39 39 else {
40 40 type = stream.eat("/") ? "closeTag" : "openTag";
41 41 stream.eatSpace();
42 42 tagName = "";
43 43 var c;
44 44 while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
45 45 state.tokenize = inTag;
46 46 return "tag";
47 47 }
48 48 }
49 49 else if (ch == "&") {
50 50 stream.eatWhile(/[^;]/);
51 51 stream.eat(";");
52 52 return "atom";
53 53 }
54 54 else {
55 55 stream.eatWhile(/[^&<]/);
56 56 return null;
57 57 }
58 58 }
59 59
60 60 function inTag(stream, state) {
61 61 var ch = stream.next();
62 62 if (ch == ">" || (ch == "/" && stream.eat(">"))) {
63 63 state.tokenize = inText;
64 64 type = ch == ">" ? "endTag" : "selfcloseTag";
65 65 return "tag";
66 66 }
67 67 else if (ch == "=") {
68 68 type = "equals";
69 69 return null;
70 70 }
71 71 else if (/[\'\"]/.test(ch)) {
72 72 state.tokenize = inAttribute(ch);
73 73 return state.tokenize(stream, state);
74 74 }
75 75 else {
76 76 stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/);
77 77 return "word";
78 78 }
79 79 }
80 80
81 81 function inAttribute(quote) {
82 82 return function(stream, state) {
83 83 while (!stream.eol()) {
84 84 if (stream.next() == quote) {
85 85 state.tokenize = inTag;
86 86 break;
87 87 }
88 88 }
89 89 return "string";
90 90 };
91 91 }
92 92
93 93 function inBlock(style, terminator) {
94 94 return function(stream, state) {
95 95 while (!stream.eol()) {
96 96 if (stream.match(terminator)) {
97 97 state.tokenize = inText;
98 98 break;
99 99 }
100 100 stream.next();
101 101 }
102 102 return style;
103 103 };
104 104 }
105 function doctype(depth) {
106 return function(stream, state) {
107 var ch;
108 while ((ch = stream.next()) != null) {
109 if (ch == "<") {
110 state.tokenize = doctype(depth + 1);
111 return state.tokenize(stream, state);
112 } else if (ch == ">") {
113 if (depth == 1) {
114 state.tokenize = inText;
115 break;
116 } else {
117 state.tokenize = doctype(depth - 1);
118 return state.tokenize(stream, state);
119 }
120 }
121 }
122 return "meta";
123 };
124 }
105 125
106 126 var curState, setStyle;
107 127 function pass() {
108 128 for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
109 129 }
110 130 function cont() {
111 131 pass.apply(null, arguments);
112 132 return true;
113 133 }
114 134
115 135 function pushContext(tagName, startOfLine) {
116 136 var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
117 137 curState.context = {
118 138 prev: curState.context,
119 139 tagName: tagName,
120 140 indent: curState.indented,
121 141 startOfLine: startOfLine,
122 142 noIndent: noIndent
123 143 };
124 144 }
125 145 function popContext() {
126 146 if (curState.context) curState.context = curState.context.prev;
127 147 }
128 148
129 149 function element(type) {
130 if (type == "openTag") {curState.tagName = tagName; return cont(attributes, endtag(curState.startOfLine));}
131 else if (type == "closeTag") {
150 if (type == "openTag") {
151 curState.tagName = tagName;
152 return cont(attributes, endtag(curState.startOfLine));
153 } else if (type == "closeTag") {
132 154 var err = false;
133 155 if (curState.context) {
134 156 err = curState.context.tagName != tagName;
135 157 } else {
136 158 err = true;
137 159 }
138 160 if (err) setStyle = "error";
139 161 return cont(endclosetag(err));
140 162 }
141 else if (type == "string") {
142 if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata");
143 if (curState.tokenize == inText) popContext();
144 return cont();
145 }
146 else return cont();
163 return cont();
147 164 }
148 165 function endtag(startOfLine) {
149 166 return function(type) {
150 167 if (type == "selfcloseTag" ||
151 168 (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase())))
152 169 return cont();
153 170 if (type == "endTag") {pushContext(curState.tagName, startOfLine); return cont();}
154 171 return cont();
155 172 };
156 173 }
157 174 function endclosetag(err) {
158 175 return function(type) {
159 176 if (err) setStyle = "error";
160 177 if (type == "endTag") { popContext(); return cont(); }
161 178 setStyle = "error";
162 179 return cont(arguments.callee);
163 180 }
164 181 }
165 182
166 183 function attributes(type) {
167 184 if (type == "word") {setStyle = "attribute"; return cont(attributes);}
168 185 if (type == "equals") return cont(attvalue, attributes);
186 if (type == "string") {setStyle = "error"; return cont(attributes);}
169 187 return pass();
170 188 }
171 189 function attvalue(type) {
172 190 if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
173 191 if (type == "string") return cont(attvaluemaybe);
174 192 return pass();
175 193 }
176 194 function attvaluemaybe(type) {
177 195 if (type == "string") return cont(attvaluemaybe);
178 196 else return pass();
179 197 }
180 198
181 199 return {
182 200 startState: function() {
183 201 return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null};
184 202 },
185 203
186 204 token: function(stream, state) {
187 205 if (stream.sol()) {
188 206 state.startOfLine = true;
189 207 state.indented = stream.indentation();
190 208 }
191 209 if (stream.eatSpace()) return null;
192 210
193 211 setStyle = type = tagName = null;
194 212 var style = state.tokenize(stream, state);
213 state.type = type;
195 214 if ((style || type) && style != "comment") {
196 215 curState = state;
197 216 while (true) {
198 217 var comb = state.cc.pop() || element;
199 218 if (comb(type || style)) break;
200 219 }
201 220 }
202 221 state.startOfLine = false;
203 222 return setStyle || style;
204 223 },
205 224
206 indent: function(state, textAfter) {
225 indent: function(state, textAfter, fullLine) {
207 226 var context = state.context;
208 if (context && context.noIndent) return 0;
227 if ((state.tokenize != inTag && state.tokenize != inText) ||
228 context && context.noIndent)
229 return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
209 230 if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
210 231 if (context && /^<\//.test(textAfter))
211 232 context = context.prev;
212 233 while (context && !context.startOfLine)
213 234 context = context.prev;
214 235 if (context) return context.indent + indentUnit;
215 236 else return 0;
216 237 },
217 238
218 239 compareStates: function(a, b) {
219 if (a.indented != b.indented) return false;
240 if (a.indented != b.indented || a.tokenize != b.tokenize) return false;
220 241 for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
221 242 if (!ca || !cb) return ca == cb;
222 243 if (ca.tagName != cb.tagName) return false;
223 244 }
224 245 },
225 246
226 247 electricChars: "/"
227 248 };
228 249 });
229 250
230 251 CodeMirror.defineMIME("application/xml", "xml");
231 252 CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
@@ -1,9 +1,10 b''
1 1 .cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom {color: #762;}
2 2 .cm-s-elegant span.cm-comment {color: #262;font-style: italic;}
3 3 .cm-s-elegant span.cm-meta {color: #555;font-style: italic;}
4 4 .cm-s-elegant span.cm-variable {color: black;}
5 5 .cm-s-elegant span.cm-variable-2 {color: #b11;}
6 6 .cm-s-elegant span.cm-qualifier {color: #555;}
7 7 .cm-s-elegant span.cm-keyword {color: #730;}
8 8 .cm-s-elegant span.cm-builtin {color: #30a;}
9 9 .cm-s-elegant span.cm-error {background-color: #fdd;}
10 .cm-s-elegant span.cm-link {color: #762;}
@@ -1,8 +1,9 b''
1 1 .cm-s-neat span.cm-comment { color: #a86; }
2 2 .cm-s-neat span.cm-keyword { font-weight: bold; color: blue; }
3 3 .cm-s-neat span.cm-string { color: #a22; }
4 4 .cm-s-neat span.cm-builtin { font-weight: bold; color: #077; }
5 5 .cm-s-neat span.cm-special { font-weight: bold; color: #0aa; }
6 6 .cm-s-neat span.cm-variable { color: black; }
7 7 .cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; }
8 8 .cm-s-neat span.cm-meta {color: #555;}
9 .cm-s-neat span.cm-link { color: #3a3; }
@@ -1,20 +1,21 b''
1 1 /* Loosely based on the Midnight Textmate theme */
2 2
3 3 .cm-s-night { background: #0a001f; color: #f8f8f8; }
4 4 .cm-s-night span.CodeMirror-selected { background: #a8f !important; }
5 5 .cm-s-night .CodeMirror-gutter { background: #0a001f; border-right: 1px solid #aaa; }
6 6 .cm-s-night .CodeMirror-gutter-text { color: #f8f8f8; }
7 7 .cm-s-night .CodeMirror-cursor { border-left: 1px solid white !important; }
8 8
9 9 .cm-s-night span.cm-comment { color: #6900a1; }
10 10 .cm-s-night span.cm-atom { color: #845dc4; }
11 11 .cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; }
12 12 .cm-s-night span.cm-keyword { color: #599eff; }
13 13 .cm-s-night span.cm-string { color: #37f14a; }
14 14 .cm-s-night span.cm-meta { color: #7678e2; }
15 15 .cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; }
16 16 .cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { color: white; }
17 17 .cm-s-night span.cm-error { color: #9d1e15; }
18 18 .cm-s-night span.cm-bracket { color: #8da6ce; }
19 19 .cm-s-night span.cm-comment { color: #6900a1; }
20 20 .cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; }
21 .cm-s-night span.cm-link { color: #845dc4; }
@@ -1,208 +1,205 b''
1 1 <!DOCTYPE HTML>
2 2 <html>
3 3
4 4 <head>
5 5 <meta charset="utf-8">
6 6
7 7 <title>IPython Notebook</title>
8 8
9 9 {% if mathjax_url %}
10 10 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML" charset="utf-8"></script>
11 11 {% end %}
12 12 <script type="text/javascript">
13 13 // MathJax disabled, set as null to distingish from *missing* MathJax,
14 14 // where it will be undefined, and should prompt a dialog later.
15 15 window.mathjax_url = "{{mathjax_url}}";
16 16 </script>
17 17
18 18 <link rel="stylesheet" href="/static/jquery/css/themes/base/jquery-ui.min.css" type="text/css" />
19 19 <link rel="stylesheet" href="/static/codemirror/lib/codemirror.css">
20 <link rel="stylesheet" href="/static/codemirror/mode/markdown/markdown.css">
21 <link rel="stylesheet" href="/static/codemirror/mode/rst/rst.css">
22 20 <link rel="stylesheet" href="/static/codemirror/theme/ipython.css">
23 <link rel="stylesheet" href="/static/codemirror/theme/default.css">
24 21
25 22 <link rel="stylesheet" href="/static/prettify/prettify.css"/>
26 23
27 24 <link rel="stylesheet" href="/static/css/boilerplate.css" type="text/css" />
28 25 <link rel="stylesheet" href="/static/css/layout.css" type="text/css" />
29 26 <link rel="stylesheet" href="/static/css/base.css" type="text/css" />
30 27 <link rel="stylesheet" href="/static/css/notebook.css" type="text/css" />
31 28 <link rel="stylesheet" href="/static/css/renderedhtml.css" type="text/css" />
32 29
33 30 {% comment In the notebook, the read-only flag is used to determine %}
34 31 {% comment whether to hide the side panels and switch off input %}
35 32 <meta name="read_only" content="{{read_only and not logged_in}}"/>
36 33
37 34 </head>
38 35
39 36 <body
40 37 data-project={{project}} data-notebook-id={{notebook_id}}
41 38 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
42 39 >
43 40
44 41 <div id="header">
45 42 <span id="ipython_notebook"><h1><a href='..' alt='dashboard'><img src='/static/ipynblogo.png' alt='IPython Notebook'/></a></h1></span>
46 43 <span id="save_widget">
47 44 <span id="notebook_name"></span>
48 45 <span id="save_status"></span>
49 46 </span>
50 47
51 48 <span id="login_widget">
52 49 {% comment This is a temporary workaround to hide the logout button %}
53 50 {% comment when appropriate until notebook.html is templated %}
54 51 {% if logged_in %}
55 52 <button id="logout">Logout</button>
56 53 {% elif not logged_in and login_available %}
57 54 <button id="login">Login</button>
58 55 {% end %}
59 56 </span>
60 57
61 58 <span id="kernel_status">Idle</span>
62 59 </div>
63 60
64 61 <div id="menubar">
65 62 <ul id="menus">
66 63 <li><a href="#">File</a>
67 64 <ul>
68 65 <li id="new_notebook"><a href="#">New</a></li>
69 66 <li id="open_notebook"><a href="#">Open...</a></li>
70 67 <hr/>
71 68 <li id="copy_notebook"><a href="#">Make a Copy...</a></li>
72 69 <li id="rename_notebook"><a href="#">Rename...</a></li>
73 70 <li id="save_notebook"><a href="#">Save</a></li>
74 71 <hr/>
75 72 <li><a href="#">Download as</a>
76 73 <ul>
77 74 <li id="download_ipynb"><a href="#">IPython (.ipynb)</a></li>
78 75 <li id="download_py"><a href="#">Python (.py)</a></li>
79 76 </ul>
80 77 </li>
81 78 <hr/>
82 79 <li id="print_notebook"><a href="/{{notebook_id}}/print" target="_blank">Print View</a></li>
83 80 </ul>
84 81 </li>
85 82 <li><a href="#">Edit</a>
86 83 <ul>
87 84 <li id="cut_cell"><a href="#">Cut</a></li>
88 85 <li id="copy_cell"><a href="#">Copy</a></li>
89 86 <li id="paste_cell" class="ui-state-disabled"><a href="#">Paste</a></li>
90 87 <li id="paste_cell_above" class="ui-state-disabled"><a href="#">Paste Above</a></li>
91 88 <li id="paste_cell_below" class="ui-state-disabled"><a href="#">Paste Below</a></li>
92 89 <li id="delete_cell"><a href="#">Delete</a></li>
93 90 <hr/>
94 91 <li id="split_cell"><a href="#">Split</a></li>
95 92 <li id="merge_cell_above"><a href="#">Merge Above</a></li>
96 93 <li id="merge_cell_below"><a href="#">Merge Below</a></li>
97 94 <hr/>
98 95 <li id="move_cell_up"><a href="#">Move Up</a></li>
99 96 <li id="move_cell_down"><a href="#">Move Down</a></li>
100 97 <hr/>
101 98 <li id="select_previous"><a href="#">Select Previous</a></li>
102 99 <li id="select_next"><a href="#">Select Next</a></li>
103 100 </ul>
104 101 </li>
105 102 <li><a href="#">Insert</a>
106 103 <ul>
107 104 <li id="insert_cell_above"><a href="#">Insert Cell Above</a></li>
108 105 <li id="insert_cell_below"><a href="#">Insert Cell Below</a></li>
109 106 </ul>
110 107 </li>
111 108 <li><a href="#">Cell</a>
112 109 <ul>
113 110 <li id="full_edit_cell"><a href="#">Edit in Ace</a></li>
114 111 <hr/>
115 112 <li id="run_cell"><a href="#">Run</a></li>
116 113 <li id="run_cell_in_place"><a href="#">Run in Place</a></li>
117 114 <li id="run_all_cells"><a href="#">Run All</a></li>
118 115 <hr/>
119 116 <li id="to_code"><a href="#">Code Cell</a></li>
120 117 <li id="to_markdown"><a href="#">Markdown Cell</a></li>
121 118 <hr/>
122 119 <li id="toggle_output"><a href="#">Toggle Output</a></li>
123 120 <li id="clear_all_output"><a href="#">Clear All Output</a></li>
124 121 </ul>
125 122 </li>
126 123 <li><a href="#">Kernel</a>
127 124 <ul>
128 125 <li id="int_kernel"><a href="#">Interrupt</a></li>
129 126 <li id="restart_kernel"><a href="#">Restart</a></li>
130 127 </ul>
131 128 </li>
132 129 <li><a href="#">Help</a>
133 130 <ul>
134 131 <li><a href="http://ipython.org/documentation.html" target="_blank">IPython Help</a></li>
135 132 <li><a href="http://ipython.org/ipython-doc/stable/interactive/htmlnotebook.html" target="_blank">Notebook Help</a></li>
136 133 <li id="keyboard_shortcuts"><a href="#">Keyboard Shortcuts</a></li>
137 134 <hr/>
138 135 <li><a href="https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts" target="_blank">Ace Keyboard Shortcuts</a></li>
139 136 <hr/>
140 137 <li><a href="http://docs.python.org" target="_blank">Python</a></li>
141 138 <li><a href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a></li>
142 139 <li><a href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a></li>
143 140 <li><a href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a></li>
144 141 <li><a href="http://matplotlib.sourceforge.net/" target="_blank">Matplotlib</a></li>
145 142 </ul>
146 143 </li>
147 144 </ul>
148 145
149 146 </div>
150 147
151 148 <div id="main_app">
152 149
153 150 <div id="notebook_panel">
154 151 <div id="notebook"></div>
155 152 <div id="pager_splitter"></div>
156 153 <div id="pager"></div>
157 154 </div>
158 155
159 156 </div>
160 157
161 158 <div id="fulledit_widget" >
162 159 <div id="fulledit_header">
163 160 <button id="close_fulledit">Close</button>
164 161 </div>
165 162 <div id="fulledit_editor">some text</div>
166 163 </div>
167 164
168 165 <script src="/static/jquery/js/jquery-1.7.1.min.js" type="text/javascript" charset="utf-8"></script>
169 166 <script src="/static/jquery/js/jquery-ui.min.js" type="text/javascript" charset="utf-8"></script>
170 167
171 168 <script src="/static/codemirror/lib/codemirror.js" charset="utf-8"></script>
172 169 <script src="/static/codemirror/mode/python/python.js" charset="utf-8"></script>
173 170 <script src="/static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
174 171 <script src="/static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
175 172 <script src="/static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
176 173 <script src="/static/codemirror/mode/css/css.js" charset="utf-8"></script>
177 174 <script src="/static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
178 175 <script src="/static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
179 176
180 177 <script src="/static/ace/ace.js" type="text/javascript" charset="utf-8"></script>
181 178 <script src="/static/ace/mode-python.js" type="text/javascript" charset="utf-8"></script>
182 179 <script src="/static/ace/theme-textmate.js" type="text/javascript" charset="utf-8"></script>
183 180
184 181 <script src="/static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
185 182
186 183 <script src="/static/prettify/prettify.js" charset="utf-8"></script>
187 184 <script src="/static/dateformat/date.format.js" charset="utf-8"></script>
188 185
189 186 <script src="/static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
190 187 <script src="/static/js/utils.js" type="text/javascript" charset="utf-8"></script>
191 188 <script src="/static/js/cell.js" type="text/javascript" charset="utf-8"></script>
192 189 <script src="/static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
193 190 <script src="/static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
194 191 <script src="/static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
195 192 <script src="/static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
196 193 <script src="/static/js/layout.js" type="text/javascript" charset="utf-8"></script>
197 194 <script src="/static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
198 195 <script src="/static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
199 196 <script src="/static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
200 197 <script src="/static/js/pager.js" type="text/javascript" charset="utf-8"></script>
201 198 <script src="/static/js/menubar.js" type="text/javascript" charset="utf-8"></script>
202 199 <script src="/static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
203 200 <script src="/static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
204 201 <script src="/static/js/fulleditwidget.js" type="text/javascript" charset="utf-8"></script>
205 202
206 203 </body>
207 204
208 205 </html>
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now