closebrackets.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. (function(mod) {
  4. if (typeof exports == "object" && typeof module == "object") // CommonJS
  5. mod(require("../../lib/codemirror"));
  6. else if (typeof define == "function" && define.amd) // AMD
  7. define(["../../lib/codemirror"], mod);
  8. else // Plain browser env
  9. mod(CodeMirror);
  10. })(function(CodeMirror) {
  11. var defaults = {
  12. pairs: "()[]{}''\"\"",
  13. triples: "",
  14. explode: "[]{}"
  15. };
  16. var Pos = CodeMirror.Pos;
  17. CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
  18. if (old && old != CodeMirror.Init) {
  19. cm.removeKeyMap(keyMap);
  20. cm.state.closeBrackets = null;
  21. }
  22. if (val) {
  23. ensureBound(getOption(val, "pairs"))
  24. cm.state.closeBrackets = val;
  25. cm.addKeyMap(keyMap);
  26. }
  27. });
  28. function getOption(conf, name) {
  29. if (name == "pairs" && typeof conf == "string") return conf;
  30. if (typeof conf == "object" && conf[name] != null) return conf[name];
  31. return defaults[name];
  32. }
  33. var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
  34. function ensureBound(chars) {
  35. for (var i = 0; i < chars.length; i++) {
  36. var ch = chars.charAt(i), key = "'" + ch + "'"
  37. if (!keyMap[key]) keyMap[key] = handler(ch)
  38. }
  39. }
  40. ensureBound(defaults.pairs + "`")
  41. function handler(ch) {
  42. return function(cm) { return handleChar(cm, ch); };
  43. }
  44. function getConfig(cm) {
  45. var deflt = cm.state.closeBrackets;
  46. if (!deflt || deflt.override) return deflt;
  47. var mode = cm.getModeAt(cm.getCursor());
  48. return mode.closeBrackets || deflt;
  49. }
  50. function handleBackspace(cm) {
  51. var conf = getConfig(cm);
  52. if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
  53. var pairs = getOption(conf, "pairs");
  54. var ranges = cm.listSelections();
  55. for (var i = 0; i < ranges.length; i++) {
  56. if (!ranges[i].empty()) return CodeMirror.Pass;
  57. var around = charsAround(cm, ranges[i].head);
  58. if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
  59. }
  60. for (var i = ranges.length - 1; i >= 0; i--) {
  61. var cur = ranges[i].head;
  62. cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
  63. }
  64. }
  65. function handleEnter(cm) {
  66. var conf = getConfig(cm);
  67. var explode = conf && getOption(conf, "explode");
  68. if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
  69. var ranges = cm.listSelections();
  70. for (var i = 0; i < ranges.length; i++) {
  71. if (!ranges[i].empty()) return CodeMirror.Pass;
  72. var around = charsAround(cm, ranges[i].head);
  73. if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
  74. }
  75. cm.operation(function() {
  76. var linesep = cm.lineSeparator() || "\n";
  77. cm.replaceSelection(linesep + linesep, null);
  78. cm.execCommand("goCharLeft");
  79. ranges = cm.listSelections();
  80. for (var i = 0; i < ranges.length; i++) {
  81. var line = ranges[i].head.line;
  82. cm.indentLine(line, null, true);
  83. cm.indentLine(line + 1, null, true);
  84. }
  85. });
  86. }
  87. function contractSelection(sel) {
  88. var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
  89. return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
  90. head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
  91. }
  92. function handleChar(cm, ch) {
  93. var conf = getConfig(cm);
  94. if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
  95. var pairs = getOption(conf, "pairs");
  96. var pos = pairs.indexOf(ch);
  97. if (pos == -1) return CodeMirror.Pass;
  98. var triples = getOption(conf, "triples");
  99. var identical = pairs.charAt(pos + 1) == ch;
  100. var ranges = cm.listSelections();
  101. var opening = pos % 2 == 0;
  102. var type;
  103. for (var i = 0; i < ranges.length; i++) {
  104. var range = ranges[i], cur = range.head, curType;
  105. var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
  106. if (opening && !range.empty()) {
  107. curType = "surround";
  108. } else if ((identical || !opening) && next == ch) {
  109. if (identical && stringStartsAfter(cm, cur))
  110. curType = "both";
  111. else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
  112. curType = "skipThree";
  113. else
  114. curType = "skip";
  115. } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
  116. cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
  117. if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
  118. curType = "addFour";
  119. } else if (identical) {
  120. var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
  121. if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
  122. else return CodeMirror.Pass;
  123. } else if (opening) {
  124. curType = "both";
  125. } else {
  126. return CodeMirror.Pass;
  127. }
  128. if (!type) type = curType;
  129. else if (type != curType) return CodeMirror.Pass;
  130. }
  131. var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
  132. var right = pos % 2 ? ch : pairs.charAt(pos + 1);
  133. cm.operation(function() {
  134. if (type == "skip") {
  135. cm.execCommand("goCharRight");
  136. } else if (type == "skipThree") {
  137. for (var i = 0; i < 3; i++)
  138. cm.execCommand("goCharRight");
  139. } else if (type == "surround") {
  140. var sels = cm.getSelections();
  141. for (var i = 0; i < sels.length; i++)
  142. sels[i] = left + sels[i] + right;
  143. cm.replaceSelections(sels, "around");
  144. sels = cm.listSelections().slice();
  145. for (var i = 0; i < sels.length; i++)
  146. sels[i] = contractSelection(sels[i]);
  147. cm.setSelections(sels);
  148. } else if (type == "both") {
  149. cm.replaceSelection(left + right, null);
  150. cm.triggerElectric(left + right);
  151. cm.execCommand("goCharLeft");
  152. } else if (type == "addFour") {
  153. cm.replaceSelection(left + left + left + left, "before");
  154. cm.execCommand("goCharRight");
  155. }
  156. });
  157. }
  158. function charsAround(cm, pos) {
  159. var str = cm.getRange(Pos(pos.line, pos.ch - 1),
  160. Pos(pos.line, pos.ch + 1));
  161. return str.length == 2 ? str : null;
  162. }
  163. function stringStartsAfter(cm, pos) {
  164. var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
  165. return /\bstring/.test(token.type) && token.start == pos.ch &&
  166. (pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
  167. }
  168. });