emacs.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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, by, dir) {
  115. var pos = cm.getCursor(), 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, by, dir));
  127. };
  128. f.motion = true;
  129. return f;
  130. }
  131. function killTo(cm, by, dir) {
  132. kill(cm, cm.getCursor(), findEnd(cm, by, dir), true);
  133. }
  134. function addPrefix(cm, digit) {
  135. if (cm.state.emacsPrefix) {
  136. if (digit != "-") cm.state.emacsPrefix += digit;
  137. return;
  138. }
  139. // Not active yet
  140. cm.state.emacsPrefix = digit;
  141. cm.on("keyHandled", maybeClearPrefix);
  142. cm.on("inputRead", maybeDuplicateInput);
  143. }
  144. var prefixPreservingKeys = {"Alt-G": true, "Ctrl-X": true, "Ctrl-Q": true, "Ctrl-U": true};
  145. function maybeClearPrefix(cm, arg) {
  146. if (!cm.state.emacsPrefixMap && !prefixPreservingKeys.hasOwnProperty(arg))
  147. clearPrefix(cm);
  148. }
  149. function clearPrefix(cm) {
  150. cm.state.emacsPrefix = null;
  151. cm.off("keyHandled", maybeClearPrefix);
  152. cm.off("inputRead", maybeDuplicateInput);
  153. }
  154. function maybeDuplicateInput(cm, event) {
  155. var dup = getPrefix(cm);
  156. if (dup > 1 && event.origin == "+input") {
  157. var one = event.text.join("\n"), txt = "";
  158. for (var i = 1; i < dup; ++i) txt += one;
  159. cm.replaceSelection(txt);
  160. }
  161. }
  162. function addPrefixMap(cm) {
  163. cm.state.emacsPrefixMap = true;
  164. cm.addKeyMap(prefixMap);
  165. cm.on("keyHandled", maybeRemovePrefixMap);
  166. cm.on("inputRead", maybeRemovePrefixMap);
  167. }
  168. function maybeRemovePrefixMap(cm, arg) {
  169. if (typeof arg == "string" && (/^\d$/.test(arg) || arg == "Ctrl-U")) return;
  170. cm.removeKeyMap(prefixMap);
  171. cm.state.emacsPrefixMap = false;
  172. cm.off("keyHandled", maybeRemovePrefixMap);
  173. cm.off("inputRead", maybeRemovePrefixMap);
  174. }
  175. // Utilities
  176. function setMark(cm) {
  177. cm.setCursor(cm.getCursor());
  178. cm.setExtending(!cm.getExtending());
  179. cm.on("change", function() { cm.setExtending(false); });
  180. }
  181. function clearMark(cm) {
  182. cm.setExtending(false);
  183. cm.setCursor(cm.getCursor());
  184. }
  185. function getInput(cm, msg, f) {
  186. if (cm.openDialog)
  187. cm.openDialog(msg + ": <input type=\"text\" style=\"width: 10em\"/>", f, {bottom: true});
  188. else
  189. f(prompt(msg, ""));
  190. }
  191. function operateOnWord(cm, op) {
  192. var start = cm.getCursor(), end = cm.findPosH(start, 1, "word");
  193. cm.replaceRange(op(cm.getRange(start, end)), start, end);
  194. cm.setCursor(end);
  195. }
  196. function toEnclosingExpr(cm) {
  197. var pos = cm.getCursor(), line = pos.line, ch = pos.ch;
  198. var stack = [];
  199. while (line >= cm.firstLine()) {
  200. var text = cm.getLine(line);
  201. for (var i = ch == null ? text.length : ch; i > 0;) {
  202. var ch = text.charAt(--i);
  203. if (ch == ")")
  204. stack.push("(");
  205. else if (ch == "]")
  206. stack.push("[");
  207. else if (ch == "}")
  208. stack.push("{");
  209. else if (/[\(\{\[]/.test(ch) && (!stack.length || stack.pop() != ch))
  210. return cm.extendSelection(Pos(line, i));
  211. }
  212. --line; ch = null;
  213. }
  214. }
  215. function quit(cm) {
  216. cm.execCommand("clearSearch");
  217. clearMark(cm);
  218. }
  219. // Actual keymap
  220. var keyMap = CodeMirror.keyMap.emacs = CodeMirror.normalizeKeyMap({
  221. "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));},
  222. "Ctrl-K": repeated(function(cm) {
  223. var start = cm.getCursor(), end = cm.clipPos(Pos(start.line));
  224. var text = cm.getRange(start, end);
  225. if (!/\S/.test(text)) {
  226. text += "\n";
  227. end = Pos(start.line + 1, 0);
  228. }
  229. kill(cm, start, end, true, text);
  230. }),
  231. "Alt-W": function(cm) {
  232. addToRing(cm.getSelection());
  233. clearMark(cm);
  234. },
  235. "Ctrl-Y": function(cm) {
  236. var start = cm.getCursor();
  237. cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste");
  238. cm.setSelection(start, cm.getCursor());
  239. },
  240. "Alt-Y": function(cm) {cm.replaceSelection(popFromRing(), "around", "paste");},
  241. "Ctrl-Space": setMark, "Ctrl-Shift-2": setMark,
  242. "Ctrl-F": move(byChar, 1), "Ctrl-B": move(byChar, -1),
  243. "Right": move(byChar, 1), "Left": move(byChar, -1),
  244. "Ctrl-D": function(cm) { killTo(cm, byChar, 1); },
  245. "Delete": function(cm) { killTo(cm, byChar, 1); },
  246. "Ctrl-H": function(cm) { killTo(cm, byChar, -1); },
  247. "Backspace": function(cm) { killTo(cm, byChar, -1); },
  248. "Alt-F": move(byWord, 1), "Alt-B": move(byWord, -1),
  249. "Alt-D": function(cm) { killTo(cm, byWord, 1); },
  250. "Alt-Backspace": function(cm) { killTo(cm, byWord, -1); },
  251. "Ctrl-N": move(byLine, 1), "Ctrl-P": move(byLine, -1),
  252. "Down": move(byLine, 1), "Up": move(byLine, -1),
  253. "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
  254. "End": "goLineEnd", "Home": "goLineStart",
  255. "Alt-V": move(byPage, -1), "Ctrl-V": move(byPage, 1),
  256. "PageUp": move(byPage, -1), "PageDown": move(byPage, 1),
  257. "Ctrl-Up": move(byParagraph, -1), "Ctrl-Down": move(byParagraph, 1),
  258. "Alt-A": move(bySentence, -1), "Alt-E": move(bySentence, 1),
  259. "Alt-K": function(cm) { killTo(cm, bySentence, 1); },
  260. "Ctrl-Alt-K": function(cm) { killTo(cm, byExpr, 1); },
  261. "Ctrl-Alt-Backspace": function(cm) { killTo(cm, byExpr, -1); },
  262. "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1),
  263. "Shift-Ctrl-Alt-2": function(cm) {
  264. cm.setSelection(findEnd(cm, byExpr, 1), cm.getCursor());
  265. },
  266. "Ctrl-Alt-T": function(cm) {
  267. var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1);
  268. var rightEnd = byExpr(cm, leftEnd, 1), rightStart = byExpr(cm, rightEnd, -1);
  269. cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) +
  270. cm.getRange(leftStart, leftEnd), leftStart, rightEnd);
  271. },
  272. "Ctrl-Alt-U": repeated(toEnclosingExpr),
  273. "Alt-Space": function(cm) {
  274. var pos = cm.getCursor(), from = pos.ch, to = pos.ch, text = cm.getLine(pos.line);
  275. while (from && /\s/.test(text.charAt(from - 1))) --from;
  276. while (to < text.length && /\s/.test(text.charAt(to))) ++to;
  277. cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to));
  278. },
  279. "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }),
  280. "Ctrl-T": repeated(function(cm) {
  281. cm.execCommand("transposeChars");
  282. }),
  283. "Alt-C": repeated(function(cm) {
  284. operateOnWord(cm, function(w) {
  285. var letter = w.search(/\w/);
  286. if (letter == -1) return w;
  287. return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase();
  288. });
  289. }),
  290. "Alt-U": repeated(function(cm) {
  291. operateOnWord(cm, function(w) { return w.toUpperCase(); });
  292. }),
  293. "Alt-L": repeated(function(cm) {
  294. operateOnWord(cm, function(w) { return w.toLowerCase(); });
  295. }),
  296. "Alt-;": "toggleComment",
  297. "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"),
  298. "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"),
  299. "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
  300. "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace",
  301. "Alt-/": "autocomplete",
  302. "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto",
  303. "Alt-G G": function(cm) {
  304. var prefix = getPrefix(cm, true);
  305. if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1);
  306. getInput(cm, "Goto line", function(str) {
  307. var num;
  308. if (str && !isNaN(num = Number(str)) && num == num|0 && num > 0)
  309. cm.setCursor(num - 1);
  310. });
  311. },
  312. "Ctrl-X Tab": function(cm) {
  313. cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit"));
  314. },
  315. "Ctrl-X Ctrl-X": function(cm) {
  316. cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor"));
  317. },
  318. "Ctrl-X Ctrl-S": "save",
  319. "Ctrl-X Ctrl-W": "save",
  320. "Ctrl-X S": "saveAll",
  321. "Ctrl-X F": "open",
  322. "Ctrl-X U": repeated("undo"),
  323. "Ctrl-X K": "close",
  324. "Ctrl-X Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); },
  325. "Ctrl-Q Tab": repeated("insertTab"),
  326. "Ctrl-U": addPrefixMap
  327. });
  328. var prefixMap = {"Ctrl-G": clearPrefix};
  329. function regPrefix(d) {
  330. prefixMap[d] = function(cm) { addPrefix(cm, d); };
  331. keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); };
  332. prefixPreservingKeys["Ctrl-" + d] = true;
  333. }
  334. for (var i = 0; i < 10; ++i) regPrefix(String(i));
  335. regPrefix("-");
  336. });