UNPKG

neutrinoscript

Version:
295 lines (271 loc) 10.5 kB
// ============== Formatting extensions ============================ // A common storage for all mode-specific formatting features if (!CodeMirror.modeExtensions) CodeMirror.modeExtensions = {}; // Returns the extension of the editor's current mode CodeMirror.defineExtension('getModeExt', function () { var mname = CodeMirror.resolveMode(this.getOption('mode')).name; var ext = CodeMirror.modeExtensions[mname]; if (!ext) throw new Error('No extensions found for mode ' + mname); return ext; }); // 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 // returns the extension of the editor's current mode. CodeMirror.defineExtension('getModeExtAtPos', function (pos) { var token = this.getTokenAt(pos); if (token && token.state && token.state.mode) return CodeMirror.modeExtensions[token.state.mode == 'html' ? 'htmlmixed' : token.state.mode]; else return this.getModeExt(); }); // Comment/uncomment the specified range CodeMirror.defineExtension('commentRange', function (isComment, from, to) { var curMode = this.getModeExtAtPos(this.getCursor()); if (isComment) { // Comment range var commentedText = this.getRange(from, to); this.replaceRange(curMode.commentStart + this.getRange(from, to) + curMode.commentEnd , from, to); 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); } } else { // Uncomment range var selText = this.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); } this.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 absStart = this.indexFromPos(from); var absEnd = this.indexFromPos(to); // Insert additional line breaks where necessary according to the // mode's syntax var res = this.getModeExt().autoFormatLineBreaks(this.getValue(), absStart, absEnd); var cmInstance = this; // Replace and auto-indent the range this.operation(function () { cmInstance.replaceRange(res, from, to); var startLine = cmInstance.posFromIndex(absStart).line; var endLine = cmInstance.posFromIndex(absStart + res.length).line; for (var i = startLine; i <= endLine; i++) { cmInstance.indentLine(i, 'smart'); } }); }); // Define extensions for a few modes CodeMirror.modeExtensions['css'] = { commentStart: '/*', commentEnd: '*/', wordWrapChars: [';', '\\{', '\\}'], autoFormatLineBreaks: function (text) { return text.replace(new RegExp('(;|\\{|\\})([^\r\n])', 'g'), '$1\n$2'); } }; CodeMirror.modeExtensions['javascript'] = { commentStart: '/*', commentEnd: '*/', wordWrapChars: [';', '\\{', '\\}'], getNonBreakableBlocks: function (text) { var nonBreakableRegexes = [ new RegExp('for\\s*?\\(([\\s\\S]*?)\\)'), new RegExp('\'([\\s\\S]*?)(\'|$)'), new RegExp('"([\\s\\S]*?)("|$)'), new RegExp('//.*([\r\n]|$)') ]; var nonBreakableBlocks = new Array(); 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; }, autoFormatLineBreaks: function (text) { var curPos = 0; var reLinesSplitter = new RegExp('(;|\\{|\\})([^\r\n])', 'g'); var nonBreakableBlocks = this.getNonBreakableBlocks(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 - 1) { res += text.substr(curPos).replace(reLinesSplitter, '$1\n$2'); } return res; } else { return text.replace(reLinesSplitter, '$1\n$2'); } } }; CodeMirror.modeExtensions['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'); } }; CodeMirror.modeExtensions['htmlmixed'] = { commentStart: '<!--', commentEnd: '-->', wordWrapChars: ['>', ';', '\\{', '\\}'], getModeInfos: function (text, absPos) { var modeInfos = new Array(); modeInfos[0] = { pos: 0, modeExt: CodeMirror.modeExtensions['xml'], modeName: 'xml' }; var modeMatchers = new Array(); modeMatchers[0] = { regex: new RegExp('<style[^>]*>([\\s\\S]*?)(</style[^>]*>|$)', 'i'), modeExt: CodeMirror.modeExtensions['css'], modeName: 'css' }; modeMatchers[1] = { regex: new RegExp('<script[^>]*>([\\s\\S]*?)(</script[^>]*>|$)', 'i'), modeExt: CodeMirror.modeExtensions['javascript'], modeName: 'javascript' }; var lastCharPos = (typeof (absPos) !== 'undefined' ? absPos : text.length - 1); // Detect modes for the entire text for (var i = 0; i < modeMatchers.length; i++) { var curPos = 0; while (curPos <= lastCharPos) { var m = text.substr(curPos).match(modeMatchers[i].regex); if (m != null) { if (m.length > 1 && m[1].length > 0) { // Push block begin pos var blockBegin = curPos + m.index + m[0].indexOf(m[1]); modeInfos.push( { pos: blockBegin, modeExt: modeMatchers[i].modeExt, modeName: modeMatchers[i].modeName }); // Push block end pos modeInfos.push( { pos: blockBegin + m[1].length, modeExt: modeInfos[0].modeExt, modeName: modeInfos[0].modeName }); curPos += m.index + m[0].length; continue; } else { curPos += m.index + Math.max(m[0].length, 1); } } else { // No more matches break; } } } // Sort mode infos modeInfos.sort(function sortModeInfo(a, b) { return a.pos - b.pos; }); return modeInfos; }, autoFormatLineBreaks: function (text, startPos, endPos) { var modeInfos = this.getModeInfos(text); var reBlockStartsWithNewline = new RegExp('^\\s*?\n'); var reBlockEndsWithNewline = new RegExp('\n\\s*?$'); var res = ''; // Use modes info to break lines correspondingly if (modeInfos.length > 1) { // Deal with multi-mode text for (var i = 1; i <= modeInfos.length; i++) { var selStart = modeInfos[i - 1].pos; var selEnd = (i < modeInfos.length ? modeInfos[i].pos : endPos); if (selStart >= endPos) { // The block starts later than the needed fragment break; } if (selStart < startPos) { if (selEnd <= startPos) { // The block starts earlier than the needed fragment continue; } selStart = startPos; } if (selEnd > endPos) { selEnd = endPos; } var textPortion = text.substring(selStart, selEnd); if (modeInfos[i - 1].modeName != 'xml') { // Starting a CSS or JavaScript block if (!reBlockStartsWithNewline.test(textPortion) && selStart > 0) { // The block does not start with a line break textPortion = '\n' + textPortion; } if (!reBlockEndsWithNewline.test(textPortion) && selEnd < text.length - 1) { // The block does not end with a line break textPortion += '\n'; } } res += modeInfos[i - 1].modeExt.autoFormatLineBreaks(textPortion); } } else { // Single-mode text res = modeInfos[0].modeExt.autoFormatLineBreaks(text.substring(startPos, endPos)); } return res; } };