emacs.js 13 KB

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