text: // CodeMirror, copyright (c) by Marijn Haverbeke and others
  // Distributed under an MIT license: https://codemirror.net/LICENSE
  
  (function (mod) {
      if (typeof exports == "object" && typeof module == "object") // CommonJS
          mod(require("../../lib/codemirror"));
      else if (typeof define == "function" && define.amd) // AMD
          define(["../../lib/codemirror"], mod);
      else // Plain browser env
          mod(CodeMirror);
  })(function (CodeMirror) {
      var defaults = {
          pairs: "()[]{}''\"\"",
          closeBefore: ")]}'\":;>",
          triples: "",
          explode: "[]{}"
      };
  
      var Pos = CodeMirror.Pos;
  
      CodeMirror.defineOption("autoCloseBrackets", false, function (cm, val, old) {
          if (old && old != CodeMirror.Init) {
              cm.removeKeyMap(keyMap);
              cm.state.closeBrackets = null;
          }
          if (val) {
              ensureBound(getOption(val, "pairs"))
              cm.state.closeBrackets = val;
              cm.addKeyMap(keyMap);
          }
      });
  
      function getOption(conf, name) {
          if (name == "pairs" && typeof conf == "string") return conf;
          if (typeof conf == "object" && conf[name] != null) return conf[name];
          return defaults[name];
      }
  
      var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
  
      function ensureBound(chars) {
          for (var i = 0; i < chars.length; i++) {
              var ch = chars.charAt(i), key = "'" + ch + "'"
              if (!keyMap[key]) keyMap[key] = handler(ch)
          }
      }
  
      ensureBound(defaults.pairs + "`")
  
      function handler(ch) {
          return function (cm) {
              return handleChar(cm, ch);
          };
      }
  
      function getConfig(cm) {
          var deflt = cm.state.closeBrackets;
          if (!deflt || deflt.override) return deflt;
          var mode = cm.getModeAt(cm.getCursor());
          return mode.closeBrackets || deflt;
      }
  
      function handleBackspace(cm) {
          var conf = getConfig(cm);
          if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
  
          var pairs = getOption(conf, "pairs");
          var ranges = cm.listSelections();
          for (var i = 0; i < ranges.length; i++) {
              if (!ranges[i].empty()) return CodeMirror.Pass;
              var around = charsAround(cm, ranges[i].head);
              if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
          }
          for (var i = ranges.length - 1; i >= 0; i--) {
              var cur = ranges[i].head;
              cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
          }
      }
  
      function handleEnter(cm) {
          var conf = getConfig(cm);
          var explode = conf && getOption(conf, "explode");
          if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
  
          var ranges = cm.listSelections();
          for (var i = 0; i < ranges.length; i++) {
              if (!ranges[i].empty()) return CodeMirror.Pass;
              var around = charsAround(cm, ranges[i].head);
              if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
          }
          cm.operation(function () {
              var linesep = cm.lineSeparator() || "\n";
              cm.replaceSelection(linesep + linesep, null);
              moveSel(cm, -1)
              ranges = cm.listSelections();
              for (var i = 0; i < ranges.length; i++) {
                  var line = ranges[i].head.line;
                  cm.indentLine(line, null, true);
                  cm.indentLine(line + 1, null, true);
              }
          });
      }
  
      function moveSel(cm, dir) {
          var newRanges = [], ranges = cm.listSelections(), primary = 0
          for (var i = 0; i < ranges.length; i++) {
              var range = ranges[i]
              if (range.head == cm.getCursor()) primary = i
              var pos = range.head.ch || dir > 0 ? {
                  line: range.head.line,
                  ch: range.head.ch + dir
              } : {line: range.head.line - 1}
              newRanges.push({anchor: pos, head: pos})
          }
          cm.setSelections(newRanges, primary)
      }
  
      function contractSelection(sel) {
          var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
          return {
              anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
              head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))
          };
      }
  
      function handleChar(cm, ch) {
          var conf = getConfig(cm);
          if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
  
          var pairs = getOption(conf, "pairs");
          var pos = pairs.indexOf(ch);
          if (pos == -1) return CodeMirror.Pass;
  
          var closeBefore = getOption(conf, "closeBefore");
  
          var triples = getOption(conf, "triples");
  
          var identical = pairs.charAt(pos + 1) == ch;
          var ranges = cm.listSelections();
          var opening = pos % 2 == 0;
  
          var type;
          for (var i = 0; i < ranges.length; i++) {
              var range = ranges[i], cur = range.head, curType;
              var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
              if (opening && !range.empty()) {
                  curType = "surround";
              } else if ((identical || !opening) && next == ch) {
                  if (identical && stringStartsAfter(cm, cur))
                      curType = "both";
                  else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
                      curType = "skipThree";
                  else
                      curType = "skip";
              } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
                  cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
                  if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
                  curType = "addFour";
              } else if (identical) {
                  var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
                  if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
                  else return CodeMirror.Pass;
              } else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
                  curType = "both";
              } else {
                  return CodeMirror.Pass;
              }
              if (!type) type = curType;
              else if (type != curType) return CodeMirror.Pass;
          }
  
          var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
          var right = pos % 2 ? ch : pairs.charAt(pos + 1);
          cm.operation(function () {
              if (type == "skip") {
                  moveSel(cm, 1)
              } else if (type == "skipThree") {
                  moveSel(cm, 3)
              } else if (type == "surround") {
                  var sels = cm.getSelections();
                  for (var i = 0; i < sels.length; i++)
                      sels[i] = left + sels[i] + right;
                  cm.replaceSelections(sels, "around");
                  sels = cm.listSelections().slice();
                  for (var i = 0; i < sels.length; i++)
                      sels[i] = contractSelection(sels[i]);
                  cm.setSelections(sels);
              } else if (type == "both") {
                  cm.replaceSelection(left + right, null);
                  cm.triggerElectric(left + right);
                  moveSel(cm, -1)
              } else if (type == "addFour") {
                  cm.replaceSelection(left + left + left + left, "before");
                  moveSel(cm, 1)
              }
          });
      }
  
      function charsAround(cm, pos) {
          var str = cm.getRange(Pos(pos.line, pos.ch - 1),
              Pos(pos.line, pos.ch + 1));
          return str.length == 2 ? str : null;
      }
  
      function stringStartsAfter(cm, pos) {
          var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
          return /\bstring/.test(token.type) && token.start == pos.ch &&
              (pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
      }
  });
  