emacs.min.js (13660B)
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, 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 // 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, {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 // 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, 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 // 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 CodeMirror.emacs = {kill: kill, killRegion: killRegion, repeated: repeated}; 275 276 // Actual keymap 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-D": function(cm) { killTo(cm, byWord, 1, "grow"); }, 311 "Alt-Backspace": function(cm) { killTo(cm, byWord, -1, "grow"); }, 312 313 "Ctrl-N": move(byLine, 1), "Ctrl-P": move(byLine, -1), 314 "Down": move(byLine, 1), "Up": move(byLine, -1), 315 "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", 316 "End": "goLineEnd", "Home": "goLineStart", 317 318 "Alt-V": move(byPage, -1), "Ctrl-V": move(byPage, 1), 319 "PageUp": move(byPage, -1), "PageDown": move(byPage, 1), 320 321 "Ctrl-Up": move(byParagraph, -1), "Ctrl-Down": move(byParagraph, 1), 322 323 "Alt-A": move(bySentence, -1), "Alt-E": move(bySentence, 1), 324 "Alt-K": function(cm) { killTo(cm, bySentence, 1, "grow"); }, 325 326 "Ctrl-Alt-K": function(cm) { killTo(cm, byExpr, 1, "grow"); }, 327 "Ctrl-Alt-Backspace": function(cm) { killTo(cm, byExpr, -1, "grow"); }, 328 "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1, "grow"), 329 330 "Shift-Ctrl-Alt-2": function(cm) { 331 var cursor = cm.getCursor(); 332 cm.setSelection(findEnd(cm, cursor, byExpr, 1), cursor); 333 }, 334 "Ctrl-Alt-T": function(cm) { 335 var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1); 336 var rightEnd = byExpr(cm, leftEnd, 1), rightStart = byExpr(cm, rightEnd, -1); 337 cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) + 338 cm.getRange(leftStart, leftEnd), leftStart, rightEnd); 339 }, 340 "Ctrl-Alt-U": repeated(toEnclosingExpr), 341 342 "Alt-Space": function(cm) { 343 var pos = cm.getCursor(), from = pos.ch, to = pos.ch, text = cm.getLine(pos.line); 344 while (from && /\s/.test(text.charAt(from - 1))) --from; 345 while (to < text.length && /\s/.test(text.charAt(to))) ++to; 346 cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to)); 347 }, 348 "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }), 349 "Ctrl-T": repeated(function(cm) { 350 cm.execCommand("transposeChars"); 351 }), 352 353 "Alt-C": repeated(function(cm) { 354 operateOnWord(cm, function(w) { 355 var letter = w.search(/\w/); 356 if (letter == -1) return w; 357 return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase(); 358 }); 359 }), 360 "Alt-U": repeated(function(cm) { 361 operateOnWord(cm, function(w) { return w.toUpperCase(); }); 362 }), 363 "Alt-L": repeated(function(cm) { 364 operateOnWord(cm, function(w) { return w.toLowerCase(); }); 365 }), 366 367 "Alt-;": "toggleComment", 368 369 "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"), 370 "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"), 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) { cm.replaceSelection("\n", "end"); }), 376 "Tab": "indentAuto", 377 378 "Alt-G G": function(cm) { 379 var prefix = getPrefix(cm, true); 380 if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1); 381 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 389 "Ctrl-X Tab": function(cm) { 390 cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit")); 391 }, 392 "Ctrl-X Ctrl-X": function(cm) { 393 cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor")); 394 }, 395 "Ctrl-X Ctrl-S": "save", 396 "Ctrl-X Ctrl-W": "save", 397 "Ctrl-X S": "saveAll", 398 "Ctrl-X F": "open", 399 "Ctrl-X U": repeated("undo"), 400 "Ctrl-X K": "close", 401 "Ctrl-X Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), "grow"); }, 402 "Ctrl-X H": "selectAll", 403 404 "Ctrl-Q Tab": repeated("insertTab"), 405 "Ctrl-U": addPrefixMap 406 }); 407 408 var prefixMap = {"Ctrl-G": clearPrefix}; 409 function regPrefix(d) { 410 prefixMap[d] = function(cm) { addPrefix(cm, d); }; 411 keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); }; 412 prefixPreservingKeys["Ctrl-" + d] = true; 413 } 414 for (var i = 0; i < 10; ++i) regPrefix(String(i)); 415 regPrefix("-"); 416 });