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