1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/sourceeditor/codemirror/keymap/emacs.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,405 @@ 1.4 +(function(mod) { 1.5 + if (typeof exports == "object" && typeof module == "object") // CommonJS 1.6 + mod(require("../lib/codemirror")); 1.7 + else if (typeof define == "function" && define.amd) // AMD 1.8 + define(["../lib/codemirror"], mod); 1.9 + else // Plain browser env 1.10 + mod(CodeMirror); 1.11 +})(function(CodeMirror) { 1.12 + "use strict"; 1.13 + 1.14 + var Pos = CodeMirror.Pos; 1.15 + function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } 1.16 + 1.17 + // Kill 'ring' 1.18 + 1.19 + var killRing = []; 1.20 + function addToRing(str) { 1.21 + killRing.push(str); 1.22 + if (killRing.length > 50) killRing.shift(); 1.23 + } 1.24 + function growRingTop(str) { 1.25 + if (!killRing.length) return addToRing(str); 1.26 + killRing[killRing.length - 1] += str; 1.27 + } 1.28 + function getFromRing(n) { return killRing[killRing.length - (n ? Math.min(n, 1) : 1)] || ""; } 1.29 + function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); } 1.30 + 1.31 + var lastKill = null; 1.32 + 1.33 + function kill(cm, from, to, mayGrow, text) { 1.34 + if (text == null) text = cm.getRange(from, to); 1.35 + 1.36 + if (mayGrow && lastKill && lastKill.cm == cm && posEq(from, lastKill.pos) && cm.isClean(lastKill.gen)) 1.37 + growRingTop(text); 1.38 + else 1.39 + addToRing(text); 1.40 + cm.replaceRange("", from, to, "+delete"); 1.41 + 1.42 + if (mayGrow) lastKill = {cm: cm, pos: from, gen: cm.changeGeneration()}; 1.43 + else lastKill = null; 1.44 + } 1.45 + 1.46 + // Boundaries of various units 1.47 + 1.48 + function byChar(cm, pos, dir) { 1.49 + return cm.findPosH(pos, dir, "char", true); 1.50 + } 1.51 + 1.52 + function byWord(cm, pos, dir) { 1.53 + return cm.findPosH(pos, dir, "word", true); 1.54 + } 1.55 + 1.56 + function byLine(cm, pos, dir) { 1.57 + return cm.findPosV(pos, dir, "line", cm.doc.sel.goalColumn); 1.58 + } 1.59 + 1.60 + function byPage(cm, pos, dir) { 1.61 + return cm.findPosV(pos, dir, "page", cm.doc.sel.goalColumn); 1.62 + } 1.63 + 1.64 + function byParagraph(cm, pos, dir) { 1.65 + var no = pos.line, line = cm.getLine(no); 1.66 + var sawText = /\S/.test(dir < 0 ? line.slice(0, pos.ch) : line.slice(pos.ch)); 1.67 + var fst = cm.firstLine(), lst = cm.lastLine(); 1.68 + for (;;) { 1.69 + no += dir; 1.70 + if (no < fst || no > lst) 1.71 + return cm.clipPos(Pos(no - dir, dir < 0 ? 0 : null)); 1.72 + line = cm.getLine(no); 1.73 + var hasText = /\S/.test(line); 1.74 + if (hasText) sawText = true; 1.75 + else if (sawText) return Pos(no, 0); 1.76 + } 1.77 + } 1.78 + 1.79 + function bySentence(cm, pos, dir) { 1.80 + var line = pos.line, ch = pos.ch; 1.81 + var text = cm.getLine(pos.line), sawWord = false; 1.82 + for (;;) { 1.83 + var next = text.charAt(ch + (dir < 0 ? -1 : 0)); 1.84 + if (!next) { // End/beginning of line reached 1.85 + if (line == (dir < 0 ? cm.firstLine() : cm.lastLine())) return Pos(line, ch); 1.86 + text = cm.getLine(line + dir); 1.87 + if (!/\S/.test(text)) return Pos(line, ch); 1.88 + line += dir; 1.89 + ch = dir < 0 ? text.length : 0; 1.90 + continue; 1.91 + } 1.92 + if (sawWord && /[!?.]/.test(next)) return Pos(line, ch + (dir > 0 ? 1 : 0)); 1.93 + if (!sawWord) sawWord = /\w/.test(next); 1.94 + ch += dir; 1.95 + } 1.96 + } 1.97 + 1.98 + function byExpr(cm, pos, dir) { 1.99 + var wrap; 1.100 + if (cm.findMatchingBracket && (wrap = cm.findMatchingBracket(pos, true)) 1.101 + && wrap.match && (wrap.forward ? 1 : -1) == dir) 1.102 + return dir > 0 ? Pos(wrap.to.line, wrap.to.ch + 1) : wrap.to; 1.103 + 1.104 + for (var first = true;; first = false) { 1.105 + var token = cm.getTokenAt(pos); 1.106 + var after = Pos(pos.line, dir < 0 ? token.start : token.end); 1.107 + if (first && dir > 0 && token.end == pos.ch || !/\w/.test(token.string)) { 1.108 + var newPos = cm.findPosH(after, dir, "char"); 1.109 + if (posEq(after, newPos)) return pos; 1.110 + else pos = newPos; 1.111 + } else { 1.112 + return after; 1.113 + } 1.114 + } 1.115 + } 1.116 + 1.117 + // Prefixes (only crudely supported) 1.118 + 1.119 + function getPrefix(cm, precise) { 1.120 + var digits = cm.state.emacsPrefix; 1.121 + if (!digits) return precise ? null : 1; 1.122 + clearPrefix(cm); 1.123 + return digits == "-" ? -1 : Number(digits); 1.124 + } 1.125 + 1.126 + function repeated(cmd) { 1.127 + var f = typeof cmd == "string" ? function(cm) { cm.execCommand(cmd); } : cmd; 1.128 + return function(cm) { 1.129 + var prefix = getPrefix(cm); 1.130 + f(cm); 1.131 + for (var i = 1; i < prefix; ++i) f(cm); 1.132 + }; 1.133 + } 1.134 + 1.135 + function findEnd(cm, by, dir) { 1.136 + var pos = cm.getCursor(), prefix = getPrefix(cm); 1.137 + if (prefix < 0) { dir = -dir; prefix = -prefix; } 1.138 + for (var i = 0; i < prefix; ++i) { 1.139 + var newPos = by(cm, pos, dir); 1.140 + if (posEq(newPos, pos)) break; 1.141 + pos = newPos; 1.142 + } 1.143 + return pos; 1.144 + } 1.145 + 1.146 + function move(by, dir) { 1.147 + var f = function(cm) { 1.148 + cm.extendSelection(findEnd(cm, by, dir)); 1.149 + }; 1.150 + f.motion = true; 1.151 + return f; 1.152 + } 1.153 + 1.154 + function killTo(cm, by, dir) { 1.155 + kill(cm, cm.getCursor(), findEnd(cm, by, dir), true); 1.156 + } 1.157 + 1.158 + function addPrefix(cm, digit) { 1.159 + if (cm.state.emacsPrefix) { 1.160 + if (digit != "-") cm.state.emacsPrefix += digit; 1.161 + return; 1.162 + } 1.163 + // Not active yet 1.164 + cm.state.emacsPrefix = digit; 1.165 + cm.on("keyHandled", maybeClearPrefix); 1.166 + cm.on("inputRead", maybeDuplicateInput); 1.167 + } 1.168 + 1.169 + var prefixPreservingKeys = {"Alt-G": true, "Ctrl-X": true, "Ctrl-Q": true, "Ctrl-U": true}; 1.170 + 1.171 + function maybeClearPrefix(cm, arg) { 1.172 + if (!cm.state.emacsPrefixMap && !prefixPreservingKeys.hasOwnProperty(arg)) 1.173 + clearPrefix(cm); 1.174 + } 1.175 + 1.176 + function clearPrefix(cm) { 1.177 + cm.state.emacsPrefix = null; 1.178 + cm.off("keyHandled", maybeClearPrefix); 1.179 + cm.off("inputRead", maybeDuplicateInput); 1.180 + } 1.181 + 1.182 + function maybeDuplicateInput(cm, event) { 1.183 + var dup = getPrefix(cm); 1.184 + if (dup > 1 && event.origin == "+input") { 1.185 + var one = event.text.join("\n"), txt = ""; 1.186 + for (var i = 1; i < dup; ++i) txt += one; 1.187 + cm.replaceSelection(txt); 1.188 + } 1.189 + } 1.190 + 1.191 + function addPrefixMap(cm) { 1.192 + cm.state.emacsPrefixMap = true; 1.193 + cm.addKeyMap(prefixMap); 1.194 + cm.on("keyHandled", maybeRemovePrefixMap); 1.195 + cm.on("inputRead", maybeRemovePrefixMap); 1.196 + } 1.197 + 1.198 + function maybeRemovePrefixMap(cm, arg) { 1.199 + if (typeof arg == "string" && (/^\d$/.test(arg) || arg == "Ctrl-U")) return; 1.200 + cm.removeKeyMap(prefixMap); 1.201 + cm.state.emacsPrefixMap = false; 1.202 + cm.off("keyHandled", maybeRemovePrefixMap); 1.203 + cm.off("inputRead", maybeRemovePrefixMap); 1.204 + } 1.205 + 1.206 + // Utilities 1.207 + 1.208 + function setMark(cm) { 1.209 + cm.setCursor(cm.getCursor()); 1.210 + cm.setExtending(!cm.getExtending()); 1.211 + cm.on("change", function() { cm.setExtending(false); }); 1.212 + } 1.213 + 1.214 + function clearMark(cm) { 1.215 + cm.setExtending(false); 1.216 + cm.setCursor(cm.getCursor()); 1.217 + } 1.218 + 1.219 + function getInput(cm, msg, f) { 1.220 + if (cm.openDialog) 1.221 + cm.openDialog(msg + ": <input type=\"text\" style=\"width: 10em\"/>", f, {bottom: true}); 1.222 + else 1.223 + f(prompt(msg, "")); 1.224 + } 1.225 + 1.226 + function operateOnWord(cm, op) { 1.227 + var start = cm.getCursor(), end = cm.findPosH(start, 1, "word"); 1.228 + cm.replaceRange(op(cm.getRange(start, end)), start, end); 1.229 + cm.setCursor(end); 1.230 + } 1.231 + 1.232 + function toEnclosingExpr(cm) { 1.233 + var pos = cm.getCursor(), line = pos.line, ch = pos.ch; 1.234 + var stack = []; 1.235 + while (line >= cm.firstLine()) { 1.236 + var text = cm.getLine(line); 1.237 + for (var i = ch == null ? text.length : ch; i > 0;) { 1.238 + var ch = text.charAt(--i); 1.239 + if (ch == ")") 1.240 + stack.push("("); 1.241 + else if (ch == "]") 1.242 + stack.push("["); 1.243 + else if (ch == "}") 1.244 + stack.push("{"); 1.245 + else if (/[\(\{\[]/.test(ch) && (!stack.length || stack.pop() != ch)) 1.246 + return cm.extendSelection(Pos(line, i)); 1.247 + } 1.248 + --line; ch = null; 1.249 + } 1.250 + } 1.251 + 1.252 + function quit(cm) { 1.253 + cm.execCommand("clearSearch"); 1.254 + clearMark(cm); 1.255 + } 1.256 + 1.257 + // Actual keymap 1.258 + 1.259 + var keyMap = CodeMirror.keyMap.emacs = { 1.260 + "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));}, 1.261 + "Ctrl-K": repeated(function(cm) { 1.262 + var start = cm.getCursor(), end = cm.clipPos(Pos(start.line)); 1.263 + var text = cm.getRange(start, end); 1.264 + if (!/\S/.test(text)) { 1.265 + text += "\n"; 1.266 + end = Pos(start.line + 1, 0); 1.267 + } 1.268 + kill(cm, start, end, true, text); 1.269 + }), 1.270 + "Alt-W": function(cm) { 1.271 + addToRing(cm.getSelection()); 1.272 + clearMark(cm); 1.273 + }, 1.274 + "Ctrl-Y": function(cm) { 1.275 + var start = cm.getCursor(); 1.276 + cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste"); 1.277 + cm.setSelection(start, cm.getCursor()); 1.278 + }, 1.279 + "Alt-Y": function(cm) {cm.replaceSelection(popFromRing(), "around", "paste");}, 1.280 + 1.281 + "Ctrl-Space": setMark, "Ctrl-Shift-2": setMark, 1.282 + 1.283 + "Ctrl-F": move(byChar, 1), "Ctrl-B": move(byChar, -1), 1.284 + "Right": move(byChar, 1), "Left": move(byChar, -1), 1.285 + "Ctrl-D": function(cm) { killTo(cm, byChar, 1); }, 1.286 + "Delete": function(cm) { killTo(cm, byChar, 1); }, 1.287 + "Ctrl-H": function(cm) { killTo(cm, byChar, -1); }, 1.288 + "Backspace": function(cm) { killTo(cm, byChar, -1); }, 1.289 + 1.290 + "Alt-F": move(byWord, 1), "Alt-B": move(byWord, -1), 1.291 + "Alt-D": function(cm) { killTo(cm, byWord, 1); }, 1.292 + "Alt-Backspace": function(cm) { killTo(cm, byWord, -1); }, 1.293 + 1.294 + "Ctrl-N": move(byLine, 1), "Ctrl-P": move(byLine, -1), 1.295 + "Down": move(byLine, 1), "Up": move(byLine, -1), 1.296 + "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", 1.297 + "End": "goLineEnd", "Home": "goLineStart", 1.298 + 1.299 + "Alt-V": move(byPage, -1), "Ctrl-V": move(byPage, 1), 1.300 + "PageUp": move(byPage, -1), "PageDown": move(byPage, 1), 1.301 + 1.302 + "Ctrl-Up": move(byParagraph, -1), "Ctrl-Down": move(byParagraph, 1), 1.303 + 1.304 + "Alt-A": move(bySentence, -1), "Alt-E": move(bySentence, 1), 1.305 + "Alt-K": function(cm) { killTo(cm, bySentence, 1); }, 1.306 + 1.307 + "Ctrl-Alt-K": function(cm) { killTo(cm, byExpr, 1); }, 1.308 + "Ctrl-Alt-Backspace": function(cm) { killTo(cm, byExpr, -1); }, 1.309 + "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1), 1.310 + 1.311 + "Shift-Ctrl-Alt-2": function(cm) { 1.312 + cm.setSelection(findEnd(cm, byExpr, 1), cm.getCursor()); 1.313 + }, 1.314 + "Ctrl-Alt-T": function(cm) { 1.315 + var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1); 1.316 + var rightEnd = byExpr(cm, leftEnd, 1), rightStart = byExpr(cm, rightEnd, -1); 1.317 + cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) + 1.318 + cm.getRange(leftStart, leftEnd), leftStart, rightEnd); 1.319 + }, 1.320 + "Ctrl-Alt-U": repeated(toEnclosingExpr), 1.321 + 1.322 + "Alt-Space": function(cm) { 1.323 + var pos = cm.getCursor(), from = pos.ch, to = pos.ch, text = cm.getLine(pos.line); 1.324 + while (from && /\s/.test(text.charAt(from - 1))) --from; 1.325 + while (to < text.length && /\s/.test(text.charAt(to))) ++to; 1.326 + cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to)); 1.327 + }, 1.328 + "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }), 1.329 + "Ctrl-T": repeated(function(cm) { 1.330 + var pos = cm.getCursor(); 1.331 + if (pos.ch < cm.getLine(pos.line).length) pos = Pos(pos.line, pos.ch + 1); 1.332 + var from = cm.findPosH(pos, -2, "char"); 1.333 + var range = cm.getRange(from, pos); 1.334 + if (range.length != 2) return; 1.335 + cm.setSelection(from, pos); 1.336 + cm.replaceSelection(range.charAt(1) + range.charAt(0), null, "+transpose"); 1.337 + }), 1.338 + 1.339 + "Alt-C": repeated(function(cm) { 1.340 + operateOnWord(cm, function(w) { 1.341 + var letter = w.search(/\w/); 1.342 + if (letter == -1) return w; 1.343 + return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase(); 1.344 + }); 1.345 + }), 1.346 + "Alt-U": repeated(function(cm) { 1.347 + operateOnWord(cm, function(w) { return w.toUpperCase(); }); 1.348 + }), 1.349 + "Alt-L": repeated(function(cm) { 1.350 + operateOnWord(cm, function(w) { return w.toLowerCase(); }); 1.351 + }), 1.352 + 1.353 + "Alt-;": "toggleComment", 1.354 + 1.355 + "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"), 1.356 + "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"), 1.357 + "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd", 1.358 + "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace", 1.359 + "Alt-/": "autocomplete", 1.360 + "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto", 1.361 + 1.362 + "Alt-G": function(cm) {cm.setOption("keyMap", "emacs-Alt-G");}, 1.363 + "Ctrl-X": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-X");}, 1.364 + "Ctrl-Q": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-Q");}, 1.365 + "Ctrl-U": addPrefixMap 1.366 + }; 1.367 + 1.368 + CodeMirror.keyMap["emacs-Ctrl-X"] = { 1.369 + "Tab": function(cm) { 1.370 + cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit")); 1.371 + }, 1.372 + "Ctrl-X": function(cm) { 1.373 + cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor")); 1.374 + }, 1.375 + 1.376 + "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": repeated("undo"), "K": "close", 1.377 + "Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); }, 1.378 + auto: "emacs", nofallthrough: true, disableInput: true 1.379 + }; 1.380 + 1.381 + CodeMirror.keyMap["emacs-Alt-G"] = { 1.382 + "G": function(cm) { 1.383 + var prefix = getPrefix(cm, true); 1.384 + if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1); 1.385 + 1.386 + getInput(cm, "Goto line", function(str) { 1.387 + var num; 1.388 + if (str && !isNaN(num = Number(str)) && num == num|0 && num > 0) 1.389 + cm.setCursor(num - 1); 1.390 + }); 1.391 + }, 1.392 + auto: "emacs", nofallthrough: true, disableInput: true 1.393 + }; 1.394 + 1.395 + CodeMirror.keyMap["emacs-Ctrl-Q"] = { 1.396 + "Tab": repeated("insertTab"), 1.397 + auto: "emacs", nofallthrough: true 1.398 + }; 1.399 + 1.400 + var prefixMap = {"Ctrl-G": clearPrefix}; 1.401 + function regPrefix(d) { 1.402 + prefixMap[d] = function(cm) { addPrefix(cm, d); }; 1.403 + keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); }; 1.404 + prefixPreservingKeys["Ctrl-" + d] = true; 1.405 + } 1.406 + for (var i = 0; i < 10; ++i) regPrefix(String(i)); 1.407 + regPrefix("-"); 1.408 +});