formatting.js (7673B)
1 // ============== Formatting extensions ============================ 2 (function() { 3 // Define extensions for a few modes 4 CodeMirror.extendMode("css", { 5 commentStart: "/*", 6 commentEnd: "*/", 7 wordWrapChars: [";", "\\{", "\\}"], 8 autoFormatLineBreaks: function (text) { 9 return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2"); 10 } 11 }); 12 13 function jsNonBreakableBlocks(text) { 14 var nonBreakableRegexes = [/for\s*?\((.*?)\)/, 15 /\"(.*?)(\"|$)/, 16 /\'(.*?)(\'|$)/, 17 /\/\*(.*?)(\*\/|$)/, 18 /\/\/.*/]; 19 var nonBreakableBlocks = []; 20 for (var i = 0; i < nonBreakableRegexes.length; i++) { 21 var curPos = 0; 22 while (curPos < text.length) { 23 var m = text.substr(curPos).match(nonBreakableRegexes[i]); 24 if (m != null) { 25 nonBreakableBlocks.push({ 26 start: curPos + m.index, 27 end: curPos + m.index + m[0].length 28 }); 29 curPos += m.index + Math.max(1, m[0].length); 30 } 31 else { // No more matches 32 break; 33 } 34 } 35 } 36 nonBreakableBlocks.sort(function (a, b) { 37 return a.start - b.start; 38 }); 39 40 return nonBreakableBlocks; 41 } 42 43 CodeMirror.extendMode("javascript", { 44 commentStart: "/*", 45 commentEnd: "*/", 46 wordWrapChars: [";", "\\{", "\\}"], 47 48 autoFormatLineBreaks: function (text) { 49 var curPos = 0; 50 var split = this.jsonMode ? function(str) { 51 return str.replace(/([,{])/g, "$1\n").replace(/}/g, "\n}"); 52 } : function(str) { 53 return str.replace(/(;|\{|\})([^\r\n;])/g, "$1\n$2"); 54 }; 55 var nonBreakableBlocks = jsNonBreakableBlocks(text), res = ""; 56 if (nonBreakableBlocks != null) { 57 for (var i = 0; i < nonBreakableBlocks.length; i++) { 58 if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block 59 res += split(text.substring(curPos, nonBreakableBlocks[i].start)); 60 curPos = nonBreakableBlocks[i].start; 61 } 62 if (nonBreakableBlocks[i].start <= curPos 63 && nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block 64 res += text.substring(curPos, nonBreakableBlocks[i].end); 65 curPos = nonBreakableBlocks[i].end; 66 } 67 } 68 if (curPos < text.length) 69 res += split(text.substr(curPos)); 70 } else { 71 res = split(text); 72 } 73 return res.replace(/^\n*|\n*$/, ""); 74 } 75 }); 76 77 CodeMirror.extendMode("xml", { 78 commentStart: "<!--", 79 commentEnd: "-->", 80 wordWrapChars: [">"], 81 82 autoFormatLineBreaks: function (text) { 83 var lines = text.split("\n"); 84 var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)"); 85 var reOpenBrackets = new RegExp("<", "g"); 86 var reCloseBrackets = new RegExp("(>)([^\r\n])", "g"); 87 for (var i = 0; i < lines.length; i++) { 88 var mToProcess = lines[i].match(reProcessedPortion); 89 if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces 90 lines[i] = mToProcess[1] 91 + mToProcess[2].replace(reOpenBrackets, "\n$&").replace(reCloseBrackets, "$1\n$2") 92 + mToProcess[3]; 93 continue; 94 } 95 } 96 return lines.join("\n"); 97 } 98 }); 99 100 function localModeAt(cm, pos) { 101 return CodeMirror.innerMode(cm.getMode(), cm.getTokenAt(pos).state).mode; 102 } 103 104 function enumerateModesBetween(cm, line, start, end) { 105 var outer = cm.getMode(), text = cm.getLine(line); 106 if (end == null) end = text.length; 107 if (!outer.innerMode) return [{from: start, to: end, mode: outer}]; 108 var state = cm.getTokenAt({line: line, ch: start}).state; 109 var mode = CodeMirror.innerMode(outer, state).mode; 110 var found = [], stream = new CodeMirror.StringStream(text); 111 stream.pos = stream.start = start; 112 for (;;) { 113 outer.token(stream, state); 114 var curMode = CodeMirror.innerMode(outer, state).mode; 115 if (curMode != mode) { 116 var cut = stream.start; 117 // Crappy heuristic to deal with the fact that a change in 118 // mode can occur both at the end and the start of a token, 119 // and we don't know which it was. 120 if (mode.name == "xml" && text.charAt(stream.pos - 1) == ">") cut = stream.pos; 121 found.push({from: start, to: cut, mode: mode}); 122 start = cut; 123 mode = curMode; 124 } 125 if (stream.pos >= end) break; 126 stream.start = stream.pos; 127 } 128 if (start < end) found.push({from: start, to: end, mode: mode}); 129 return found; 130 } 131 132 // Comment/uncomment the specified range 133 CodeMirror.defineExtension("commentRange", function (isComment, from, to) { 134 var curMode = localModeAt(this, from), cm = this; 135 this.operation(function() { 136 if (isComment) { // Comment range 137 cm.replaceRange(curMode.commentEnd, to); 138 cm.replaceRange(curMode.commentStart, from); 139 if (from.line == to.line && from.ch == to.ch) // An empty comment inserted - put cursor inside 140 cm.setCursor(from.line, from.ch + curMode.commentStart.length); 141 } else { // Uncomment range 142 var selText = cm.getRange(from, to); 143 var startIndex = selText.indexOf(curMode.commentStart); 144 var endIndex = selText.lastIndexOf(curMode.commentEnd); 145 if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) { 146 // Take string till comment start 147 selText = selText.substr(0, startIndex) 148 // From comment start till comment end 149 + selText.substring(startIndex + curMode.commentStart.length, endIndex) 150 // From comment end till string end 151 + selText.substr(endIndex + curMode.commentEnd.length); 152 } 153 cm.replaceRange(selText, from, to); 154 } 155 }); 156 }); 157 158 // Applies automatic mode-aware indentation to the specified range 159 CodeMirror.defineExtension("autoIndentRange", function (from, to) { 160 var cmInstance = this; 161 this.operation(function () { 162 for (var i = from.line; i <= to.line; i++) { 163 cmInstance.indentLine(i, "smart"); 164 } 165 }); 166 }); 167 168 // Applies automatic formatting to the specified range 169 CodeMirror.defineExtension("autoFormatRange", function (from, to) { 170 var cm = this; 171 cm.operation(function () { 172 for (var cur = from.line, end = to.line; cur <= end; ++cur) { 173 var f = {line: cur, ch: cur == from.line ? from.ch : 0}; 174 var t = {line: cur, ch: cur == end ? to.ch : null}; 175 var modes = enumerateModesBetween(cm, cur, f.ch, t.ch), mangled = ""; 176 var text = cm.getRange(f, t); 177 for (var i = 0; i < modes.length; ++i) { 178 var part = modes.length > 1 ? text.slice(modes[i].from, modes[i].to) : text; 179 if (mangled) mangled += "\n"; 180 if (modes[i].mode.autoFormatLineBreaks) { 181 mangled += modes[i].mode.autoFormatLineBreaks(part); 182 } else mangled += text; 183 } 184 if (mangled != text) { 185 for (var count = 0, pos = mangled.indexOf("\n"); pos != -1; pos = mangled.indexOf("\n", pos + 1), ++count) {} 186 cm.replaceRange(mangled, f, t); 187 cur += count; 188 end += count; 189 } 190 } 191 for (var cur = from.line + 1; cur <= end; ++cur) 192 cm.indentLine(cur, "smart"); 193 cm.setSelection(from, cm.getCursor(false)); 194 }); 195 }); 196 })();