You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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