text: CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
  
    var htmlFound = CodeMirror.mimeModes.hasOwnProperty("text/html");
    var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? "text/html" : "text/plain");
    var aliases = {
      html: "htmlmixed",
      js: "javascript",
      json: "application/json",
      c: "text/x-csrc",
      "c++": "text/x-c++src",
      java: "text/x-java",
      csharp: "text/x-csharp",
      "c#": "text/x-csharp",
      scala: "text/x-scala"
    };
  
    var getMode = (function () {
      var i, modes = {}, mimes = {}, mime;
  
      var list = [];
      for (var m in CodeMirror.modes)
        if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m);
      for (i = 0; i < list.length; i++) {
        modes[list[i]] = list[i];
      }
      var mimesList = [];
      for (var m in CodeMirror.mimeModes)
        if (CodeMirror.mimeModes.propertyIsEnumerable(m))
          mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]});
      for (i = 0; i < mimesList.length; i++) {
        mime = mimesList[i].mime;
        mimes[mime] = mimesList[i].mime;
      }
  
      for (var a in aliases) {
        if (aliases[a] in modes || aliases[a] in mimes)
          modes[a] = aliases[a];
      }
      
      return function (lang) {
        return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null;
      };
    }());
    
    // Should underscores in words open/close em/strong?
    if (modeCfg.underscoresBreakWords === undefined)
      modeCfg.underscoresBreakWords = true;
    
    // Turn on fenced code blocks? ("```" to start/end)
    if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false;
    
    // Turn on task lists? ("- [ ] " and "- [x] ")
    if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
    
    var codeDepth = 0;
  
    var header   = 'header'
    ,   code     = 'comment'
    ,   quote1   = 'atom'
    ,   quote2   = 'number'
    ,   list1    = 'variable-2'
    ,   list2    = 'variable-3'
    ,   list3    = 'keyword'
    ,   hr       = 'hr'
    ,   image    = 'tag'
    ,   linkinline = 'link'
    ,   linkemail = 'link'
    ,   linktext = 'link'
    ,   linkhref = 'string'
    ,   em       = 'em'
    ,   strong   = 'strong';
  
    var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/
    ,   ulRE = /^[*\-+]\s+/
    ,   olRE = /^[0-9]+\.\s+/
    ,   taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
    ,   headerRE = /^(?:\={1,}|-{1,})$/
    ,   textRE = /^[^!\[\]*_\\<>` "'(]+/;
  
    function switchInline(stream, state, f) {
      state.f = state.inline = f;
      return f(stream, state);
    }
  
    function switchBlock(stream, state, f) {
      state.f = state.block = f;
      return f(stream, state);
    }
  
  
    // Blocks
  
    function blankLine(state) {
      // Reset linkTitle state
      state.linkTitle = false;
      // Reset EM state
      state.em = false;
      // Reset STRONG state
      state.strong = false;
      // Reset state.quote
      state.quote = 0;
      if (!htmlFound && state.f == htmlBlock) {
        state.f = inlineNormal;
        state.block = blockNormal;
      }
      // Mark this line as blank
      state.thisLineHasContent = false;
      return null;
    }
  
    function blockNormal(stream, state) {
      
      var prevLineIsList = (state.list !== false);
      if (state.list !== false && state.indentationDiff >= 0) { // Continued list
        if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
          state.indentation -= state.indentationDiff;
        }
        state.list = null;
      } else if (state.list !== false && state.indentation > 0) {
        state.list = null;
        state.listDepth = Math.floor(state.indentation / 4);
      } else if (state.list !== false) { // No longer a list
        state.list = false;
        state.listDepth = 0;
      }
      
      if (state.indentationDiff >= 4) {
        state.indentation -= 4;
        stream.skipToEnd();
        return code;
      } else if (stream.eatSpace()) {
        return null;
      } else if (stream.peek() === '#' || (state.prevLineHasContent && stream.match(headerRE)) ) {
        state.header = true;
      } else if (stream.eat('>')) {
        state.indentation++;
        state.quote = 1;
        stream.eatSpace();
        while (stream.eat('>')) {
          stream.eatSpace();
          state.quote++;
        }
      } else if (stream.peek() === '[') {
        return switchInline(stream, state, footnoteLink);
      } else if (stream.match(hrRE, true)) {
        return hr;
      } else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, true) || stream.match(olRE, true))) {
        state.indentation += 4;
        state.list = true;
        state.listDepth++;
        if (modeCfg.taskLists && stream.match(taskListRE, false)) {
          state.taskList = true;
        }
      } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) {
        // try switching mode
        state.localMode = getMode(RegExp.$1);
        if (state.localMode) state.localState = state.localMode.startState();
        switchBlock(stream, state, local);
        return code;
      }
      
      return switchInline(stream, state, state.inline);
    }
  
    function htmlBlock(stream, state) {
      var style = htmlMode.token(stream, state.htmlState);
      if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) {
        state.f = inlineNormal;
        state.block = blockNormal;
      }
      if (state.md_inside && stream.current().indexOf(">")!=-1) {
        state.f = inlineNormal;
        state.block = blockNormal;
        state.htmlState.context = undefined;
      }
      return style;
    }
  
    function local(stream, state) {
      if (stream.sol() && stream.match(/^```/, true)) {
        state.localMode = state.localState = null;
        state.f = inlineNormal;
        state.block = blockNormal;
        return code;
      } else if (state.localMode) {
        return state.localMode.token(stream, state.localState);
      } else {
        stream.skipToEnd();
        return code;
      }
    }
  
    // Inline
    function getType(state) {
      var styles = [];
      
      if (state.taskOpen) { return "meta"; }
      if (state.taskClosed) { return "property"; }
      
      if (state.strong) { styles.push(strong); }
      if (state.em) { styles.push(em); }
      
      if (state.linkText) { styles.push(linktext); }
      
      if (state.code) { styles.push(code); }
      
      if (state.header) { styles.push(header); }
      if (state.quote) { styles.push(state.quote % 2 ? quote1 : quote2); }
      if (state.list !== false) {
        var listMod = (state.listDepth - 1) % 3;
        if (!listMod) {
          styles.push(list1);
        } else if (listMod === 1) {
          styles.push(list2);
        } else {
          styles.push(list3);
        }
      }
  
      return styles.length ? styles.join(' ') : null;
    }
  
    function handleText(stream, state) {
      if (stream.match(textRE, true)) {
        return getType(state);
      }
      return undefined;        
    }
  
    function inlineNormal(stream, state) {
      var style = state.text(stream, state);
      if (typeof style !== 'undefined')
        return style;
      
      if (state.list) { // List marker (*, +, -, 1., etc)
        state.list = null;
        return getType(state);
      }
      
      if (state.taskList) {
        var taskOpen = stream.match(taskListRE, true)[1] !== "x";
        if (taskOpen) state.taskOpen = true;
        else state.taskClosed = true;
        state.taskList = false;
        return getType(state);
      }
      
      state.taskOpen = false;
      state.taskClosed = false;
      
      var ch = stream.next();
      
      if (ch === '\\') {
        stream.next();
        return getType(state);
      }
      
      // Matches link titles present on next line
      if (state.linkTitle) {
        state.linkTitle = false;
        var matchCh = ch;
        if (ch === '(') {
          matchCh = ')';
        }
        matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
        var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
        if (stream.match(new RegExp(regex), true)) {
          return linkhref;
        }
      }
      
      // If this block is changed, it may need to be updated in GFM mode
      if (ch === '`') {
        var t = getType(state);
        var before = stream.pos;
        stream.eatWhile('`');
        var difference = 1 + stream.pos - before;
        if (!state.code) {
          codeDepth = difference;
          state.code = true;
          return getType(state);
        } else {
          if (difference === codeDepth) { // Must be exact
            state.code = false;
            return t;
          }
          return getType(state);
        }
      } else if (state.code) {
        return getType(state);
      }
      
      if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
        stream.match(/\[[^\]]*\]/);
        state.inline = state.f = linkHref;
        return image;
      }
  
      if (ch === '[' && stream.match(/.*\](\(| ?\[)/, false)) {
        state.linkText = true;
        return getType(state);
      }
  
      if (ch === ']' && state.linkText) {
        var type = getType(state);
        state.linkText = false;
        state.inline = state.f = linkHref;
        return type;
      }
      
      if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, true)) {
        return switchInline(stream, state, inlineElement(linkinline, '>'));
      }
      
      if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, true)) {
        return switchInline(stream, state, inlineElement(linkemail, '>'));
      }
      
      if (ch === '<' && stream.match(/^\w/, false)) {
        if (stream.string.indexOf(">")!=-1) {
          var atts = stream.string.substring(1,stream.string.indexOf(">"));
          if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) {
            state.md_inside = true;
          }
        }
        stream.backUp(1);
        return switchBlock(stream, state, htmlBlock);
      }
        
      if (ch === '<' && stream.match(/^\/\w*?>/)) {
        state.md_inside = false;
        return "tag";
      }
        
      var ignoreUnderscore = false;
      if (!modeCfg.underscoresBreakWords) {
        if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) {
          var prevPos = stream.pos - 2;
          if (prevPos >= 0) {
            var prevCh = stream.string.charAt(prevPos);
            if (prevCh !== '_' && prevCh.match(/(\w)/, false)) {
              ignoreUnderscore = true;
            }
          }
        }
      }
      var t = getType(state);
      if (ch === '*' || (ch === '_' && !ignoreUnderscore)) {
        if (state.strong === ch && stream.eat(ch)) { // Remove STRONG
          state.strong = false;
          return t;
        } else if (!state.strong && stream.eat(ch)) { // Add STRONG
          state.strong = ch;
          return getType(state);
        } else if (state.em === ch) { // Remove EM
          state.em = false;
          return t;
        } else if (!state.em) { // Add EM
          state.em = ch;
          return getType(state);
        }
      } else if (ch === ' ') {
        if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
          if (stream.peek() === ' ') { // Surrounded by spaces, ignore
            return getType(state);
          } else { // Not surrounded by spaces, back up pointer
            stream.backUp(1);
          }
        }
      }
      
      return getType(state);
    }
  
    function linkHref(stream, state) {
      // Check if space, and return NULL if so (to avoid marking the space)
      if(stream.eatSpace()){
        return null;
      }
      var ch = stream.next();
      if (ch === '(' || ch === '[') {
        return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']'));
      }
      return 'error';
    }
  
    function footnoteLink(stream, state) {
      if (stream.match(/^[^\]]*\]:/, true)) {
        state.f = footnoteUrl;
        return linktext;
      }
      return switchInline(stream, state, inlineNormal);
    }
  
    function footnoteUrl(stream, state) {
      // Check if space, and return NULL if so (to avoid marking the space)
      if(stream.eatSpace()){
        return null;
      }
      // Match URL
      stream.match(/^[^\s]+/, true);
      // Check for link title
      if (stream.peek() === undefined) { // End of line, set flag to check next line
        state.linkTitle = true;
      } else { // More content on line, check if link title
        stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
      }
      state.f = state.inline = inlineNormal;
      return linkhref;
    }
  
    var savedInlineRE = [];
    function inlineRE(endChar) {
      if (!savedInlineRE[endChar]) {
        // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741)
        endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
        // Match any non-endChar, escaped character, as well as the closing 
        // endChar.
        savedInlineRE[endChar] = new RegExp('^(?:[^\\\\]|\\\\.)*?(' + endChar + ')');
      }
      return savedInlineRE[endChar];
    }
  
    function inlineElement(type, endChar, next) {
      next = next || inlineNormal;
      return function(stream, state) {
        stream.match(inlineRE(endChar));
        state.inline = state.f = next;
        return type;
      };
    }
  
    return {
      startState: function() {
        return {
          f: blockNormal,
          
          prevLineHasContent: false,
          thisLineHasContent: false,
          
          block: blockNormal,
          htmlState: CodeMirror.startState(htmlMode),
          indentation: 0,
          
          inline: inlineNormal,
          text: handleText,
          
          linkText: false,
          linkTitle: false,
          em: false,
          strong: false,
          header: false,
          taskList: false,
          list: false,
          listDepth: 0,
          quote: 0
        };
      },
  
      copyState: function(s) {
        return {
          f: s.f,
          
          prevLineHasContent: s.prevLineHasContent,
          thisLineHasContent: s.thisLineHasContent,
          
          block: s.block,
          htmlState: CodeMirror.copyState(htmlMode, s.htmlState),
          indentation: s.indentation,
          
          localMode: s.localMode,
          localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,
            
          inline: s.inline,
          text: s.text,
          linkTitle: s.linkTitle,
          em: s.em,
          strong: s.strong,
          header: s.header,
          taskList: s.taskList,
          list: s.list,
          listDepth: s.listDepth,
          quote: s.quote,
          md_inside: s.md_inside
        };
      },
  
      token: function(stream, state) {
        if (stream.sol()) {
          if (stream.match(/^\s*$/, true)) {
            state.prevLineHasContent = false;
            return blankLine(state);
          } else {
            state.prevLineHasContent = state.thisLineHasContent;
            state.thisLineHasContent = true;
          }
  
          // Reset state.header
          state.header = false;
          
          // Reset state.taskList
          state.taskList = false;
          
          // Reset state.code
          state.code = false;
  
          state.f = state.block;
          var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, '    ').length;
          var difference = Math.floor((indentation - state.indentation) / 4) * 4;
          if (difference > 4) difference = 4;
          var adjustedIndentation = state.indentation + difference;
          state.indentationDiff = adjustedIndentation - state.indentation;
          state.indentation = adjustedIndentation;
          if (indentation > 0) return null;
        }
        return state.f(stream, state);
      },
  
      blankLine: blankLine,
  
      getType: getType
    };
  
  }, "xml");
  
  CodeMirror.defineMIME("text/x-markdown", "markdown");
  