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 | "use strict"; |
---|
13 | |
---|
14 | var Pos = CodeMirror.Pos; |
---|
15 | function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } |
---|
16 | |
---|
17 | // Kill 'ring' |
---|
18 | |
---|
19 | var killRing = []; |
---|
20 | function addToRing(str) { |
---|
21 | killRing.push(str); |
---|
22 | if (killRing.length > 50) killRing.shift(); |
---|
23 | } |
---|
24 | function growRingTop(str) { |
---|
25 | if (!killRing.length) return addToRing(str); |
---|
26 | killRing[killRing.length - 1] += str; |
---|
27 | } |
---|
28 | function getFromRing(n) { return killRing[killRing.length - (n ? Math.min(n, 1) : 1)] || ""; } |
---|
29 | function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); } |
---|
30 | |
---|
31 | var lastKill = null; |
---|
32 | |
---|
33 | function kill(cm, from, to, mayGrow, text) { |
---|
34 | if (text == null) text = cm.getRange(from, to); |
---|
35 | |
---|
36 | if (mayGrow && lastKill && lastKill.cm == cm && posEq(from, lastKill.pos) && cm.isClean(lastKill.gen)) |
---|
37 | growRingTop(text); |
---|
38 | else |
---|
39 | addToRing(text); |
---|
40 | cm.replaceRange("", from, to, "+delete"); |
---|
41 | |
---|
42 | if (mayGrow) lastKill = {cm: cm, pos: from, gen: cm.changeGeneration()}; |
---|
43 | else lastKill = null; |
---|
44 | } |
---|
45 | |
---|
46 | // Boundaries of various units |
---|
47 | |
---|
48 | function byChar(cm, pos, dir) { |
---|
49 | return cm.findPosH(pos, dir, "char", true); |
---|
50 | } |
---|
51 | |
---|
52 | function byWord(cm, pos, dir) { |
---|
53 | return cm.findPosH(pos, dir, "word", true); |
---|
54 | } |
---|
55 | |
---|
56 | function byLine(cm, pos, dir) { |
---|
57 | return cm.findPosV(pos, dir, "line", cm.doc.sel.goalColumn); |
---|
58 | } |
---|
59 | |
---|
60 | function byPage(cm, pos, dir) { |
---|
61 | return cm.findPosV(pos, dir, "page", cm.doc.sel.goalColumn); |
---|
62 | } |
---|
63 | |
---|
64 | function byParagraph(cm, pos, dir) { |
---|
65 | var no = pos.line, line = cm.getLine(no); |
---|
66 | var sawText = /\S/.test(dir < 0 ? line.slice(0, pos.ch) : line.slice(pos.ch)); |
---|
67 | var fst = cm.firstLine(), lst = cm.lastLine(); |
---|
68 | for (;;) { |
---|
69 | no += dir; |
---|
70 | if (no < fst || no > lst) |
---|
71 | return cm.clipPos(Pos(no - dir, dir < 0 ? 0 : null)); |
---|
72 | line = cm.getLine(no); |
---|
73 | var hasText = /\S/.test(line); |
---|
74 | if (hasText) sawText = true; |
---|
75 | else if (sawText) return Pos(no, 0); |
---|
76 | } |
---|
77 | } |
---|
78 | |
---|
79 | function bySentence(cm, pos, dir) { |
---|
80 | var line = pos.line, ch = pos.ch; |
---|
81 | var text = cm.getLine(pos.line), sawWord = false; |
---|
82 | for (;;) { |
---|
83 | var next = text.charAt(ch + (dir < 0 ? -1 : 0)); |
---|
84 | if (!next) { // End/beginning of line reached |
---|
85 | if (line == (dir < 0 ? cm.firstLine() : cm.lastLine())) return Pos(line, ch); |
---|
86 | text = cm.getLine(line + dir); |
---|
87 | if (!/\S/.test(text)) return Pos(line, ch); |
---|
88 | line += dir; |
---|
89 | ch = dir < 0 ? text.length : 0; |
---|
90 | continue; |
---|
91 | } |
---|
92 | if (sawWord && /[!?.]/.test(next)) return Pos(line, ch + (dir > 0 ? 1 : 0)); |
---|
93 | if (!sawWord) sawWord = /\w/.test(next); |
---|
94 | ch += dir; |
---|
95 | } |
---|
96 | } |
---|
97 | |
---|
98 | function byExpr(cm, pos, dir) { |
---|
99 | var wrap; |
---|
100 | if (cm.findMatchingBracket && (wrap = cm.findMatchingBracket(pos, true)) |
---|
101 | && wrap.match && (wrap.forward ? 1 : -1) == dir) |
---|
102 | return dir > 0 ? Pos(wrap.to.line, wrap.to.ch + 1) : wrap.to; |
---|
103 | |
---|
104 | for (var first = true;; first = false) { |
---|
105 | var token = cm.getTokenAt(pos); |
---|
106 | var after = Pos(pos.line, dir < 0 ? token.start : token.end); |
---|
107 | if (first && dir > 0 && token.end == pos.ch || !/\w/.test(token.string)) { |
---|
108 | var newPos = cm.findPosH(after, dir, "char"); |
---|
109 | if (posEq(after, newPos)) return pos; |
---|
110 | else pos = newPos; |
---|
111 | } else { |
---|
112 | return after; |
---|
113 | } |
---|
114 | } |
---|
115 | } |
---|
116 | |
---|
117 | // Prefixes (only crudely supported) |
---|
118 | |
---|
119 | function getPrefix(cm, precise) { |
---|
120 | var digits = cm.state.emacsPrefix; |
---|
121 | if (!digits) return precise ? null : 1; |
---|
122 | clearPrefix(cm); |
---|
123 | return digits == "-" ? -1 : Number(digits); |
---|
124 | } |
---|
125 | |
---|
126 | function repeated(cmd) { |
---|
127 | var f = typeof cmd == "string" ? function(cm) { cm.execCommand(cmd); } : cmd; |
---|
128 | return function(cm) { |
---|
129 | var prefix = getPrefix(cm); |
---|
130 | f(cm); |
---|
131 | for (var i = 1; i < prefix; ++i) f(cm); |
---|
132 | }; |
---|
133 | } |
---|
134 | |
---|
135 | function findEnd(cm, pos, by, dir) { |
---|
136 | var prefix = getPrefix(cm); |
---|
137 | if (prefix < 0) { dir = -dir; prefix = -prefix; } |
---|
138 | for (var i = 0; i < prefix; ++i) { |
---|
139 | var newPos = by(cm, pos, dir); |
---|
140 | if (posEq(newPos, pos)) break; |
---|
141 | pos = newPos; |
---|
142 | } |
---|
143 | return pos; |
---|
144 | } |
---|
145 | |
---|
146 | function move(by, dir) { |
---|
147 | var f = function(cm) { |
---|
148 | cm.extendSelection(findEnd(cm, cm.getCursor(), by, dir)); |
---|
149 | }; |
---|
150 | f.motion = true; |
---|
151 | return f; |
---|
152 | } |
---|
153 | |
---|
154 | function killTo(cm, by, dir) { |
---|
155 | var selections = cm.listSelections(), cursor; |
---|
156 | var i = selections.length; |
---|
157 | while (i--) { |
---|
158 | cursor = selections[i].head; |
---|
159 | kill(cm, cursor, findEnd(cm, cursor, by, dir), true); |
---|
160 | } |
---|
161 | } |
---|
162 | |
---|
163 | function killRegion(cm) { |
---|
164 | if (cm.somethingSelected()) { |
---|
165 | var selections = cm.listSelections(), selection; |
---|
166 | var i = selections.length; |
---|
167 | while (i--) { |
---|
168 | selection = selections[i]; |
---|
169 | kill(cm, selection.anchor, selection.head); |
---|
170 | } |
---|
171 | return true; |
---|
172 | } |
---|
173 | } |
---|
174 | |
---|
175 | function addPrefix(cm, digit) { |
---|
176 | if (cm.state.emacsPrefix) { |
---|
177 | if (digit != "-") cm.state.emacsPrefix += digit; |
---|
178 | return; |
---|
179 | } |
---|
180 | // Not active yet |
---|
181 | cm.state.emacsPrefix = digit; |
---|
182 | cm.on("keyHandled", maybeClearPrefix); |
---|
183 | cm.on("inputRead", maybeDuplicateInput); |
---|
184 | } |
---|
185 | |
---|
186 | var prefixPreservingKeys = {"Alt-G": true, "Ctrl-X": true, "Ctrl-Q": true, "Ctrl-U": true}; |
---|
187 | |
---|
188 | function maybeClearPrefix(cm, arg) { |
---|
189 | if (!cm.state.emacsPrefixMap && !prefixPreservingKeys.hasOwnProperty(arg)) |
---|
190 | clearPrefix(cm); |
---|
191 | } |
---|
192 | |
---|
193 | function clearPrefix(cm) { |
---|
194 | cm.state.emacsPrefix = null; |
---|
195 | cm.off("keyHandled", maybeClearPrefix); |
---|
196 | cm.off("inputRead", maybeDuplicateInput); |
---|
197 | } |
---|
198 | |
---|
199 | function maybeDuplicateInput(cm, event) { |
---|
200 | var dup = getPrefix(cm); |
---|
201 | if (dup > 1 && event.origin == "+input") { |
---|
202 | var one = event.text.join("\n"), txt = ""; |
---|
203 | for (var i = 1; i < dup; ++i) txt += one; |
---|
204 | cm.replaceSelection(txt); |
---|
205 | } |
---|
206 | } |
---|
207 | |
---|
208 | function addPrefixMap(cm) { |
---|
209 | cm.state.emacsPrefixMap = true; |
---|
210 | cm.addKeyMap(prefixMap); |
---|
211 | cm.on("keyHandled", maybeRemovePrefixMap); |
---|
212 | cm.on("inputRead", maybeRemovePrefixMap); |
---|
213 | } |
---|
214 | |
---|
215 | function maybeRemovePrefixMap(cm, arg) { |
---|
216 | if (typeof arg == "string" && (/^\d$/.test(arg) || arg == "Ctrl-U")) return; |
---|
217 | cm.removeKeyMap(prefixMap); |
---|
218 | cm.state.emacsPrefixMap = false; |
---|
219 | cm.off("keyHandled", maybeRemovePrefixMap); |
---|
220 | cm.off("inputRead", maybeRemovePrefixMap); |
---|
221 | } |
---|
222 | |
---|
223 | // Utilities |
---|
224 | |
---|
225 | function setMark(cm) { |
---|
226 | cm.setCursor(cm.getCursor()); |
---|
227 | cm.setExtending(!cm.getExtending()); |
---|
228 | cm.on("change", function() { cm.setExtending(false); }); |
---|
229 | } |
---|
230 | |
---|
231 | function clearMark(cm) { |
---|
232 | cm.setExtending(false); |
---|
233 | cm.setCursor(cm.getCursor()); |
---|
234 | } |
---|
235 | |
---|
236 | function getInput(cm, msg, f) { |
---|
237 | if (cm.openDialog) |
---|
238 | cm.openDialog(msg + ": <input type=\"text\" style=\"width: 10em\"/>", f, {bottom: true}); |
---|
239 | else |
---|
240 | f(prompt(msg, "")); |
---|
241 | } |
---|
242 | |
---|
243 | function operateOnWord(cm, op) { |
---|
244 | var start = cm.getCursor(), end = cm.findPosH(start, 1, "word"); |
---|
245 | cm.replaceRange(op(cm.getRange(start, end)), start, end); |
---|
246 | cm.setCursor(end); |
---|
247 | } |
---|
248 | |
---|
249 | function toEnclosingExpr(cm) { |
---|
250 | var pos = cm.getCursor(), line = pos.line, ch = pos.ch; |
---|
251 | var stack = []; |
---|
252 | while (line >= cm.firstLine()) { |
---|
253 | var text = cm.getLine(line); |
---|
254 | for (var i = ch == null ? text.length : ch; i > 0;) { |
---|
255 | var ch = text.charAt(--i); |
---|
256 | if (ch == ")") |
---|
257 | stack.push("("); |
---|
258 | else if (ch == "]") |
---|
259 | stack.push("["); |
---|
260 | else if (ch == "}") |
---|
261 | stack.push("{"); |
---|
262 | else if (/[\(\{\[]/.test(ch) && (!stack.length || stack.pop() != ch)) |
---|
263 | return cm.extendSelection(Pos(line, i)); |
---|
264 | } |
---|
265 | --line; ch = null; |
---|
266 | } |
---|
267 | } |
---|
268 | |
---|
269 | function quit(cm) { |
---|
270 | cm.execCommand("clearSearch"); |
---|
271 | clearMark(cm); |
---|
272 | } |
---|
273 | |
---|
274 | // Actual keymap |
---|
275 | |
---|
276 | var keyMap = CodeMirror.keyMap.emacs = CodeMirror.normalizeKeyMap({ |
---|
277 | "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));}, |
---|
278 | "Ctrl-K": repeated(function(cm) { |
---|
279 | var start = cm.getCursor(), end = cm.clipPos(Pos(start.line)); |
---|
280 | var text = cm.getRange(start, end); |
---|
281 | if (!/\S/.test(text)) { |
---|
282 | text += "\n"; |
---|
283 | end = Pos(start.line + 1, 0); |
---|
284 | } |
---|
285 | kill(cm, start, end, true, text); |
---|
286 | }), |
---|
287 | "Alt-W": function(cm) { |
---|
288 | addToRing(cm.getSelection()); |
---|
289 | clearMark(cm); |
---|
290 | }, |
---|
291 | "Ctrl-Y": function(cm) { |
---|
292 | var start = cm.getCursor(); |
---|
293 | cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste"); |
---|
294 | cm.setSelection(start, cm.getCursor()); |
---|
295 | }, |
---|
296 | "Alt-Y": function(cm) {cm.replaceSelection(popFromRing(), "around", "paste");}, |
---|
297 | |
---|
298 | "Ctrl-Space": setMark, "Ctrl-Shift-2": setMark, |
---|
299 | |
---|
300 | "Ctrl-F": move(byChar, 1), "Ctrl-B": move(byChar, -1), |
---|
301 | "Right": move(byChar, 1), "Left": move(byChar, -1), |
---|
302 | "Ctrl-D": function(cm) { killTo(cm, byChar, 1); }, |
---|
303 | "Delete": function(cm) { killRegion(cm) || killTo(cm, byChar, 1); }, |
---|
304 | "Ctrl-H": function(cm) { killTo(cm, byChar, -1); }, |
---|
305 | "Backspace": function(cm) { killRegion(cm) || killTo(cm, byChar, -1); }, |
---|
306 | |
---|
307 | "Alt-F": move(byWord, 1), "Alt-B": move(byWord, -1), |
---|
308 | "Alt-D": function(cm) { killTo(cm, byWord, 1); }, |
---|
309 | "Alt-Backspace": function(cm) { killTo(cm, byWord, -1); }, |
---|
310 | |
---|
311 | "Ctrl-N": move(byLine, 1), "Ctrl-P": move(byLine, -1), |
---|
312 | "Down": move(byLine, 1), "Up": move(byLine, -1), |
---|
313 | "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", |
---|
314 | "End": "goLineEnd", "Home": "goLineStart", |
---|
315 | |
---|
316 | "Alt-V": move(byPage, -1), "Ctrl-V": move(byPage, 1), |
---|
317 | "PageUp": move(byPage, -1), "PageDown": move(byPage, 1), |
---|
318 | |
---|
319 | "Ctrl-Up": move(byParagraph, -1), "Ctrl-Down": move(byParagraph, 1), |
---|
320 | |
---|
321 | "Alt-A": move(bySentence, -1), "Alt-E": move(bySentence, 1), |
---|
322 | "Alt-K": function(cm) { killTo(cm, bySentence, 1); }, |
---|
323 | |
---|
324 | "Ctrl-Alt-K": function(cm) { killTo(cm, byExpr, 1); }, |
---|
325 | "Ctrl-Alt-Backspace": function(cm) { killTo(cm, byExpr, -1); }, |
---|
326 | "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1), |
---|
327 | |
---|
328 | "Shift-Ctrl-Alt-2": function(cm) { |
---|
329 | var cursor = cm.getCursor(); |
---|
330 | cm.setSelection(findEnd(cm, cursor, byExpr, 1), cursor); |
---|
331 | }, |
---|
332 | "Ctrl-Alt-T": function(cm) { |
---|
333 | var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1); |
---|
334 | var rightEnd = byExpr(cm, leftEnd, 1), rightStart = byExpr(cm, rightEnd, -1); |
---|
335 | cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) + |
---|
336 | cm.getRange(leftStart, leftEnd), leftStart, rightEnd); |
---|
337 | }, |
---|
338 | "Ctrl-Alt-U": repeated(toEnclosingExpr), |
---|
339 | |
---|
340 | "Alt-Space": function(cm) { |
---|
341 | var pos = cm.getCursor(), from = pos.ch, to = pos.ch, text = cm.getLine(pos.line); |
---|
342 | while (from && /\s/.test(text.charAt(from - 1))) --from; |
---|
343 | while (to < text.length && /\s/.test(text.charAt(to))) ++to; |
---|
344 | cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to)); |
---|
345 | }, |
---|
346 | "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }), |
---|
347 | "Ctrl-T": repeated(function(cm) { |
---|
348 | cm.execCommand("transposeChars"); |
---|
349 | }), |
---|
350 | |
---|
351 | "Alt-C": repeated(function(cm) { |
---|
352 | operateOnWord(cm, function(w) { |
---|
353 | var letter = w.search(/\w/); |
---|
354 | if (letter == -1) return w; |
---|
355 | return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase(); |
---|
356 | }); |
---|
357 | }), |
---|
358 | "Alt-U": repeated(function(cm) { |
---|
359 | operateOnWord(cm, function(w) { return w.toUpperCase(); }); |
---|
360 | }), |
---|
361 | "Alt-L": repeated(function(cm) { |
---|
362 | operateOnWord(cm, function(w) { return w.toLowerCase(); }); |
---|
363 | }), |
---|
364 | |
---|
365 | "Alt-;": "toggleComment", |
---|
366 | |
---|
367 | "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"), |
---|
368 | "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"), |
---|
369 | "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd", |
---|
370 | "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace", |
---|
371 | "Alt-/": "autocomplete", |
---|
372 | "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto", |
---|
373 | |
---|
374 | "Alt-G G": function(cm) { |
---|
375 | var prefix = getPrefix(cm, true); |
---|
376 | if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1); |
---|
377 | |
---|
378 | getInput(cm, "Goto line", function(str) { |
---|
379 | var num; |
---|
380 | if (str && !isNaN(num = Number(str)) && num == num|0 && num > 0) |
---|
381 | cm.setCursor(num - 1); |
---|
382 | }); |
---|
383 | }, |
---|
384 | |
---|
385 | "Ctrl-X Tab": function(cm) { |
---|
386 | cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit")); |
---|
387 | }, |
---|
388 | "Ctrl-X Ctrl-X": function(cm) { |
---|
389 | cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor")); |
---|
390 | }, |
---|
391 | "Ctrl-X Ctrl-S": "save", |
---|
392 | "Ctrl-X Ctrl-W": "save", |
---|
393 | "Ctrl-X S": "saveAll", |
---|
394 | "Ctrl-X F": "open", |
---|
395 | "Ctrl-X U": repeated("undo"), |
---|
396 | "Ctrl-X K": "close", |
---|
397 | "Ctrl-X Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); }, |
---|
398 | "Ctrl-X H": "selectAll", |
---|
399 | |
---|
400 | "Ctrl-Q Tab": repeated("insertTab"), |
---|
401 | "Ctrl-U": addPrefixMap |
---|
402 | }); |
---|
403 | |
---|
404 | var prefixMap = {"Ctrl-G": clearPrefix}; |
---|
405 | function regPrefix(d) { |
---|
406 | prefixMap[d] = function(cm) { addPrefix(cm, d); }; |
---|
407 | keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); }; |
---|
408 | prefixPreservingKeys["Ctrl-" + d] = true; |
---|
409 | } |
---|
410 | for (var i = 0; i < 10; ++i) regPrefix(String(i)); |
---|
411 | regPrefix("-"); |
---|
412 | }); |
---|