// ============== Formatting extensions ============================ |
// ============== Formatting extensions ============================ |
// A common storage for all mode-specific formatting features |
// A common storage for all mode-specific formatting features |
if (!CodeMirror.modeExtensions) CodeMirror.modeExtensions = {}; |
if (!CodeMirror.modeExtensions) CodeMirror.modeExtensions = {}; |
|
|
// Returns the extension of the editor's current mode |
// Returns the extension of the editor's current mode |
CodeMirror.defineExtension("getModeExt", function () { |
CodeMirror.defineExtension("getModeExt", function () { |
return CodeMirror.modeExtensions[this.getOption("mode")]; |
return CodeMirror.modeExtensions[this.getOption("mode")]; |
}); |
}); |
|
|
// If the current mode is 'htmlmixed', returns the extension of a mode located at |
// If the current mode is 'htmlmixed', returns the extension of a mode located at |
// the specified position (can be htmlmixed, css or javascript). Otherwise, simply |
// the specified position (can be htmlmixed, css or javascript). Otherwise, simply |
// returns the extension of the editor's current mode. |
// returns the extension of the editor's current mode. |
CodeMirror.defineExtension("getModeExtAtPos", function (pos) { |
CodeMirror.defineExtension("getModeExtAtPos", function (pos) { |
var token = this.getTokenAt(pos); |
var token = this.getTokenAt(pos); |
if (token && token.state && token.state.mode) |
if (token && token.state && token.state.mode) |
return CodeMirror.modeExtensions[token.state.mode == "html" ? "htmlmixed" : token.state.mode]; |
return CodeMirror.modeExtensions[token.state.mode == "html" ? "htmlmixed" : token.state.mode]; |
else |
else |
return this.getModeExt(); |
return this.getModeExt(); |
}); |
}); |
|
|
// Comment/uncomment the specified range |
// Comment/uncomment the specified range |
CodeMirror.defineExtension("commentRange", function (isComment, from, to) { |
CodeMirror.defineExtension("commentRange", function (isComment, from, to) { |
var curMode = this.getModeExtAtPos(this.getCursor()); |
var curMode = this.getModeExtAtPos(this.getCursor()); |
if (isComment) { // Comment range |
if (isComment) { // Comment range |
var commentedText = this.getRange(from, to); |
var commentedText = this.getRange(from, to); |
this.replaceRange(curMode.commentStart + this.getRange(from, to) + curMode.commentEnd |
this.replaceRange(curMode.commentStart + this.getRange(from, to) + curMode.commentEnd |
, from, to); |
, from, to); |
if (from.line == to.line && from.ch == to.ch) { // An empty comment inserted - put cursor inside |
if (from.line == to.line && from.ch == to.ch) { // An empty comment inserted - put cursor inside |
this.setCursor(from.line, from.ch + curMode.commentStart.length); |
this.setCursor(from.line, from.ch + curMode.commentStart.length); |
} |
} |
} |
} |
else { // Uncomment range |
else { // Uncomment range |
var selText = this.getRange(from, to); |
var selText = this.getRange(from, to); |
var startIndex = selText.indexOf(curMode.commentStart); |
var startIndex = selText.indexOf(curMode.commentStart); |
var endIndex = selText.lastIndexOf(curMode.commentEnd); |
var endIndex = selText.lastIndexOf(curMode.commentEnd); |
if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) { |
if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) { |
// Take string till comment start |
// Take string till comment start |
selText = selText.substr(0, startIndex) |
selText = selText.substr(0, startIndex) |
// From comment start till comment end |
// From comment start till comment end |
+ selText.substring(startIndex + curMode.commentStart.length, endIndex) |
+ selText.substring(startIndex + curMode.commentStart.length, endIndex) |
// From comment end till string end |
// From comment end till string end |
+ selText.substr(endIndex + curMode.commentEnd.length); |
+ selText.substr(endIndex + curMode.commentEnd.length); |
} |
} |
this.replaceRange(selText, from, to); |
this.replaceRange(selText, from, to); |
} |
} |
}); |
}); |
|
|
// Applies automatic mode-aware indentation to the specified range |
// Applies automatic mode-aware indentation to the specified range |
CodeMirror.defineExtension("autoIndentRange", function (from, to) { |
CodeMirror.defineExtension("autoIndentRange", function (from, to) { |
var cmInstance = this; |
var cmInstance = this; |
this.operation(function () { |
this.operation(function () { |
for (var i = from.line; i <= to.line; i++) { |
for (var i = from.line; i <= to.line; i++) { |
cmInstance.indentLine(i); |
cmInstance.indentLine(i); |
} |
} |
}); |
}); |
}); |
}); |
|
|
// Applies automatic formatting to the specified range |
// Applies automatic formatting to the specified range |
CodeMirror.defineExtension("autoFormatRange", function (from, to) { |
CodeMirror.defineExtension("autoFormatRange", function (from, to) { |
var absStart = this.indexFromPos(from); |
var absStart = this.indexFromPos(from); |
var absEnd = this.indexFromPos(to); |
var absEnd = this.indexFromPos(to); |
// Insert additional line breaks where necessary according to the |
// Insert additional line breaks where necessary according to the |
// mode's syntax |
// mode's syntax |
var res = this.getModeExt().autoFormatLineBreaks(this.getValue(), absStart, absEnd); |
var res = this.getModeExt().autoFormatLineBreaks(this.getValue(), absStart, absEnd); |
var cmInstance = this; |
var cmInstance = this; |
|
|
// Replace and auto-indent the range |
// Replace and auto-indent the range |
this.operation(function () { |
this.operation(function () { |
cmInstance.replaceRange(res, from, to); |
cmInstance.replaceRange(res, from, to); |
var startLine = cmInstance.posFromIndex(absStart).line; |
var startLine = cmInstance.posFromIndex(absStart).line; |
var endLine = cmInstance.posFromIndex(absStart + res.length).line; |
var endLine = cmInstance.posFromIndex(absStart + res.length).line; |
for (var i = startLine; i <= endLine; i++) { |
for (var i = startLine; i <= endLine; i++) { |
cmInstance.indentLine(i); |
cmInstance.indentLine(i); |
} |
} |
}); |
}); |
}); |
}); |
|
|
// Define extensions for a few modes |
// Define extensions for a few modes |
|
|
CodeMirror.modeExtensions["css"] = { |
CodeMirror.modeExtensions["css"] = { |
commentStart: "/*", |
commentStart: "/*", |
commentEnd: "*/", |
commentEnd: "*/", |
wordWrapChars: [";", "\\{", "\\}"], |
wordWrapChars: [";", "\\{", "\\}"], |
autoFormatLineBreaks: function (text) { |
autoFormatLineBreaks: function (text) { |
return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2"); |
return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2"); |
} |
} |
}; |
}; |
|
|
CodeMirror.modeExtensions["javascript"] = { |
CodeMirror.modeExtensions["javascript"] = { |
commentStart: "/*", |
commentStart: "/*", |
commentEnd: "*/", |
commentEnd: "*/", |
wordWrapChars: [";", "\\{", "\\}"], |
wordWrapChars: [";", "\\{", "\\}"], |
|
|
getNonBreakableBlocks: function (text) { |
getNonBreakableBlocks: function (text) { |
var nonBreakableRegexes = [ |
var nonBreakableRegexes = [ |
new RegExp("for\\s*?\\(([\\s\\S]*?)\\)"), |
new RegExp("for\\s*?\\(([\\s\\S]*?)\\)"), |
new RegExp("'([\\s\\S]*?)('|$)"), |
new RegExp("'([\\s\\S]*?)('|$)"), |
new RegExp("\"([\\s\\S]*?)(\"|$)"), |
new RegExp("\"([\\s\\S]*?)(\"|$)"), |
new RegExp("//.*([\r\n]|$)") |
new RegExp("//.*([\r\n]|$)") |
]; |
]; |
var nonBreakableBlocks = new Array(); |
var nonBreakableBlocks = new Array(); |
for (var i = 0; i < nonBreakableRegexes.length; i++) { |
for (var i = 0; i < nonBreakableRegexes.length; i++) { |
var curPos = 0; |
var curPos = 0; |
while (curPos < text.length) { |
while (curPos < text.length) { |
var m = text.substr(curPos).match(nonBreakableRegexes[i]); |
var m = text.substr(curPos).match(nonBreakableRegexes[i]); |
if (m != null) { |
if (m != null) { |
nonBreakableBlocks.push({ |
nonBreakableBlocks.push({ |
start: curPos + m.index, |
start: curPos + m.index, |
end: curPos + m.index + m[0].length |
end: curPos + m.index + m[0].length |
}); |
}); |
curPos += m.index + Math.max(1, m[0].length); |
curPos += m.index + Math.max(1, m[0].length); |
} |
} |
else { // No more matches |
else { // No more matches |
break; |
break; |
} |
} |
} |
} |
} |
} |
nonBreakableBlocks.sort(function (a, b) { |
nonBreakableBlocks.sort(function (a, b) { |
return a.start - b.start; |
return a.start - b.start; |
}); |
}); |
|
|
return nonBreakableBlocks; |
return nonBreakableBlocks; |
}, |
}, |
|
|
autoFormatLineBreaks: function (text) { |
autoFormatLineBreaks: function (text) { |
var curPos = 0; |
var curPos = 0; |
var reLinesSplitter = new RegExp("(;|\\{|\\})([^\r\n])", "g"); |
var reLinesSplitter = new RegExp("(;|\\{|\\})([^\r\n])", "g"); |
var nonBreakableBlocks = this.getNonBreakableBlocks(text); |
var nonBreakableBlocks = this.getNonBreakableBlocks(text); |
if (nonBreakableBlocks != null) { |
if (nonBreakableBlocks != null) { |
var res = ""; |
var res = ""; |
for (var i = 0; i < nonBreakableBlocks.length; i++) { |
for (var i = 0; i < nonBreakableBlocks.length; i++) { |
if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block |
if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block |
res += text.substring(curPos, nonBreakableBlocks[i].start).replace(reLinesSplitter, "$1\n$2"); |
res += text.substring(curPos, nonBreakableBlocks[i].start).replace(reLinesSplitter, "$1\n$2"); |
curPos = nonBreakableBlocks[i].start; |
curPos = nonBreakableBlocks[i].start; |
} |
} |
if (nonBreakableBlocks[i].start <= curPos |
if (nonBreakableBlocks[i].start <= curPos |
&& nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block |
&& nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block |
res += text.substring(curPos, nonBreakableBlocks[i].end); |
res += text.substring(curPos, nonBreakableBlocks[i].end); |
curPos = nonBreakableBlocks[i].end; |
curPos = nonBreakableBlocks[i].end; |
} |
} |
} |
} |
if (curPos < text.length - 1) { |
if (curPos < text.length - 1) { |
res += text.substr(curPos).replace(reLinesSplitter, "$1\n$2"); |
res += text.substr(curPos).replace(reLinesSplitter, "$1\n$2"); |
} |
} |
return res; |
return res; |
} |
} |
else { |
else { |
return text.replace(reLinesSplitter, "$1\n$2"); |
return text.replace(reLinesSplitter, "$1\n$2"); |
} |
} |
} |
} |
}; |
}; |
|
|
CodeMirror.modeExtensions["xml"] = { |
CodeMirror.modeExtensions["xml"] = { |
commentStart: "<!--", |
commentStart: "<!--", |
commentEnd: "-->", |
commentEnd: "-->", |
wordWrapChars: [">"], |
wordWrapChars: [">"], |
|
|
autoFormatLineBreaks: function (text) { |
autoFormatLineBreaks: function (text) { |
var lines = text.split("\n"); |
var lines = text.split("\n"); |
var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)"); |
var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)"); |
var reOpenBrackets = new RegExp("<", "g"); |
var reOpenBrackets = new RegExp("<", "g"); |
var reCloseBrackets = new RegExp("(>)([^\r\n])", "g"); |
var reCloseBrackets = new RegExp("(>)([^\r\n])", "g"); |
for (var i = 0; i < lines.length; i++) { |
for (var i = 0; i < lines.length; i++) { |
var mToProcess = lines[i].match(reProcessedPortion); |
var mToProcess = lines[i].match(reProcessedPortion); |
if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces |
if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces |
lines[i] = mToProcess[1] |
lines[i] = mToProcess[1] |
+ mToProcess[2].replace(reOpenBrackets, "\n$&").replace(reCloseBrackets, "$1\n$2") |
+ mToProcess[2].replace(reOpenBrackets, "\n$&").replace(reCloseBrackets, "$1\n$2") |
+ mToProcess[3]; |
+ mToProcess[3]; |
continue; |
continue; |
} |
} |
} |
} |
|
|
return lines.join("\n"); |
return lines.join("\n"); |
} |
} |
}; |
}; |
|
|
CodeMirror.modeExtensions["htmlmixed"] = { |
CodeMirror.modeExtensions["htmlmixed"] = { |
commentStart: "<!--", |
commentStart: "<!--", |
commentEnd: "-->", |
commentEnd: "-->", |
wordWrapChars: [">", ";", "\\{", "\\}"], |
wordWrapChars: [">", ";", "\\{", "\\}"], |
|
|
getModeInfos: function (text, absPos) { |
getModeInfos: function (text, absPos) { |
var modeInfos = new Array(); |
var modeInfos = new Array(); |
modeInfos[0] = |
modeInfos[0] = |
{ |
{ |
pos: 0, |
pos: 0, |
modeExt: CodeMirror.modeExtensions["xml"], |
modeExt: CodeMirror.modeExtensions["xml"], |
modeName: "xml" |
modeName: "xml" |
}; |
}; |
|
|
var modeMatchers = new Array(); |
var modeMatchers = new Array(); |
modeMatchers[0] = |
modeMatchers[0] = |
{ |
{ |
regex: new RegExp("<style[^>]*>([\\s\\S]*?)(</style[^>]*>|$)", "i"), |
regex: new RegExp("<style[^>]*>([\\s\\S]*?)(</style[^>]*>|$)", "i"), |
modeExt: CodeMirror.modeExtensions["css"], |
modeExt: CodeMirror.modeExtensions["css"], |
modeName: "css" |
modeName: "css" |
}; |
}; |
modeMatchers[1] = |
modeMatchers[1] = |
{ |
{ |
regex: new RegExp("<script[^>]*>([\\s\\S]*?)(</script[^>]*>|$)", "i"), |
regex: new RegExp("<script[^>]*>([\\s\\S]*?)(</script[^>]*>|$)", "i"), |
modeExt: CodeMirror.modeExtensions["javascript"], |
modeExt: CodeMirror.modeExtensions["javascript"], |
modeName: "javascript" |
modeName: "javascript" |
}; |
}; |
|
|
var lastCharPos = (typeof (absPos) !== "undefined" ? absPos : text.length - 1); |
var lastCharPos = (typeof (absPos) !== "undefined" ? absPos : text.length - 1); |
// Detect modes for the entire text |
// Detect modes for the entire text |
for (var i = 0; i < modeMatchers.length; i++) { |
for (var i = 0; i < modeMatchers.length; i++) { |
var curPos = 0; |
var curPos = 0; |
while (curPos <= lastCharPos) { |
while (curPos <= lastCharPos) { |
var m = text.substr(curPos).match(modeMatchers[i].regex); |
var m = text.substr(curPos).match(modeMatchers[i].regex); |
if (m != null) { |
if (m != null) { |
if (m.length > 1 && m[1].length > 0) { |
if (m.length > 1 && m[1].length > 0) { |
// Push block begin pos |
// Push block begin pos |
var blockBegin = curPos + m.index + m[0].indexOf(m[1]); |
var blockBegin = curPos + m.index + m[0].indexOf(m[1]); |
modeInfos.push( |
modeInfos.push( |
{ |
{ |
pos: blockBegin, |
pos: blockBegin, |
modeExt: modeMatchers[i].modeExt, |
modeExt: modeMatchers[i].modeExt, |
modeName: modeMatchers[i].modeName |
modeName: modeMatchers[i].modeName |
}); |
}); |
// Push block end pos |
// Push block end pos |
modeInfos.push( |
modeInfos.push( |
{ |
{ |
pos: blockBegin + m[1].length, |
pos: blockBegin + m[1].length, |
modeExt: modeInfos[0].modeExt, |
modeExt: modeInfos[0].modeExt, |
modeName: modeInfos[0].modeName |
modeName: modeInfos[0].modeName |
}); |
}); |
curPos += m.index + m[0].length; |
curPos += m.index + m[0].length; |
continue; |
continue; |
} |
} |
else { |
else { |
curPos += m.index + Math.max(m[0].length, 1); |
curPos += m.index + Math.max(m[0].length, 1); |
} |
} |
} |
} |
else { // No more matches |
else { // No more matches |
break; |
break; |
} |
} |
} |
} |
} |
} |
// Sort mode infos |
// Sort mode infos |
modeInfos.sort(function sortModeInfo(a, b) { |
modeInfos.sort(function sortModeInfo(a, b) { |
return a.pos - b.pos; |
return a.pos - b.pos; |
}); |
}); |
|
|
return modeInfos; |
return modeInfos; |
}, |
}, |
|
|
autoFormatLineBreaks: function (text, startPos, endPos) { |
autoFormatLineBreaks: function (text, startPos, endPos) { |
var modeInfos = this.getModeInfos(text); |
var modeInfos = this.getModeInfos(text); |
var reBlockStartsWithNewline = new RegExp("^\\s*?\n"); |
var reBlockStartsWithNewline = new RegExp("^\\s*?\n"); |
var reBlockEndsWithNewline = new RegExp("\n\\s*?$"); |
var reBlockEndsWithNewline = new RegExp("\n\\s*?$"); |
var res = ""; |
var res = ""; |
// Use modes info to break lines correspondingly |
// Use modes info to break lines correspondingly |
if (modeInfos.length > 1) { // Deal with multi-mode text |
if (modeInfos.length > 1) { // Deal with multi-mode text |
for (var i = 1; i <= modeInfos.length; i++) { |
for (var i = 1; i <= modeInfos.length; i++) { |
var selStart = modeInfos[i - 1].pos; |
var selStart = modeInfos[i - 1].pos; |
var selEnd = (i < modeInfos.length ? modeInfos[i].pos : endPos); |
var selEnd = (i < modeInfos.length ? modeInfos[i].pos : endPos); |
|
|
if (selStart >= endPos) { // The block starts later than the needed fragment |
if (selStart >= endPos) { // The block starts later than the needed fragment |
break; |
break; |
} |
} |
if (selStart < startPos) { |
if (selStart < startPos) { |
if (selEnd <= startPos) { // The block starts earlier than the needed fragment |
if (selEnd <= startPos) { // The block starts earlier than the needed fragment |
continue; |
continue; |
} |
} |
selStart = startPos; |
selStart = startPos; |
} |
} |
if (selEnd > endPos) { |
if (selEnd > endPos) { |
selEnd = endPos; |
selEnd = endPos; |
} |
} |
var textPortion = text.substring(selStart, selEnd); |
var textPortion = text.substring(selStart, selEnd); |
if (modeInfos[i - 1].modeName != "xml") { // Starting a CSS or JavaScript block |
if (modeInfos[i - 1].modeName != "xml") { // Starting a CSS or JavaScript block |
if (!reBlockStartsWithNewline.test(textPortion) |
if (!reBlockStartsWithNewline.test(textPortion) |
&& selStart > 0) { // The block does not start with a line break |
&& selStart > 0) { // The block does not start with a line break |
textPortion = "\n" + textPortion; |
textPortion = "\n" + textPortion; |
} |
} |
if (!reBlockEndsWithNewline.test(textPortion) |
if (!reBlockEndsWithNewline.test(textPortion) |
&& selEnd < text.length - 1) { // The block does not end with a line break |
&& selEnd < text.length - 1) { // The block does not end with a line break |
textPortion += "\n"; |
textPortion += "\n"; |
} |
} |
} |
} |
res += modeInfos[i - 1].modeExt.autoFormatLineBreaks(textPortion); |
res += modeInfos[i - 1].modeExt.autoFormatLineBreaks(textPortion); |
} |
} |
} |
} |
else { // Single-mode text |
else { // Single-mode text |
res = modeInfos[0].modeExt.autoFormatLineBreaks(text.substring(startPos, endPos)); |
res = modeInfos[0].modeExt.autoFormatLineBreaks(text.substring(startPos, endPos)); |
} |
} |
|
|
return res; |
return res; |
} |
} |
}; |
}; |
|
|