You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

202 line
7.0KB

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