topical media & game development

talk show tell print

mobile-query-three-www-vendor-CodeMirror2-lib-util-formatting.js / js



  // ============== 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 () {
    return CodeMirror.modeExtensions[this.getOption("mode")];
  });
  
  // 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;
    }
  };
  


(C) Æliens 04/09/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.