| // ============== Formatting extensions ============================ |
| (function() { |
| // Define extensions for a few modes |
| CodeMirror.extendMode("css", { |
| commentStart: "/*", |
| commentEnd: "*/", |
| wordWrapChars: [";", "\\{", "\\}"], |
| autoFormatLineBreaks: function (text) { |
| return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2"); |
| } |
| }); |
| |
| function jsNonBreakableBlocks(text) { |
| var nonBreakableRegexes = [/for\s*?\((.*?)\)/, |
| /\"(.*?)(\"|$)/, |
| /\'(.*?)(\'|$)/, |
| /\/\*(.*?)(\*\/|$)/, |
| /\/\/.*/]; |
| var nonBreakableBlocks = []; |
| for (var i = 0; i < nonBreakableRegexes.length; i++) { |
| var curPos = 0; |
| while (curPos < text.length) { |
| var m = text.substr(curPos).match(nonBreakableRegexes[i]); |
| if (m != null) { |
| nonBreakableBlocks.push({ |
| start: curPos + m.index, |
| end: curPos + m.index + m[0].length |
| }); |
| curPos += m.index + Math.max(1, m[0].length); |
| } |
| else { // No more matches |
| break; |
| } |
| } |
| } |
| nonBreakableBlocks.sort(function (a, b) { |
| return a.start - b.start; |
| }); |
| |
| return nonBreakableBlocks; |
| } |
| |
| CodeMirror.extendMode("javascript", { |
| commentStart: "/*", |
| commentEnd: "*/", |
| wordWrapChars: [";", "\\{", "\\}"], |
| |
| autoFormatLineBreaks: function (text) { |
| var curPos = 0; |
| var reLinesSplitter = /(;|\{|\})([^\r\n;])/g; |
| var nonBreakableBlocks = jsNonBreakableBlocks(text); |
| if (nonBreakableBlocks != null) { |
| var res = ""; |
| for (var i = 0; i < nonBreakableBlocks.length; i++) { |
| if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block |
| res += text.substring(curPos, nonBreakableBlocks[i].start).replace(reLinesSplitter, "$1\n$2"); |
| curPos = nonBreakableBlocks[i].start; |
| } |
| if (nonBreakableBlocks[i].start <= curPos |
| && nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block |
| res += text.substring(curPos, nonBreakableBlocks[i].end); |
| curPos = nonBreakableBlocks[i].end; |
| } |
| } |
| if (curPos < text.length) |
| res += text.substr(curPos).replace(reLinesSplitter, "$1\n$2"); |
| return res; |
| } else { |
| return text.replace(reLinesSplitter, "$1\n$2"); |
| } |
| } |
| }); |
| |
| CodeMirror.extendMode("xml", { |
| commentStart: "<!--", |
| commentEnd: "-->", |
| wordWrapChars: [">"], |
| |
| autoFormatLineBreaks: function (text) { |
| var lines = text.split("\n"); |
| var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)"); |
| var reOpenBrackets = new RegExp("<", "g"); |
| var reCloseBrackets = new RegExp("(>)([^\r\n])", "g"); |
| for (var i = 0; i < lines.length; i++) { |
| var mToProcess = lines[i].match(reProcessedPortion); |
| if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces |
| lines[i] = mToProcess[1] |
| + mToProcess[2].replace(reOpenBrackets, "\n$&").replace(reCloseBrackets, "$1\n$2") |
| + mToProcess[3]; |
| continue; |
| } |
| } |
| return lines.join("\n"); |
| } |
| }); |
| |
| function localModeAt(cm, pos) { |
| return CodeMirror.innerMode(cm.getMode(), cm.getTokenAt(pos).state).mode; |
| } |
| |
| function enumerateModesBetween(cm, line, start, end) { |
| var outer = cm.getMode(), text = cm.getLine(line); |
| if (end == null) end = text.length; |
| if (!outer.innerMode) return [{from: start, to: end, mode: outer}]; |
| var state = cm.getTokenAt({line: line, ch: start}).state; |
| var mode = CodeMirror.innerMode(outer, state).mode; |
| var found = [], stream = new CodeMirror.StringStream(text); |
| stream.pos = stream.start = start; |
| for (;;) { |
| outer.token(stream, state); |
| var curMode = CodeMirror.innerMode(outer, state).mode; |
| if (curMode != mode) { |
| var cut = stream.start; |
| // Crappy heuristic to deal with the fact that a change in |
| // mode can occur both at the end and the start of a token, |
| // and we don't know which it was. |
| if (mode.name == "xml" && text.charAt(stream.pos - 1) == ">") cut = stream.pos; |
| found.push({from: start, to: cut, mode: mode}); |
| start = cut; |
| mode = curMode; |
| } |
| if (stream.pos >= end) break; |
| stream.start = stream.pos; |
| } |
| if (start < end) found.push({from: start, to: end, mode: mode}); |
| return found; |
| } |
| |
| // Comment/uncomment the specified range |
| CodeMirror.defineExtension("commentRange", function (isComment, from, to) { |
| var curMode = localModeAt(this, from), cm = this; |
| this.operation(function() { |
| if (isComment) { // Comment range |
| cm.replaceRange(curMode.commentEnd, to); |
| cm.replaceRange(curMode.commentStart, from); |
| if (from.line == to.line && from.ch == to.ch) // An empty comment inserted - put cursor inside |
| cm.setCursor(from.line, from.ch + curMode.commentStart.length); |
| } else { // Uncomment range |
| var selText = cm.getRange(from, to); |
| var startIndex = selText.indexOf(curMode.commentStart); |
| var endIndex = selText.lastIndexOf(curMode.commentEnd); |
| if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) { |
| // Take string till comment start |
| selText = selText.substr(0, startIndex) |
| // From comment start till comment end |
| + selText.substring(startIndex + curMode.commentStart.length, endIndex) |
| // From comment end till string end |
| + selText.substr(endIndex + curMode.commentEnd.length); |
| } |
| cm.replaceRange(selText, from, to); |
| } |
| }); |
| }); |
| |
| // Applies automatic mode-aware indentation to the specified range |
| CodeMirror.defineExtension("autoIndentRange", function (from, to) { |
| var cmInstance = this; |
| this.operation(function () { |
| for (var i = from.line; i <= to.line; i++) { |
| cmInstance.indentLine(i, "smart"); |
| } |
| }); |
| }); |
| |
| // Applies automatic formatting to the specified range |
| CodeMirror.defineExtension("autoFormatRange", function (from, to) { |
| var cm = this; |
| cm.operation(function () { |
| for (var cur = from.line, end = to.line; cur <= end; ++cur) { |
| var f = {line: cur, ch: cur == from.line ? from.ch : 0}; |
| var t = {line: cur, ch: cur == end ? to.ch : null}; |
| var modes = enumerateModesBetween(cm, cur, f.ch, t.ch), mangled = ""; |
| var text = cm.getRange(f, t); |
| for (var i = 0; i < modes.length; ++i) { |
| var part = modes.length > 1 ? text.slice(modes[i].from, modes[i].to) : text; |
| if (mangled) mangled += "\n"; |
| if (modes[i].mode.autoFormatLineBreaks) { |
| mangled += modes[i].mode.autoFormatLineBreaks(part); |
| } else mangled += text; |
| } |
| if (mangled != text) { |
| for (var count = 0, pos = mangled.indexOf("\n"); pos != -1; pos = mangled.indexOf("\n", pos + 1), ++count) {} |
| cm.replaceRange(mangled, f, t); |
| cur += count; |
| end += count; |
| } |
| } |
| for (var cur = from.line + 1; cur <= end; ++cur) |
| cm.indentLine(cur, "smart"); |
| cm.setSelection(from, cm.getCursor(false)); |
| }); |
| }); |
| })(); |