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