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 | cm.state.closeBrackets = val; |
---|
27 | cm.addKeyMap(keyMap); |
---|
28 | } |
---|
29 | }); |
---|
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 || deflt.override) return deflt; |
---|
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 (identical && stringStartsAfter(cm, cur)) |
---|
120 | curType = "both"; |
---|
121 | else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch) |
---|
122 | curType = "skipThree"; |
---|
123 | else |
---|
124 | curType = "skip"; |
---|
125 | } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 && |
---|
126 | cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch && |
---|
127 | (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) { |
---|
128 | curType = "addFour"; |
---|
129 | } else if (identical) { |
---|
130 | if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both"; |
---|
131 | else return CodeMirror.Pass; |
---|
132 | } else if (opening && (cm.getLine(cur.line).length == cur.ch || |
---|
133 | isClosingBracket(next, pairs) || |
---|
134 | /\s/.test(next))) { |
---|
135 | curType = "both"; |
---|
136 | } else { |
---|
137 | return CodeMirror.Pass; |
---|
138 | } |
---|
139 | if (!type) type = curType; |
---|
140 | else if (type != curType) return CodeMirror.Pass; |
---|
141 | } |
---|
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 | cm.execCommand("goCharRight"); |
---|
148 | } else if (type == "skipThree") { |
---|
149 | for (var i = 0; i < 3; i++) |
---|
150 | cm.execCommand("goCharRight"); |
---|
151 | } else if (type == "surround") { |
---|
152 | var sels = cm.getSelections(); |
---|
153 | for (var i = 0; i < sels.length; i++) |
---|
154 | sels[i] = left + sels[i] + right; |
---|
155 | cm.replaceSelections(sels, "around"); |
---|
156 | sels = cm.listSelections().slice(); |
---|
157 | for (var i = 0; i < sels.length; i++) |
---|
158 | sels[i] = contractSelection(sels[i]); |
---|
159 | cm.setSelections(sels); |
---|
160 | } else if (type == "both") { |
---|
161 | cm.replaceSelection(left + right, null); |
---|
162 | cm.triggerElectric(left + right); |
---|
163 | cm.execCommand("goCharLeft"); |
---|
164 | } else if (type == "addFour") { |
---|
165 | cm.replaceSelection(left + left + left + left, "before"); |
---|
166 | cm.execCommand("goCharRight"); |
---|
167 | } |
---|
168 | }); |
---|
169 | } |
---|
170 | |
---|
171 | function isClosingBracket(ch, pairs) { |
---|
172 | var pos = pairs.lastIndexOf(ch); |
---|
173 | return pos > -1 && pos % 2 == 1; |
---|
174 | } |
---|
175 | |
---|
176 | function charsAround(cm, pos) { |
---|
177 | var str = cm.getRange(Pos(pos.line, pos.ch - 1), |
---|
178 | Pos(pos.line, pos.ch + 1)); |
---|
179 | return str.length == 2 ? str : null; |
---|
180 | } |
---|
181 | |
---|
182 | // Project the token type that will exists after the given char is |
---|
183 | // typed, and use it to determine whether it would cause the start |
---|
184 | // of a string token. |
---|
185 | function enteringString(cm, pos, ch) { |
---|
186 | var line = cm.getLine(pos.line); |
---|
187 | var token = cm.getTokenAt(pos); |
---|
188 | if (/\bstring2?\b/.test(token.type) || stringStartsAfter(cm, pos)) return false; |
---|
189 | var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4); |
---|
190 | stream.pos = stream.start = token.start; |
---|
191 | for (;;) { |
---|
192 | var type1 = cm.getMode().token(stream, token.state); |
---|
193 | if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1); |
---|
194 | stream.start = stream.pos; |
---|
195 | } |
---|
196 | } |
---|
197 | |
---|
198 | function stringStartsAfter(cm, pos) { |
---|
199 | var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1)) |
---|
200 | return /\bstring/.test(token.type) && token.start == pos.ch |
---|
201 | } |
---|
202 | }); |
---|