1 |
|
2 |
|
3 |
|
4 | (function(mod) {
|
5 | if (typeof exports == "object" && typeof module == "object")
|
6 | mod(require("../lib/codemirror"));
|
7 | else if (typeof define == "function" && define.amd)
|
8 | define(["../lib/codemirror"], mod);
|
9 | else
|
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 |
|
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, ring, text) {
|
34 | if (text == null) text = cm.getRange(from, to);
|
35 |
|
36 | if (ring == "grow" && lastKill && lastKill.cm == cm && posEq(from, lastKill.pos) && cm.isClean(lastKill.gen))
|
37 | growRingTop(text);
|
38 | else if (ring !== false)
|
39 | addToRing(text);
|
40 | cm.replaceRange("", from, to, "+delete");
|
41 |
|
42 | if (ring == "grow") lastKill = {cm: cm, pos: from, gen: cm.changeGeneration()};
|
43 | else lastKill = null;
|
44 | }
|
45 |
|
46 |
|
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) {
|
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, {strict: 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 |
|
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, ring) {
|
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), ring);
|
160 | }
|
161 | }
|
162 |
|
163 | function killRegion(cm, ring) {
|
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, ring);
|
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 |
|
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 |
|
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 | CodeMirror.emacs = {kill: kill, killRegion: killRegion, repeated: repeated};
|
275 |
|
276 |
|
277 |
|
278 | var keyMap = CodeMirror.keyMap.emacs = CodeMirror.normalizeKeyMap({
|
279 | "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"), true);},
|
280 | "Ctrl-K": repeated(function(cm) {
|
281 | var start = cm.getCursor(), end = cm.clipPos(Pos(start.line));
|
282 | var text = cm.getRange(start, end);
|
283 | if (!/\S/.test(text)) {
|
284 | text += "\n";
|
285 | end = Pos(start.line + 1, 0);
|
286 | }
|
287 | kill(cm, start, end, "grow", text);
|
288 | }),
|
289 | "Alt-W": function(cm) {
|
290 | addToRing(cm.getSelection());
|
291 | clearMark(cm);
|
292 | },
|
293 | "Ctrl-Y": function(cm) {
|
294 | var start = cm.getCursor();
|
295 | cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste");
|
296 | cm.setSelection(start, cm.getCursor());
|
297 | },
|
298 | "Alt-Y": function(cm) {cm.replaceSelection(popFromRing(), "around", "paste");},
|
299 |
|
300 | "Ctrl-Space": setMark, "Ctrl-Shift-2": setMark,
|
301 |
|
302 | "Ctrl-F": move(byChar, 1), "Ctrl-B": move(byChar, -1),
|
303 | "Right": move(byChar, 1), "Left": move(byChar, -1),
|
304 | "Ctrl-D": function(cm) { killTo(cm, byChar, 1, false); },
|
305 | "Delete": function(cm) { killRegion(cm, false) || killTo(cm, byChar, 1, false); },
|
306 | "Ctrl-H": function(cm) { killTo(cm, byChar, -1, false); },
|
307 | "Backspace": function(cm) { killRegion(cm, false) || killTo(cm, byChar, -1, false); },
|
308 |
|
309 | "Alt-F": move(byWord, 1), "Alt-B": move(byWord, -1),
|
310 | "Alt-Right": move(byWord, 1), "Alt-Left": move(byWord, -1),
|
311 | "Alt-D": function(cm) { killTo(cm, byWord, 1, "grow"); },
|
312 | "Alt-Backspace": function(cm) { killTo(cm, byWord, -1, "grow"); },
|
313 |
|
314 | "Ctrl-N": move(byLine, 1), "Ctrl-P": move(byLine, -1),
|
315 | "Down": move(byLine, 1), "Up": move(byLine, -1),
|
316 | "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
|
317 | "End": "goLineEnd", "Home": "goLineStart",
|
318 |
|
319 | "Alt-V": move(byPage, -1), "Ctrl-V": move(byPage, 1),
|
320 | "PageUp": move(byPage, -1), "PageDown": move(byPage, 1),
|
321 |
|
322 | "Ctrl-Up": move(byParagraph, -1), "Ctrl-Down": move(byParagraph, 1),
|
323 |
|
324 | "Alt-A": move(bySentence, -1), "Alt-E": move(bySentence, 1),
|
325 | "Alt-K": function(cm) { killTo(cm, bySentence, 1, "grow"); },
|
326 |
|
327 | "Ctrl-Alt-K": function(cm) { killTo(cm, byExpr, 1, "grow"); },
|
328 | "Ctrl-Alt-Backspace": function(cm) { killTo(cm, byExpr, -1, "grow"); },
|
329 | "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1, "grow"),
|
330 |
|
331 | "Shift-Ctrl-Alt-2": function(cm) {
|
332 | var cursor = cm.getCursor();
|
333 | cm.setSelection(findEnd(cm, cursor, byExpr, 1), cursor);
|
334 | },
|
335 | "Ctrl-Alt-T": function(cm) {
|
336 | var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1);
|
337 | var rightEnd = byExpr(cm, leftEnd, 1), rightStart = byExpr(cm, rightEnd, -1);
|
338 | cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) +
|
339 | cm.getRange(leftStart, leftEnd), leftStart, rightEnd);
|
340 | },
|
341 | "Ctrl-Alt-U": repeated(toEnclosingExpr),
|
342 |
|
343 | "Alt-Space": function(cm) {
|
344 | var pos = cm.getCursor(), from = pos.ch, to = pos.ch, text = cm.getLine(pos.line);
|
345 | while (from && /\s/.test(text.charAt(from - 1))) --from;
|
346 | while (to < text.length && /\s/.test(text.charAt(to))) ++to;
|
347 | cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to));
|
348 | },
|
349 | "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }),
|
350 | "Ctrl-T": repeated(function(cm) {
|
351 | cm.execCommand("transposeChars");
|
352 | }),
|
353 |
|
354 | "Alt-C": repeated(function(cm) {
|
355 | operateOnWord(cm, function(w) {
|
356 | var letter = w.search(/\w/);
|
357 | if (letter == -1) return w;
|
358 | return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase();
|
359 | });
|
360 | }),
|
361 | "Alt-U": repeated(function(cm) {
|
362 | operateOnWord(cm, function(w) { return w.toUpperCase(); });
|
363 | }),
|
364 | "Alt-L": repeated(function(cm) {
|
365 | operateOnWord(cm, function(w) { return w.toLowerCase(); });
|
366 | }),
|
367 |
|
368 | "Alt-;": "toggleComment",
|
369 |
|
370 | "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"),
|
371 | "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"),
|
372 | "Shift-Ctrl-Z": "redo",
|
373 | "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
|
374 | "Ctrl-S": "findPersistentNext", "Ctrl-R": "findPersistentPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace",
|
375 | "Alt-/": "autocomplete",
|
376 | "Enter": "newlineAndIndent",
|
377 | "Ctrl-J": repeated(function(cm) { cm.replaceSelection("\n", "end"); }),
|
378 | "Tab": "indentAuto",
|
379 |
|
380 | "Alt-G G": function(cm) {
|
381 | var prefix = getPrefix(cm, true);
|
382 | if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1);
|
383 |
|
384 | getInput(cm, "Goto line", function(str) {
|
385 | var num;
|
386 | if (str && !isNaN(num = Number(str)) && num == (num|0) && num > 0)
|
387 | cm.setCursor(num - 1);
|
388 | });
|
389 | },
|
390 |
|
391 | "Ctrl-X Tab": function(cm) {
|
392 | cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit"));
|
393 | },
|
394 | "Ctrl-X Ctrl-X": function(cm) {
|
395 | cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor"));
|
396 | },
|
397 | "Ctrl-X Ctrl-S": "save",
|
398 | "Ctrl-X Ctrl-W": "save",
|
399 | "Ctrl-X S": "saveAll",
|
400 | "Ctrl-X F": "open",
|
401 | "Ctrl-X U": repeated("undo"),
|
402 | "Ctrl-X K": "close",
|
403 | "Ctrl-X Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), "grow"); },
|
404 | "Ctrl-X H": "selectAll",
|
405 |
|
406 | "Ctrl-Q Tab": repeated("insertTab"),
|
407 | "Ctrl-U": addPrefixMap
|
408 | });
|
409 |
|
410 | var prefixMap = {"Ctrl-G": clearPrefix};
|
411 | function regPrefix(d) {
|
412 | prefixMap[d] = function(cm) { addPrefix(cm, d); };
|
413 | keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); };
|
414 | prefixPreservingKeys["Ctrl-" + d] = true;
|
415 | }
|
416 | for (var i = 0; i < 10; ++i) regPrefix(String(i));
|
417 | regPrefix("-");
|
418 | });
|