text: CodeMirror.defineMode("gfm", function(config, parserConfig) {
    var mdMode = CodeMirror.getMode(config, "markdown");
    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"
    };
  
    // make this lazy so that we don't need to load GFM last
    var getMode = (function () {
      var i, modes = {}, mimes = {}, mime;
  
      var list = CodeMirror.listModes();
      for (i = 0; i < list.length; i++) {
        modes[list[i]] = list[i];
      }
      var mimesList = CodeMirror.listMIMEs();
      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(config, modes[lang]) : null;
      }
    }());
  
    function markdown(stream, state) {
      // intercept fenced code blocks
      if (stream.sol() && stream.match(/^```([\w+#]*)/)) {
        // try switching mode
        state.localMode = getMode(RegExp.$1)
        if (state.localMode)
          state.localState = state.localMode.startState();
  
        state.token = local;
        return 'code';
      }
  
      return mdMode.token(stream, state.mdState);
    }
  
    function local(stream, state) {
      if (stream.sol() && stream.match(/^```/)) {
        state.localMode = state.localState = null;
        state.token = markdown;
        return 'code';
      }
      else if (state.localMode) {
        return state.localMode.token(stream, state.localState);
      } else {
        stream.skipToEnd();
        return 'code';
      }
    }
  
    // custom handleText to prevent emphasis in the middle of a word
    // and add autolinking
    function handleText(stream, mdState) {
      var match;
      if (stream.match(/^\w+:\/\/\S+/)) {
        return 'link';
      }
      if (stream.match(/^[^\[*\\<>` _][^\[*\\<>` ]*[^\[*\\<>` _]/)) {
        return mdMode.getType(mdState);
      }
      if (match = stream.match(/^[^\[*\\<>` ]+/)) {
        var word = match[0];
        if (word[0] === '_' && word[word.length-1] === '_') {
          stream.backUp(word.length);
          return undefined;
        }
        return mdMode.getType(mdState);
      }
      if (stream.eatSpace()) {
        return null;
      }
    }
  
    return {
      startState: function() {
        var mdState = mdMode.startState();
        mdState.text = handleText;
        return {token: markdown, mode: "markdown", mdState: mdState,
                localMode: null, localState: null};
      },
  
      copyState: function(state) {
        return {token: state.token, mode: state.mode, mdState: CodeMirror.copyState(mdMode, state.mdState),
                localMode: state.localMode,
                localState: state.localMode ? CodeMirror.copyState(state.localMode, state.localState) : null};
      },
  
      token: function(stream, state) {
          /* Parse GFM double bracket links */
          if ((ch = stream.peek()) != undefined && ch == '[') {
              stream.next(); // Advance the stream
  
              /* Only handle double bracket links */
              if ((ch = stream.peek()) == undefined || ch != '[') {
                  stream.backUp(1);
                  return state.token(stream, state);
              } 
  
              while ((ch = stream.next()) != undefined && ch != ']') {}
  
              if (ch == ']' && (ch = stream.next()) != undefined && ch == ']') 
                  return 'link';
  
              /* If we did not find the second ']' */
              stream.backUp(1);
          }
  
          /* Match GFM latex formulas, as well as latex formulas within '$' */
          if (stream.match(/^\$[^\$]+\$/)) {
              return "string";
          }
  
          if (stream.match(/^\\\((.*?)\\\)/)) {
              return "string";
          }
  
          if (stream.match(/^\$\$[^\$]+\$\$/)) {
              return "string";
          }
          
          if (stream.match(/^\\\[(.*?)\\\]/)) {
              return "string";
          }
  
          return state.token(stream, state);
      }
    }
  }, "markdown");