browser/devtools/sourceeditor/codemirror/keymap/emacs.js

changeset 0
6474c204b198
     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 +});

mercurial