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