Bug 941725 - Add emacs and vim key bindings to source editor. r=harth r=benvie r=msucan
☠☠ backed out by ad44a7aa67e1 ☠ ☠
authorAnton Kovalyov <anton@mozilla.com>
Tue, 07 Jan 2014 15:02:01 -0800
changeset 162511 f122349d9fbda2ca6d4e27a3cca4f84ee9beeff2
parent 162510 912a0f7924f0918a36a9454a3f0f0edc69806645
child 162512 0f0276174f3735c1413518d1c21f1049775a916a
push id38221
push usercbook@mozilla.com
push dateWed, 08 Jan 2014 12:25:46 +0000
treeherdermozilla-inbound@3a0207da3210 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersharth, benvie, msucan
bugs941725
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 941725 - Add emacs and vim key bindings to source editor. r=harth r=benvie r=msucan
browser/app/profile/firefox.js
browser/devtools/jar.mn
browser/devtools/scratchpad/scratchpad.js
browser/devtools/sourceeditor/codemirror/README
browser/devtools/sourceeditor/codemirror/keymap/emacs.js
browser/devtools/sourceeditor/codemirror/keymap/vim.js
browser/devtools/sourceeditor/editor.js
browser/devtools/sourceeditor/test/browser.ini
browser/devtools/sourceeditor/test/cm_emacs_test.js
browser/devtools/sourceeditor/test/cm_vim_test.js
browser/devtools/sourceeditor/test/codemirror.html
browser/devtools/styleeditor/StyleSheetEditor.jsm
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1226,18 +1226,20 @@ pref("devtools.webconsole.timestampMessa
 pref("devtools.hud.loglimit.network", 200);
 pref("devtools.hud.loglimit.cssparser", 200);
 pref("devtools.hud.loglimit.exception", 200);
 pref("devtools.hud.loglimit.console", 200);
 
 // The developer tools editor configuration:
 // - tabsize: how many spaces to use when a Tab character is displayed.
 // - expandtab: expand Tab characters to spaces.
+// - keymap: which keymap to use (can be 'default', 'emacs' or 'vim')
 pref("devtools.editor.tabsize", 4);
 pref("devtools.editor.expandtab", true);
+pref("devtools.editor.keymap", "default");
 
 // Enable the Font Inspector
 pref("devtools.fontinspector.enabled", true);
 
 // Pref to store the browser version at the time of a telemetry ping for an
 // opened developer tool. This allows us to ping telemetry just once per browser
 // version for each user.
 pref("devtools.telemetry.tools.opened.version", "{}");
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -38,16 +38,18 @@ browser.jar:
     content/browser/devtools/codemirror/activeline.js                  (sourceeditor/codemirror/activeline.js)
     content/browser/devtools/codemirror/matchbrackets.js               (sourceeditor/codemirror/matchbrackets.js)
     content/browser/devtools/codemirror/closebrackets.js               (sourceeditor/codemirror/closebrackets.js)
     content/browser/devtools/codemirror/comment.js                     (sourceeditor/codemirror/comment.js)
     content/browser/devtools/codemirror/searchcursor.js                (sourceeditor/codemirror/search/searchcursor.js)
     content/browser/devtools/codemirror/search.js                      (sourceeditor/codemirror/search/search.js)
     content/browser/devtools/codemirror/dialog.js                      (sourceeditor/codemirror/dialog/dialog.js)
     content/browser/devtools/codemirror/dialog.css                     (sourceeditor/codemirror/dialog/dialog.css)
+    content/browser/devtools/codemirror/emacs.js                       (sourceeditor/codemirror/keymap/emacs.js)
+    content/browser/devtools/codemirror/vim.js                         (sourceeditor/codemirror/keymap/vim.js)
     content/browser/devtools/codemirror/mozilla.css                    (sourceeditor/codemirror/mozilla.css)
     content/browser/devtools/debugger.xul                              (debugger/debugger.xul)
     content/browser/devtools/debugger.css                              (debugger/debugger.css)
     content/browser/devtools/debugger-controller.js                    (debugger/debugger-controller.js)
     content/browser/devtools/debugger-view.js                          (debugger/debugger-view.js)
     content/browser/devtools/debugger-toolbar.js                       (debugger/debugger-toolbar.js)
     content/browser/devtools/debugger-panes.js                         (debugger/debugger-panes.js)
     content/browser/devtools/shadereditor.xul                          (shadereditor/shadereditor.xul)
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -1489,16 +1489,17 @@ var Scratchpad = {
       lineNumbers: true,
       contextMenu: "scratchpad-text-popup"
     });
 
     this.editor.appendTo(document.querySelector("#scratchpad-editor")).then(() => {
       var lines = initialText.split("\n");
 
       this.editor.on("change", this._onChanged);
+      this.editor.on("save", () => this.saveFile());
       this.editor.focus();
       this.editor.setCursor({ line: lines.length, ch: lines.pop().length });
 
       if (state)
         this.dirty = !state.saved;
 
       this.initialized = true;
       this._triggerObservers("Ready");
--- a/browser/devtools/sourceeditor/codemirror/README
+++ b/browser/devtools/sourceeditor/codemirror/README
@@ -34,16 +34,18 @@ below.
 The following files in this directory are licensed according to the contents
 in the LICENSE file:
 
  * codemirror.css
  * codemirror.js
  * comment.js
  * dialog/dialog.css
  * dialog/dialog.js
+ * keymap/emacs.js
+ * keymap/vim.js
  * xml.js
  * css.js
  * javascript.js
  * clike.js
  * matchbrackets.js
  * closebrackets.js
  * search/match-highlighter.js
  * search/search.js
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/codemirror/keymap/emacs.js
@@ -0,0 +1,387 @@
+(function() {
+  "use strict";
+
+  var Pos = CodeMirror.Pos;
+  function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
+
+  // Kill 'ring'
+
+  var killRing = [];
+  function addToRing(str) {
+    killRing.push(str);
+    if (killRing.length > 50) killRing.shift();
+  }
+  function growRingTop(str) {
+    if (!killRing.length) return addToRing(str);
+    killRing[killRing.length - 1] += str;
+  }
+  function getFromRing(n) { return killRing[killRing.length - (n ? Math.min(n, 1) : 1)] || ""; }
+  function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); }
+
+  var lastKill = null;
+
+  function kill(cm, from, to, mayGrow, text) {
+    if (text == null) text = cm.getRange(from, to);
+
+    if (mayGrow && lastKill && lastKill.cm == cm && posEq(from, lastKill.pos) && cm.isClean(lastKill.gen))
+      growRingTop(text);
+    else
+      addToRing(text);
+    cm.replaceRange("", from, to, "+delete");
+
+    if (mayGrow) lastKill = {cm: cm, pos: from, gen: cm.changeGeneration()};
+    else lastKill = null;
+  }
+
+  // Boundaries of various units
+
+  function byChar(cm, pos, dir) {
+    return cm.findPosH(pos, dir, "char", true);
+  }
+
+  function byWord(cm, pos, dir) {
+    return cm.findPosH(pos, dir, "word", true);
+  }
+
+  function byLine(cm, pos, dir) {
+    return cm.findPosV(pos, dir, "line", cm.doc.sel.goalColumn);
+  }
+
+  function byPage(cm, pos, dir) {
+    return cm.findPosV(pos, dir, "page", cm.doc.sel.goalColumn);
+  }
+
+  function byParagraph(cm, pos, dir) {
+    var no = pos.line, line = cm.getLine(no);
+    var sawText = /\S/.test(dir < 0 ? line.slice(0, pos.ch) : line.slice(pos.ch));
+    var fst = cm.firstLine(), lst = cm.lastLine();
+    for (;;) {
+      no += dir;
+      if (no < fst || no > lst)
+        return cm.clipPos(Pos(no - dir, dir < 0 ? 0 : null));
+      line = cm.getLine(no);
+      var hasText = /\S/.test(line);
+      if (hasText) sawText = true;
+      else if (sawText) return Pos(no, 0);
+    }
+  }
+
+  function bySentence(cm, pos, dir) {
+    var line = pos.line, ch = pos.ch;
+    var text = cm.getLine(pos.line), sawWord = false;
+    for (;;) {
+      var next = text.charAt(ch + (dir < 0 ? -1 : 0));
+      if (!next) { // End/beginning of line reached
+        if (line == (dir < 0 ? cm.firstLine() : cm.lastLine())) return Pos(line, ch);
+        text = cm.getLine(line + dir);
+        if (!/\S/.test(text)) return Pos(line, ch);
+        line += dir;
+        ch = dir < 0 ? text.length : 0;
+        continue;
+      }
+      if (sawWord && /[!?.]/.test(next)) return Pos(line, ch + (dir > 0 ? 1 : 0));
+      if (!sawWord) sawWord = /\w/.test(next);
+      ch += dir;
+    }
+  }
+
+  function byExpr(cm, pos, dir) {
+    var wrap;
+    if (cm.findMatchingBracket && (wrap = cm.findMatchingBracket(pos, true))
+        && wrap.match && (wrap.forward ? 1 : -1) == dir)
+      return dir > 0 ? Pos(wrap.to.line, wrap.to.ch + 1) : wrap.to;
+
+    for (var first = true;; first = false) {
+      var token = cm.getTokenAt(pos);
+      var after = Pos(pos.line, dir < 0 ? token.start : token.end);
+      if (first && dir > 0 && token.end == pos.ch || !/\w/.test(token.string)) {
+        var newPos = cm.findPosH(after, dir, "char");
+        if (posEq(after, newPos)) return pos;
+        else pos = newPos;
+      } else {
+        return after;
+      }
+    }
+  }
+
+  // Prefixes (only crudely supported)
+
+  function getPrefix(cm, precise) {
+    var digits = cm.state.emacsPrefix;
+    if (!digits) return precise ? null : 1;
+    clearPrefix(cm);
+    return digits == "-" ? -1 : Number(digits);
+  }
+
+  function repeated(cmd) {
+    var f = typeof cmd == "string" ? function(cm) { cm.execCommand(cmd); } : cmd;
+    return function(cm) {
+      var prefix = getPrefix(cm);
+      f(cm);
+      for (var i = 1; i < prefix; ++i) f(cm);
+    };
+  }
+
+  function findEnd(cm, by, dir) {
+    var pos = cm.getCursor(), prefix = getPrefix(cm);
+    if (prefix < 0) { dir = -dir; prefix = -prefix; }
+    for (var i = 0; i < prefix; ++i) {
+      var newPos = by(cm, pos, dir);
+      if (posEq(newPos, pos)) break;
+      pos = newPos;
+    }
+    return pos;
+  }
+
+  function move(by, dir) {
+    var f = function(cm) {
+      cm.extendSelection(findEnd(cm, by, dir));
+    };
+    f.motion = true;
+    return f;
+  }
+
+  function killTo(cm, by, dir) {
+    kill(cm, cm.getCursor(), findEnd(cm, by, dir), true);
+  }
+
+  function addPrefix(cm, digit) {
+    if (cm.state.emacsPrefix) {
+      if (digit != "-") cm.state.emacsPrefix += digit;
+      return;
+    }
+    // Not active yet
+    cm.state.emacsPrefix = digit;
+    cm.on("keyHandled", maybeClearPrefix);
+    cm.on("inputRead", maybeDuplicateInput);
+  }
+
+  var prefixPreservingKeys = {"Alt-G": true, "Ctrl-X": true, "Ctrl-Q": true, "Ctrl-U": true};
+
+  function maybeClearPrefix(cm, arg) {
+    if (!cm.state.emacsPrefixMap && !prefixPreservingKeys.hasOwnProperty(arg))
+      clearPrefix(cm);
+  }
+
+  function clearPrefix(cm) {
+    cm.state.emacsPrefix = null;
+    cm.off("keyHandled", maybeClearPrefix);
+    cm.off("inputRead", maybeDuplicateInput);
+  }
+
+  function maybeDuplicateInput(cm, event) {
+    var dup = getPrefix(cm);
+    if (dup > 1 && event.origin == "+input") {
+      var one = event.text.join("\n"), txt = "";
+      for (var i = 1; i < dup; ++i) txt += one;
+      cm.replaceSelection(txt, "end", "+input");
+    }
+  }
+
+  function addPrefixMap(cm) {
+    cm.state.emacsPrefixMap = true;
+    cm.addKeyMap(prefixMap);
+    cm.on("keyHandled", maybeRemovePrefixMap);
+    cm.on("inputRead", maybeRemovePrefixMap);
+  }
+
+  function maybeRemovePrefixMap(cm, arg) {
+    if (typeof arg == "string" && (/^\d$/.test(arg) || arg == "Ctrl-U")) return;
+    cm.removeKeyMap(prefixMap);
+    cm.state.emacsPrefixMap = false;
+    cm.off("keyHandled", maybeRemovePrefixMap);
+    cm.off("inputRead", maybeRemovePrefixMap);
+  }
+
+  // Utilities
+
+  function setMark(cm) {
+    cm.setCursor(cm.getCursor());
+    cm.setExtending(true);
+    cm.on("change", function() { cm.setExtending(false); });
+  }
+
+  function getInput(cm, msg, f) {
+    if (cm.openDialog)
+      cm.openDialog(msg + ": <input type=\"text\" style=\"width: 10em\"/>", f, {bottom: true});
+    else
+      f(prompt(msg, ""));
+  }
+
+  function operateOnWord(cm, op) {
+    var start = cm.getCursor(), end = cm.findPosH(start, 1, "word");
+    cm.replaceRange(op(cm.getRange(start, end)), start, end);
+    cm.setCursor(end);
+  }
+
+  function toEnclosingExpr(cm) {
+    var pos = cm.getCursor(), line = pos.line, ch = pos.ch;
+    var stack = [];
+    while (line >= cm.firstLine()) {
+      var text = cm.getLine(line);
+      for (var i = ch == null ? text.length : ch; i > 0;) {
+        var ch = text.charAt(--i);
+        if (ch == ")")
+          stack.push("(");
+        else if (ch == "]")
+          stack.push("[");
+        else if (ch == "}")
+          stack.push("{");
+        else if (/[\(\{\[]/.test(ch) && (!stack.length || stack.pop() != ch))
+          return cm.extendSelection(Pos(line, i));
+      }
+      --line; ch = null;
+    }
+  }
+
+  // Actual keymap
+
+  var keyMap = CodeMirror.keyMap.emacs = {
+    "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));},
+    "Ctrl-K": repeated(function(cm) {
+      var start = cm.getCursor(), end = cm.clipPos(Pos(start.line));
+      var text = cm.getRange(start, end);
+      if (!/\S/.test(text)) {
+        text += "\n";
+        end = Pos(start.line + 1, 0);
+      }
+      kill(cm, start, end, true, text);
+    }),
+    "Alt-W": function(cm) {
+      addToRing(cm.getSelection());
+    },
+    "Ctrl-Y": function(cm) {
+      var start = cm.getCursor();
+      cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste");
+      cm.setSelection(start, cm.getCursor());
+    },
+    "Alt-Y": function(cm) {cm.replaceSelection(popFromRing());},
+
+    "Ctrl-Space": setMark, "Ctrl-Shift-2": setMark,
+
+    "Ctrl-F": move(byChar, 1), "Ctrl-B": move(byChar, -1),
+    "Right": move(byChar, 1), "Left": move(byChar, -1),
+    "Ctrl-D": function(cm) { killTo(cm, byChar, 1); },
+    "Delete": function(cm) { killTo(cm, byChar, 1); },
+    "Ctrl-H": function(cm) { killTo(cm, byChar, -1); },
+    "Backspace": function(cm) { killTo(cm, byChar, -1); },
+
+    "Alt-F": move(byWord, 1), "Alt-B": move(byWord, -1),
+    "Alt-D": function(cm) { killTo(cm, byWord, 1); },
+    "Alt-Backspace": function(cm) { killTo(cm, byWord, -1); },
+
+    "Ctrl-N": move(byLine, 1), "Ctrl-P": move(byLine, -1),
+    "Down": move(byLine, 1), "Up": move(byLine, -1),
+    "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
+    "End": "goLineEnd", "Home": "goLineStart",
+
+    "Alt-V": move(byPage, -1), "Ctrl-V": move(byPage, 1),
+    "PageUp": move(byPage, -1), "PageDown": move(byPage, 1),
+
+    "Ctrl-Up": move(byParagraph, -1), "Ctrl-Down": move(byParagraph, 1),
+
+    "Alt-A": move(bySentence, -1), "Alt-E": move(bySentence, 1),
+    "Alt-K": function(cm) { killTo(cm, bySentence, 1); },
+
+    "Ctrl-Alt-K": function(cm) { killTo(cm, byExpr, 1); },
+    "Ctrl-Alt-Backspace": function(cm) { killTo(cm, byExpr, -1); },
+    "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1),
+
+    "Shift-Ctrl-Alt-2": function(cm) {
+      cm.setSelection(findEnd(cm, byExpr, 1), cm.getCursor());
+    },
+    "Ctrl-Alt-T": function(cm) {
+      var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1);
+      var rightEnd = byExpr(cm, leftEnd, 1), rightStart = byExpr(cm, rightEnd, -1);
+      cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) +
+                      cm.getRange(leftStart, leftEnd), leftStart, rightEnd);
+    },
+    "Ctrl-Alt-U": repeated(toEnclosingExpr),
+
+    "Alt-Space": function(cm) {
+      var pos = cm.getCursor(), from = pos.ch, to = pos.ch, text = cm.getLine(pos.line);
+      while (from && /\s/.test(text.charAt(from - 1))) --from;
+      while (to < text.length && /\s/.test(text.charAt(to))) ++to;
+      cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to));
+    },
+    "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }),
+    "Ctrl-T": repeated(function(cm) {
+      var pos = cm.getCursor();
+      if (pos.ch < cm.getLine(pos.line).length) pos = Pos(pos.line, pos.ch + 1);
+      var from = cm.findPosH(pos, -2, "char");
+      var range = cm.getRange(from, pos);
+      if (range.length != 2) return;
+      cm.setSelection(from, pos);
+      cm.replaceSelection(range.charAt(1) + range.charAt(0), "end");
+    }),
+
+    "Alt-C": repeated(function(cm) {
+      operateOnWord(cm, function(w) {
+        var letter = w.search(/\w/);
+        if (letter == -1) return w;
+        return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase();
+      });
+    }),
+    "Alt-U": repeated(function(cm) {
+      operateOnWord(cm, function(w) { return w.toUpperCase(); });
+    }),
+    "Alt-L": repeated(function(cm) {
+      operateOnWord(cm, function(w) { return w.toLowerCase(); });
+    }),
+
+    "Alt-;": "toggleComment",
+
+    "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"),
+    "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"),
+    "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
+    "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace",
+    "Alt-/": "autocomplete",
+    "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto",
+
+    "Alt-G": function(cm) {cm.setOption("keyMap", "emacs-Alt-G");},
+    "Ctrl-X": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-X");},
+    "Ctrl-Q": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-Q");},
+    "Ctrl-U": addPrefixMap
+  };
+
+  CodeMirror.keyMap["emacs-Ctrl-X"] = {
+    "Tab": function(cm) {
+      cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit"));
+    },
+    "Ctrl-X": function(cm) {
+      cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor"));
+    },
+
+    "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": repeated("undo"), "K": "close",
+    "Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); },
+    auto: "emacs", nofallthrough: true, disableInput: true
+  };
+
+  CodeMirror.keyMap["emacs-Alt-G"] = {
+    "G": function(cm) {
+      var prefix = getPrefix(cm, true);
+      if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1);
+
+      getInput(cm, "Goto line", function(str) {
+        var num;
+        if (str && !isNaN(num = Number(str)) && num == num|0 && num > 0)
+          cm.setCursor(num - 1);
+      });
+    },
+    auto: "emacs", nofallthrough: true, disableInput: true
+  };
+
+  CodeMirror.keyMap["emacs-Ctrl-Q"] = {
+    "Tab": repeated("insertTab"),
+    auto: "emacs", nofallthrough: true
+  };
+
+  var prefixMap = {"Ctrl-G": clearPrefix};
+  function regPrefix(d) {
+    prefixMap[d] = function(cm) { addPrefix(cm, d); };
+    keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); };
+    prefixPreservingKeys["Ctrl-" + d] = true;
+  }
+  for (var i = 0; i < 10; ++i) regPrefix(String(i));
+  regPrefix("-");
+})();
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/codemirror/keymap/vim.js
@@ -0,0 +1,3703 @@
+/**
+ * Supported keybindings:
+ *
+ *   Motion:
+ *   h, j, k, l
+ *   gj, gk
+ *   e, E, w, W, b, B, ge, gE
+ *   f<character>, F<character>, t<character>, T<character>
+ *   $, ^, 0, -, +, _
+ *   gg, G
+ *   %
+ *   '<character>, `<character>
+ *
+ *   Operator:
+ *   d, y, c
+ *   dd, yy, cc
+ *   g~, g~g~
+ *   >, <, >>, <<
+ *
+ *   Operator-Motion:
+ *   x, X, D, Y, C, ~
+ *
+ *   Action:
+ *   a, i, s, A, I, S, o, O
+ *   zz, z., z<CR>, zt, zb, z-
+ *   J
+ *   u, Ctrl-r
+ *   m<character>
+ *   r<character>
+ *
+ *   Modes:
+ *   ESC - leave insert mode, visual mode, and clear input state.
+ *   Ctrl-[, Ctrl-c - same as ESC.
+ *
+ * Registers: unamed, -, a-z, A-Z, 0-9
+ *   (Does not respect the special case for number registers when delete
+ *    operator is made with these commands: %, (, ),  , /, ?, n, N, {, } )
+ *   TODO: Implement the remaining registers.
+ * Marks: a-z, A-Z, and 0-9
+ *   TODO: Implement the remaining special marks. They have more complex
+ *       behavior.
+ *
+ * Events:
+ *  'vim-mode-change' - raised on the editor anytime the current mode changes,
+ *                      Event object: {mode: "visual", subMode: "linewise"}
+ *
+ * Code structure:
+ *  1. Default keymap
+ *  2. Variable declarations and short basic helpers
+ *  3. Instance (External API) implementation
+ *  4. Internal state tracking objects (input state, counter) implementation
+ *     and instanstiation
+ *  5. Key handler (the main command dispatcher) implementation
+ *  6. Motion, operator, and action implementations
+ *  7. Helper functions for the key handler, motions, operators, and actions
+ *  8. Set up Vim to work as a keymap for CodeMirror.
+ */
+
+(function() {
+  'use strict';
+
+  var defaultKeymap = [
+    // Key to key mapping. This goes first to make it possible to override
+    // existing mappings.
+    { keys: ['<Left>'], type: 'keyToKey', toKeys: ['h'] },
+    { keys: ['<Right>'], type: 'keyToKey', toKeys: ['l'] },
+    { keys: ['<Up>'], type: 'keyToKey', toKeys: ['k'] },
+    { keys: ['<Down>'], type: 'keyToKey', toKeys: ['j'] },
+    { keys: ['<Space>'], type: 'keyToKey', toKeys: ['l'] },
+    { keys: ['<BS>'], type: 'keyToKey', toKeys: ['h'] },
+    { keys: ['<C-Space>'], type: 'keyToKey', toKeys: ['W'] },
+    { keys: ['<C-BS>'], type: 'keyToKey', toKeys: ['B'] },
+    { keys: ['<S-Space>'], type: 'keyToKey', toKeys: ['w'] },
+    { keys: ['<S-BS>'], type: 'keyToKey', toKeys: ['b'] },
+    { keys: ['<C-n>'], type: 'keyToKey', toKeys: ['j'] },
+    { keys: ['<C-p>'], type: 'keyToKey', toKeys: ['k'] },
+    { keys: ['C-['], type: 'keyToKey', toKeys: ['<Esc>'] },
+    { keys: ['<C-c>'], type: 'keyToKey', toKeys: ['<Esc>'] },
+    { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'], context: 'normal' },
+    { keys: ['s'], type: 'keyToKey', toKeys: ['x', 'i'], context: 'visual'},
+    { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'], context: 'normal' },
+    { keys: ['S'], type: 'keyToKey', toKeys: ['d', 'c', 'c'], context: 'visual' },
+    { keys: ['<Home>'], type: 'keyToKey', toKeys: ['0'] },
+    { keys: ['<End>'], type: 'keyToKey', toKeys: ['$'] },
+    { keys: ['<PageUp>'], type: 'keyToKey', toKeys: ['<C-b>'] },
+    { keys: ['<PageDown>'], type: 'keyToKey', toKeys: ['<C-f>'] },
+    // Motions
+    { keys: ['H'], type: 'motion',
+        motion: 'moveToTopLine',
+        motionArgs: { linewise: true, toJumplist: true }},
+    { keys: ['M'], type: 'motion',
+        motion: 'moveToMiddleLine',
+        motionArgs: { linewise: true, toJumplist: true }},
+    { keys: ['L'], type: 'motion',
+        motion: 'moveToBottomLine',
+        motionArgs: { linewise: true, toJumplist: true }},
+    { keys: ['h'], type: 'motion',
+        motion: 'moveByCharacters',
+        motionArgs: { forward: false }},
+    { keys: ['l'], type: 'motion',
+        motion: 'moveByCharacters',
+        motionArgs: { forward: true }},
+    { keys: ['j'], type: 'motion',
+        motion: 'moveByLines',
+        motionArgs: { forward: true, linewise: true }},
+    { keys: ['k'], type: 'motion',
+        motion: 'moveByLines',
+        motionArgs: { forward: false, linewise: true }},
+    { keys: ['g','j'], type: 'motion',
+        motion: 'moveByDisplayLines',
+        motionArgs: { forward: true }},
+    { keys: ['g','k'], type: 'motion',
+        motion: 'moveByDisplayLines',
+        motionArgs: { forward: false }},
+    { keys: ['w'], type: 'motion',
+        motion: 'moveByWords',
+        motionArgs: { forward: true, wordEnd: false }},
+    { keys: ['W'], type: 'motion',
+        motion: 'moveByWords',
+        motionArgs: { forward: true, wordEnd: false, bigWord: true }},
+    { keys: ['e'], type: 'motion',
+        motion: 'moveByWords',
+        motionArgs: { forward: true, wordEnd: true, inclusive: true }},
+    { keys: ['E'], type: 'motion',
+        motion: 'moveByWords',
+        motionArgs: { forward: true, wordEnd: true, bigWord: true,
+            inclusive: true }},
+    { keys: ['b'], type: 'motion',
+        motion: 'moveByWords',
+        motionArgs: { forward: false, wordEnd: false }},
+    { keys: ['B'], type: 'motion',
+        motion: 'moveByWords',
+        motionArgs: { forward: false, wordEnd: false, bigWord: true }},
+    { keys: ['g', 'e'], type: 'motion',
+        motion: 'moveByWords',
+        motionArgs: { forward: false, wordEnd: true, inclusive: true }},
+    { keys: ['g', 'E'], type: 'motion',
+        motion: 'moveByWords',
+        motionArgs: { forward: false, wordEnd: true, bigWord: true,
+            inclusive: true }},
+    { keys: ['{'], type: 'motion', motion: 'moveByParagraph',
+        motionArgs: { forward: false, toJumplist: true }},
+    { keys: ['}'], type: 'motion', motion: 'moveByParagraph',
+        motionArgs: { forward: true, toJumplist: true }},
+    { keys: ['<C-f>'], type: 'motion',
+        motion: 'moveByPage', motionArgs: { forward: true }},
+    { keys: ['<C-b>'], type: 'motion',
+        motion: 'moveByPage', motionArgs: { forward: false }},
+    { keys: ['<C-d>'], type: 'motion',
+        motion: 'moveByScroll',
+        motionArgs: { forward: true, explicitRepeat: true }},
+    { keys: ['<C-u>'], type: 'motion',
+        motion: 'moveByScroll',
+        motionArgs: { forward: false, explicitRepeat: true }},
+    { keys: ['g', 'g'], type: 'motion',
+        motion: 'moveToLineOrEdgeOfDocument',
+        motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
+    { keys: ['G'], type: 'motion',
+        motion: 'moveToLineOrEdgeOfDocument',
+        motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
+    { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' },
+    { keys: ['^'], type: 'motion',
+        motion: 'moveToFirstNonWhiteSpaceCharacter' },
+    { keys: ['+'], type: 'motion',
+        motion: 'moveByLines',
+        motionArgs: { forward: true, toFirstChar:true }},
+    { keys: ['-'], type: 'motion',
+        motion: 'moveByLines',
+        motionArgs: { forward: false, toFirstChar:true }},
+    { keys: ['_'], type: 'motion',
+        motion: 'moveByLines',
+        motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
+    { keys: ['$'], type: 'motion',
+        motion: 'moveToEol',
+        motionArgs: { inclusive: true }},
+    { keys: ['%'], type: 'motion',
+        motion: 'moveToMatchedSymbol',
+        motionArgs: { inclusive: true, toJumplist: true }},
+    { keys: ['f', 'character'], type: 'motion',
+        motion: 'moveToCharacter',
+        motionArgs: { forward: true , inclusive: true }},
+    { keys: ['F', 'character'], type: 'motion',
+        motion: 'moveToCharacter',
+        motionArgs: { forward: false }},
+    { keys: ['t', 'character'], type: 'motion',
+        motion: 'moveTillCharacter',
+        motionArgs: { forward: true, inclusive: true }},
+    { keys: ['T', 'character'], type: 'motion',
+        motion: 'moveTillCharacter',
+        motionArgs: { forward: false }},
+    { keys: [';'], type: 'motion', motion: 'repeatLastCharacterSearch',
+        motionArgs: { forward: true }},
+    { keys: [','], type: 'motion', motion: 'repeatLastCharacterSearch',
+        motionArgs: { forward: false }},
+    { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark',
+        motionArgs: {toJumplist: true}},
+    { keys: ['`', 'character'], type: 'motion', motion: 'goToMark',
+        motionArgs: {toJumplist: true}},
+    { keys: [']', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
+    { keys: ['[', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
+    { keys: [']', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
+    { keys: ['[', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
+    { keys: [']', 'character'], type: 'motion',
+        motion: 'moveToSymbol',
+        motionArgs: { forward: true, toJumplist: true}},
+    { keys: ['[', 'character'], type: 'motion',
+        motion: 'moveToSymbol',
+        motionArgs: { forward: false, toJumplist: true}},
+    { keys: ['|'], type: 'motion',
+        motion: 'moveToColumn',
+        motionArgs: { }},
+    // Operators
+    { keys: ['d'], type: 'operator', operator: 'delete' },
+    { keys: ['y'], type: 'operator', operator: 'yank' },
+    { keys: ['c'], type: 'operator', operator: 'change' },
+    { keys: ['>'], type: 'operator', operator: 'indent',
+        operatorArgs: { indentRight: true }},
+    { keys: ['<'], type: 'operator', operator: 'indent',
+        operatorArgs: { indentRight: false }},
+    { keys: ['g', '~'], type: 'operator', operator: 'swapcase' },
+    { keys: ['n'], type: 'motion', motion: 'findNext',
+        motionArgs: { forward: true, toJumplist: true }},
+    { keys: ['N'], type: 'motion', motion: 'findNext',
+        motionArgs: { forward: false, toJumplist: true }},
+    // Operator-Motion dual commands
+    { keys: ['x'], type: 'operatorMotion', operator: 'delete',
+        motion: 'moveByCharacters', motionArgs: { forward: true },
+        operatorMotionArgs: { visualLine: false }},
+    { keys: ['X'], type: 'operatorMotion', operator: 'delete',
+        motion: 'moveByCharacters', motionArgs: { forward: false },
+        operatorMotionArgs: { visualLine: true }},
+    { keys: ['D'], type: 'operatorMotion', operator: 'delete',
+      motion: 'moveToEol', motionArgs: { inclusive: true },
+        operatorMotionArgs: { visualLine: true }},
+    { keys: ['Y'], type: 'operatorMotion', operator: 'yank',
+        motion: 'moveToEol', motionArgs: { inclusive: true },
+        operatorMotionArgs: { visualLine: true }},
+    { keys: ['C'], type: 'operatorMotion',
+        operator: 'change',
+        motion: 'moveToEol', motionArgs: { inclusive: true },
+        operatorMotionArgs: { visualLine: true }},
+    { keys: ['~'], type: 'operatorMotion',
+        operator: 'swapcase', operatorArgs: { shouldMoveCursor: true },
+        motion: 'moveByCharacters', motionArgs: { forward: true }},
+    // Actions
+    { keys: ['<C-i>'], type: 'action', action: 'jumpListWalk',
+        actionArgs: { forward: true }},
+    { keys: ['<C-o>'], type: 'action', action: 'jumpListWalk',
+        actionArgs: { forward: false }},
+    { keys: ['a'], type: 'action', action: 'enterInsertMode', isEdit: true,
+        actionArgs: { insertAt: 'charAfter' }},
+    { keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true,
+        actionArgs: { insertAt: 'eol' }},
+    { keys: ['i'], type: 'action', action: 'enterInsertMode', isEdit: true,
+        actionArgs: { insertAt: 'inplace' }},
+    { keys: ['I'], type: 'action', action: 'enterInsertMode', isEdit: true,
+        actionArgs: { insertAt: 'firstNonBlank' }},
+    { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode',
+        isEdit: true, interlaceInsertRepeat: true,
+        actionArgs: { after: true }},
+    { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode',
+        isEdit: true, interlaceInsertRepeat: true,
+        actionArgs: { after: false }},
+    { keys: ['v'], type: 'action', action: 'toggleVisualMode' },
+    { keys: ['V'], type: 'action', action: 'toggleVisualMode',
+        actionArgs: { linewise: true }},
+    { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true },
+    { keys: ['p'], type: 'action', action: 'paste', isEdit: true,
+        actionArgs: { after: true, isEdit: true }},
+    { keys: ['P'], type: 'action', action: 'paste', isEdit: true,
+        actionArgs: { after: false, isEdit: true }},
+    { keys: ['r', 'character'], type: 'action', action: 'replace', isEdit: true },
+    { keys: ['@', 'character'], type: 'action', action: 'replayMacro' },
+    { keys: ['q', 'character'], type: 'action', action: 'enterMacroRecordMode' },
+    // Handle Replace-mode as a special case of insert mode.
+    { keys: ['R'], type: 'action', action: 'enterInsertMode', isEdit: true,
+        actionArgs: { replace: true }},
+    { keys: ['u'], type: 'action', action: 'undo' },
+    { keys: ['<C-r>'], type: 'action', action: 'redo' },
+    { keys: ['m', 'character'], type: 'action', action: 'setMark' },
+    { keys: ['"', 'character'], type: 'action', action: 'setRegister' },
+    { keys: ['z', 'z'], type: 'action', action: 'scrollToCursor',
+        actionArgs: { position: 'center' }},
+    { keys: ['z', '.'], type: 'action', action: 'scrollToCursor',
+        actionArgs: { position: 'center' },
+        motion: 'moveToFirstNonWhiteSpaceCharacter' },
+    { keys: ['z', 't'], type: 'action', action: 'scrollToCursor',
+        actionArgs: { position: 'top' }},
+    { keys: ['z', '<CR>'], type: 'action', action: 'scrollToCursor',
+        actionArgs: { position: 'top' },
+        motion: 'moveToFirstNonWhiteSpaceCharacter' },
+    { keys: ['z', '-'], type: 'action', action: 'scrollToCursor',
+        actionArgs: { position: 'bottom' }},
+    { keys: ['z', 'b'], type: 'action', action: 'scrollToCursor',
+        actionArgs: { position: 'bottom' },
+        motion: 'moveToFirstNonWhiteSpaceCharacter' },
+    { keys: ['.'], type: 'action', action: 'repeatLastEdit' },
+    { keys: ['<C-a>'], type: 'action', action: 'incrementNumberToken',
+        isEdit: true,
+        actionArgs: {increase: true, backtrack: false}},
+    { keys: ['<C-x>'], type: 'action', action: 'incrementNumberToken',
+        isEdit: true,
+        actionArgs: {increase: false, backtrack: false}},
+    // Text object motions
+    { keys: ['a', 'character'], type: 'motion',
+        motion: 'textObjectManipulation' },
+    { keys: ['i', 'character'], type: 'motion',
+        motion: 'textObjectManipulation',
+        motionArgs: { textObjectInner: true }},
+    // Search
+    { keys: ['/'], type: 'search',
+        searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
+    { keys: ['?'], type: 'search',
+        searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
+    { keys: ['*'], type: 'search',
+        searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
+    { keys: ['#'], type: 'search',
+        searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
+    // Ex command
+    { keys: [':'], type: 'ex' }
+  ];
+
+  var Vim = function() {
+    CodeMirror.defineOption('vimMode', false, function(cm, val) {
+      if (val) {
+        cm.setOption('keyMap', 'vim');
+        CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
+        cm.on('beforeSelectionChange', beforeSelectionChange);
+        maybeInitVimState(cm);
+      } else if (cm.state.vim) {
+        cm.setOption('keyMap', 'default');
+        cm.off('beforeSelectionChange', beforeSelectionChange);
+        cm.state.vim = null;
+      }
+    });
+    function beforeSelectionChange(cm, cur) {
+      var vim = cm.state.vim;
+      if (vim.insertMode || vim.exMode) return;
+
+      var head = cur.head;
+      if (head.ch && head.ch == cm.doc.getLine(head.line).length) {
+        head.ch--;
+      }
+    }
+
+    var numberRegex = /[\d]/;
+    var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)];
+    function makeKeyRange(start, size) {
+      var keys = [];
+      for (var i = start; i < start + size; i++) {
+        keys.push(String.fromCharCode(i));
+      }
+      return keys;
+    }
+    var upperCaseAlphabet = makeKeyRange(65, 26);
+    var lowerCaseAlphabet = makeKeyRange(97, 26);
+    var numbers = makeKeyRange(48, 10);
+    var specialSymbols = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;"\''.split('');
+    var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace',
+        'Esc', 'Home', 'End', 'PageUp', 'PageDown', 'Enter'];
+    var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);
+    var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"']);
+
+    function isLine(cm, line) {
+      return line >= cm.firstLine() && line <= cm.lastLine();
+    }
+    function isLowerCase(k) {
+      return (/^[a-z]$/).test(k);
+    }
+    function isMatchableSymbol(k) {
+      return '()[]{}'.indexOf(k) != -1;
+    }
+    function isNumber(k) {
+      return numberRegex.test(k);
+    }
+    function isUpperCase(k) {
+      return (/^[A-Z]$/).test(k);
+    }
+    function isWhiteSpaceString(k) {
+      return (/^\s*$/).test(k);
+    }
+    function inArray(val, arr) {
+      for (var i = 0; i < arr.length; i++) {
+        if (arr[i] == val) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    var createCircularJumpList = function() {
+      var size = 100;
+      var pointer = -1;
+      var head = 0;
+      var tail = 0;
+      var buffer = new Array(size);
+      function add(cm, oldCur, newCur) {
+        var current = pointer % size;
+        var curMark = buffer[current];
+        function useNextSlot(cursor) {
+          var next = ++pointer % size;
+          var trashMark = buffer[next];
+          if (trashMark) {
+            trashMark.clear();
+          }
+          buffer[next] = cm.setBookmark(cursor);
+        }
+        if (curMark) {
+          var markPos = curMark.find();
+          // avoid recording redundant cursor position
+          if (markPos && !cursorEqual(markPos, oldCur)) {
+            useNextSlot(oldCur);
+          }
+        } else {
+          useNextSlot(oldCur);
+        }
+        useNextSlot(newCur);
+        head = pointer;
+        tail = pointer - size + 1;
+        if (tail < 0) {
+          tail = 0;
+        }
+      }
+      function move(cm, offset) {
+        pointer += offset;
+        if (pointer > head) {
+          pointer = head;
+        } else if (pointer < tail) {
+          pointer = tail;
+        }
+        var mark = buffer[(size + pointer) % size];
+        // skip marks that are temporarily removed from text buffer
+        if (mark && !mark.find()) {
+          var inc = offset > 0 ? 1 : -1;
+          var newCur;
+          var oldCur = cm.getCursor();
+          do {
+            pointer += inc;
+            mark = buffer[(size + pointer) % size];
+            // skip marks that are the same as current position
+            if (mark &&
+                (newCur = mark.find()) &&
+                !cursorEqual(oldCur, newCur)) {
+              break;
+            }
+          } while (pointer < head && pointer > tail);
+        }
+        return mark;
+      }
+      return {
+        cachedCursor: undefined, //used for # and * jumps
+        add: add,
+        move: move
+      };
+    };
+
+    var createMacroState = function() {
+      return {
+        macroKeyBuffer: [],
+        latestRegister: undefined,
+        inReplay: false,
+        lastInsertModeChanges: {
+          changes: [], // Change list
+          expectCursorActivityForChange: false // Set to true on change, false on cursorActivity.
+        },
+        enteredMacroMode: undefined,
+        isMacroPlaying: false,
+        toggle: function(cm, registerName) {
+          if (this.enteredMacroMode) { //onExit
+            this.enteredMacroMode(); // close dialog
+            this.enteredMacroMode = undefined;
+          } else { //onEnter
+            this.latestRegister = registerName;
+            this.enteredMacroMode = cm.openDialog(
+              '(recording)['+registerName+']', null, {bottom:true});
+          }
+        }
+      };
+    };
+
+
+    function maybeInitVimState(cm) {
+      if (!cm.state.vim) {
+        // Store instance state in the CodeMirror object.
+        cm.state.vim = {
+          inputState: new InputState(),
+          // Vim's input state that triggered the last edit, used to repeat
+          // motions and operators with '.'.
+          lastEditInputState: undefined,
+          // Vim's action command before the last edit, used to repeat actions
+          // with '.' and insert mode repeat.
+          lastEditActionCommand: undefined,
+          // When using jk for navigation, if you move from a longer line to a
+          // shorter line, the cursor may clip to the end of the shorter line.
+          // If j is pressed again and cursor goes to the next line, the
+          // cursor should go back to its horizontal position on the longer
+          // line if it can. This is to keep track of the horizontal position.
+          lastHPos: -1,
+          // Doing the same with screen-position for gj/gk
+          lastHSPos: -1,
+          // The last motion command run. Cleared if a non-motion command gets
+          // executed in between.
+          lastMotion: null,
+          marks: {},
+          insertMode: false,
+          // Repeat count for changes made in insert mode, triggered by key
+          // sequences like 3,i. Only exists when insertMode is true.
+          insertModeRepeat: undefined,
+          visualMode: false,
+          // If we are in visual line mode. No effect if visualMode is false.
+          visualLine: false
+        };
+      }
+      return cm.state.vim;
+    }
+    var vimGlobalState;
+    function resetVimGlobalState() {
+      vimGlobalState = {
+        // The current search query.
+        searchQuery: null,
+        // Whether we are searching backwards.
+        searchIsReversed: false,
+        jumpList: createCircularJumpList(),
+        macroModeState: createMacroState(),
+        // Recording latest f, t, F or T motion command.
+        lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''},
+        registerController: new RegisterController({})
+      };
+    }
+
+    var vimApi= {
+      buildKeyMap: function() {
+        // TODO: Convert keymap into dictionary format for fast lookup.
+      },
+      // Testing hook, though it might be useful to expose the register
+      // controller anyways.
+      getRegisterController: function() {
+        return vimGlobalState.registerController;
+      },
+      // Testing hook.
+      resetVimGlobalState_: resetVimGlobalState,
+
+      // Testing hook.
+      getVimGlobalState_: function() {
+        return vimGlobalState;
+      },
+
+      // Testing hook.
+      maybeInitVimState_: maybeInitVimState,
+
+      InsertModeKey: InsertModeKey,
+      map: function(lhs, rhs) {
+        // Add user defined key bindings.
+        exCommandDispatcher.map(lhs, rhs);
+      },
+      defineEx: function(name, prefix, func){
+        if (name.indexOf(prefix) !== 0) {
+          throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered');
+        }
+        exCommands[name]=func;
+        exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
+      },
+      // This is the outermost function called by CodeMirror, after keys have
+      // been mapped to their Vim equivalents.
+      handleKey: function(cm, key) {
+        var command;
+        var vim = maybeInitVimState(cm);
+        var macroModeState = vimGlobalState.macroModeState;
+        if (macroModeState.enteredMacroMode) {
+          if (key == 'q') {
+            actions.exitMacroRecordMode();
+            vim.inputState = new InputState();
+            return;
+          }
+        }
+        if (key == '<Esc>') {
+          // Clear input state and get back to normal mode.
+          vim.inputState = new InputState();
+          if (vim.visualMode) {
+            exitVisualMode(cm);
+          }
+          return;
+        }
+        // Enter visual mode when the mouse selects text.
+        if (!vim.visualMode &&
+            !cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) {
+          vim.visualMode = true;
+          vim.visualLine = false;
+          CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
+          cm.on('mousedown', exitVisualMode);
+        }
+        if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) {
+          // Have to special case 0 since it's both a motion and a number.
+          command = commandDispatcher.matchCommand(key, defaultKeymap, vim);
+        }
+        if (!command) {
+          if (isNumber(key)) {
+            // Increment count unless count is 0 and key is 0.
+            vim.inputState.pushRepeatDigit(key);
+          }
+          return;
+        }
+        if (command.type == 'keyToKey') {
+          // TODO: prevent infinite recursion.
+          for (var i = 0; i < command.toKeys.length; i++) {
+            this.handleKey(cm, command.toKeys[i]);
+          }
+        } else {
+          if (macroModeState.enteredMacroMode) {
+            logKey(macroModeState, key);
+          }
+          commandDispatcher.processCommand(cm, vim, command);
+        }
+      },
+      handleEx: function(cm, input) {
+        exCommandDispatcher.processCommand(cm, input);
+      }
+    };
+
+    // Represents the current input state.
+    function InputState() {
+      this.prefixRepeat = [];
+      this.motionRepeat = [];
+
+      this.operator = null;
+      this.operatorArgs = null;
+      this.motion = null;
+      this.motionArgs = null;
+      this.keyBuffer = []; // For matching multi-key commands.
+      this.registerName = null; // Defaults to the unamed register.
+    }
+    InputState.prototype.pushRepeatDigit = function(n) {
+      if (!this.operator) {
+        this.prefixRepeat = this.prefixRepeat.concat(n);
+      } else {
+        this.motionRepeat = this.motionRepeat.concat(n);
+      }
+    };
+    InputState.prototype.getRepeat = function() {
+      var repeat = 0;
+      if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) {
+        repeat = 1;
+        if (this.prefixRepeat.length > 0) {
+          repeat *= parseInt(this.prefixRepeat.join(''), 10);
+        }
+        if (this.motionRepeat.length > 0) {
+          repeat *= parseInt(this.motionRepeat.join(''), 10);
+        }
+      }
+      return repeat;
+    };
+
+    /*
+     * Register stores information about copy and paste registers.  Besides
+     * text, a register must store whether it is linewise (i.e., when it is
+     * pasted, should it insert itself into a new line, or should the text be
+     * inserted at the cursor position.)
+     */
+    function Register(text, linewise) {
+      this.clear();
+      if (text) {
+        this.set(text, linewise);
+      }
+    }
+    Register.prototype = {
+      set: function(text, linewise) {
+        this.text = text;
+        this.linewise = !!linewise;
+      },
+      append: function(text, linewise) {
+        // if this register has ever been set to linewise, use linewise.
+        if (linewise || this.linewise) {
+          this.text += '\n' + text;
+          this.linewise = true;
+        } else {
+          this.text += text;
+        }
+      },
+      clear: function() {
+        this.text = '';
+        this.linewise = false;
+      },
+      toString: function() { return this.text; }
+    };
+
+    /*
+     * vim registers allow you to keep many independent copy and paste buffers.
+     * See http://usevim.com/2012/04/13/registers/ for an introduction.
+     *
+     * RegisterController keeps the state of all the registers.  An initial
+     * state may be passed in.  The unnamed register '"' will always be
+     * overridden.
+     */
+    function RegisterController(registers) {
+      this.registers = registers;
+      this.unamedRegister = registers['"'] = new Register();
+    }
+    RegisterController.prototype = {
+      pushText: function(registerName, operator, text, linewise) {
+        if (linewise && text.charAt(0) == '\n') {
+          text = text.slice(1) + '\n';
+        }
+        if(linewise && text.charAt(text.length - 1) !== '\n'){
+          text += '\n';
+        }
+        // Lowercase and uppercase registers refer to the same register.
+        // Uppercase just means append.
+        var register = this.isValidRegister(registerName) ?
+            this.getRegister(registerName) : null;
+        // if no register/an invalid register was specified, things go to the
+        // default registers
+        if (!register) {
+          switch (operator) {
+            case 'yank':
+              // The 0 register contains the text from the most recent yank.
+              this.registers['0'] = new Register(text, linewise);
+              break;
+            case 'delete':
+            case 'change':
+              if (text.indexOf('\n') == -1) {
+                // Delete less than 1 line. Update the small delete register.
+                this.registers['-'] = new Register(text, linewise);
+              } else {
+                // Shift down the contents of the numbered registers and put the
+                // deleted text into register 1.
+                this.shiftNumericRegisters_();
+                this.registers['1'] = new Register(text, linewise);
+              }
+              break;
+          }
+          // Make sure the unnamed register is set to what just happened
+          this.unamedRegister.set(text, linewise);
+          return;
+        }
+
+        // If we've gotten to this point, we've actually specified a register
+        var append = isUpperCase(registerName);
+        if (append) {
+          register.append(text, linewise);
+          // The unamed register always has the same value as the last used
+          // register.
+          this.unamedRegister.append(text, linewise);
+        } else {
+          register.set(text, linewise);
+          this.unamedRegister.set(text, linewise);
+        }
+      },
+      setRegisterText: function(name, text, linewise) {
+        this.getRegister(name).set(text, linewise);
+      },
+      // Gets the register named @name.  If one of @name doesn't already exist,
+      // create it.  If @name is invalid, return the unamedRegister.
+      getRegister: function(name) {
+        if (!this.isValidRegister(name)) {
+          return this.unamedRegister;
+        }
+        name = name.toLowerCase();
+        if (!this.registers[name]) {
+          this.registers[name] = new Register();
+        }
+        return this.registers[name];
+      },
+      isValidRegister: function(name) {
+        return name && inArray(name, validRegisters);
+      },
+      shiftNumericRegisters_: function() {
+        for (var i = 9; i >= 2; i--) {
+          this.registers[i] = this.getRegister('' + (i - 1));
+        }
+      }
+    };
+
+    var commandDispatcher = {
+      matchCommand: function(key, keyMap, vim) {
+        var inputState = vim.inputState;
+        var keys = inputState.keyBuffer.concat(key);
+        var matchedCommands = [];
+        var selectedCharacter;
+        for (var i = 0; i < keyMap.length; i++) {
+          var command = keyMap[i];
+          if (matchKeysPartial(keys, command.keys)) {
+            if (inputState.operator && command.type == 'action') {
+              // Ignore matched action commands after an operator. Operators
+              // only operate on motions. This check is really for text
+              // objects since aW, a[ etcs conflicts with a.
+              continue;
+            }
+            // Match commands that take <character> as an argument.
+            if (command.keys[keys.length - 1] == 'character') {
+              selectedCharacter = keys[keys.length - 1];
+              if(selectedCharacter.length>1){
+                switch(selectedCharacter){
+                  case '<CR>':
+                    selectedCharacter='\n';
+                    break;
+                  case '<Space>':
+                    selectedCharacter=' ';
+                    break;
+                  default:
+                    continue;
+                }
+              }
+            }
+            // Add the command to the list of matched commands. Choose the best
+            // command later.
+            matchedCommands.push(command);
+          }
+        }
+
+        // Returns the command if it is a full match, or null if not.
+        function getFullyMatchedCommandOrNull(command) {
+          if (keys.length < command.keys.length) {
+            // Matches part of a multi-key command. Buffer and wait for next
+            // stroke.
+            inputState.keyBuffer.push(key);
+            return null;
+          } else {
+            if (command.keys[keys.length - 1] == 'character') {
+              inputState.selectedCharacter = selectedCharacter;
+            }
+            // Clear the buffer since a full match was found.
+            inputState.keyBuffer = [];
+            return command;
+          }
+        }
+
+        if (!matchedCommands.length) {
+          // Clear the buffer since there were no matches.
+          inputState.keyBuffer = [];
+          return null;
+        } else if (matchedCommands.length == 1) {
+          return getFullyMatchedCommandOrNull(matchedCommands[0]);
+        } else {
+          // Find the best match in the list of matchedCommands.
+          var context = vim.visualMode ? 'visual' : 'normal';
+          var bestMatch = matchedCommands[0]; // Default to first in the list.
+          for (var i = 0; i < matchedCommands.length; i++) {
+            if (matchedCommands[i].context == context) {
+              bestMatch = matchedCommands[i];
+              break;
+            }
+          }
+          return getFullyMatchedCommandOrNull(bestMatch);
+        }
+      },
+      processCommand: function(cm, vim, command) {
+        vim.inputState.repeatOverride = command.repeatOverride;
+        switch (command.type) {
+          case 'motion':
+            this.processMotion(cm, vim, command);
+            break;
+          case 'operator':
+            this.processOperator(cm, vim, command);
+            break;
+          case 'operatorMotion':
+            this.processOperatorMotion(cm, vim, command);
+            break;
+          case 'action':
+            this.processAction(cm, vim, command);
+            break;
+          case 'search':
+            this.processSearch(cm, vim, command);
+            break;
+          case 'ex':
+          case 'keyToEx':
+            this.processEx(cm, vim, command);
+            break;
+          default:
+            break;
+        }
+      },
+      processMotion: function(cm, vim, command) {
+        vim.inputState.motion = command.motion;
+        vim.inputState.motionArgs = copyArgs(command.motionArgs);
+        this.evalInput(cm, vim);
+      },
+      processOperator: function(cm, vim, command) {
+        var inputState = vim.inputState;
+        if (inputState.operator) {
+          if (inputState.operator == command.operator) {
+            // Typing an operator twice like 'dd' makes the operator operate
+            // linewise
+            inputState.motion = 'expandToLine';
+            inputState.motionArgs = { linewise: true };
+            this.evalInput(cm, vim);
+            return;
+          } else {
+            // 2 different operators in a row doesn't make sense.
+            vim.inputState = new InputState();
+          }
+        }
+        inputState.operator = command.operator;
+        inputState.operatorArgs = copyArgs(command.operatorArgs);
+        if (vim.visualMode) {
+          // Operating on a selection in visual mode. We don't need a motion.
+          this.evalInput(cm, vim);
+        }
+      },
+      processOperatorMotion: function(cm, vim, command) {
+        var visualMode = vim.visualMode;
+        var operatorMotionArgs = copyArgs(command.operatorMotionArgs);
+        if (operatorMotionArgs) {
+          // Operator motions may have special behavior in visual mode.
+          if (visualMode && operatorMotionArgs.visualLine) {
+            vim.visualLine = true;
+          }
+        }
+        this.processOperator(cm, vim, command);
+        if (!visualMode) {
+          this.processMotion(cm, vim, command);
+        }
+      },
+      processAction: function(cm, vim, command) {
+        var inputState = vim.inputState;
+        var repeat = inputState.getRepeat();
+        var repeatIsExplicit = !!repeat;
+        var actionArgs = copyArgs(command.actionArgs) || {};
+        if (inputState.selectedCharacter) {
+          actionArgs.selectedCharacter = inputState.selectedCharacter;
+        }
+        // Actions may or may not have motions and operators. Do these first.
+        if (command.operator) {
+          this.processOperator(cm, vim, command);
+        }
+        if (command.motion) {
+          this.processMotion(cm, vim, command);
+        }
+        if (command.motion || command.operator) {
+          this.evalInput(cm, vim);
+        }
+        actionArgs.repeat = repeat || 1;
+        actionArgs.repeatIsExplicit = repeatIsExplicit;
+        actionArgs.registerName = inputState.registerName;
+        vim.inputState = new InputState();
+        vim.lastMotion = null;
+        if (command.isEdit) {
+          this.recordLastEdit(vim, inputState, command);
+        }
+        actions[command.action](cm, actionArgs, vim);
+      },
+      processSearch: function(cm, vim, command) {
+        if (!cm.getSearchCursor) {
+          // Search depends on SearchCursor.
+          return;
+        }
+        var forward = command.searchArgs.forward;
+        getSearchState(cm).setReversed(!forward);
+        var promptPrefix = (forward) ? '/' : '?';
+        var originalQuery = getSearchState(cm).getQuery();
+        var originalScrollPos = cm.getScrollInfo();
+        function handleQuery(query, ignoreCase, smartCase) {
+          try {
+            updateSearchQuery(cm, query, ignoreCase, smartCase);
+          } catch (e) {
+            showConfirm(cm, 'Invalid regex: ' + query);
+            return;
+          }
+          commandDispatcher.processMotion(cm, vim, {
+            type: 'motion',
+            motion: 'findNext',
+            motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist }
+          });
+        }
+        function onPromptClose(query) {
+          cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
+          handleQuery(query, true /** ignoreCase */, true /** smartCase */);
+        }
+        function onPromptKeyUp(_e, query) {
+          var parsedQuery;
+          try {
+            parsedQuery = updateSearchQuery(cm, query,
+                true /** ignoreCase */, true /** smartCase */);
+          } catch (e) {
+            // Swallow bad regexes for incremental search.
+          }
+          if (parsedQuery) {
+            cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30);
+          } else {
+            clearSearchHighlight(cm);
+            cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
+          }
+        }
+        function onPromptKeyDown(e, _query, close) {
+          var keyName = CodeMirror.keyName(e);
+          if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
+            updateSearchQuery(cm, originalQuery);
+            clearSearchHighlight(cm);
+            cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
+
+            CodeMirror.e_stop(e);
+            close();
+            cm.focus();
+          }
+        }
+        switch (command.searchArgs.querySrc) {
+          case 'prompt':
+            showPrompt(cm, {
+                onClose: onPromptClose,
+                prefix: promptPrefix,
+                desc: searchPromptDesc,
+                onKeyUp: onPromptKeyUp,
+                onKeyDown: onPromptKeyDown
+            });
+            break;
+          case 'wordUnderCursor':
+            var word = expandWordUnderCursor(cm, false /** inclusive */,
+                true /** forward */, false /** bigWord */,
+                true /** noSymbol */);
+            var isKeyword = true;
+            if (!word) {
+              word = expandWordUnderCursor(cm, false /** inclusive */,
+                  true /** forward */, false /** bigWord */,
+                  false /** noSymbol */);
+              isKeyword = false;
+            }
+            if (!word) {
+              return;
+            }
+            var query = cm.getLine(word.start.line).substring(word.start.ch,
+                word.end.ch);
+            if (isKeyword) {
+              query = '\\b' + query + '\\b';
+            } else {
+              query = escapeRegex(query);
+            }
+
+            // cachedCursor is used to save the old position of the cursor
+            // when * or # causes vim to seek for the nearest word and shift
+            // the cursor before entering the motion.
+            vimGlobalState.jumpList.cachedCursor = cm.getCursor();
+            cm.setCursor(word.start);
+
+            handleQuery(query, true /** ignoreCase */, false /** smartCase */);
+            break;
+        }
+      },
+      processEx: function(cm, vim, command) {
+        function onPromptClose(input) {
+          // Give the prompt some time to close so that if processCommand shows
+          // an error, the elements don't overlap.
+          exCommandDispatcher.processCommand(cm, input);
+        }
+        function onPromptKeyDown(e, _input, close) {
+          var keyName = CodeMirror.keyName(e);
+          if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
+            CodeMirror.e_stop(e);
+            close();
+            cm.focus();
+          }
+        }
+        if (command.type == 'keyToEx') {
+          // Handle user defined Ex to Ex mappings
+          exCommandDispatcher.processCommand(cm, command.exArgs.input);
+        } else {
+          if (vim.visualMode) {
+            showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',
+                onKeyDown: onPromptKeyDown});
+          } else {
+            showPrompt(cm, { onClose: onPromptClose, prefix: ':',
+                onKeyDown: onPromptKeyDown});
+          }
+        }
+      },
+      evalInput: function(cm, vim) {
+        // If the motion comand is set, execute both the operator and motion.
+        // Otherwise return.
+        var inputState = vim.inputState;
+        var motion = inputState.motion;
+        var motionArgs = inputState.motionArgs || {};
+        var operator = inputState.operator;
+        var operatorArgs = inputState.operatorArgs || {};
+        var registerName = inputState.registerName;
+        var selectionEnd = cm.getCursor('head');
+        var selectionStart = cm.getCursor('anchor');
+        // The difference between cur and selection cursors are that cur is
+        // being operated on and ignores that there is a selection.
+        var curStart = copyCursor(selectionEnd);
+        var curOriginal = copyCursor(curStart);
+        var curEnd;
+        var repeat;
+        if (operator) {
+          this.recordLastEdit(vim, inputState);
+        }
+        if (inputState.repeatOverride !== undefined) {
+          // If repeatOverride is specified, that takes precedence over the
+          // input state's repeat. Used by Ex mode and can be user defined.
+          repeat = inputState.repeatOverride;
+        } else {
+          repeat = inputState.getRepeat();
+        }
+        if (repeat > 0 && motionArgs.explicitRepeat) {
+          motionArgs.repeatIsExplicit = true;
+        } else if (motionArgs.noRepeat ||
+            (!motionArgs.explicitRepeat && repeat === 0)) {
+          repeat = 1;
+          motionArgs.repeatIsExplicit = false;
+        }
+        if (inputState.selectedCharacter) {
+          // If there is a character input, stick it in all of the arg arrays.
+          motionArgs.selectedCharacter = operatorArgs.selectedCharacter =
+              inputState.selectedCharacter;
+        }
+        motionArgs.repeat = repeat;
+        vim.inputState = new InputState();
+        if (motion) {
+          var motionResult = motions[motion](cm, motionArgs, vim);
+          vim.lastMotion = motions[motion];
+          if (!motionResult) {
+            return;
+          }
+          if (motionArgs.toJumplist) {
+            var jumpList = vimGlobalState.jumpList;
+            // if the current motion is # or *, use cachedCursor
+            var cachedCursor = jumpList.cachedCursor;
+            if (cachedCursor) {
+              recordJumpPosition(cm, cachedCursor, motionResult);
+              delete jumpList.cachedCursor;
+            } else {
+              recordJumpPosition(cm, curOriginal, motionResult);
+            }
+          }
+          if (motionResult instanceof Array) {
+            curStart = motionResult[0];
+            curEnd = motionResult[1];
+          } else {
+            curEnd = motionResult;
+          }
+          // TODO: Handle null returns from motion commands better.
+          if (!curEnd) {
+            curEnd = { ch: curStart.ch, line: curStart.line };
+          }
+          if (vim.visualMode) {
+            // Check if the selection crossed over itself. Will need to shift
+            // the start point if that happened.
+            if (cursorIsBefore(selectionStart, selectionEnd) &&
+                (cursorEqual(selectionStart, curEnd) ||
+                    cursorIsBefore(curEnd, selectionStart))) {
+              // The end of the selection has moved from after the start to
+              // before the start. We will shift the start right by 1.
+              selectionStart.ch += 1;
+            } else if (cursorIsBefore(selectionEnd, selectionStart) &&
+                (cursorEqual(selectionStart, curEnd) ||
+                    cursorIsBefore(selectionStart, curEnd))) {
+              // The opposite happened. We will shift the start left by 1.
+              selectionStart.ch -= 1;
+            }
+            selectionEnd = curEnd;
+            if (vim.visualLine) {
+              if (cursorIsBefore(selectionStart, selectionEnd)) {
+                selectionStart.ch = 0;
+
+                var lastLine = cm.lastLine();
+                if (selectionEnd.line > lastLine) {
+                  selectionEnd.line = lastLine;
+                }
+                selectionEnd.ch = lineLength(cm, selectionEnd.line);
+              } else {
+                selectionEnd.ch = 0;
+                selectionStart.ch = lineLength(cm, selectionStart.line);
+              }
+            }
+            cm.setSelection(selectionStart, selectionEnd);
+            updateMark(cm, vim, '<',
+                cursorIsBefore(selectionStart, selectionEnd) ? selectionStart
+                    : selectionEnd);
+            updateMark(cm, vim, '>',
+                cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd
+                    : selectionStart);
+          } else if (!operator) {
+            curEnd = clipCursorToContent(cm, curEnd);
+            cm.setCursor(curEnd.line, curEnd.ch);
+          }
+        }
+
+        if (operator) {
+          var inverted = false;
+          vim.lastMotion = null;
+          operatorArgs.repeat = repeat; // Indent in visual mode needs this.
+          if (vim.visualMode) {
+            curStart = selectionStart;
+            curEnd = selectionEnd;
+            motionArgs.inclusive = true;
+          }
+          // Swap start and end if motion was backward.
+          if (cursorIsBefore(curEnd, curStart)) {
+            var tmp = curStart;
+            curStart = curEnd;
+            curEnd = tmp;
+            inverted = true;
+          }
+          if (motionArgs.inclusive && !(vim.visualMode && inverted)) {
+            // Move the selection end one to the right to include the last
+            // character.
+            curEnd.ch++;
+          }
+          var linewise = motionArgs.linewise ||
+              (vim.visualMode && vim.visualLine);
+          if (linewise) {
+            // Expand selection to entire line.
+            expandSelectionToLine(cm, curStart, curEnd);
+          } else if (motionArgs.forward) {
+            // Clip to trailing newlines only if the motion goes forward.
+            clipToLine(cm, curStart, curEnd);
+          }
+          operatorArgs.registerName = registerName;
+          // Keep track of linewise as it affects how paste and change behave.
+          operatorArgs.linewise = linewise;
+          operators[operator](cm, operatorArgs, vim, curStart,
+              curEnd, curOriginal);
+          if (vim.visualMode) {
+            exitVisualMode(cm);
+          }
+        }
+      },
+      recordLastEdit: function(vim, inputState, actionCommand) {
+        var macroModeState = vimGlobalState.macroModeState;
+        if (macroModeState.inReplay) { return; }
+        vim.lastEditInputState = inputState;
+        vim.lastEditActionCommand = actionCommand;
+        macroModeState.lastInsertModeChanges.changes = [];
+        macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false;
+      }
+    };
+
+    /**
+     * typedef {Object{line:number,ch:number}} Cursor An object containing the
+     *     position of the cursor.
+     */
+    // All of the functions below return Cursor objects.
+    var motions = {
+      moveToTopLine: function(cm, motionArgs) {
+        var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;
+        return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) };
+      },
+      moveToMiddleLine: function(cm) {
+        var range = getUserVisibleLines(cm);
+        var line = Math.floor((range.top + range.bottom) * 0.5);
+        return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) };
+      },
+      moveToBottomLine: function(cm, motionArgs) {
+        var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;
+        return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) };
+      },
+      expandToLine: function(cm, motionArgs) {
+        // Expands forward to end of line, and then to next line if repeat is
+        // >1. Does not handle backward motion!
+        var cur = cm.getCursor();
+        return { line: cur.line + motionArgs.repeat - 1, ch: Infinity };
+      },
+      findNext: function(cm, motionArgs) {
+        var state = getSearchState(cm);
+        var query = state.getQuery();
+        if (!query) {
+          return;
+        }
+        var prev = !motionArgs.forward;
+        // If search is initiated with ? instead of /, negate direction.
+        prev = (state.isReversed()) ? !prev : prev;
+        highlightSearchMatches(cm, query);
+        return findNext(cm, prev/** prev */, query, motionArgs.repeat);
+      },
+      goToMark: function(_cm, motionArgs, vim) {
+        var mark = vim.marks[motionArgs.selectedCharacter];
+        if (mark) {
+          return mark.find();
+        }
+        return null;
+      },
+      jumpToMark: function(cm, motionArgs, vim) {
+        var best = cm.getCursor();
+        for (var i = 0; i < motionArgs.repeat; i++) {
+          var cursor = best;
+          for (var key in vim.marks) {
+            if (!isLowerCase(key)) {
+              continue;
+            }
+            var mark = vim.marks[key].find();
+            var isWrongDirection = (motionArgs.forward) ?
+              cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark);
+
+            if (isWrongDirection) {
+              continue;
+            }
+            if (motionArgs.linewise && (mark.line == cursor.line)) {
+              continue;
+            }
+
+            var equal = cursorEqual(cursor, best);
+            var between = (motionArgs.forward) ?
+              cusrorIsBetween(cursor, mark, best) :
+              cusrorIsBetween(best, mark, cursor);
+
+            if (equal || between) {
+              best = mark;
+            }
+          }
+        }
+
+        if (motionArgs.linewise) {
+          // Vim places the cursor on the first non-whitespace character of
+          // the line if there is one, else it places the cursor at the end
+          // of the line, regardless of whether a mark was found.
+          best.ch = findFirstNonWhiteSpaceCharacter(cm.getLine(best.line));
+        }
+        return best;
+      },
+      moveByCharacters: function(cm, motionArgs) {
+        var cur = cm.getCursor();
+        var repeat = motionArgs.repeat;
+        var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
+        return { line: cur.line, ch: ch };
+      },
+      moveByLines: function(cm, motionArgs, vim) {
+        var cur = cm.getCursor();
+        var endCh = cur.ch;
+        // Depending what our last motion was, we may want to do different
+        // things. If our last motion was moving vertically, we want to
+        // preserve the HPos from our last horizontal move.  If our last motion
+        // was going to the end of a line, moving vertically we should go to
+        // the end of the line, etc.
+        switch (vim.lastMotion) {
+          case this.moveByLines:
+          case this.moveByDisplayLines:
+          case this.moveByScroll:
+          case this.moveToColumn:
+          case this.moveToEol:
+            endCh = vim.lastHPos;
+            break;
+          default:
+            vim.lastHPos = endCh;
+        }
+        var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0);
+        var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
+        var first = cm.firstLine();
+        var last = cm.lastLine();
+        // Vim cancels linewise motions that start on an edge and move beyond
+        // that edge. It does not cancel motions that do not start on an edge.
+        if ((line < first && cur.line == first) ||
+            (line > last && cur.line == last)) {
+          return;
+        }
+        if(motionArgs.toFirstChar){
+          endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
+          vim.lastHPos = endCh;
+        }
+        vim.lastHSPos = cm.charCoords({line:line, ch:endCh},'div').left;
+        return { line: line, ch: endCh };
+      },
+      moveByDisplayLines: function(cm, motionArgs, vim) {
+        var cur = cm.getCursor();
+        switch (vim.lastMotion) {
+          case this.moveByDisplayLines:
+          case this.moveByScroll:
+          case this.moveByLines:
+          case this.moveToColumn:
+          case this.moveToEol:
+            break;
+          default:
+            vim.lastHSPos = cm.charCoords(cur,'div').left;
+        }
+        var repeat = motionArgs.repeat;
+        var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos);
+        if (res.hitSide) {
+          if (motionArgs.forward) {
+            var lastCharCoords = cm.charCoords(res, 'div');
+            var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos };
+            var res = cm.coordsChar(goalCoords, 'div');
+          } else {
+            var resCoords = cm.charCoords({ line: cm.firstLine(), ch: 0}, 'div');
+            resCoords.left = vim.lastHSPos;
+            res = cm.coordsChar(resCoords, 'div');
+          }
+        }
+        vim.lastHPos = res.ch;
+        return res;
+      },
+      moveByPage: function(cm, motionArgs) {
+        // CodeMirror only exposes functions that move the cursor page down, so
+        // doing this bad hack to move the cursor and move it back. evalInput
+        // will move the cursor to where it should be in the end.
+        var curStart = cm.getCursor();
+        var repeat = motionArgs.repeat;
+        cm.moveV((motionArgs.forward ? repeat : -repeat), 'page');
+        var curEnd = cm.getCursor();
+        cm.setCursor(curStart);
+        return curEnd;
+      },
+      moveByParagraph: function(cm, motionArgs) {
+        var line = cm.getCursor().line;
+        var repeat = motionArgs.repeat;
+        var inc = motionArgs.forward ? 1 : -1;
+        for (var i = 0; i < repeat; i++) {
+          if ((!motionArgs.forward && line === cm.firstLine() ) ||
+              (motionArgs.forward && line == cm.lastLine())) {
+            break;
+          }
+          line += inc;
+          while (line !== cm.firstLine() && line != cm.lastLine() && cm.getLine(line)) {
+            line += inc;
+          }
+        }
+        return { line: line, ch: 0 };
+      },
+      moveByScroll: function(cm, motionArgs, vim) {
+        var scrollbox = cm.getScrollInfo();
+        var curEnd = null;
+        var repeat = motionArgs.repeat;
+        if (!repeat) {
+          repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());
+        }
+        var orig = cm.charCoords(cm.getCursor(), 'local');
+        motionArgs.repeat = repeat;
+        var curEnd = motions.moveByDisplayLines(cm, motionArgs, vim);
+        if (!curEnd) {
+          return null;
+        }
+        var dest = cm.charCoords(curEnd, 'local');
+        cm.scrollTo(null, scrollbox.top + dest.top - orig.top);
+        return curEnd;
+      },
+      moveByWords: function(cm, motionArgs) {
+        return moveToWord(cm, motionArgs.repeat, !!motionArgs.forward,
+            !!motionArgs.wordEnd, !!motionArgs.bigWord);
+      },
+      moveTillCharacter: function(cm, motionArgs) {
+        var repeat = motionArgs.repeat;
+        var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
+            motionArgs.selectedCharacter);
+        var increment = motionArgs.forward ? -1 : 1;
+        recordLastCharacterSearch(increment, motionArgs);
+        if(!curEnd)return cm.getCursor();
+        curEnd.ch += increment;
+        return curEnd;
+      },
+      moveToCharacter: function(cm, motionArgs) {
+        var repeat = motionArgs.repeat;
+        recordLastCharacterSearch(0, motionArgs);
+        return moveToCharacter(cm, repeat, motionArgs.forward,
+            motionArgs.selectedCharacter) || cm.getCursor();
+      },
+      moveToSymbol: function(cm, motionArgs) {
+        var repeat = motionArgs.repeat;
+        return findSymbol(cm, repeat, motionArgs.forward,
+            motionArgs.selectedCharacter) || cm.getCursor();
+      },
+      moveToColumn: function(cm, motionArgs, vim) {
+        var repeat = motionArgs.repeat;
+        // repeat is equivalent to which column we want to move to!
+        vim.lastHPos = repeat - 1;
+        vim.lastHSPos = cm.charCoords(cm.getCursor(),'div').left;
+        return moveToColumn(cm, repeat);
+      },
+      moveToEol: function(cm, motionArgs, vim) {
+        var cur = cm.getCursor();
+        vim.lastHPos = Infinity;
+        var retval={ line: cur.line + motionArgs.repeat - 1, ch: Infinity };
+        var end=cm.clipPos(retval);
+        end.ch--;
+        vim.lastHSPos = cm.charCoords(end,'div').left;
+        return retval;
+      },
+      moveToFirstNonWhiteSpaceCharacter: function(cm) {
+        // Go to the start of the line where the text begins, or the end for
+        // whitespace-only lines
+        var cursor = cm.getCursor();
+        return { line: cursor.line,
+            ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)) };
+      },
+      moveToMatchedSymbol: function(cm) {
+        var cursor = cm.getCursor();
+        var line = cursor.line;
+        var ch = cursor.ch;
+        var lineText = cm.getLine(line);
+        var symbol;
+        var startContext = cm.getTokenAt(cursor).type;
+        var startCtxLevel = getContextLevel(startContext);
+        do {
+          symbol = lineText.charAt(ch++);
+          if (symbol && isMatchableSymbol(symbol)) {
+            var endContext = cm.getTokenAt({line:line, ch:ch}).type;
+            var endCtxLevel = getContextLevel(endContext);
+            if (startCtxLevel >= endCtxLevel) {
+              break;
+            }
+          }
+        } while (symbol);
+        if (symbol) {
+          return findMatchedSymbol(cm, {line:line, ch:ch-1}, symbol);
+        } else {
+          return cursor;
+        }
+      },
+      moveToStartOfLine: function(cm) {
+        var cursor = cm.getCursor();
+        return { line: cursor.line, ch: 0 };
+      },
+      moveToLineOrEdgeOfDocument: function(cm, motionArgs) {
+        var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();
+        if (motionArgs.repeatIsExplicit) {
+          lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');
+        }
+        return { line: lineNum,
+            ch: findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)) };
+      },
+      textObjectManipulation: function(cm, motionArgs) {
+        var character = motionArgs.selectedCharacter;
+        // Inclusive is the difference between a and i
+        // TODO: Instead of using the additional text object map to perform text
+        //     object operations, merge the map into the defaultKeyMap and use
+        //     motionArgs to define behavior. Define separate entries for 'aw',
+        //     'iw', 'a[', 'i[', etc.
+        var inclusive = !motionArgs.textObjectInner;
+        if (!textObjects[character]) {
+          // No text object defined for this, don't move.
+          return null;
+        }
+        var tmp = textObjects[character](cm, inclusive);
+        var start = tmp.start;
+        var end = tmp.end;
+        return [start, end];
+      },
+      repeatLastCharacterSearch: function(cm, motionArgs) {
+        var lastSearch = vimGlobalState.lastChararacterSearch;
+        var repeat = motionArgs.repeat;
+        var forward = motionArgs.forward === lastSearch.forward;
+        var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1);
+        cm.moveH(-increment, 'char');
+        motionArgs.inclusive = forward ? true : false;
+        var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
+        if (!curEnd) {
+          cm.moveH(increment, 'char');
+          return cm.getCursor();
+        }
+        curEnd.ch += increment;
+        return curEnd;
+      }
+    };
+
+    var operators = {
+      change: function(cm, operatorArgs, _vim, curStart, curEnd) {
+        vimGlobalState.registerController.pushText(
+            operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd),
+            operatorArgs.linewise);
+        if (operatorArgs.linewise) {
+          // Push the next line back down, if there is a next line.
+          var replacement = curEnd.line > cm.lastLine() ? '' : '\n';
+          cm.replaceRange(replacement, curStart, curEnd);
+          cm.indentLine(curStart.line, 'smart');
+          // null ch so setCursor moves to end of line.
+          curStart.ch = null;
+        } else {
+          // Exclude trailing whitespace if the range is not all whitespace.
+          var text = cm.getRange(curStart, curEnd);
+          if (!isWhiteSpaceString(text)) {
+            var match = (/\s+$/).exec(text);
+            if (match) {
+              curEnd = offsetCursor(curEnd, 0, - match[0].length);
+            }
+          }
+          cm.replaceRange('', curStart, curEnd);
+        }
+        actions.enterInsertMode(cm, {}, cm.state.vim);
+        cm.setCursor(curStart);
+      },
+      // delete is a javascript keyword.
+      'delete': function(cm, operatorArgs, _vim, curStart, curEnd) {
+        // If the ending line is past the last line, inclusive, instead of
+        // including the trailing \n, include the \n before the starting line
+        if (operatorArgs.linewise &&
+            curEnd.line > cm.lastLine() && curStart.line > cm.firstLine()) {
+          curStart.line--;
+          curStart.ch = lineLength(cm, curStart.line);
+        }
+        vimGlobalState.registerController.pushText(
+            operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd),
+            operatorArgs.linewise);
+        cm.replaceRange('', curStart, curEnd);
+        if (operatorArgs.linewise) {
+          cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
+        } else {
+          cm.setCursor(curStart);
+        }
+      },
+      indent: function(cm, operatorArgs, vim, curStart, curEnd) {
+        var startLine = curStart.line;
+        var endLine = curEnd.line;
+        // In visual mode, n> shifts the selection right n times, instead of
+        // shifting n lines right once.
+        var repeat = (vim.visualMode) ? operatorArgs.repeat : 1;
+        if (operatorArgs.linewise) {
+          // The only way to delete a newline is to delete until the start of
+          // the next line, so in linewise mode evalInput will include the next
+          // line. We don't want this in indent, so we go back a line.
+          endLine--;
+        }
+        for (var i = startLine; i <= endLine; i++) {
+          for (var j = 0; j < repeat; j++) {
+            cm.indentLine(i, operatorArgs.indentRight);
+          }
+        }
+        cm.setCursor(curStart);
+        cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
+      },
+      swapcase: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
+        var toSwap = cm.getRange(curStart, curEnd);
+        var swapped = '';
+        for (var i = 0; i < toSwap.length; i++) {
+          var character = toSwap.charAt(i);
+          swapped += isUpperCase(character) ? character.toLowerCase() :
+              character.toUpperCase();
+        }
+        cm.replaceRange(swapped, curStart, curEnd);
+        if (!operatorArgs.shouldMoveCursor) {
+          cm.setCursor(curOriginal);
+        }
+      },
+      yank: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
+        vimGlobalState.registerController.pushText(
+            operatorArgs.registerName, 'yank',
+            cm.getRange(curStart, curEnd), operatorArgs.linewise);
+        cm.setCursor(curOriginal);
+      }
+    };
+
+    var actions = {
+      jumpListWalk: function(cm, actionArgs, vim) {
+        if (vim.visualMode) {
+          return;
+        }
+        var repeat = actionArgs.repeat;
+        var forward = actionArgs.forward;
+        var jumpList = vimGlobalState.jumpList;
+
+        var mark = jumpList.move(cm, forward ? repeat : -repeat);
+        var markPos = mark ? mark.find() : undefined;
+        markPos = markPos ? markPos : cm.getCursor();
+        cm.setCursor(markPos);
+      },
+      scrollToCursor: function(cm, actionArgs) {
+        var lineNum = cm.getCursor().line;
+        var charCoords = cm.charCoords({line: lineNum, ch: 0}, 'local');
+        var height = cm.getScrollInfo().clientHeight;
+        var y = charCoords.top;
+        var lineHeight = charCoords.bottom - y;
+        switch (actionArgs.position) {
+          case 'center': y = y - (height / 2) + lineHeight;
+            break;
+          case 'bottom': y = y - height + lineHeight*1.4;
+            break;
+          case 'top': y = y + lineHeight*0.4;
+            break;
+        }
+        cm.scrollTo(null, y);
+      },
+      replayMacro: function(cm, actionArgs) {
+        var registerName = actionArgs.selectedCharacter;
+        var repeat = actionArgs.repeat;
+        var macroModeState = vimGlobalState.macroModeState;
+        if (registerName == '@') {
+          registerName = macroModeState.latestRegister;
+        }
+        var keyBuffer = parseRegisterToKeyBuffer(macroModeState, registerName);
+        while(repeat--){
+          executeMacroKeyBuffer(cm, macroModeState, keyBuffer);
+        }
+      },
+      exitMacroRecordMode: function() {
+        var macroModeState = vimGlobalState.macroModeState;
+        macroModeState.toggle();
+        parseKeyBufferToRegister(macroModeState.latestRegister,
+                                 macroModeState.macroKeyBuffer);
+      },
+      enterMacroRecordMode: function(cm, actionArgs) {
+        var macroModeState = vimGlobalState.macroModeState;
+        var registerName = actionArgs.selectedCharacter;
+        macroModeState.toggle(cm, registerName);
+        emptyMacroKeyBuffer(macroModeState);
+      },
+      enterInsertMode: function(cm, actionArgs, vim) {
+        if (cm.getOption('readOnly')) { return; }
+        vim.insertMode = true;
+        vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
+        var insertAt = (actionArgs) ? actionArgs.insertAt : null;
+        if (insertAt == 'eol') {
+          var cursor = cm.getCursor();
+          cursor = { line: cursor.line, ch: lineLength(cm, cursor.line) };
+          cm.setCursor(cursor);
+        } else if (insertAt == 'charAfter') {
+          cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
+        } else if (insertAt == 'firstNonBlank') {
+          cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
+        }
+        cm.setOption('keyMap', 'vim-insert');
+        if (actionArgs && actionArgs.replace) {
+          // Handle Replace-mode as a special case of insert mode.
+          cm.toggleOverwrite(true);
+          cm.setOption('keyMap', 'vim-replace');
+          CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});
+        } else {
+          cm.setOption('keyMap', 'vim-insert');
+          CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});
+        }
+        if (!vimGlobalState.macroModeState.inReplay) {
+          // Only record if not replaying.
+          cm.on('change', onChange);
+          cm.on('cursorActivity', onCursorActivity);
+          CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
+        }
+      },
+      toggleVisualMode: function(cm, actionArgs, vim) {
+        var repeat = actionArgs.repeat;
+        var curStart = cm.getCursor();
+        var curEnd;
+        // TODO: The repeat should actually select number of characters/lines
+        //     equal to the repeat times the size of the previous visual
+        //     operation.
+        if (!vim.visualMode) {
+          cm.on('mousedown', exitVisualMode);
+          vim.visualMode = true;
+          vim.visualLine = !!actionArgs.linewise;
+          if (vim.visualLine) {
+            curStart.ch = 0;
+            curEnd = clipCursorToContent(cm, {
+              line: curStart.line + repeat - 1,
+              ch: lineLength(cm, curStart.line)
+            }, true /** includeLineBreak */);
+          } else {
+            curEnd = clipCursorToContent(cm, {
+              line: curStart.line,
+              ch: curStart.ch + repeat
+            }, true /** includeLineBreak */);
+          }
+          // Make the initial selection.
+          if (!actionArgs.repeatIsExplicit && !vim.visualLine) {
+            // This is a strange case. Here the implicit repeat is 1. The
+            // following commands lets the cursor hover over the 1 character
+            // selection.
+            cm.setCursor(curEnd);
+            cm.setSelection(curEnd, curStart);
+          } else {
+            cm.setSelection(curStart, curEnd);
+          }
+          CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""});
+        } else {
+          curStart = cm.getCursor('anchor');
+          curEnd = cm.getCursor('head');
+          if (!vim.visualLine && actionArgs.linewise) {
+            // Shift-V pressed in characterwise visual mode. Switch to linewise
+            // visual mode instead of exiting visual mode.
+            vim.visualLine = true;
+            curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 :
+                lineLength(cm, curStart.line);
+            curEnd.ch = cursorIsBefore(curStart, curEnd) ?
+                lineLength(cm, curEnd.line) : 0;
+            cm.setSelection(curStart, curEnd);
+            CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"});
+          } else if (vim.visualLine && !actionArgs.linewise) {
+            // v pressed in linewise visual mode. Switch to characterwise visual
+            // mode instead of exiting visual mode.
+            vim.visualLine = false;
+            CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
+          } else {
+            exitVisualMode(cm);
+          }
+        }
+        updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart
+            : curEnd);
+        updateMark(cm, vim, '>', cursorIsBefore(curStart, curEnd) ? curEnd
+            : curStart);
+      },
+      joinLines: function(cm, actionArgs, vim) {
+        var curStart, curEnd;
+        if (vim.visualMode) {
+          curStart = cm.getCursor('anchor');
+          curEnd = cm.getCursor('head');
+          curEnd.ch = lineLength(cm, curEnd.line) - 1;
+        } else {
+          // Repeat is the number of lines to join. Minimum 2 lines.
+          var repeat = Math.max(actionArgs.repeat, 2);
+          curStart = cm.getCursor();
+          curEnd = clipCursorToContent(cm, { line: curStart.line + repeat - 1,
+              ch: Infinity });
+        }
+        var finalCh = 0;
+        cm.operation(function() {
+          for (var i = curStart.line; i < curEnd.line; i++) {
+            finalCh = lineLength(cm, curStart.line);
+            var tmp = { line: curStart.line + 1,
+                ch: lineLength(cm, curStart.line + 1) };
+            var text = cm.getRange(curStart, tmp);
+            text = text.replace(/\n\s*/g, ' ');
+            cm.replaceRange(text, curStart, tmp);
+          }
+          var curFinalPos = { line: curStart.line, ch: finalCh };
+          cm.setCursor(curFinalPos);
+        });
+      },
+      newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
+        vim.insertMode = true;
+        var insertAt = cm.getCursor();
+        if (insertAt.line === cm.firstLine() && !actionArgs.after) {
+          // Special case for inserting newline before start of document.
+          cm.replaceRange('\n', { line: cm.firstLine(), ch: 0 });
+          cm.setCursor(cm.firstLine(), 0);
+        } else {
+          insertAt.line = (actionArgs.after) ? insertAt.line :
+              insertAt.line - 1;
+          insertAt.ch = lineLength(cm, insertAt.line);
+          cm.setCursor(insertAt);
+          var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment ||
+              CodeMirror.commands.newlineAndIndent;
+          newlineFn(cm);
+        }
+        this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim);
+      },
+      paste: function(cm, actionArgs) {
+        var cur = cm.getCursor();
+        var register = vimGlobalState.registerController.getRegister(
+            actionArgs.registerName);
+        if (!register.text) {
+          return;
+        }
+        for (var text = '', i = 0; i < actionArgs.repeat; i++) {
+          text += register.text;
+        }
+        var linewise = register.linewise;
+        if (linewise) {
+          if (actionArgs.after) {
+            // Move the newline at the end to the start instead, and paste just
+            // before the newline character of the line we are on right now.
+            text = '\n' + text.slice(0, text.length - 1);
+            cur.ch = lineLength(cm, cur.line);
+          } else {
+            cur.ch = 0;
+          }
+        } else {
+          cur.ch += actionArgs.after ? 1 : 0;
+        }
+        cm.replaceRange(text, cur);
+        // Now fine tune the cursor to where we want it.
+        var curPosFinal;
+        var idx;
+        if (linewise && actionArgs.after) {
+          curPosFinal = { line: cur.line + 1,
+              ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)) };
+        } else if (linewise && !actionArgs.after) {
+          curPosFinal = { line: cur.line,
+              ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)) };
+        } else if (!linewise && actionArgs.after) {
+          idx = cm.indexFromPos(cur);
+          curPosFinal = cm.posFromIndex(idx + text.length - 1);
+        } else {
+          idx = cm.indexFromPos(cur);
+          curPosFinal = cm.posFromIndex(idx + text.length);
+        }
+        cm.setCursor(curPosFinal);
+      },
+      undo: function(cm, actionArgs) {
+        cm.operation(function() {
+          repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();
+          cm.setCursor(cm.getCursor('anchor'));
+        });
+      },
+      redo: function(cm, actionArgs) {
+        repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)();
+      },
+      setRegister: function(_cm, actionArgs, vim) {
+        vim.inputState.registerName = actionArgs.selectedCharacter;
+      },
+      setMark: function(cm, actionArgs, vim) {
+        var markName = actionArgs.selectedCharacter;
+        updateMark(cm, vim, markName, cm.getCursor());
+      },
+      replace: function(cm, actionArgs, vim) {
+        var replaceWith = actionArgs.selectedCharacter;
+        var curStart = cm.getCursor();
+        var replaceTo;
+        var curEnd;
+        if(vim.visualMode){
+          curStart=cm.getCursor('start');
+          curEnd=cm.getCursor('end');
+          // workaround to catch the character under the cursor
+          //  existing workaround doesn't cover actions
+          curEnd=cm.clipPos({line: curEnd.line, ch: curEnd.ch+1});
+        }else{
+          var line = cm.getLine(curStart.line);
+          replaceTo = curStart.ch + actionArgs.repeat;
+          if (replaceTo > line.length) {
+            replaceTo=line.length;
+          }
+          curEnd = { line: curStart.line, ch: replaceTo };
+        }
+        if(replaceWith=='\n'){
+          if(!vim.visualMode) cm.replaceRange('', curStart, curEnd);
+          // special case, where vim help says to replace by just one line-break
+          (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
+        }else {
+          var replaceWithStr=cm.getRange(curStart, curEnd);
+          //replace all characters in range by selected, but keep linebreaks
+          replaceWithStr=replaceWithStr.replace(/[^\n]/g,replaceWith);
+          cm.replaceRange(replaceWithStr, curStart, curEnd);
+          if(vim.visualMode){
+            cm.setCursor(curStart);
+            exitVisualMode(cm);
+          }else{
+            cm.setCursor(offsetCursor(curEnd, 0, -1));
+          }
+        }
+      },
+      incrementNumberToken: function(cm, actionArgs) {
+        var cur = cm.getCursor();
+        var lineStr = cm.getLine(cur.line);
+        var re = /-?\d+/g;
+        var match;
+        var start;
+        var end;
+        var numberStr;
+        var token;
+        while ((match = re.exec(lineStr)) !== null) {
+          token = match[0];
+          start = match.index;
+          end = start + token.length;
+          if(cur.ch < end)break;
+        }
+        if(!actionArgs.backtrack && (end <= cur.ch))return;
+        if (token) {
+          var increment = actionArgs.increase ? 1 : -1;
+          var number = parseInt(token) + (increment * actionArgs.repeat);
+          var from = {ch:start, line:cur.line};
+          var to = {ch:end, line:cur.line};
+          numberStr = number.toString();
+          cm.replaceRange(numberStr, from, to);
+        } else {
+          return;
+        }
+        cm.setCursor({line: cur.line, ch: start + numberStr.length - 1});
+      },
+      repeatLastEdit: function(cm, actionArgs, vim) {
+        var lastEditInputState = vim.lastEditInputState;
+        if (!lastEditInputState) { return; }
+        var repeat = actionArgs.repeat;
+        if (repeat && actionArgs.repeatIsExplicit) {
+          vim.lastEditInputState.repeatOverride = repeat;
+        } else {
+          repeat = vim.lastEditInputState.repeatOverride || repeat;
+        }
+        repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);
+      }
+    };
+
+    var textObjects = {
+      // TODO: lots of possible exceptions that can be thrown here. Try da(
+      //     outside of a () block.
+      // TODO: implement text objects for the reverse like }. Should just be
+      //     an additional mapping after moving to the defaultKeyMap.
+      'w': function(cm, inclusive) {
+        return expandWordUnderCursor(cm, inclusive, true /** forward */,
+            false /** bigWord */);
+      },
+      'W': function(cm, inclusive) {
+        return expandWordUnderCursor(cm, inclusive,
+            true /** forward */, true /** bigWord */);
+      },
+      '{': function(cm, inclusive) {
+        return selectCompanionObject(cm, '}', inclusive);
+      },
+      '(': function(cm, inclusive) {
+        return selectCompanionObject(cm, ')', inclusive);
+      },
+      '[': function(cm, inclusive) {
+        return selectCompanionObject(cm, ']', inclusive);
+      },
+      '\'': function(cm, inclusive) {
+        return findBeginningAndEnd(cm, "'", inclusive);
+      },
+      '"': function(cm, inclusive) {
+        return findBeginningAndEnd(cm, '"', inclusive);
+      }
+    };
+
+    /*
+     * Below are miscellaneous utility functions used by vim.js
+     */
+
+    /**
+     * Clips cursor to ensure that line is within the buffer's range
+     * If includeLineBreak is true, then allow cur.ch == lineLength.
+     */
+    function clipCursorToContent(cm, cur, includeLineBreak) {
+      var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() );
+      var maxCh = lineLength(cm, line) - 1;
+      maxCh = (includeLineBreak) ? maxCh + 1 : maxCh;
+      var ch = Math.min(Math.max(0, cur.ch), maxCh);
+      return { line: line, ch: ch };
+    }
+    function copyArgs(args) {
+      var ret = {};
+      for (var prop in args) {
+        if (args.hasOwnProperty(prop)) {
+          ret[prop] = args[prop];
+        }
+      }
+      return ret;
+    }
+    function offsetCursor(cur, offsetLine, offsetCh) {
+      return { line: cur.line + offsetLine, ch: cur.ch + offsetCh };
+    }
+    function matchKeysPartial(pressed, mapped) {
+      for (var i = 0; i < pressed.length; i++) {
+        // 'character' means any character. For mark, register commads, etc.
+        if (pressed[i] != mapped[i] && mapped[i] != 'character') {
+          return false;
+        }
+      }
+      return true;
+    }
+    function repeatFn(cm, fn, repeat) {
+      return function() {
+        for (var i = 0; i < repeat; i++) {
+          fn(cm);
+        }
+      };
+    }
+    function copyCursor(cur) {
+      return { line: cur.line, ch: cur.ch };
+    }
+    function cursorEqual(cur1, cur2) {
+      return cur1.ch == cur2.ch && cur1.line == cur2.line;
+    }
+    function cursorIsBefore(cur1, cur2) {
+      if (cur1.line < cur2.line) {
+        return true;
+      }
+      if (cur1.line == cur2.line && cur1.ch < cur2.ch) {
+        return true;
+      }
+      return false;
+    }
+    function cusrorIsBetween(cur1, cur2, cur3) {
+      // returns true if cur2 is between cur1 and cur3.
+      var cur1before2 = cursorIsBefore(cur1, cur2);
+      var cur2before3 = cursorIsBefore(cur2, cur3);
+      return cur1before2 && cur2before3;
+    }
+    function lineLength(cm, lineNum) {
+      return cm.getLine(lineNum).length;
+    }
+    function reverse(s){
+      return s.split('').reverse().join('');
+    }
+    function trim(s) {
+      if (s.trim) {
+        return s.trim();
+      }
+      return s.replace(/^\s+|\s+$/g, '');
+    }
+    function escapeRegex(s) {
+      return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
+    }
+
+    function exitVisualMode(cm) {
+      cm.off('mousedown', exitVisualMode);
+      var vim = cm.state.vim;
+      vim.visualMode = false;
+      vim.visualLine = false;
+      var selectionStart = cm.getCursor('anchor');
+      var selectionEnd = cm.getCursor('head');
+      if (!cursorEqual(selectionStart, selectionEnd)) {
+        // Clear the selection and set the cursor only if the selection has not
+        // already been cleared. Otherwise we risk moving the cursor somewhere
+        // it's not supposed to be.
+        cm.setCursor(clipCursorToContent(cm, selectionEnd));
+      }
+      CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
+    }
+
+    // Remove any trailing newlines from the selection. For
+    // example, with the caret at the start of the last word on the line,
+    // 'dw' should word, but not the newline, while 'w' should advance the
+    // caret to the first character of the next line.
+    function clipToLine(cm, curStart, curEnd) {
+      var selection = cm.getRange(curStart, curEnd);
+      // Only clip if the selection ends with trailing newline + whitespace
+      if (/\n\s*$/.test(selection)) {
+        var lines = selection.split('\n');
+        // We know this is all whitepsace.
+        lines.pop();
+
+        // Cases:
+        // 1. Last word is an empty line - do not clip the trailing '\n'
+        // 2. Last word is not an empty line - clip the trailing '\n'
+        var line;
+        // Find the line containing the last word, and clip all whitespace up
+        // to it.
+        for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) {
+          curEnd.line--;
+          curEnd.ch = 0;
+        }
+        // If the last word is not an empty line, clip an additional newline
+        if (line) {
+          curEnd.line--;
+          curEnd.ch = lineLength(cm, curEnd.line);
+        } else {
+          curEnd.ch = 0;
+        }
+      }
+    }
+
+    // Expand the selection to line ends.
+    function expandSelectionToLine(_cm, curStart, curEnd) {
+      curStart.ch = 0;
+      curEnd.ch = 0;
+      curEnd.line++;
+    }
+
+    function findFirstNonWhiteSpaceCharacter(text) {
+      if (!text) {
+        return 0;
+      }
+      var firstNonWS = text.search(/\S/);
+      return firstNonWS == -1 ? text.length : firstNonWS;
+    }
+
+    function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {
+      var cur = cm.getCursor();
+      var line = cm.getLine(cur.line);
+      var idx = cur.ch;
+
+      // Seek to first word or non-whitespace character, depending on if
+      // noSymbol is true.
+      var textAfterIdx = line.substring(idx);
+      var firstMatchedChar;
+      if (noSymbol) {
+        firstMatchedChar = textAfterIdx.search(/\w/);
+      } else {
+        firstMatchedChar = textAfterIdx.search(/\S/);
+      }
+      if (firstMatchedChar == -1) {
+        return null;
+      }
+      idx += firstMatchedChar;
+      textAfterIdx = line.substring(idx);
+      var textBeforeIdx = line.substring(0, idx);
+
+      var matchRegex;
+      // Greedy matchers for the "word" we are trying to expand.
+      if (bigWord) {
+        matchRegex = /^\S+/;
+      } else {
+        if ((/\w/).test(line.charAt(idx))) {
+          matchRegex = /^\w+/;
+        } else {
+          matchRegex = /^[^\w\s]+/;
+        }
+      }
+
+      var wordAfterRegex = matchRegex.exec(textAfterIdx);
+      var wordStart = idx;
+      var wordEnd = idx + wordAfterRegex[0].length;
+      // TODO: Find a better way to do this. It will be slow on very long lines.
+      var revTextBeforeIdx = reverse(textBeforeIdx);
+      var wordBeforeRegex = matchRegex.exec(revTextBeforeIdx);
+      if (wordBeforeRegex) {
+        wordStart -= wordBeforeRegex[0].length;
+      }
+
+      if (inclusive) {
+        // If present, trim all whitespace after word.
+        // Otherwise, trim all whitespace before word.
+        var textAfterWordEnd = line.substring(wordEnd);
+        var whitespacesAfterWord = textAfterWordEnd.match(/^\s*/)[0].length;
+        if (whitespacesAfterWord > 0) {
+          wordEnd += whitespacesAfterWord;
+        } else {
+          var revTrim = revTextBeforeIdx.length - wordStart;
+          var textBeforeWordStart = revTextBeforeIdx.substring(revTrim);
+          var whitespacesBeforeWord = textBeforeWordStart.match(/^\s*/)[0].length;
+          wordStart -= whitespacesBeforeWord;
+        }
+      }
+
+      return { start: { line: cur.line, ch: wordStart },
+        end: { line: cur.line, ch: wordEnd }};
+    }
+
+    function recordJumpPosition(cm, oldCur, newCur) {
+      if(!cursorEqual(oldCur, newCur)) {
+        vimGlobalState.jumpList.add(cm, oldCur, newCur);
+      }
+    }
+
+    function recordLastCharacterSearch(increment, args) {
+        vimGlobalState.lastChararacterSearch.increment = increment;
+        vimGlobalState.lastChararacterSearch.forward = args.forward;
+        vimGlobalState.lastChararacterSearch.selectedCharacter = args.selectedCharacter;
+    }
+
+    var symbolToMode = {
+        '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket',
+        '[': 'section', ']': 'section',
+        '*': 'comment', '/': 'comment',
+        'm': 'method', 'M': 'method',
+        '#': 'preprocess'
+    };
+    var findSymbolModes = {
+      bracket: {
+        isComplete: function(state) {
+          if (state.nextCh === state.symb) {
+            state.depth++;
+            if(state.depth >= 1)return true;
+          } else if (state.nextCh === state.reverseSymb) {
+            state.depth--;
+          }
+          return false;
+        }
+      },
+      section: {
+        init: function(state) {
+          state.curMoveThrough = true;
+          state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}';
+        },
+        isComplete: function(state) {
+          return state.index === 0 && state.nextCh === state.symb;
+        }
+      },
+      comment: {
+        isComplete: function(state) {
+          var found = state.lastCh === '*' && state.nextCh === '/';
+          state.lastCh = state.nextCh;
+          return found;
+        }
+      },
+      // TODO: The original Vim implementation only operates on level 1 and 2.
+      // The current implementation doesn't check for code block level and
+      // therefore it operates on any levels.
+      method: {
+        init: function(state) {
+          state.symb = (state.symb === 'm' ? '{' : '}');
+          state.reverseSymb = state.symb === '{' ? '}' : '{';
+        },
+        isComplete: function(state) {
+          if(state.nextCh === state.symb)return true;
+          return false;
+        }
+      },
+      preprocess: {
+        init: function(state) {
+          state.index = 0;
+        },
+        isComplete: function(state) {
+          if (state.nextCh === '#') {
+            var token = state.lineText.match(/#(\w+)/)[1];
+            if (token === 'endif') {
+              if (state.forward && state.depth === 0) {
+                return true;
+              }
+              state.depth++;
+            } else if (token === 'if') {
+              if (!state.forward && state.depth === 0) {
+                return true;
+              }
+              state.depth--;
+            }
+            if(token === 'else' && state.depth === 0)return true;
+          }
+          return false;
+        }
+      }
+    };
+    function findSymbol(cm, repeat, forward, symb) {
+      var cur = cm.getCursor();
+      var increment = forward ? 1 : -1;
+      var endLine = forward ? cm.lineCount() : -1;
+      var curCh = cur.ch;
+      var line = cur.line;
+      var lineText = cm.getLine(line);
+      var state = {
+        lineText: lineText,
+        nextCh: lineText.charAt(curCh),
+        lastCh: null,
+        index: curCh,
+        symb: symb,
+        reverseSymb: (forward ?  { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb],
+        forward: forward,
+        depth: 0,
+        curMoveThrough: false
+      };
+      var mode = symbolToMode[symb];
+      if(!mode)return cur;
+      var init = findSymbolModes[mode].init;
+      var isComplete = findSymbolModes[mode].isComplete;
+      if(init)init(state);
+      while (line !== endLine && repeat) {
+        state.index += increment;
+        state.nextCh = state.lineText.charAt(state.index);
+        if (!state.nextCh) {
+          line += increment;
+          state.lineText = cm.getLine(line) || '';
+          if (increment > 0) {
+            state.index = 0;
+          } else {
+            var lineLen = state.lineText.length;
+            state.index = (lineLen > 0) ? (lineLen-1) : 0;
+          }
+          state.nextCh = state.lineText.charAt(state.index);
+        }
+        if (isComplete(state)) {
+          cur.line = line;
+          cur.ch = state.index;
+          repeat--;
+        }
+      }
+      if (state.nextCh || state.curMoveThrough) {
+        return { line: line, ch: state.index };
+      }
+      return cur;
+    }
+
+    /*
+     * Returns the boundaries of the next word. If the cursor in the middle of
+     * the word, then returns the boundaries of the current word, starting at
+     * the cursor. If the cursor is at the start/end of a word, and we are going
+     * forward/backward, respectively, find the boundaries of the next word.
+     *
+     * @param {CodeMirror} cm CodeMirror object.
+     * @param {Cursor} cur The cursor position.
+     * @param {boolean} forward True to search forward. False to search
+     *     backward.
+     * @param {boolean} bigWord True if punctuation count as part of the word.
+     *     False if only [a-zA-Z0-9] characters count as part of the word.
+     * @param {boolean} emptyLineIsWord True if empty lines should be treated
+     *     as words.
+     * @return {Object{from:number, to:number, line: number}} The boundaries of
+     *     the word, or null if there are no more words.
+     */
+    function findWord(cm, cur, forward, bigWord, emptyLineIsWord) {
+      var lineNum = cur.line;
+      var pos = cur.ch;
+      var line = cm.getLine(lineNum);
+      var dir = forward ? 1 : -1;
+      var regexps = bigWord ? bigWordRegexp : wordRegexp;
+
+      if (emptyLineIsWord && line == '') {
+        lineNum += dir;
+        line = cm.getLine(lineNum);
+        if (!isLine(cm, lineNum)) {
+          return null;
+        }
+        pos = (forward) ? 0 : line.length;
+      }
+
+      while (true) {
+        if (emptyLineIsWord && line == '') {
+          return { from: 0, to: 0, line: lineNum };
+        }
+        var stop = (dir > 0) ? line.length : -1;
+        var wordStart = stop, wordEnd = stop;
+        // Find bounds of next word.
+        while (pos != stop) {
+          var foundWord = false;
+          for (var i = 0; i < regexps.length && !foundWord; ++i) {
+            if (regexps[i].test(line.charAt(pos))) {
+              wordStart = pos;
+              // Advance to end of word.
+              while (pos != stop && regexps[i].test(line.charAt(pos))) {
+                pos += dir;
+              }
+              wordEnd = pos;
+              foundWord = wordStart != wordEnd;
+              if (wordStart == cur.ch && lineNum == cur.line &&
+                  wordEnd == wordStart + dir) {
+                // We started at the end of a word. Find the next one.
+                continue;
+              } else {
+                return {
+                  from: Math.min(wordStart, wordEnd + 1),
+                  to: Math.max(wordStart, wordEnd),
+                  line: lineNum };
+              }
+            }
+          }
+          if (!foundWord) {
+            pos += dir;
+          }
+        }
+        // Advance to next/prev line.
+        lineNum += dir;
+        if (!isLine(cm, lineNum)) {
+          return null;
+        }
+        line = cm.getLine(lineNum);
+        pos = (dir > 0) ? 0 : line.length;
+      }
+      // Should never get here.
+      throw new Error('The impossible happened.');
+    }
+
+    /**
+     * @param {CodeMirror} cm CodeMirror object.
+     * @param {int} repeat Number of words to move past.
+     * @param {boolean} forward True to search forward. False to search
+     *     backward.
+     * @param {boolean} wordEnd True to move to end of word. False to move to
+     *     beginning of word.
+     * @param {boolean} bigWord True if punctuation count as part of the word.
+     *     False if only alphabet characters count as part of the word.
+     * @return {Cursor} The position the cursor should move to.
+     */
+    function moveToWord(cm, repeat, forward, wordEnd, bigWord) {
+      var cur = cm.getCursor();
+      var curStart = copyCursor(cur);
+      var words = [];
+      if (forward && !wordEnd || !forward && wordEnd) {
+        repeat++;
+      }
+      // For 'e', empty lines are not considered words, go figure.
+      var emptyLineIsWord = !(forward && wordEnd);
+      for (var i = 0; i < repeat; i++) {
+        var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord);
+        if (!word) {
+          var eodCh = lineLength(cm, cm.lastLine());
+          words.push(forward
+              ? {line: cm.lastLine(), from: eodCh, to: eodCh}
+              : {line: 0, from: 0, to: 0});
+          break;
+        }
+        words.push(word);
+        cur = {line: word.line, ch: forward ? (word.to - 1) : word.from};
+      }
+      var shortCircuit = words.length != repeat;
+      var firstWord = words[0];
+      var lastWord = words.pop();
+      if (forward && !wordEnd) {
+        // w
+        if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) {
+          // We did not start in the middle of a word. Discard the extra word at the end.
+          lastWord = words.pop();
+        }
+        return {line: lastWord.line, ch: lastWord.from};
+      } else if (forward && wordEnd) {
+        return {line: lastWord.line, ch: lastWord.to - 1};
+      } else if (!forward && wordEnd) {
+        // ge
+        if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) {
+          // We did not start in the middle of a word. Discard the extra word at the end.
+          lastWord = words.pop();
+        }
+        return {line: lastWord.line, ch: lastWord.to};
+      } else {
+        // b
+        return {line: lastWord.line, ch: lastWord.from};
+      }
+    }
+
+    function moveToCharacter(cm, repeat, forward, character) {
+      var cur = cm.getCursor();
+      var start = cur.ch;
+      var idx;
+      for (var i = 0; i < repeat; i ++) {
+        var line = cm.getLine(cur.line);
+        idx = charIdxInLine(start, line, character, forward, true);
+        if (idx == -1) {
+          return null;
+        }
+        start = idx;
+      }
+      return { line: cm.getCursor().line, ch: idx };
+    }
+
+    function moveToColumn(cm, repeat) {
+      // repeat is always >= 1, so repeat - 1 always corresponds
+      // to the column we want to go to.
+      var line = cm.getCursor().line;
+      return clipCursorToContent(cm, { line: line, ch: repeat - 1 });
+    }
+
+    function updateMark(cm, vim, markName, pos) {
+      if (!inArray(markName, validMarks)) {
+        return;
+      }
+      if (vim.marks[markName]) {
+        vim.marks[markName].clear();
+      }
+      vim.marks[markName] = cm.setBookmark(pos);
+    }
+
+    function charIdxInLine(start, line, character, forward, includeChar) {
+      // Search for char in line.
+      // motion_options: {forward, includeChar}
+      // If includeChar = true, include it too.
+      // If forward = true, search forward, else search backwards.
+      // If char is not found on this line, do nothing
+      var idx;
+      if (forward) {
+        idx = line.indexOf(character, start + 1);
+        if (idx != -1 && !includeChar) {
+          idx -= 1;
+        }
+      } else {
+        idx = line.lastIndexOf(character, start - 1);
+        if (idx != -1 && !includeChar) {
+          idx += 1;
+        }
+      }
+      return idx;
+    }
+
+    function getContextLevel(ctx) {
+      return (ctx === 'string' || ctx === 'comment') ? 1 : 0;
+    }
+
+    function findMatchedSymbol(cm, cur, symb) {
+      var line = cur.line;
+      var ch = cur.ch;
+      symb = symb ? symb : cm.getLine(line).charAt(ch);
+
+      var symbContext = cm.getTokenAt({line:line, ch:ch+1}).type;
+      var symbCtxLevel = getContextLevel(symbContext);
+
+      var reverseSymb = ({
+        '(': ')', ')': '(',
+        '[': ']', ']': '[',
+        '{': '}', '}': '{'})[symb];
+
+      // Couldn't find a matching symbol, abort
+      if (!reverseSymb) {
+        return cur;
+      }
+
+      // set our increment to move forward (+1) or backwards (-1)
+      // depending on which bracket we're matching
+      var increment = ({'(': 1, '{': 1, '[': 1})[symb] || -1;
+      var endLine = increment === 1 ? cm.lineCount() : -1;
+      var depth = 1, nextCh = symb, index = ch, lineText = cm.getLine(line);
+      // Simple search for closing paren--just count openings and closings till
+      // we find our match
+      // TODO: use info from CodeMirror to ignore closing brackets in comments
+      // and quotes, etc.
+      while (line !== endLine && depth > 0) {
+        index += increment;
+        nextCh = lineText.charAt(index);
+        if (!nextCh) {
+          line += increment;
+          lineText = cm.getLine(line) || '';
+          if (increment > 0) {
+            index = 0;
+          } else {
+            var lineLen = lineText.length;
+            index = (lineLen > 0) ? (lineLen-1) : 0;
+          }
+          nextCh = lineText.charAt(index);
+        }
+        var revSymbContext = cm.getTokenAt({line:line, ch:index+1}).type;
+        var revSymbCtxLevel = getContextLevel(revSymbContext);
+        if (symbCtxLevel >= revSymbCtxLevel) {
+          if (nextCh === symb) {
+            depth++;
+          } else if (nextCh === reverseSymb) {
+            depth--;
+          }
+        }
+      }
+
+      if (nextCh) {
+        return { line: line, ch: index };
+      }
+      return cur;
+    }
+
+    function selectCompanionObject(cm, revSymb, inclusive) {
+      var cur = cm.getCursor();
+
+      var end = findMatchedSymbol(cm, cur, revSymb);
+      var start = findMatchedSymbol(cm, end);
+      start.ch += inclusive ? 1 : 0;
+      end.ch += inclusive ? 0 : 1;
+
+      return { start: start, end: end };
+    }
+
+    // Takes in a symbol and a cursor and tries to simulate text objects that
+    // have identical opening and closing symbols
+    // TODO support across multiple lines
+    function findBeginningAndEnd(cm, symb, inclusive) {
+      var cur = cm.getCursor();
+      var line = cm.getLine(cur.line);
+      var chars = line.split('');
+      var start, end, i, len;
+      var firstIndex = chars.indexOf(symb);
+
+      // the decision tree is to always look backwards for the beginning first,
+      // but if the cursor is in front of the first instance of the symb,
+      // then move the cursor forward
+      if (cur.ch < firstIndex) {
+        cur.ch = firstIndex;
+        // Why is this line even here???
+        // cm.setCursor(cur.line, firstIndex+1);
+      }
+      // otherwise if the cursor is currently on the closing symbol
+      else if (firstIndex < cur.ch && chars[cur.ch] == symb) {
+        end = cur.ch; // assign end to the current cursor
+        --cur.ch; // make sure to look backwards
+      }
+
+      // if we're currently on the symbol, we've got a start
+      if (chars[cur.ch] == symb && !end) {
+        start = cur.ch + 1; // assign start to ahead of the cursor
+      } else {
+        // go backwards to find the start
+        for (i = cur.ch; i > -1 && !start; i--) {
+          if (chars[i] == symb) {
+            start = i + 1;
+          }
+        }
+      }
+
+      // look forwards for the end symbol
+      if (start && !end) {
+        for (i = start, len = chars.length; i < len && !end; i++) {
+          if (chars[i] == symb) {
+            end = i;
+          }
+        }
+      }
+
+      // nothing found
+      if (!start || !end) {
+        return { start: cur, end: cur };
+      }
+
+      // include the symbols
+      if (inclusive) {
+        --start; ++end;
+      }
+
+      return {
+        start: { line: cur.line, ch: start },
+        end: { line: cur.line, ch: end }
+      };
+    }
+
+    // Search functions
+    function SearchState() {}
+    SearchState.prototype = {
+      getQuery: function() {
+        return vimGlobalState.query;
+      },
+      setQuery: function(query) {
+        vimGlobalState.query = query;
+      },
+      getOverlay: function() {
+        return this.searchOverlay;
+      },
+      setOverlay: function(overlay) {
+        this.searchOverlay = overlay;
+      },
+      isReversed: function() {
+        return vimGlobalState.isReversed;
+      },
+      setReversed: function(reversed) {
+        vimGlobalState.isReversed = reversed;
+      }
+    };
+    function getSearchState(cm) {
+      var vim = cm.state.vim;
+      return vim.searchState_ || (vim.searchState_ = new SearchState());
+    }
+    function dialog(cm, template, shortText, onClose, options) {
+      if (cm.openDialog) {
+        cm.openDialog(template, onClose, { bottom: true, value: options.value,
+            onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp });
+      }
+      else {
+        onClose(prompt(shortText, ''));
+      }
+    }
+
+    function findUnescapedSlashes(str) {
+      var escapeNextChar = false;
+      var slashes = [];
+      for (var i = 0; i < str.length; i++) {
+        var c = str.charAt(i);
+        if (!escapeNextChar && c == '/') {
+          slashes.push(i);
+        }
+        escapeNextChar = (c == '\\');
+      }
+      return slashes;
+    }
+    /**
+     * Extract the regular expression from the query and return a Regexp object.
+     * Returns null if the query is blank.
+     * If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
+     * If smartCase is passed in, and the query contains upper case letters,
+     *   then ignoreCase is overridden, and the 'i' flag will not be set.
+     * If the query contains the /i in the flag part of the regular expression,
+     *   then both ignoreCase and smartCase are ignored, and 'i' will be passed
+     *   through to the Regex object.
+     */
+    function parseQuery(query, ignoreCase, smartCase) {
+      // Check if the query is already a regex.
+      if (query instanceof RegExp) { return query; }
+      // First try to extract regex + flags from the input. If no flags found,
+      // extract just the regex. IE does not accept flags directly defined in
+      // the regex string in the form /regex/flags
+      var slashes = findUnescapedSlashes(query);
+      var regexPart;
+      var forceIgnoreCase;
+      if (!slashes.length) {
+        // Query looks like 'regexp'
+        regexPart = query;
+      } else {
+        // Query looks like 'regexp/...'
+        regexPart = query.substring(0, slashes[0]);
+        var flagsPart = query.substring(slashes[0]);
+        forceIgnoreCase = (flagsPart.indexOf('i') != -1);
+      }
+      if (!regexPart) {
+        return null;
+      }
+      if (smartCase) {
+        ignoreCase = (/^[^A-Z]*$/).test(regexPart);
+      }
+      var regexp = new RegExp(regexPart,
+          (ignoreCase || forceIgnoreCase) ? 'i' : undefined);
+      return regexp;
+    }
+    function showConfirm(cm, text) {
+      if (cm.openNotification) {
+        cm.openNotification('<span style="color: red">' + text + '</span>',
+                            {bottom: true, duration: 5000});
+      } else {
+        alert(text);
+      }
+    }
+    function makePrompt(prefix, desc) {
+      var raw = '';
+      if (prefix) {
+        raw += '<span style="font-family: monospace">' + prefix + '</span>';
+      }
+      raw += '<input type="text"/> ' +
+          '<span style="color: #888">';
+      if (desc) {
+        raw += '<span style="color: #888">';
+        raw += desc;
+        raw += '</span>';
+      }
+      return raw;
+    }
+    var searchPromptDesc = '(Javascript regexp)';
+    function showPrompt(cm, options) {
+      var shortText = (options.prefix || '') + ' ' + (options.desc || '');
+      var prompt = makePrompt(options.prefix, options.desc);
+      dialog(cm, prompt, shortText, options.onClose, options);
+    }
+    function regexEqual(r1, r2) {
+      if (r1 instanceof RegExp && r2 instanceof RegExp) {
+          var props = ['global', 'multiline', 'ignoreCase', 'source'];
+          for (var i = 0; i < props.length; i++) {
+              var prop = props[i];
+              if (r1[prop] !== r2[prop]) {
+                  return false;
+              }
+          }
+          return true;
+      }
+      return false;
+    }
+    // Returns true if the query is valid.
+    function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {
+      if (!rawQuery) {
+        return;
+      }
+      var state = getSearchState(cm);
+      var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase);
+      if (!query) {
+        return;
+      }
+      highlightSearchMatches(cm, query);
+      if (regexEqual(query, state.getQuery())) {
+        return query;
+      }
+      state.setQuery(query);
+      return query;
+    }
+    function searchOverlay(query) {
+      if (query.source.charAt(0) == '^') {
+        var matchSol = true;
+      }
+      return {
+        token: function(stream) {
+          if (matchSol && !stream.sol()) {
+            stream.skipToEnd();
+            return;
+          }
+          var match = stream.match(query, false);
+          if (match) {
+            if (match[0].length == 0) {
+              // Matched empty string, skip to next.
+              stream.next();
+              return 'searching';
+            }
+            if (!stream.sol()) {
+              // Backtrack 1 to match \b
+              stream.backUp(1);
+              if (!query.exec(stream.next() + match[0])) {
+                stream.next();
+                return null;
+              }
+            }
+            stream.match(query);
+            return 'searching';
+          }
+          while (!stream.eol()) {
+            stream.next();
+            if (stream.match(query, false)) break;
+          }
+        },
+        query: query
+      };
+    }
+    function highlightSearchMatches(cm, query) {
+      var overlay = getSearchState(cm).getOverlay();
+      if (!overlay || query != overlay.query) {
+        if (overlay) {
+          cm.removeOverlay(overlay);
+        }
+        overlay = searchOverlay(query);
+        cm.addOverlay(overlay);
+        getSearchState(cm).setOverlay(overlay);
+      }
+    }
+    function findNext(cm, prev, query, repeat) {
+      if (repeat === undefined) { repeat = 1; }
+      return cm.operation(function() {
+        var pos = cm.getCursor();
+        var cursor = cm.getSearchCursor(query, pos);
+        for (var i = 0; i < repeat; i++) {
+          var found = cursor.find(prev);
+          if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); }
+          if (!found) {
+            // SearchCursor may have returned null because it hit EOF, wrap
+            // around and try again.
+            cursor = cm.getSearchCursor(query,
+                (prev) ? { line: cm.lastLine() } : {line: cm.firstLine(), ch: 0} );
+            if (!cursor.find(prev)) {
+              return;
+            }
+          }
+        }
+        return cursor.from();
+      });
+    }
+    function clearSearchHighlight(cm) {
+      cm.removeOverlay(getSearchState(cm).getOverlay());
+      getSearchState(cm).setOverlay(null);
+    }
+    /**
+     * Check if pos is in the specified range, INCLUSIVE.
+     * Range can be specified with 1 or 2 arguments.
+     * If the first range argument is an array, treat it as an array of line
+     * numbers. Match pos against any of the lines.
+     * If the first range argument is a number,
+     *   if there is only 1 range argument, check if pos has the same line
+     *       number
+     *   if there are 2 range arguments, then check if pos is in between the two
+     *       range arguments.
+     */
+    function isInRange(pos, start, end) {
+      if (typeof pos != 'number') {
+        // Assume it is a cursor position. Get the line number.
+        pos = pos.line;
+      }
+      if (start instanceof Array) {
+        return inArray(pos, start);
+      } else {
+        if (end) {
+          return (pos >= start && pos <= end);
+        } else {
+          return pos == start;
+        }
+      }
+    }
+    function getUserVisibleLines(cm) {
+      var scrollInfo = cm.getScrollInfo();
+      var occludeToleranceTop = 6;
+      var occludeToleranceBottom = 10;
+      var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local');
+      var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top;
+      var to = cm.coordsChar({left:0, top: bottomY}, 'local');
+      return {top: from.line, bottom: to.line};
+    }
+
+    // Ex command handling
+    // Care must be taken when adding to the default Ex command map. For any
+    // pair of commands that have a shared prefix, at least one of their
+    // shortNames must not match the prefix of the other command.
+    var defaultExCommandMap = [
+      { name: 'map', type: 'builtIn' },
+      { name: 'write', shortName: 'w', type: 'builtIn' },
+      { name: 'undo', shortName: 'u', type: 'builtIn' },
+      { name: 'redo', shortName: 'red', type: 'builtIn' },
+      { name: 'sort', shortName: 'sor', type: 'builtIn'},
+      { name: 'substitute', shortName: 's', type: 'builtIn'},
+      { name: 'nohlsearch', shortName: 'noh', type: 'builtIn'},
+      { name: 'delmarks', shortName: 'delm', type: 'builtin'}
+    ];
+    Vim.ExCommandDispatcher = function() {
+      this.buildCommandMap_();
+    };
+    Vim.ExCommandDispatcher.prototype = {
+      processCommand: function(cm, input) {
+        var vim = cm.state.vim;
+        if (vim.visualMode) {
+          exitVisualMode(cm);
+        }
+        var inputStream = new CodeMirror.StringStream(input);
+        var params = {};
+        params.input = input;
+        try {
+          this.parseInput_(cm, inputStream, params);
+        } catch(e) {
+          showConfirm(cm, e);
+          return;
+        }
+        var commandName;
+        if (!params.commandName) {
+          // If only a line range is defined, move to the line.
+          if (params.line !== undefined) {
+            commandName = 'move';
+          }
+        } else {
+          var command = this.matchCommand_(params.commandName);
+          if (command) {
+            commandName = command.name;
+            this.parseCommandArgs_(inputStream, params, command);
+            if (command.type == 'exToKey') {
+              // Handle Ex to Key mapping.
+              for (var i = 0; i < command.toKeys.length; i++) {
+                CodeMirror.Vim.handleKey(cm, command.toKeys[i]);
+              }
+              return;
+            } else if (command.type == 'exToEx') {
+              // Handle Ex to Ex mapping.
+              this.processCommand(cm, command.toInput);
+              return;
+            }
+          }
+        }
+        if (!commandName) {
+          showConfirm(cm, 'Not an editor command ":' + input + '"');
+          return;
+        }
+        try {
+          exCommands[commandName](cm, params);
+        } catch(e) {
+          showConfirm(cm, e);
+        }
+      },
+      parseInput_: function(cm, inputStream, result) {
+        inputStream.eatWhile(':');
+        // Parse range.
+        if (inputStream.eat('%')) {
+          result.line = cm.firstLine();
+          result.lineEnd = cm.lastLine();
+        } else {
+          result.line = this.parseLineSpec_(cm, inputStream);
+          if (result.line !== undefined && inputStream.eat(',')) {
+            result.lineEnd = this.parseLineSpec_(cm, inputStream);
+          }
+        }
+
+        // Parse command name.
+        var commandMatch = inputStream.match(/^(\w+)/);
+        if (commandMatch) {
+          result.commandName = commandMatch[1];
+        } else {
+          result.commandName = inputStream.match(/.*/)[0];
+        }
+
+        return result;
+      },
+      parseLineSpec_: function(cm, inputStream) {
+        var numberMatch = inputStream.match(/^(\d+)/);
+        if (numberMatch) {
+          return parseInt(numberMatch[1], 10) - 1;
+        }
+        switch (inputStream.next()) {
+          case '.':
+            return cm.getCursor().line;
+          case '$':
+            return cm.lastLine();
+          case '\'':
+            var mark = cm.state.vim.marks[inputStream.next()];
+            if (mark && mark.find()) {
+              return mark.find().line;
+            }
+            throw new Error('Mark not set');
+          default:
+            inputStream.backUp(1);
+            return undefined;
+        }
+      },
+      parseCommandArgs_: function(inputStream, params, command) {
+        if (inputStream.eol()) {
+          return;
+        }
+        params.argString = inputStream.match(/.*/)[0];
+        // Parse command-line arguments
+        var delim = command.argDelimiter || /\s+/;
+        var args = trim(params.argString).split(delim);
+        if (args.length && args[0]) {
+          params.args = args;
+        }
+      },
+      matchCommand_: function(commandName) {
+        // Return the command in the command map that matches the shortest
+        // prefix of the passed in command name. The match is guaranteed to be
+        // unambiguous if the defaultExCommandMap's shortNames are set up
+        // correctly. (see @code{defaultExCommandMap}).
+        for (var i = commandName.length; i > 0; i--) {
+          var prefix = commandName.substring(0, i);
+          if (this.commandMap_[prefix]) {
+            var command = this.commandMap_[prefix];
+            if (command.name.indexOf(commandName) === 0) {
+              return command;
+            }
+          }
+        }
+        return null;
+      },
+      buildCommandMap_: function() {
+        this.commandMap_ = {};
+        for (var i = 0; i < defaultExCommandMap.length; i++) {
+          var command = defaultExCommandMap[i];
+          var key = command.shortName || command.name;
+          this.commandMap_[key] = command;
+        }
+      },
+      map: function(lhs, rhs) {
+        if (lhs != ':' && lhs.charAt(0) == ':') {
+          var commandName = lhs.substring(1);
+          if (rhs != ':' && rhs.charAt(0) == ':') {
+            // Ex to Ex mapping
+            this.commandMap_[commandName] = {
+              name: commandName,
+              type: 'exToEx',
+              toInput: rhs.substring(1)
+            };
+          } else {
+            // Ex to key mapping
+            this.commandMap_[commandName] = {
+              name: commandName,
+              type: 'exToKey',
+              toKeys: parseKeyString(rhs)
+            };
+          }
+        } else {
+          if (rhs != ':' && rhs.charAt(0) == ':') {
+            // Key to Ex mapping.
+            defaultKeymap.unshift({
+              keys: parseKeyString(lhs),
+              type: 'keyToEx',
+              exArgs: { input: rhs.substring(1) }});
+          } else {
+            // Key to key mapping
+            defaultKeymap.unshift({
+              keys: parseKeyString(lhs),
+              type: 'keyToKey',
+              toKeys: parseKeyString(rhs)
+            });
+          }
+        }
+      }
+    };
+
+    // Converts a key string sequence of the form a<C-w>bd<Left> into Vim's
+    // keymap representation.
+    function parseKeyString(str) {
+      var key, match;
+      var keys = [];
+      while (str) {
+        match = (/<\w+-.+?>|<\w+>|./).exec(str);
+        if(match === null)break;
+        key = match[0];
+        str = str.substring(match.index + key.length);
+        keys.push(key);
+      }
+      return keys;
+    }
+
+    var exCommands = {
+      map: function(cm, params) {
+        var mapArgs = params.args;
+        if (!mapArgs || mapArgs.length < 2) {
+          if (cm) {
+            showConfirm(cm, 'Invalid mapping: ' + params.input);
+          }
+          return;
+        }
+        exCommandDispatcher.map(mapArgs[0], mapArgs[1], cm);
+      },
+      move: function(cm, params) {
+        commandDispatcher.processCommand(cm, cm.state.vim, {
+            type: 'motion',
+            motion: 'moveToLineOrEdgeOfDocument',
+            motionArgs: { forward: false, explicitRepeat: true,
+              linewise: true },
+            repeatOverride: params.line+1});
+      },
+      sort: function(cm, params) {
+        var reverse, ignoreCase, unique, number;
+        function parseArgs() {
+          if (params.argString) {
+            var args = new CodeMirror.StringStream(params.argString);
+            if (args.eat('!')) { reverse = true; }
+            if (args.eol()) { return; }
+            if (!args.eatSpace()) { throw new Error('invalid arguments ' + args.match(/.*/)[0]); }
+            var opts = args.match(/[a-z]+/);
+            if (opts) {
+              opts = opts[0];
+              ignoreCase = opts.indexOf('i') != -1;
+              unique = opts.indexOf('u') != -1;
+              var decimal = opts.indexOf('d') != -1 && 1;
+              var hex = opts.indexOf('x') != -1 && 1;
+              var octal = opts.indexOf('o') != -1 && 1;
+              if (decimal + hex + octal > 1) { throw new Error('invalid arguments'); }
+              number = decimal && 'decimal' || hex && 'hex' || octal && 'octal';
+            }
+            if (args.eatSpace() && args.match(/\/.*\//)) { throw new Error('patterns not supported'); }
+          }
+        }
+        parseArgs();
+        var lineStart = params.line || cm.firstLine();
+        var lineEnd = params.lineEnd || params.line || cm.lastLine();
+        if (lineStart == lineEnd) { return; }
+        var curStart = { line: lineStart, ch: 0 };
+        var curEnd = { line: lineEnd, ch: lineLength(cm, lineEnd) };
+        var text = cm.getRange(curStart, curEnd).split('\n');
+        var numberRegex = (number == 'decimal') ? /(-?)([\d]+)/ :
+           (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i :
+           (number == 'octal') ? /([0-7]+)/ : null;
+        var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null;
+        var numPart = [], textPart = [];
+        if (number) {
+          for (var i = 0; i < text.length; i++) {
+            if (numberRegex.exec(text[i])) {
+              numPart.push(text[i]);
+            } else {
+              textPart.push(text[i]);
+            }
+          }
+        } else {
+          textPart = text;
+        }
+        function compareFn(a, b) {
+          if (reverse) { var tmp; tmp = a; a = b; b = tmp; }
+          if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); }
+          var anum = number && numberRegex.exec(a);
+          var bnum = number && numberRegex.exec(b);
+          if (!anum) { return a < b ? -1 : 1; }
+          anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix);
+          bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix);
+          return anum - bnum;
+        }
+        numPart.sort(compareFn);
+        textPart.sort(compareFn);
+        text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart);
+        if (unique) { // Remove duplicate lines
+          var textOld = text;
+          var lastLine;
+          text = [];
+          for (var i = 0; i < textOld.length; i++) {
+            if (textOld[i] != lastLine) {
+              text.push(textOld[i]);
+            }
+            lastLine = textOld[i];
+          }
+        }
+        cm.replaceRange(text.join('\n'), curStart, curEnd);
+      },
+      substitute: function(cm, params) {
+        if (!cm.getSearchCursor) {
+          throw new Error('Search feature not available. Requires searchcursor.js or ' +
+              'any other getSearchCursor implementation.');
+        }
+        var argString = params.argString;
+        var slashes = findUnescapedSlashes(argString);
+        if (slashes[0] !== 0) {
+          showConfirm(cm, 'Substitutions should be of the form ' +
+              ':s/pattern/replace/');
+          return;
+        }
+        var regexPart = argString.substring(slashes[0] + 1, slashes[1]);
+        var replacePart = '';
+        var flagsPart;
+        var count;
+        var confirm = false; // Whether to confirm each replace.
+        if (slashes[1]) {
+          replacePart = argString.substring(slashes[1] + 1, slashes[2]);
+        }
+        if (slashes[2]) {
+          // After the 3rd slash, we can have flags followed by a space followed
+          // by count.
+          var trailing = argString.substring(slashes[2] + 1).split(' ');
+          flagsPart = trailing[0];
+          count = parseInt(trailing[1]);
+        }
+        if (flagsPart) {
+          if (flagsPart.indexOf('c') != -1) {
+            confirm = true;
+            flagsPart.replace('c', '');
+          }
+          regexPart = regexPart + '/' + flagsPart;
+        }
+        if (regexPart) {
+          // If regex part is empty, then use the previous query. Otherwise use
+          // the regex part as the new query.
+          try {
+            updateSearchQuery(cm, regexPart, true /** ignoreCase */,
+              true /** smartCase */);
+          } catch (e) {
+            showConfirm(cm, 'Invalid regex: ' + regexPart);
+            return;
+          }
+        }
+        var state = getSearchState(cm);
+        var query = state.getQuery();
+        var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line;
+        var lineEnd = params.lineEnd || lineStart;
+        if (count) {
+          lineStart = lineEnd;
+          lineEnd = lineStart + count - 1;
+        }
+        var startPos = clipCursorToContent(cm, { line: lineStart, ch: 0 });
+        var cursor = cm.getSearchCursor(query, startPos);
+        doReplace(cm, confirm, lineStart, lineEnd, cursor, query, replacePart);
+      },
+      redo: CodeMirror.commands.redo,
+      undo: CodeMirror.commands.undo,
+      write: function(cm) {
+        if (CodeMirror.commands.save) {
+          // If a save command is defined, call it.
+          CodeMirror.commands.save(cm);
+        } else {
+          // Saves to text area if no save command is defined.
+          cm.save();
+        }
+      },
+      nohlsearch: function(cm) {
+        clearSearchHighlight(cm);
+      },
+      delmarks: function(cm, params) {
+        if (!params.argString || !params.argString.trim()) {
+          showConfirm(cm, 'Argument required');
+          return;
+        }
+
+        var state = cm.state.vim;
+        var stream = new CodeMirror.StringStream(params.argString.trim());
+        while (!stream.eol()) {
+          stream.eatSpace();
+
+          // Record the streams position at the beginning of the loop for use
+          // in error messages.
+          var count = stream.pos;
+
+          if (!stream.match(/[a-zA-Z]/, false)) {
+            showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
+            return;
+          }
+
+          var sym = stream.next();
+          // Check if this symbol is part of a range
+          if (stream.match('-', true)) {
+            // This symbol is part of a range.
+
+            // The range must terminate at an alphabetic character.
+            if (!stream.match(/[a-zA-Z]/, false)) {
+              showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
+              return;
+            }
+
+            var startMark = sym;
+            var finishMark = stream.next();
+            // The range must terminate at an alphabetic character which
+            // shares the same case as the start of the range.
+            if (isLowerCase(startMark) && isLowerCase(finishMark) ||
+                isUpperCase(startMark) && isUpperCase(finishMark)) {
+              var start = startMark.charCodeAt(0);
+              var finish = finishMark.charCodeAt(0);
+              if (start >= finish) {
+                showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
+                return;
+              }
+
+              // Because marks are always ASCII values, and we have
+              // determined that they are the same case, we can use
+              // their char codes to iterate through the defined range.
+              for (var j = 0; j <= finish - start; j++) {
+                var mark = String.fromCharCode(start + j);
+                delete state.marks[mark];
+              }
+            } else {
+              showConfirm(cm, 'Invalid argument: ' + startMark + '-');
+              return;
+            }
+          } else {
+            // This symbol is a valid mark, and is not part of a range.
+            delete state.marks[sym];
+          }
+        }
+      }
+    };
+
+    var exCommandDispatcher = new Vim.ExCommandDispatcher();
+
+    /**
+    * @param {CodeMirror} cm CodeMirror instance we are in.
+    * @param {boolean} confirm Whether to confirm each replace.
+    * @param {Cursor} lineStart Line to start replacing from.
+    * @param {Cursor} lineEnd Line to stop replacing at.
+    * @param {RegExp} query Query for performing matches with.
+    * @param {string} replaceWith Text to replace matches with. May contain $1,
+    *     $2, etc for replacing captured groups using Javascript replace.
+    */
+    function doReplace(cm, confirm, lineStart, lineEnd, searchCursor, query,
+        replaceWith) {
+      // Set up all the functions.
+      cm.state.vim.exMode = true;
+      var done = false;
+      var lastPos = searchCursor.from();
+      function replaceAll() {
+        cm.operation(function() {
+          while (!done) {
+            replace();
+            next();
+          }
+          stop();
+        });
+      }
+      function replace() {
+        var text = cm.getRange(searchCursor.from(), searchCursor.to());
+        var newText = text.replace(query, replaceWith);
+        searchCursor.replace(newText);
+      }
+      function next() {
+        var found = searchCursor.findNext();
+        if (!found) {
+          done = true;
+        } else if (isInRange(searchCursor.from(), lineStart, lineEnd)) {
+          cm.scrollIntoView(searchCursor.from(), 30);
+          cm.setSelection(searchCursor.from(), searchCursor.to());
+          lastPos = searchCursor.from();
+          done = false;
+        } else {
+          done = true;
+        }
+      }
+      function stop(close) {
+        if (close) { close(); }
+        cm.focus();
+        if (lastPos) {
+          cm.setCursor(lastPos);
+          var vim = cm.state.vim;
+          vim.exMode = false;
+          vim.lastHPos = vim.lastHSPos = lastPos.ch;
+        }
+      }
+      function onPromptKeyDown(e, _value, close) {
+        // Swallow all keys.
+        CodeMirror.e_stop(e);
+        var keyName = CodeMirror.keyName(e);
+        switch (keyName) {
+          case 'Y':
+            replace(); next(); break;
+          case 'N':
+            next(); break;
+          case 'A':
+            cm.operation(replaceAll); break;
+          case 'L':
+            replace();
+            // fall through and exit.
+          case 'Q':
+          case 'Esc':
+          case 'Ctrl-C':
+          case 'Ctrl-[':
+            stop(close);
+            break;
+        }
+        if (done) { stop(close); }
+      }
+
+      // Actually do replace.
+      next();
+      if (done) {
+        throw new Error('No matches for ' + query.source);
+      }
+      if (!confirm) {
+        replaceAll();
+        return;
+      }
+      showPrompt(cm, {
+        prefix: 'replace with <strong>' + replaceWith + '</strong> (y/n/a/q/l)',
+        onKeyDown: onPromptKeyDown
+      });
+    }
+
+    // Register Vim with CodeMirror
+    function buildVimKeyMap() {
+      /**
+       * Handle the raw key event from CodeMirror. Translate the
+       * Shift + key modifier to the resulting letter, while preserving other
+       * modifers.
+       */
+      // TODO: Figure out a way to catch capslock.
+      function cmKeyToVimKey(key, modifier) {
+        var vimKey = key;
+        if (isUpperCase(vimKey)) {
+          // Convert to lower case if shift is not the modifier since the key
+          // we get from CodeMirror is always upper case.
+          if (modifier == 'Shift') {
+            modifier = null;
+          }
+          else {
+            vimKey = vimKey.toLowerCase();
+          }
+        }
+        if (modifier) {
+          // Vim will parse modifier+key combination as a single key.
+          vimKey = modifier.charAt(0) + '-' + vimKey;
+        }
+        var specialKey = ({Enter:'CR',Backspace:'BS',Delete:'Del'})[vimKey];
+        vimKey = specialKey ? specialKey : vimKey;
+        vimKey = vimKey.length > 1 ? '<'+ vimKey + '>' : vimKey;
+        return vimKey;
+      }
+
+      // Closure to bind CodeMirror, key, modifier.
+      function keyMapper(vimKey) {
+        return function(cm) {
+          CodeMirror.Vim.handleKey(cm, vimKey);
+        };
+      }
+
+      var cmToVimKeymap = {
+        'nofallthrough': true,
+        'disableInput': true,
+        'style': 'fat-cursor'
+      };
+      function bindKeys(keys, modifier) {
+        for (var i = 0; i < keys.length; i++) {
+          var key = keys[i];
+          if (!modifier && inArray(key, specialSymbols)) {
+            // Wrap special symbols with '' because that's how CodeMirror binds
+            // them.
+            key = "'" + key + "'";
+          }
+          var vimKey = cmKeyToVimKey(keys[i], modifier);
+          var cmKey = modifier ? modifier + '-' + key : key;
+          cmToVimKeymap[cmKey] = keyMapper(vimKey);
+        }
+      }
+      bindKeys(upperCaseAlphabet);
+      bindKeys(upperCaseAlphabet, 'Shift');
+      bindKeys(upperCaseAlphabet, 'Ctrl');
+      bindKeys(specialSymbols);
+      bindKeys(specialSymbols, 'Ctrl');
+      bindKeys(numbers);
+      bindKeys(numbers, 'Ctrl');
+      bindKeys(specialKeys);
+      bindKeys(specialKeys, 'Ctrl');
+      return cmToVimKeymap;
+    }
+    CodeMirror.keyMap.vim = buildVimKeyMap();
+
+    function exitInsertMode(cm) {
+      var vim = cm.state.vim;
+      var inReplay = vimGlobalState.macroModeState.inReplay;
+      if (!inReplay) {
+        cm.off('change', onChange);
+        cm.off('cursorActivity', onCursorActivity);
+        CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
+      }
+      if (!inReplay && vim.insertModeRepeat > 1) {
+        // Perform insert mode repeat for commands like 3,a and 3,o.
+        repeatLastEdit(cm, vim, vim.insertModeRepeat - 1,
+            true /** repeatForInsert */);
+        vim.lastEditInputState.repeatOverride = vim.insertModeRepeat;
+      }
+      delete vim.insertModeRepeat;
+      cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
+      vim.insertMode = false;
+      cm.setOption('keyMap', 'vim');
+      cm.toggleOverwrite(false); // exit replace mode if we were in it.
+      CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
+    }
+
+    CodeMirror.keyMap['vim-insert'] = {
+      // TODO: override navigation keys so that Esc will cancel automatic
+      // indentation from o, O, i_<CR>
+      'Esc': exitInsertMode,
+      'Ctrl-[': exitInsertMode,
+      'Ctrl-C': exitInsertMode,
+      'Ctrl-N': 'autocomplete',
+      'Ctrl-P': 'autocomplete',
+      'Enter': function(cm) {
+        var fn = CodeMirror.commands.newlineAndIndentContinueComment ||
+            CodeMirror.commands.newlineAndIndent;
+        fn(cm);
+      },
+      fallthrough: ['default']
+    };
+
+    CodeMirror.keyMap['vim-replace'] = {
+      'Backspace': 'goCharLeft',
+      fallthrough: ['vim-insert']
+    };
+
+    function parseRegisterToKeyBuffer(macroModeState, registerName) {
+      var match, key;
+      var register = vimGlobalState.registerController.getRegister(registerName);
+      var text = register.toString();
+      var macroKeyBuffer = macroModeState.macroKeyBuffer;
+      emptyMacroKeyBuffer(macroModeState);
+      do {
+        match = (/<\w+-.+?>|<\w+>|./).exec(text);
+        if(match === null)break;
+        key = match[0];
+        text = text.substring(match.index + key.length);
+        macroKeyBuffer.push(key);
+      } while (text);
+      return macroKeyBuffer;
+    }
+
+    function parseKeyBufferToRegister(registerName, keyBuffer) {
+      var text = keyBuffer.join('');
+      vimGlobalState.registerController.setRegisterText(registerName, text);
+    }
+
+    function emptyMacroKeyBuffer(macroModeState) {
+      if(macroModeState.isMacroPlaying)return;
+      var macroKeyBuffer = macroModeState.macroKeyBuffer;
+      macroKeyBuffer.length = 0;
+    }
+
+    function executeMacroKeyBuffer(cm, macroModeState, keyBuffer) {
+      macroModeState.isMacroPlaying = true;
+      for (var i = 0, len = keyBuffer.length; i < len; i++) {
+        CodeMirror.Vim.handleKey(cm, keyBuffer[i]);
+      };
+      macroModeState.isMacroPlaying = false;
+    }
+
+    function logKey(macroModeState, key) {
+      if(macroModeState.isMacroPlaying)return;
+      var macroKeyBuffer = macroModeState.macroKeyBuffer;
+      macroKeyBuffer.push(key);
+    }
+
+    /**
+     * Listens for changes made in insert mode.
+     * Should only be active in insert mode.
+     */
+    function onChange(_cm, changeObj) {
+      var macroModeState = vimGlobalState.macroModeState;
+      var lastChange = macroModeState.lastInsertModeChanges;
+      while (changeObj) {
+        lastChange.expectCursorActivityForChange = true;
+        if (changeObj.origin == '+input' || changeObj.origin == 'paste'
+            || changeObj.origin === undefined /* only in testing */) {
+          var text = changeObj.text.join('\n');
+          lastChange.changes.push(text);
+        }
+        // Change objects may be chained with next.
+        changeObj = changeObj.next;
+      }
+    }
+
+    /**
+    * Listens for any kind of cursor activity on CodeMirror.
+    * - For tracking cursor activity in insert mode.
+    * - Should only be active in insert mode.
+    */
+    function onCursorActivity() {
+      var macroModeState = vimGlobalState.macroModeState;
+      var lastChange = macroModeState.lastInsertModeChanges;
+      if (lastChange.expectCursorActivityForChange) {
+        lastChange.expectCursorActivityForChange = false;
+      } else {
+        // Cursor moved outside the context of an edit. Reset the change.
+        lastChange.changes = [];
+      }
+    }
+
+    /** Wrapper for special keys pressed in insert mode */
+    function InsertModeKey(keyName) {
+      this.keyName = keyName;
+    }
+
+    /**
+    * Handles raw key down events from the text area.
+    * - Should only be active in insert mode.
+    * - For recording deletes in insert mode.
+    */
+    function onKeyEventTargetKeyDown(e) {
+      var macroModeState = vimGlobalState.macroModeState;
+      var lastChange = macroModeState.lastInsertModeChanges;
+      var keyName = CodeMirror.keyName(e);
+      function onKeyFound() {
+        lastChange.changes.push(new InsertModeKey(keyName));
+        return true;
+      }
+      if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) {
+        CodeMirror.lookupKey(keyName, ['vim-insert'], onKeyFound);
+      }
+    }
+
+    /**
+     * Repeats the last edit, which includes exactly 1 command and at most 1
+     * insert. Operator and motion commands are read from lastEditInputState,
+     * while action commands are read from lastEditActionCommand.
+     *
+     * If repeatForInsert is true, then the function was called by
+     * exitInsertMode to repeat the insert mode changes the user just made. The
+     * corresponding enterInsertMode call was made with a count.
+     */
+    function repeatLastEdit(cm, vim, repeat, repeatForInsert) {
+      var macroModeState = vimGlobalState.macroModeState;
+      macroModeState.inReplay = true;
+      var isAction = !!vim.lastEditActionCommand;
+      var cachedInputState = vim.inputState;
+      function repeatCommand() {
+        if (isAction) {
+          commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand);
+        } else {
+          commandDispatcher.evalInput(cm, vim);
+        }
+      }
+      function repeatInsert(repeat) {
+        if (macroModeState.lastInsertModeChanges.changes.length > 0) {
+          // For some reason, repeat cw in desktop VIM will does not repeat
+          // insert mode changes. Will conform to that behavior.
+          repeat = !vim.lastEditActionCommand ? 1 : repeat;
+          repeatLastInsertModeChanges(cm, repeat, macroModeState);
+        }
+      }
+      vim.inputState = vim.lastEditInputState;
+      if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) {
+        // o and O repeat have to be interlaced with insert repeats so that the
+        // insertions appear on separate lines instead of the last line.
+        for (var i = 0; i < repeat; i++) {
+          repeatCommand();
+          repeatInsert(1);
+        }
+      } else {
+        if (!repeatForInsert) {
+          // Hack to get the cursor to end up at the right place. If I is
+          // repeated in insert mode repeat, cursor will be 1 insert
+          // change set left of where it should be.
+          repeatCommand();
+        }
+        repeatInsert(repeat);
+      }
+      vim.inputState = cachedInputState;
+      if (vim.insertMode && !repeatForInsert) {
+        // Don't exit insert mode twice. If repeatForInsert is set, then we
+        // were called by an exitInsertMode call lower on the stack.
+        exitInsertMode(cm);
+      }
+      macroModeState.inReplay = false;
+    };
+
+    function repeatLastInsertModeChanges(cm, repeat, macroModeState) {
+      var lastChange = macroModeState.lastInsertModeChanges;
+      function keyHandler(binding) {
+        if (typeof binding == 'string') {
+          CodeMirror.commands[binding](cm);
+        } else {
+          binding(cm);
+        }
+        return true;
+      }
+      for (var i = 0; i < repeat; i++) {
+        for (var j = 0; j < lastChange.changes.length; j++) {
+          var change = lastChange.changes[j];
+          if (change instanceof InsertModeKey) {
+            CodeMirror.lookupKey(change.keyName, ['vim-insert'], keyHandler);
+          } else {
+            var cur = cm.getCursor();
+            cm.replaceRange(change, cur, cur);
+          }
+        }
+      }
+    }
+
+    resetVimGlobalState();
+    return vimApi;
+  };
+  // Initialize Vim and make it available as an API.
+  CodeMirror.Vim = Vim();
+}
+)();
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cu, Cc, Ci, components } = require("chrome");
 
 const TAB_SIZE    = "devtools.editor.tabsize";
 const EXPAND_TAB  = "devtools.editor.expandtab";
+const KEYMAP      = "devtools.editor.keymap";
 const L10N_BUNDLE = "chrome://browser/locale/devtools/sourceeditor.properties";
 const XUL_NS      = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 // Maximum allowed margin (in number of lines) from top or bottom of the editor
 // while shifting to a line which was initially out of view.
 const MAX_VERTICAL_OFFSET = 3;
 
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
@@ -42,17 +43,19 @@ const CM_SCRIPTS  = [
   "chrome://browser/content/devtools/codemirror/matchbrackets.js",
   "chrome://browser/content/devtools/codemirror/closebrackets.js",
   "chrome://browser/content/devtools/codemirror/comment.js",
   "chrome://browser/content/devtools/codemirror/javascript.js",
   "chrome://browser/content/devtools/codemirror/xml.js",
   "chrome://browser/content/devtools/codemirror/css.js",
   "chrome://browser/content/devtools/codemirror/htmlmixed.js",
   "chrome://browser/content/devtools/codemirror/clike.js",
-  "chrome://browser/content/devtools/codemirror/activeline.js"
+  "chrome://browser/content/devtools/codemirror/activeline.js",
+  "chrome://browser/content/devtools/codemirror/emacs.js",
+  "chrome://browser/content/devtools/codemirror/vim.js"
 ];
 
 const CM_IFRAME   =
   "data:text/html;charset=utf8,<!DOCTYPE html>" +
   "<html dir='ltr'>" +
   "  <head>" +
   "    <style>" +
   "      html, body { height: 100%; }" +
@@ -115,16 +118,17 @@ Editor.modes = {
  *
  * This object is also an event emitter.
  *
  * CodeMirror docs: http://codemirror.net/doc/manual.html
  */
 function Editor(config) {
   const tabSize = Services.prefs.getIntPref(TAB_SIZE);
   const useTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
+  const keyMap = Services.prefs.getCharPref(KEYMAP);
 
   this.version = null;
   this.config = {
     value:             "",
     mode:              Editor.modes.text,
     indentUnit:        tabSize,
     tabSize:           tabSize,
     contextMenu:       null,
@@ -141,16 +145,20 @@ function Editor(config) {
   this.config.extraKeys[Editor.keyFor("moveLineUp")] = (cm) => this.moveLineUp();
   this.config.extraKeys[Editor.keyFor("moveLineDown")] = (cm) => this.moveLineDown();
   this.config.extraKeys[Editor.keyFor("toggleComment")] = "toggleComment";
 
   // Disable ctrl-[ and ctrl-] because toolbox uses those shortcuts.
   this.config.extraKeys[Editor.keyFor("indentLess")] = false;
   this.config.extraKeys[Editor.keyFor("indentMore")] = false;
 
+  // If alternative keymap is provided, use it.
+  if (keyMap === "emacs" || keyMap === "vim")
+    this.config.keyMap = keyMap;
+
   // Overwrite default config with user-provided, if needed.
   Object.keys(config).forEach((k) => {
     if (k != "extraKeys") {
       this.config[k] = config[k];
       return;
     }
 
     if (!config.extraKeys)
@@ -228,16 +236,18 @@ Editor.prototype = {
       win.CodeMirror.defineMIME("text/css", cssSpec);
 
       let scssSpec = win.CodeMirror.resolveMode("text/x-scss");
       scssSpec.propertyKeywords = cssProperties;
       scssSpec.colorKeywords = cssColors;
       scssSpec.valueKeywords = cssValues;
       win.CodeMirror.defineMIME("text/x-scss", scssSpec);
 
+      win.CodeMirror.commands.save = () => this.emit("save");
+
       // Create a CodeMirror instance add support for context menus,
       // overwrite the default controller (otherwise items in the top and
       // context menus won't work).
 
       cm = win.CodeMirror(win.document.body, this.config);
       cm.getWrapperElement().addEventListener("contextmenu", (ev) => {
         ev.preventDefault();
         this.showContextMenu(el.ownerDocument, ev.screenX, ev.screenY);
--- a/browser/devtools/sourceeditor/test/browser.ini
+++ b/browser/devtools/sourceeditor/test/browser.ini
@@ -1,16 +1,18 @@
 [DEFAULT]
 support-files =
   cm_comment_test.js
   cm_driver.js
   cm_mode_javascript_test.js
   cm_mode_test.css
   cm_mode_test.js
   cm_test.js
+  cm_emacs_test.js
+  cm_vim_test.js
   codemirror.html
   head.js
 
 [browser_editor_basic.js]
 [browser_editor_cursor.js]
 [browser_editor_history.js]
 [browser_editor_markers.js]
 [browser_editor_movelines.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/cm_emacs_test.js
@@ -0,0 +1,135 @@
+(function() {
+  "use strict";
+
+  var Pos = CodeMirror.Pos;
+  namespace = "emacs_";
+
+  var eventCache = {};
+  function fakeEvent(keyName) {
+    var event = eventCache[key];
+    if (event) return event;
+
+    var ctrl, shift, alt;
+    var key = keyName.replace(/\w+-/g, function(type) {
+      if (type == "Ctrl-") ctrl = true;
+      else if (type == "Alt-") alt = true;
+      else if (type == "Shift-") shift = true;
+      return "";
+    });
+    var code;
+    for (var c in CodeMirror.keyNames)
+      if (CodeMirror.keyNames[c] == key) { code = c; break; }
+    if (c == null) throw new Error("Unknown key: " + key);
+
+    return eventCache[keyName] = {
+      type: "keydown", keyCode: code, ctrlKey: ctrl, shiftKey: shift, altKey: alt,
+      preventDefault: function(){}, stopPropagation: function(){}
+    };
+  }
+
+  function sim(name, start /*, actions... */) {
+    var keys = Array.prototype.slice.call(arguments, 2);
+    testCM(name, function(cm) {
+      for (var i = 0; i < keys.length; ++i) {
+        var cur = keys[i];
+        if (cur instanceof Pos) cm.setCursor(cur);
+        else if (cur.call) cur(cm);
+        else cm.triggerOnKeyDown(fakeEvent(cur));
+      }
+    }, {keyMap: "emacs", value: start, mode: "javascript"});
+  }
+
+  function at(line, ch) { return function(cm) { eqPos(cm.getCursor(), Pos(line, ch)); }; }
+  function txt(str) { return function(cm) { eq(cm.getValue(), str); }; }
+
+  sim("motionHSimple", "abc", "Ctrl-F", "Ctrl-F", "Ctrl-B", at(0, 1));
+  sim("motionHMulti", "abcde",
+      "Ctrl-4", "Ctrl-F", at(0, 4), "Ctrl--", "Ctrl-2", "Ctrl-F", at(0, 2),
+      "Ctrl-5", "Ctrl-B", at(0, 0));
+
+  sim("motionHWord", "abc. def ghi",
+      "Alt-F", at(0, 3), "Alt-F", at(0, 8),
+      "Ctrl-B", "Alt-B", at(0, 5), "Alt-B", at(0, 0));
+  sim("motionHWordMulti", "abc. def ghi ",
+      "Ctrl-3", "Alt-F", at(0, 12), "Ctrl-2", "Alt-B", at(0, 5),
+      "Ctrl--", "Alt-B", at(0, 8));
+
+  sim("motionVSimple", "a\nb\nc\n", "Ctrl-N", "Ctrl-N", "Ctrl-P", at(1, 0));
+  sim("motionVMulti", "a\nb\nc\nd\ne\n",
+      "Ctrl-2", "Ctrl-N", at(2, 0), "Ctrl-F", "Ctrl--", "Ctrl-N", at(1, 1),
+      "Ctrl--", "Ctrl-3", "Ctrl-P", at(4, 1));
+
+  sim("killYank", "abc\ndef\nghi",
+      "Ctrl-F", "Ctrl-Space", "Ctrl-N", "Ctrl-N", "Ctrl-W", "Ctrl-E", "Ctrl-Y",
+      txt("ahibc\ndef\ng"));
+  sim("killRing", "abcdef",
+      "Ctrl-Space", "Ctrl-F", "Ctrl-W", "Ctrl-Space", "Ctrl-F", "Ctrl-W",
+      "Ctrl-Y", "Alt-Y",
+      txt("acdef"));
+  sim("copyYank", "abcd",
+      "Ctrl-Space", "Ctrl-E", "Alt-W", "Ctrl-Y",
+      txt("abcdabcd"));
+
+  sim("killLineSimple", "foo\nbar", "Ctrl-F", "Ctrl-K", txt("f\nbar"));
+  sim("killLineEmptyLine", "foo\n  \nbar", "Ctrl-N", "Ctrl-K", txt("foo\nbar"));
+  sim("killLineMulti", "foo\nbar\nbaz",
+      "Ctrl-F", "Ctrl-F", "Ctrl-K", "Ctrl-K", "Ctrl-K", "Ctrl-A", "Ctrl-Y",
+      txt("o\nbarfo\nbaz"));
+
+  sim("moveByParagraph", "abc\ndef\n\n\nhij\nklm\n\n",
+      "Ctrl-F", "Ctrl-Down", at(2, 0), "Ctrl-Down", at(6, 0),
+      "Ctrl-N", "Ctrl-Up", at(3, 0), "Ctrl-Up", at(0, 0),
+      Pos(1, 2), "Ctrl-Down", at(2, 0), Pos(4, 2), "Ctrl-Up", at(3, 0));
+  sim("moveByParagraphMulti", "abc\n\ndef\n\nhij\n\nklm",
+      "Ctrl-U", "2", "Ctrl-Down", at(3, 0),
+      "Shift-Alt-.", "Ctrl-3", "Ctrl-Up", at(1, 0));
+
+  sim("moveBySentence", "sentence one! sentence\ntwo\n\nparagraph two",
+      "Alt-E", at(0, 13), "Alt-E", at(1, 3), "Ctrl-F", "Alt-A", at(0, 13));
+
+  sim("moveByExpr", "function foo(a, b) {}",
+      "Ctrl-Alt-F", at(0, 8), "Ctrl-Alt-F", at(0, 12), "Ctrl-Alt-F", at(0, 18),
+      "Ctrl-Alt-B", at(0, 12), "Ctrl-Alt-B", at(0, 9));
+  sim("moveByExprMulti", "foo bar baz bug",
+      "Ctrl-2", "Ctrl-Alt-F", at(0, 7),
+      "Ctrl--", "Ctrl-Alt-F", at(0, 4),
+      "Ctrl--", "Ctrl-2", "Ctrl-Alt-B", at(0, 11));
+  sim("delExpr", "var x = [\n  a,\n  b\n  c\n];",
+      Pos(0, 8), "Ctrl-Alt-K", txt("var x = ;"), "Ctrl-/",
+      Pos(4, 1), "Ctrl-Alt-Backspace", txt("var x = ;"));
+  sim("delExprMulti", "foo bar baz",
+      "Ctrl-2", "Ctrl-Alt-K", txt(" baz"),
+      "Ctrl-/", "Ctrl-E", "Ctrl-2", "Ctrl-Alt-Backspace", txt("foo "));
+
+  sim("justOneSpace", "hi      bye  ",
+      Pos(0, 4), "Alt-Space", txt("hi bye  "),
+      Pos(0, 4), "Alt-Space", txt("hi b ye  "),
+      "Ctrl-A", "Alt-Space", "Ctrl-E", "Alt-Space", txt(" hi b ye "));
+
+  sim("openLine", "foo bar", "Alt-F", "Ctrl-O", txt("foo\n bar"))
+
+  sim("transposeChar", "abcd\n\ne",
+      "Ctrl-F", "Ctrl-T", "Ctrl-T", txt("bcad\n\ne"), at(0, 3),
+      "Ctrl-F", "Ctrl-T", "Ctrl-T", "Ctrl-T", txt("bcda\n\ne"), at(0, 4),
+      "Ctrl-F", "Ctrl-T", txt("bcd\na\ne"), at(1, 1));
+
+  sim("manipWordCase", "foo BAR bAZ",
+      "Alt-C", "Alt-L", "Alt-U", txt("Foo bar BAZ"),
+      "Ctrl-A", "Alt-U", "Alt-L", "Alt-C", txt("FOO bar Baz"));
+  sim("manipWordCaseMulti", "foo Bar bAz",
+      "Ctrl-2", "Alt-U", txt("FOO BAR bAz"),
+      "Ctrl-A", "Ctrl-3", "Alt-C", txt("Foo Bar Baz"));
+
+  sim("upExpr", "foo {\n  bar[];\n  baz(blah);\n}",
+      Pos(2, 7), "Ctrl-Alt-U", at(2, 5), "Ctrl-Alt-U", at(0, 4));
+  sim("transposeExpr", "do foo[bar] dah",
+      Pos(0, 6), "Ctrl-Alt-T", txt("do [bar]foo dah"));
+
+  testCM("save", function(cm) {
+    var saved = false;
+    CodeMirror.commands.save = function(cm) { saved = cm.getValue(); };
+    cm.triggerOnKeyDown(fakeEvent("Ctrl-X"));
+    cm.triggerOnKeyDown(fakeEvent("Ctrl-S"));
+    is(saved, "hi");
+  }, {value: "hi", keyMap: "emacs"});
+})();
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/cm_vim_test.js
@@ -0,0 +1,2391 @@
+var code = '' +
+' wOrd1 (#%\n' +
+' word3] \n' +
+'aopop pop 0 1 2 3 4\n' +
+' (a) [b] {c} \n' +
+'int getchar(void) {\n' +
+'  static char buf[BUFSIZ];\n' +
+'  static char *bufp = buf;\n' +
+'  if (n == 0) {  /* buffer is empty */\n' +
+'    n = read(0, buf, sizeof buf);\n' +
+'    bufp = buf;\n' +
+'  }\n' +
+'\n' +
+'  return (--n >= 0) ? (unsigned char) *bufp++ : EOF;\n' +
+' \n' +
+'}\n';
+
+var lines = (function() {
+  lineText = code.split('\n');
+  var ret = [];
+  for (var i = 0; i < lineText.length; i++) {
+    ret[i] = {
+      line: i,
+      length: lineText[i].length,
+      lineText: lineText[i],
+      textStart: /^\s*/.exec(lineText[i])[0].length
+    };
+  }
+  return ret;
+})();
+var endOfDocument = makeCursor(lines.length - 1,
+    lines[lines.length - 1].length);
+var wordLine = lines[0];
+var bigWordLine = lines[1];
+var charLine = lines[2];
+var bracesLine = lines[3];
+var seekBraceLine = lines[4];
+
+var word1 = {
+  start: { line: wordLine.line, ch: 1 },
+  end: { line: wordLine.line, ch: 5 }
+};
+var word2 = {
+  start: { line: wordLine.line, ch: word1.end.ch + 2 },
+  end: { line: wordLine.line, ch: word1.end.ch + 4 }
+};
+var word3 = {
+  start: { line: bigWordLine.line, ch: 1 },
+  end: { line: bigWordLine.line, ch: 5 }
+};
+var bigWord1 = word1;
+var bigWord2 = word2;
+var bigWord3 = {
+  start: { line: bigWordLine.line, ch: 1 },
+  end: { line: bigWordLine.line, ch: 7 }
+};
+var bigWord4 = {
+  start: { line: bigWordLine.line, ch: bigWord1.end.ch + 3 },
+  end: { line: bigWordLine.line, ch: bigWord1.end.ch + 7 }
+};
+
+var oChars = [ { line: charLine.line, ch: 1 },
+    { line: charLine.line, ch: 3 },
+    { line: charLine.line, ch: 7 } ];
+var pChars = [ { line: charLine.line, ch: 2 },
+    { line: charLine.line, ch: 4 },
+    { line: charLine.line, ch: 6 },
+    { line: charLine.line, ch: 8 } ];
+var numChars = [ { line: charLine.line, ch: 10 },
+    { line: charLine.line, ch: 12 },
+    { line: charLine.line, ch: 14 },
+    { line: charLine.line, ch: 16 },
+    { line: charLine.line, ch: 18 }];
+var parens1 = {
+  start: { line: bracesLine.line, ch: 1 },
+  end: { line: bracesLine.line, ch: 3 }
+};
+var squares1 = {
+  start: { line: bracesLine.line, ch: 5 },
+  end: { line: bracesLine.line, ch: 7 }
+};
+var curlys1 = {
+  start: { line: bracesLine.line, ch: 9 },
+  end: { line: bracesLine.line, ch: 11 }
+};
+var seekOutside = {
+  start: { line: seekBraceLine.line, ch: 1 },
+  end: { line: seekBraceLine.line, ch: 16 }
+};
+var seekInside = {
+  start: { line: seekBraceLine.line, ch: 14 },
+  end: { line: seekBraceLine.line, ch: 11 }
+};
+
+function copyCursor(cur) {
+  return { ch: cur.ch, line: cur.line };
+}
+
+function testVim(name, run, opts, expectedFail) {
+  var vimOpts = {
+    lineNumbers: true,
+    vimMode: true,
+    showCursorWhenSelecting: true,
+    value: code
+  };
+  for (var prop in opts) {
+    if (opts.hasOwnProperty(prop)) {
+      vimOpts[prop] = opts[prop];
+    }
+  }
+  return test('vim_' + name, function() {
+    var place = document.getElementById("testground");
+    var cm = CodeMirror(place, vimOpts);
+    var vim = CodeMirror.Vim.maybeInitVimState_(cm);
+
+    function doKeysFn(cm) {
+      return function(args) {
+        if (args instanceof Array) {
+          arguments = args;
+        }
+        for (var i = 0; i < arguments.length; i++) {
+          CodeMirror.Vim.handleKey(cm, arguments[i]);
+        }
+      }
+    }
+    function doInsertModeKeysFn(cm) {
+      return function(args) {
+        if (args instanceof Array) { arguments = args; }
+        function executeHandler(handler) {
+          if (typeof handler == 'string') {
+            CodeMirror.commands[handler](cm);
+          } else {
+            handler(cm);
+          }
+          return true;
+        }
+        for (var i = 0; i < arguments.length; i++) {
+          var key = arguments[i];
+          // Find key in keymap and handle.
+          var handled = CodeMirror.lookupKey(key, ['vim-insert'], executeHandler);
+          // Record for insert mode.
+          if (handled === true && cm.state.vim.insertMode && arguments[i] != 'Esc') {
+            var lastChange = CodeMirror.Vim.getVimGlobalState_().macroModeState.lastInsertModeChanges;
+            if (lastChange) {
+              lastChange.changes.push(new CodeMirror.Vim.InsertModeKey(key));
+            }
+          }
+        }
+      }
+    }
+    function doExFn(cm) {
+      return function(command) {
+        cm.openDialog = helpers.fakeOpenDialog(command);
+        helpers.doKeys(':');
+      }
+    }
+    function assertCursorAtFn(cm) {
+      return function(line, ch) {
+        var pos;
+        if (ch == null && typeof line.line == 'number') {
+          pos = line;
+        } else {
+          pos = makeCursor(line, ch);
+        }
+        eqPos(pos, cm.getCursor());
+      }
+    }
+    function fakeOpenDialog(result) {
+      return function(text, callback) {
+        return callback(result);
+      }
+    }
+    var helpers = {
+      doKeys: doKeysFn(cm),
+      // Warning: Only emulates keymap events, not character insertions. Use
+      // replaceRange to simulate character insertions.
+      // Keys are in CodeMirror format, NOT vim format.
+      doInsertModeKeys: doInsertModeKeysFn(cm),
+      doEx: doExFn(cm),
+      assertCursorAt: assertCursorAtFn(cm),
+      fakeOpenDialog: fakeOpenDialog,
+      getRegisterController: function() {
+        return CodeMirror.Vim.getRegisterController();
+      }
+    }
+    CodeMirror.Vim.resetVimGlobalState_();
+    var successful = false;
+    try {
+      run(cm, vim, helpers);
+      successful = true;
+    } finally {
+      if ((debug && !successful) || verbose) {
+        place.style.visibility = "visible";
+      } else {
+        place.removeChild(cm.getWrapperElement());
+      }
+    }
+  }, expectedFail);
+};
+testVim('qq@q', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('q', 'q', 'l', 'l', 'q');
+  helpers.assertCursorAt(0,2);
+  helpers.doKeys('@', 'q');
+  helpers.assertCursorAt(0,4);
+}, { value: '            '});
+testVim('@@', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('q', 'q', 'l', 'l', 'q');
+  helpers.assertCursorAt(0,2);
+  helpers.doKeys('@', 'q');
+  helpers.assertCursorAt(0,4);
+  helpers.doKeys('@', '@');
+  helpers.assertCursorAt(0,6);
+}, { value: '            '});
+var jumplistScene = ''+
+  'word\n'+
+  '(word)\n'+
+  '{word\n'+
+  'word.\n'+
+  '\n'+
+  'word search\n'+
+  '}word\n'+
+  'word\n'+
+  'word\n';
+function testJumplist(name, keys, endPos, startPos, dialog) {
+  endPos = makeCursor(endPos[0], endPos[1]);
+  startPos = makeCursor(startPos[0], startPos[1]);
+  testVim(name, function(cm, vim, helpers) {
+    CodeMirror.Vim.resetVimGlobalState_();
+    if(dialog)cm.openDialog = helpers.fakeOpenDialog('word');
+    cm.setCursor(startPos);
+    helpers.doKeys.apply(null, keys);
+    helpers.assertCursorAt(endPos);
+  }, {value: jumplistScene});
+};
+testJumplist('jumplist_H', ['H', '<C-o>'], [5,2], [5,2]);
+testJumplist('jumplist_M', ['M', '<C-o>'], [2,2], [2,2]);
+testJumplist('jumplist_L', ['L', '<C-o>'], [2,2], [2,2]);
+testJumplist('jumplist_[[', ['[', '[', '<C-o>'], [5,2], [5,2]);
+testJumplist('jumplist_]]', [']', ']', '<C-o>'], [2,2], [2,2]);
+testJumplist('jumplist_G', ['G', '<C-o>'], [5,2], [5,2]);
+testJumplist('jumplist_gg', ['g', 'g', '<C-o>'], [5,2], [5,2]);
+testJumplist('jumplist_%', ['%', '<C-o>'], [1,5], [1,5]);
+testJumplist('jumplist_{', ['{', '<C-o>'], [1,5], [1,5]);
+testJumplist('jumplist_}', ['}', '<C-o>'], [1,5], [1,5]);
+testJumplist('jumplist_\'', ['m', 'a', 'h', '\'', 'a', 'h', '<C-i>'], [1,5], [1,5]);
+testJumplist('jumplist_`', ['m', 'a', 'h', '`', 'a', 'h', '<C-i>'], [1,5], [1,5]);
+testJumplist('jumplist_*_cachedCursor', ['*', '<C-o>'], [1,3], [1,3]);
+testJumplist('jumplist_#_cachedCursor', ['#', '<C-o>'], [1,3], [1,3]);
+testJumplist('jumplist_n', ['#', 'n', '<C-o>'], [1,1], [2,3]);
+testJumplist('jumplist_N', ['#', 'N', '<C-o>'], [1,1], [2,3]);
+testJumplist('jumplist_repeat_<c-o>', ['*', '*', '*', '3', '<C-o>'], [2,3], [2,3]);
+testJumplist('jumplist_repeat_<c-i>', ['*', '*', '*', '3', '<C-o>', '2', '<C-i>'], [5,0], [2,3]);
+testJumplist('jumplist_repeated_motion', ['3', '*', '<C-o>'], [2,3], [2,3]);
+testJumplist('jumplist_/', ['/', '<C-o>'], [2,3], [2,3], 'dialog');
+testJumplist('jumplist_?', ['?', '<C-o>'], [2,3], [2,3], 'dialog');
+testJumplist('jumplist_skip_delted_mark<c-o>',
+             ['*', 'n', 'n', 'k', 'd', 'k', '<C-o>', '<C-o>', '<C-o>'],
+             [0,2], [0,2]);
+testJumplist('jumplist_skip_delted_mark<c-i>',
+             ['*', 'n', 'n', 'k', 'd', 'k', '<C-o>', '<C-i>', '<C-i>'],
+             [1,0], [0,2]);
+/**
+ * @param name Name of the test
+ * @param keys An array of keys or a string with a single key to simulate.
+ * @param endPos The expected end position of the cursor.
+ * @param startPos The position the cursor should start at, defaults to 0, 0.
+ */
+function testMotion(name, keys, endPos, startPos) {
+  testVim(name, function(cm, vim, helpers) {
+    if (!startPos) {
+      startPos = { line: 0, ch: 0 };
+    }
+    cm.setCursor(startPos);
+    helpers.doKeys(keys);
+    helpers.assertCursorAt(endPos);
+  });
+};
+
+function makeCursor(line, ch) {
+  return { line: line, ch: ch };
+};
+
+function offsetCursor(cur, offsetLine, offsetCh) {
+  return { line: cur.line + offsetLine, ch: cur.ch + offsetCh };
+};
+
+// Motion tests
+testMotion('|', '|', makeCursor(0, 0), makeCursor(0,4));
+testMotion('|_repeat', ['3', '|'], makeCursor(0, 2), makeCursor(0,4));
+testMotion('h', 'h', makeCursor(0, 0), word1.start);
+testMotion('h_repeat', ['3', 'h'], offsetCursor(word1.end, 0, -3), word1.end);
+testMotion('l', 'l', makeCursor(0, 1));
+testMotion('l_repeat', ['2', 'l'], makeCursor(0, 2));
+testMotion('j', 'j', offsetCursor(word1.end, 1, 0), word1.end);
+testMotion('j_repeat', ['2', 'j'], offsetCursor(word1.end, 2, 0), word1.end);
+testMotion('j_repeat_clip', ['1000', 'j'], endOfDocument);
+testMotion('k', 'k', offsetCursor(word3.end, -1, 0), word3.end);
+testMotion('k_repeat', ['2', 'k'], makeCursor(0, 4), makeCursor(2, 4));
+testMotion('k_repeat_clip', ['1000', 'k'], makeCursor(0, 4), makeCursor(2, 4));
+testMotion('w', 'w', word1.start);
+testMotion('w_multiple_newlines_no_space', 'w', makeCursor(12, 2), makeCursor(11, 2));
+testMotion('w_multiple_newlines_with_space', 'w', makeCursor(14, 0), makeCursor(12, 51));
+testMotion('w_repeat', ['2', 'w'], word2.start);
+testMotion('w_wrap', ['w'], word3.start, word2.start);
+testMotion('w_endOfDocument', 'w', endOfDocument, endOfDocument);
+testMotion('w_start_to_end', ['1000', 'w'], endOfDocument, makeCursor(0, 0));
+testMotion('W', 'W', bigWord1.start);
+testMotion('W_repeat', ['2', 'W'], bigWord3.start, bigWord1.start);
+testMotion('e', 'e', word1.end);
+testMotion('e_repeat', ['2', 'e'], word2.end);
+testMotion('e_wrap', 'e', word3.end, word2.end);
+testMotion('e_endOfDocument', 'e', endOfDocument, endOfDocument);
+testMotion('e_start_to_end', ['1000', 'e'], endOfDocument, makeCursor(0, 0));
+testMotion('b', 'b', word3.start, word3.end);
+testMotion('b_repeat', ['2', 'b'], word2.start, word3.end);
+testMotion('b_wrap', 'b', word2.start, word3.start);
+testMotion('b_startOfDocument', 'b', makeCursor(0, 0), makeCursor(0, 0));
+testMotion('b_end_to_start', ['1000', 'b'], makeCursor(0, 0), endOfDocument);
+testMotion('ge', ['g', 'e'], word2.end, word3.end);
+testMotion('ge_repeat', ['2', 'g', 'e'], word1.end, word3.start);
+testMotion('ge_wrap', ['g', 'e'], word2.end, word3.start);
+testMotion('ge_startOfDocument', ['g', 'e'], makeCursor(0, 0),
+    makeCursor(0, 0));
+testMotion('ge_end_to_start', ['1000', 'g', 'e'], makeCursor(0, 0), endOfDocument);
+testMotion('gg', ['g', 'g'], makeCursor(lines[0].line, lines[0].textStart),
+    makeCursor(3, 1));
+testMotion('gg_repeat', ['3', 'g', 'g'],
+    makeCursor(lines[2].line, lines[2].textStart));
+testMotion('G', 'G',
+    makeCursor(lines[lines.length - 1].line, lines[lines.length - 1].textStart),
+    makeCursor(3, 1));
+testMotion('G_repeat', ['3', 'G'], makeCursor(lines[2].line,
+    lines[2].textStart));
+// TODO: Make the test code long enough to test Ctrl-F and Ctrl-B.
+testMotion('0', '0', makeCursor(0, 0), makeCursor(0, 8));
+testMotion('^', '^', makeCursor(0, lines[0].textStart), makeCursor(0, 8));
+testMotion('+', '+', makeCursor(1, lines[1].textStart), makeCursor(0, 8));
+testMotion('-', '-', makeCursor(0, lines[0].textStart), makeCursor(1, 4));
+testMotion('_', ['6','_'], makeCursor(5, lines[5].textStart), makeCursor(0, 8));
+testMotion('$', '$', makeCursor(0, lines[0].length - 1), makeCursor(0, 1));
+testMotion('$_repeat', ['2', '$'], makeCursor(1, lines[1].length - 1),
+    makeCursor(0, 3));
+testMotion('f', ['f', 'p'], pChars[0], makeCursor(charLine.line, 0));
+testMotion('f_repeat', ['2', 'f', 'p'], pChars[2], pChars[0]);
+testMotion('f_num', ['f', '2'], numChars[2], makeCursor(charLine.line, 0));
+testMotion('t', ['t','p'], offsetCursor(pChars[0], 0, -1),
+    makeCursor(charLine.line, 0));
+testMotion('t_repeat', ['2', 't', 'p'], offsetCursor(pChars[2], 0, -1),
+    pChars[0]);
+testMotion('F', ['F', 'p'], pChars[0], pChars[1]);
+testMotion('F_repeat', ['2', 'F', 'p'], pChars[0], pChars[2]);
+testMotion('T', ['T', 'p'], offsetCursor(pChars[0], 0, 1), pChars[1]);
+testMotion('T_repeat', ['2', 'T', 'p'], offsetCursor(pChars[0], 0, 1), pChars[2]);
+testMotion('%_parens', ['%'], parens1.end, parens1.start);
+testMotion('%_squares', ['%'], squares1.end, squares1.start);
+testMotion('%_braces', ['%'], curlys1.end, curlys1.start);
+testMotion('%_seek_outside', ['%'], seekOutside.end, seekOutside.start);
+testMotion('%_seek_inside', ['%'], seekInside.end, seekInside.start);
+testVim('%_seek_skip', function(cm, vim, helpers) {
+  cm.setCursor(0,0);
+  helpers.doKeys(['%']);
+  helpers.assertCursorAt(0,9);
+}, {value:'01234"("()'});
+testVim('%_skip_string', function(cm, vim, helpers) {
+  cm.setCursor(0,0);
+  helpers.doKeys(['%']);
+  helpers.assertCursorAt(0,4);
+  cm.setCursor(0,2);
+  helpers.doKeys(['%']);
+  helpers.assertCursorAt(0,0);
+}, {value:'(")")'});
+(')')
+testVim('%_skip_comment', function(cm, vim, helpers) {
+  cm.setCursor(0,0);
+  helpers.doKeys(['%']);
+  helpers.assertCursorAt(0,6);
+  cm.setCursor(0,3);
+  helpers.doKeys(['%']);
+  helpers.assertCursorAt(0,0);
+}, {value:'(/*)*/)'});
+// Make sure that moving down after going to the end of a line always leaves you
+// at the end of a line, but preserves the offset in other cases
+testVim('Changing lines after Eol operation', function(cm, vim, helpers) {
+  cm.setCursor(0,0);
+  helpers.doKeys(['$']);
+  helpers.doKeys(['j']);
+  // After moving to Eol and then down, we should be at Eol of line 2
+  helpers.assertCursorAt({ line: 1, ch: lines[1].length - 1 });
+  helpers.doKeys(['j']);
+  // After moving down, we should be at Eol of line 3
+  helpers.assertCursorAt({ line: 2, ch: lines[2].length - 1 });
+  helpers.doKeys(['h']);
+  helpers.doKeys(['j']);
+  // After moving back one space and then down, since line 4 is shorter than line 2, we should
+  // be at Eol of line 2 - 1
+  helpers.assertCursorAt({ line: 3, ch: lines[3].length - 1 });
+  helpers.doKeys(['j']);
+  helpers.doKeys(['j']);
+  // After moving down again, since line 3 has enough characters, we should be back to the
+  // same place we were at on line 1
+  helpers.assertCursorAt({ line: 5, ch: lines[2].length - 2 });
+});
+//making sure gj and gk recover from clipping
+testVim('gj_gk_clipping', function(cm,vim,helpers){
+  cm.setCursor(0, 1);
+  helpers.doKeys('g','j','g','j');
+  helpers.assertCursorAt(2, 1);
+  helpers.doKeys('g','k','g','k');
+  helpers.assertCursorAt(0, 1);
+},{value: 'line 1\n\nline 2'});
+//testing a mix of j/k and gj/gk
+testVim('j_k_and_gj_gk', function(cm,vim,helpers){
+  cm.setSize(120);
+  cm.setCursor(0, 0);
+  //go to the last character on the first line
+  helpers.doKeys('$');
+  //move up/down on the column within the wrapped line
+  //side-effect: cursor is not locked to eol anymore
+  helpers.doKeys('g','k');
+  var cur=cm.getCursor();
+  eq(cur.line,0);
+  is((cur.ch<176),'gk didn\'t move cursor back (1)');
+  helpers.doKeys('g','j');
+  helpers.assertCursorAt(0, 176);
+  //should move to character 177 on line 2 (j/k preserve character index within line)
+  helpers.doKeys('j');
+  //due to different line wrapping, the cursor can be on a different screen-x now
+  //gj and gk preserve screen-x on movement, much like moveV
+  helpers.doKeys('3','g','k');
+  cur=cm.getCursor();
+  eq(cur.line,1);
+  is((cur.ch<176),'gk didn\'t move cursor back (2)');
+  helpers.doKeys('g','j','2','g','j');
+  //should return to the same character-index
+  helpers.doKeys('k');
+  helpers.assertCursorAt(0, 176);
+},{ lineWrapping:true, value: 'This line is intentially long to test movement of gj and gk over wrapped lines. I will start on the end of this line, then make a step up and back to set the origin for j and k.\nThis line is supposed to be even longer than the previous. I will jump here and make another wiggle with gj and gk, before I jump back to the line above. Both wiggles should not change my cursor\'s target character but both j/k and gj/gk change each other\'s reference position.'});
+testVim('gj_gk', function(cm, vim, helpers) {
+  if (phantom) return;
+  cm.setSize(120);
+  // Test top of document edge case.
+  cm.setCursor(0, 4);
+  helpers.doKeys('g', 'j');
+  helpers.doKeys('10', 'g', 'k');
+  helpers.assertCursorAt(0, 4);
+
+  // Test moving down preserves column position.
+  helpers.doKeys('g', 'j');
+  var pos1 = cm.getCursor();
+  var expectedPos2 = { line: 0, ch: (pos1.ch - 4) * 2 + 4};
+  helpers.doKeys('g', 'j');
+  helpers.assertCursorAt(expectedPos2);
+
+  // Move to the last character
+  cm.setCursor(0, 0);
+  // Move left to reset HSPos
+  helpers.doKeys('h');
+  // Test bottom of document edge case.
+  helpers.doKeys('100', 'g', 'j');
+  var endingPos = cm.getCursor();
+  is(endingPos != 0, 'gj should not be on wrapped line 0');
+  var topLeftCharCoords = cm.charCoords(makeCursor(0, 0));
+  var endingCharCoords = cm.charCoords(endingPos);
+  is(topLeftCharCoords.left == endingCharCoords.left, 'gj should end up on column 0');
+},{ lineNumbers: false, lineWrapping:true, value: 'Thislineisintentiallylongtotestmovementofgjandgkoverwrappedlines.' });
+testVim('}', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('}');
+  helpers.assertCursorAt(1, 0);
+  cm.setCursor(0, 0);
+  helpers.doKeys('2', '}');
+  helpers.assertCursorAt(4, 0);
+  cm.setCursor(0, 0);
+  helpers.doKeys('6', '}');
+  helpers.assertCursorAt(5, 0);
+}, { value: 'a\n\nb\nc\n\nd' });
+testVim('{', function(cm, vim, helpers) {
+  cm.setCursor(5, 0);
+  helpers.doKeys('{');
+  helpers.assertCursorAt(4, 0);
+  cm.setCursor(5, 0);
+  helpers.doKeys('2', '{');
+  helpers.assertCursorAt(1, 0);
+  cm.setCursor(5, 0);
+  helpers.doKeys('6', '{');
+  helpers.assertCursorAt(0, 0);
+}, { value: 'a\n\nb\nc\n\nd' });
+
+// Operator tests
+testVim('dl', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 0);
+  cm.setCursor(curStart);
+  helpers.doKeys('d', 'l');
+  eq('word1 ', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq(' ', register.text);
+  is(!register.linewise);
+  eqPos(curStart, cm.getCursor());
+}, { value: ' word1 ' });
+testVim('dl_eol', function(cm, vim, helpers) {
+  cm.setCursor(0, 6);
+  helpers.doKeys('d', 'l');
+  eq(' word1', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq(' ', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 5);
+}, { value: ' word1 ' });
+testVim('dl_repeat', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 0);
+  cm.setCursor(curStart);
+  helpers.doKeys('2', 'd', 'l');
+  eq('ord1 ', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq(' w', register.text);
+  is(!register.linewise);
+  eqPos(curStart, cm.getCursor());
+}, { value: ' word1 ' });
+testVim('dh', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 3);
+  cm.setCursor(curStart);
+  helpers.doKeys('d', 'h');
+  eq(' wrd1 ', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('o', register.text);
+  is(!register.linewise);
+  eqPos(offsetCursor(curStart, 0 , -1), cm.getCursor());
+}, { value: ' word1 ' });
+testVim('dj', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 3);
+  cm.setCursor(curStart);
+  helpers.doKeys('d', 'j');
+  eq(' word3', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq(' word1\nword2\n', register.text);
+  is(register.linewise);
+  helpers.assertCursorAt(0, 1);
+}, { value: ' word1\nword2\n word3' });
+testVim('dj_end_of_document', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 3);
+  cm.setCursor(curStart);
+  helpers.doKeys('d', 'j');
+  eq(' word1 ', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 3);
+}, { value: ' word1 ' });
+testVim('dk', function(cm, vim, helpers) {
+  var curStart = makeCursor(1, 3);
+  cm.setCursor(curStart);
+  helpers.doKeys('d', 'k');
+  eq(' word3', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq(' word1\nword2\n', register.text);
+  is(register.linewise);
+  helpers.assertCursorAt(0, 1);
+}, { value: ' word1\nword2\n word3' });
+testVim('dk_start_of_document', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 3);
+  cm.setCursor(curStart);
+  helpers.doKeys('d', 'k');
+  eq(' word1 ', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 3);
+}, { value: ' word1 ' });
+testVim('dw_space', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 0);
+  cm.setCursor(curStart);
+  helpers.doKeys('d', 'w');
+  eq('word1 ', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq(' ', register.text);
+  is(!register.linewise);
+  eqPos(curStart, cm.getCursor());
+}, { value: ' word1 ' });
+testVim('dw_word', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 1);
+  cm.setCursor(curStart);
+  helpers.doKeys('d', 'w');
+  eq(' word2', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('word1 ', register.text);
+  is(!register.linewise);
+  eqPos(curStart, cm.getCursor());
+}, { value: ' word1 word2' });
+testVim('dw_only_word', function(cm, vim, helpers) {
+  // Test that if there is only 1 word left, dw deletes till the end of the
+  // line.
+  cm.setCursor(0, 1);
+  helpers.doKeys('d', 'w');
+  eq(' ', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('word1 ', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 0);
+}, { value: ' word1 ' });
+testVim('dw_eol', function(cm, vim, helpers) {
+  // Assert that dw does not delete the newline if last word to delete is at end
+  // of line.
+  cm.setCursor(0, 1);
+  helpers.doKeys('d', 'w');
+  eq(' \nword2', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('word1', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 0);
+}, { value: ' word1\nword2' });
+testVim('dw_eol_with_multiple_newlines', function(cm, vim, helpers) {
+  // Assert that dw does not delete the newline if last word to delete is at end
+  // of line and it is followed by multiple newlines.
+  cm.setCursor(0, 1);
+  helpers.doKeys('d', 'w');
+  eq(' \n\nword2', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('word1', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 0);
+}, { value: ' word1\n\nword2' });
+testVim('dw_empty_line_followed_by_whitespace', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', 'w');
+  eq('  \nword', cm.getValue());
+}, { value: '\n  \nword' });
+testVim('dw_empty_line_followed_by_word', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', 'w');
+  eq('word', cm.getValue());
+}, { value: '\nword' });
+testVim('dw_empty_line_followed_by_empty_line', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', 'w');
+  eq('\n', cm.getValue());
+}, { value: '\n\n' });
+testVim('dw_whitespace_followed_by_whitespace', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', 'w');
+  eq('\n   \n', cm.getValue());
+}, { value: '  \n   \n' });
+testVim('dw_whitespace_followed_by_empty_line', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', 'w');
+  eq('\n\n', cm.getValue());
+}, { value: '  \n\n' });
+testVim('dw_word_whitespace_word', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', 'w');
+  eq('\n   \nword2', cm.getValue());
+}, { value: 'word1\n   \nword2'})
+testVim('dw_end_of_document', function(cm, vim, helpers) {
+  cm.setCursor(1, 2);
+  helpers.doKeys('d', 'w');
+  eq('\nab', cm.getValue());
+}, { value: '\nabc' });
+testVim('dw_repeat', function(cm, vim, helpers) {
+  // Assert that dw does delete newline if it should go to the next line, and
+  // that repeat works properly.
+  cm.setCursor(0, 1);
+  helpers.doKeys('d', '2', 'w');
+  eq(' ', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('word1\nword2', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 0);
+}, { value: ' word1\nword2' });
+testVim('de_word_start_and_empty_lines', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', 'e');
+  eq('\n\n', cm.getValue());
+}, { value: 'word\n\n' });
+testVim('de_word_end_and_empty_lines', function(cm, vim, helpers) {
+  cm.setCursor(0, 3);
+  helpers.doKeys('d', 'e');
+  eq('wor', cm.getValue());
+}, { value: 'word\n\n\n' });
+testVim('de_whitespace_and_empty_lines', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', 'e');
+  eq('', cm.getValue());
+}, { value: '   \n\n\n' });
+testVim('de_end_of_document', function(cm, vim, helpers) {
+  cm.setCursor(1, 2);
+  helpers.doKeys('d', 'e');
+  eq('\nab', cm.getValue());
+}, { value: '\nabc' });
+testVim('db_empty_lines', function(cm, vim, helpers) {
+  cm.setCursor(2, 0);
+  helpers.doKeys('d', 'b');
+  eq('\n\n', cm.getValue());
+}, { value: '\n\n\n' });
+testVim('db_word_start_and_empty_lines', function(cm, vim, helpers) {
+  cm.setCursor(2, 0);
+  helpers.doKeys('d', 'b');
+  eq('\nword', cm.getValue());
+}, { value: '\n\nword' });
+testVim('db_word_end_and_empty_lines', function(cm, vim, helpers) {
+  cm.setCursor(2, 3);
+  helpers.doKeys('d', 'b');
+  eq('\n\nd', cm.getValue());
+}, { value: '\n\nword' });
+testVim('db_whitespace_and_empty_lines', function(cm, vim, helpers) {
+  cm.setCursor(2, 0);
+  helpers.doKeys('d', 'b');
+  eq('', cm.getValue());
+}, { value: '\n   \n' });
+testVim('db_start_of_document', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', 'b');
+  eq('abc\n', cm.getValue());
+}, { value: 'abc\n' });
+testVim('dge_empty_lines', function(cm, vim, helpers) {
+  cm.setCursor(1, 0);
+  helpers.doKeys('d', 'g', 'e');
+  // Note: In real VIM the result should be '', but it's not quite consistent,
+  // since 2 newlines are deleted. But in the similar case of word\n\n, only
+  // 1 newline is deleted. We'll diverge from VIM's behavior since it's much
+  // easier this way.
+  eq('\n', cm.getValue());
+}, { value: '\n\n' });
+testVim('dge_word_and_empty_lines', function(cm, vim, helpers) {
+  cm.setCursor(1, 0);
+  helpers.doKeys('d', 'g', 'e');
+  eq('wor\n', cm.getValue());
+}, { value: 'word\n\n'});
+testVim('dge_whitespace_and_empty_lines', function(cm, vim, helpers) {
+  cm.setCursor(2, 0);
+  helpers.doKeys('d', 'g', 'e');
+  eq('', cm.getValue());
+}, { value: '\n  \n' });
+testVim('dge_start_of_document', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', 'g', 'e');
+  eq('bc\n', cm.getValue());
+}, { value: 'abc\n' });
+testVim('d_inclusive', function(cm, vim, helpers) {
+  // Assert that when inclusive is set, the character the cursor is on gets
+  // deleted too.
+  var curStart = makeCursor(0, 1);
+  cm.setCursor(curStart);
+  helpers.doKeys('d', 'e');
+  eq('  ', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('word1', register.text);
+  is(!register.linewise);
+  eqPos(curStart, cm.getCursor());
+}, { value: ' word1 ' });
+testVim('d_reverse', function(cm, vim, helpers) {
+  // Test that deleting in reverse works.
+  cm.setCursor(1, 0);
+  helpers.doKeys('d', 'b');
+  eq(' word2 ', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('word1\n', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 1);
+}, { value: ' word1\nword2 ' });
+testVim('dd', function(cm, vim, helpers) {
+  cm.setCursor(0, 3);
+  var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
+    { line: 1, ch: 0 });
+  var expectedLineCount = cm.lineCount() - 1;
+  helpers.doKeys('d', 'd');
+  eq(expectedLineCount, cm.lineCount());
+  var register = helpers.getRegisterController().getRegister();
+  eq(expectedBuffer, register.text);
+  is(register.linewise);
+  helpers.assertCursorAt(0, lines[1].textStart);
+});
+testVim('dd_prefix_repeat', function(cm, vim, helpers) {
+  cm.setCursor(0, 3);
+  var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
+    { line: 2, ch: 0 });
+  var expectedLineCount = cm.lineCount() - 2;
+  helpers.doKeys('2', 'd', 'd');
+  eq(expectedLineCount, cm.lineCount());
+  var register = helpers.getRegisterController().getRegister();
+  eq(expectedBuffer, register.text);
+  is(register.linewise);
+  helpers.assertCursorAt(0, lines[2].textStart);
+});
+testVim('dd_motion_repeat', function(cm, vim, helpers) {
+  cm.setCursor(0, 3);
+  var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
+    { line: 2, ch: 0 });
+  var expectedLineCount = cm.lineCount() - 2;
+  helpers.doKeys('d', '2', 'd');
+  eq(expectedLineCount, cm.lineCount());
+  var register = helpers.getRegisterController().getRegister();
+  eq(expectedBuffer, register.text);
+  is(register.linewise);
+  helpers.assertCursorAt(0, lines[2].textStart);
+});
+testVim('dd_multiply_repeat', function(cm, vim, helpers) {
+  cm.setCursor(0, 3);
+  var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
+    { line: 6, ch: 0 });
+  var expectedLineCount = cm.lineCount() - 6;
+  helpers.doKeys('2', 'd', '3', 'd');
+  eq(expectedLineCount, cm.lineCount());
+  var register = helpers.getRegisterController().getRegister();
+  eq(expectedBuffer, register.text);
+  is(register.linewise);
+  helpers.assertCursorAt(0, lines[6].textStart);
+});
+testVim('dd_lastline', function(cm, vim, helpers) {
+  cm.setCursor(cm.lineCount(), 0);
+  var expectedLineCount = cm.lineCount() - 1;
+  helpers.doKeys('d', 'd');
+  eq(expectedLineCount, cm.lineCount());
+  helpers.assertCursorAt(cm.lineCount() - 1, 0);
+});
+// Yank commands should behave the exact same as d commands, expect that nothing
+// gets deleted.
+testVim('yw_repeat', function(cm, vim, helpers) {
+  // Assert that yw does yank newline if it should go to the next line, and
+  // that repeat works properly.
+  var curStart = makeCursor(0, 1);
+  cm.setCursor(curStart);
+  helpers.doKeys('y', '2', 'w');
+  eq(' word1\nword2', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('word1\nword2', register.text);
+  is(!register.linewise);
+  eqPos(curStart, cm.getCursor());
+}, { value: ' word1\nword2' });
+testVim('yy_multiply_repeat', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 3);
+  cm.setCursor(curStart);
+  var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
+    { line: 6, ch: 0 });
+  var expectedLineCount = cm.lineCount();
+  helpers.doKeys('2', 'y', '3', 'y');
+  eq(expectedLineCount, cm.lineCount());
+  var register = helpers.getRegisterController().getRegister();
+  eq(expectedBuffer, register.text);
+  is(register.linewise);
+  eqPos(curStart, cm.getCursor());
+});
+// Change commands behave like d commands except that it also enters insert
+// mode. In addition, when the change is linewise, an additional newline is
+// inserted so that insert mode starts on that line.
+testVim('cw', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('c', '2', 'w');
+  eq(' word3', cm.getValue());
+  helpers.assertCursorAt(0, 0);
+}, { value: 'word1 word2 word3'});
+testVim('cw_repeat', function(cm, vim, helpers) {
+  // Assert that cw does delete newline if it should go to the next line, and
+  // that repeat works properly.
+  var curStart = makeCursor(0, 1);
+  cm.setCursor(curStart);
+  helpers.doKeys('c', '2', 'w');
+  eq(' ', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('word1\nword2', register.text);
+  is(!register.linewise);
+  eqPos(curStart, cm.getCursor());
+  eq('vim-insert', cm.getOption('keyMap'));
+}, { value: ' word1\nword2' });
+testVim('cc_multiply_repeat', function(cm, vim, helpers) {
+  cm.setCursor(0, 3);
+  var expectedBuffer = cm.getRange({ line: 0, ch: 0 },
+    { line: 6, ch: 0 });
+  var expectedLineCount = cm.lineCount() - 5;
+  helpers.doKeys('2', 'c', '3', 'c');
+  eq(expectedLineCount, cm.lineCount());
+  var register = helpers.getRegisterController().getRegister();
+  eq(expectedBuffer, register.text);
+  is(register.linewise);
+  eq('vim-insert', cm.getOption('keyMap'));
+});
+testVim('cc_append', function(cm, vim, helpers) {
+  var expectedLineCount = cm.lineCount();
+  cm.setCursor(cm.lastLine(), 0);
+  helpers.doKeys('c', 'c');
+  eq(expectedLineCount, cm.lineCount());
+});
+// Swapcase commands edit in place and do not modify registers.
+testVim('g~w_repeat', function(cm, vim, helpers) {
+  // Assert that dw does delete newline if it should go to the next line, and
+  // that repeat works properly.
+  var curStart = makeCursor(0, 1);
+  cm.setCursor(curStart);
+  helpers.doKeys('g', '~', '2', 'w');
+  eq(' WORD1\nWORD2', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('', register.text);
+  is(!register.linewise);
+  eqPos(curStart, cm.getCursor());
+}, { value: ' word1\nword2' });
+testVim('g~g~', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 3);
+  cm.setCursor(curStart);
+  var expectedLineCount = cm.lineCount();
+  var expectedValue = cm.getValue().toUpperCase();
+  helpers.doKeys('2', 'g', '~', '3', 'g', '~');
+  eq(expectedValue, cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('', register.text);
+  is(!register.linewise);
+  eqPos(curStart, cm.getCursor());
+}, { value: ' word1\nword2\nword3\nword4\nword5\nword6' });
+testVim('>{motion}', function(cm, vim, helpers) {
+  cm.setCursor(1, 3);
+  var expectedLineCount = cm.lineCount();
+  var expectedValue = '   word1\n  word2\nword3 ';
+  helpers.doKeys('>', 'k');
+  eq(expectedValue, cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 3);
+}, { value: ' word1\nword2\nword3 ', indentUnit: 2 });
+testVim('>>', function(cm, vim, helpers) {
+  cm.setCursor(0, 3);
+  var expectedLineCount = cm.lineCount();
+  var expectedValue = '   word1\n  word2\nword3 ';
+  helpers.doKeys('2', '>', '>');
+  eq(expectedValue, cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 3);
+}, { value: ' word1\nword2\nword3 ', indentUnit: 2 });
+testVim('<{motion}', function(cm, vim, helpers) {
+  cm.setCursor(1, 3);
+  var expectedLineCount = cm.lineCount();
+  var expectedValue = ' word1\nword2\nword3 ';
+  helpers.doKeys('<', 'k');
+  eq(expectedValue, cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 1);
+}, { value: '   word1\n  word2\nword3 ', indentUnit: 2 });
+testVim('<<', function(cm, vim, helpers) {
+  cm.setCursor(0, 3);
+  var expectedLineCount = cm.lineCount();
+  var expectedValue = ' word1\nword2\nword3 ';
+  helpers.doKeys('2', '<', '<');
+  eq(expectedValue, cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 1);
+}, { value: '   word1\n  word2\nword3 ', indentUnit: 2 });
+
+// Edit tests
+function testEdit(name, before, pos, edit, after) {
+  return testVim(name, function(cm, vim, helpers) {
+             cm.setCursor(0, before.search(pos));
+             helpers.doKeys.apply(this, edit.split(''));
+             eq(after, cm.getValue());
+           }, {value: before});
+}
+
+// These Delete tests effectively cover word-wise Change, Visual & Yank.
+// Tabs are used as differentiated whitespace to catch edge cases.
+// Normal word:
+testEdit('diw_mid_spc', 'foo \tbAr\t baz', /A/, 'diw', 'foo \t\t baz');
+testEdit('daw_mid_spc', 'foo \tbAr\t baz', /A/, 'daw', 'foo \tbaz');
+testEdit('diw_mid_punct', 'foo \tbAr.\t baz', /A/, 'diw', 'foo \t.\t baz');
+testEdit('daw_mid_punct', 'foo \tbAr.\t baz', /A/, 'daw', 'foo.\t baz');
+testEdit('diw_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'diw', 'foo \t,.\t baz');
+testEdit('daw_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'daw', 'foo \t,.\t baz');
+testEdit('diw_start_spc', 'bAr \tbaz', /A/, 'diw', ' \tbaz');
+testEdit('daw_start_spc', 'bAr \tbaz', /A/, 'daw', 'baz');
+testEdit('diw_start_punct', 'bAr. \tbaz', /A/, 'diw', '. \tbaz');
+testEdit('daw_start_punct', 'bAr. \tbaz', /A/, 'daw', '. \tbaz');
+testEdit('diw_end_spc', 'foo \tbAr', /A/, 'diw', 'foo \t');
+testEdit('daw_end_spc', 'foo \tbAr', /A/, 'daw', 'foo');
+testEdit('diw_end_punct', 'foo \tbAr.', /A/, 'diw', 'foo \t.');
+testEdit('daw_end_punct', 'foo \tbAr.', /A/, 'daw', 'foo.');
+// Big word:
+testEdit('diW_mid_spc', 'foo \tbAr\t baz', /A/, 'diW', 'foo \t\t baz');
+testEdit('daW_mid_spc', 'foo \tbAr\t baz', /A/, 'daW', 'foo \tbaz');
+testEdit('diW_mid_punct', 'foo \tbAr.\t baz', /A/, 'diW', 'foo \t\t baz');
+testEdit('daW_mid_punct', 'foo \tbAr.\t baz', /A/, 'daW', 'foo \tbaz');
+testEdit('diW_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'diW', 'foo \t\t baz');
+testEdit('daW_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'daW', 'foo \tbaz');
+testEdit('diW_start_spc', 'bAr\t baz', /A/, 'diW', '\t baz');
+testEdit('daW_start_spc', 'bAr\t baz', /A/, 'daW', 'baz');
+testEdit('diW_start_punct', 'bAr.\t baz', /A/, 'diW', '\t baz');
+testEdit('daW_start_punct', 'bAr.\t baz', /A/, 'daW', 'baz');
+testEdit('diW_end_spc', 'foo \tbAr', /A/, 'diW', 'foo \t');
+testEdit('daW_end_spc', 'foo \tbAr', /A/, 'daW', 'foo');
+testEdit('diW_end_punct', 'foo \tbAr.', /A/, 'diW', 'foo \t');
+testEdit('daW_end_punct', 'foo \tbAr.', /A/, 'daW', 'foo');
+
+// Operator-motion tests
+testVim('D', function(cm, vim, helpers) {
+  cm.setCursor(0, 3);
+  helpers.doKeys('D');
+  eq(' wo\nword2\n word3', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('rd1', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 2);
+}, { value: ' word1\nword2\n word3' });
+testVim('C', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 3);
+  cm.setCursor(curStart);
+  helpers.doKeys('C');
+  eq(' wo\nword2\n word3', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('rd1', register.text);
+  is(!register.linewise);
+  eqPos(curStart, cm.getCursor());
+  eq('vim-insert', cm.getOption('keyMap'));
+}, { value: ' word1\nword2\n word3' });
+testVim('Y', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 3);
+  cm.setCursor(curStart);
+  helpers.doKeys('Y');
+  eq(' word1\nword2\n word3', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('rd1', register.text);
+  is(!register.linewise);
+  helpers.assertCursorAt(0, 3);
+}, { value: ' word1\nword2\n word3' });
+testVim('~', function(cm, vim, helpers) {
+  helpers.doKeys('3', '~');
+  eq('ABCdefg', cm.getValue());
+  helpers.assertCursorAt(0, 3);
+}, { value: 'abcdefg' });
+
+// Action tests
+testVim('ctrl-a', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('<C-a>');
+  eq('-9', cm.getValue());
+  helpers.assertCursorAt(0, 1);
+  helpers.doKeys('2','<C-a>');
+  eq('-7', cm.getValue());
+}, {value: '-10'});
+testVim('ctrl-x', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('<C-x>');
+  eq('-1', cm.getValue());
+  helpers.assertCursorAt(0, 1);
+  helpers.doKeys('2','<C-x>');
+  eq('-3', cm.getValue());
+}, {value: '0'});
+testVim('<C-x>/<C-a> search forward', function(cm, vim, helpers) {
+  ['<C-x>', '<C-a>'].forEach(function(key) {
+    cm.setCursor(0, 0);
+    helpers.doKeys(key);
+    helpers.assertCursorAt(0, 5);
+    helpers.doKeys('l');
+    helpers.doKeys(key);
+    helpers.assertCursorAt(0, 10);
+    cm.setCursor(0, 11);
+    helpers.doKeys(key);
+    helpers.assertCursorAt(0, 11);
+  });
+}, {value: '__jmp1 jmp2 jmp'});
+testVim('a', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.doKeys('a');
+  helpers.assertCursorAt(0, 2);
+  eq('vim-insert', cm.getOption('keyMap'));
+});
+testVim('a_eol', function(cm, vim, helpers) {
+  cm.setCursor(0, lines[0].length - 1);
+  helpers.doKeys('a');
+  helpers.assertCursorAt(0, lines[0].length);
+  eq('vim-insert', cm.getOption('keyMap'));
+});
+testVim('i', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.doKeys('i');
+  helpers.assertCursorAt(0, 1);
+  eq('vim-insert', cm.getOption('keyMap'));
+});
+testVim('i_repeat', function(cm, vim, helpers) {
+  helpers.doKeys('3', 'i');
+  cm.replaceRange('test', cm.getCursor());
+  helpers.doInsertModeKeys('Esc');
+  eq('testtesttest', cm.getValue());
+  helpers.assertCursorAt(0, 11);
+}, { value: '' });
+testVim('i_repeat_delete', function(cm, vim, helpers) {
+  cm.setCursor(0, 4);
+  helpers.doKeys('2', 'i');
+  cm.replaceRange('z', cm.getCursor());
+  helpers.doInsertModeKeys('Backspace', 'Backspace', 'Esc');
+  eq('abe', cm.getValue());
+  helpers.assertCursorAt(0, 1);
+}, { value: 'abcde' });
+testVim('A', function(cm, vim, helpers) {
+  helpers.doKeys('A');
+  helpers.assertCursorAt(0, lines[0].length);
+  eq('vim-insert', cm.getOption('keyMap'));
+});
+testVim('I', function(cm, vim, helpers) {
+  cm.setCursor(0, 4);
+  helpers.doKeys('I');
+  helpers.assertCursorAt(0, lines[0].textStart);
+  eq('vim-insert', cm.getOption('keyMap'));
+});
+testVim('I_repeat', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.doKeys('3', 'I');
+  cm.replaceRange('test', cm.getCursor());
+  helpers.doInsertModeKeys('Esc');
+  eq('testtesttestblah', cm.getValue());
+  helpers.assertCursorAt(0, 11);
+}, { value: 'blah' });
+testVim('o', function(cm, vim, helpers) {
+  cm.setCursor(0, 4);
+  helpers.doKeys('o');
+  eq('word1\n\nword2', cm.getValue());
+  helpers.assertCursorAt(1, 0);
+  eq('vim-insert', cm.getOption('keyMap'));
+}, { value: 'word1\nword2' });
+testVim('o_repeat', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('3', 'o');
+  cm.replaceRange('test', cm.getCursor());
+  helpers.doInsertModeKeys('Esc');
+  eq('\ntest\ntest\ntest', cm.getValue());
+  helpers.assertCursorAt(3, 3);
+}, { value: '' });
+testVim('O', function(cm, vim, helpers) {
+  cm.setCursor(0, 4);
+  helpers.doKeys('O');
+  eq('\nword1\nword2', cm.getValue());
+  helpers.assertCursorAt(0, 0);
+  eq('vim-insert', cm.getOption('keyMap'));
+}, { value: 'word1\nword2' });
+testVim('J', function(cm, vim, helpers) {
+  cm.setCursor(0, 4);
+  helpers.doKeys('J');
+  var expectedValue = 'word1  word2\nword3\n word4';
+  eq(expectedValue, cm.getValue());
+  helpers.assertCursorAt(0, expectedValue.indexOf('word2') - 1);
+}, { value: 'word1 \n    word2\nword3\n word4' });
+testVim('J_repeat', function(cm, vim, helpers) {
+  cm.setCursor(0, 4);
+  helpers.doKeys('3', 'J');
+  var expectedValue = 'word1  word2 word3\n word4';
+  eq(expectedValue, cm.getValue());
+  helpers.assertCursorAt(0, expectedValue.indexOf('word3') - 1);
+}, { value: 'word1 \n    word2\nword3\n word4' });
+testVim('p', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false);
+  helpers.doKeys('p');
+  eq('__abc\ndef_', cm.getValue());
+  helpers.assertCursorAt(1, 2);
+}, { value: '___' });
+testVim('p_register', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.getRegisterController().getRegister('a').set('abc\ndef', false);
+  helpers.doKeys('"', 'a', 'p');
+  eq('__abc\ndef_', cm.getValue());
+  helpers.assertCursorAt(1, 2);
+}, { value: '___' });
+testVim('p_wrong_register', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.getRegisterController().getRegister('a').set('abc\ndef', false);
+  helpers.doKeys('p');
+  eq('___', cm.getValue());
+  helpers.assertCursorAt(0, 1);
+}, { value: '___' });
+testVim('p_line', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.getRegisterController().pushText('"', 'yank', '  a\nd\n', true);
+  helpers.doKeys('2', 'p');
+  eq('___\n  a\nd\n  a\nd', cm.getValue());
+  helpers.assertCursorAt(1, 2);
+}, { value: '___' });
+testVim('p_lastline', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.getRegisterController().pushText('"', 'yank', '  a\nd', true);
+  helpers.doKeys('2', 'p');
+  eq('___\n  a\nd\n  a\nd', cm.getValue());
+  helpers.assertCursorAt(1, 2);
+}, { value: '___' });
+testVim('P', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false);
+  helpers.doKeys('P');
+  eq('_abc\ndef__', cm.getValue());
+  helpers.assertCursorAt(1, 3);
+}, { value: '___' });
+testVim('P_line', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.getRegisterController().pushText('"', 'yank', '  a\nd\n', true);
+  helpers.doKeys('2', 'P');
+  eq('  a\nd\n  a\nd\n___', cm.getValue());
+  helpers.assertCursorAt(0, 2);
+}, { value: '___' });
+testVim('r', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.doKeys('3', 'r', 'u');
+  eq('wuuuet\nanother', cm.getValue(),'3r failed');
+  helpers.assertCursorAt(0, 3);
+  cm.setCursor(0, 4);
+  helpers.doKeys('v', 'j', 'h', 'r', '<Space>');
+  eq('wuuu  \n    her', cm.getValue(),'Replacing selection by space-characters failed');
+}, { value: 'wordet\nanother' });
+testVim('R', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.doKeys('R');
+  helpers.assertCursorAt(0, 1);
+  eq('vim-replace', cm.getOption('keyMap'));
+  is(cm.state.overwrite, 'Setting overwrite state failed');
+});
+testVim('mark', function(cm, vim, helpers) {
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 't');
+  cm.setCursor(0, 0);
+  helpers.doKeys('\'', 't');
+  helpers.assertCursorAt(2, 2);
+  cm.setCursor(0, 0);
+  helpers.doKeys('`', 't');
+  helpers.assertCursorAt(2, 2);
+});
+testVim('jumpToMark_next', function(cm, vim, helpers) {
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 't');
+  cm.setCursor(0, 0);
+  helpers.doKeys(']', '`');
+  helpers.assertCursorAt(2, 2);
+  cm.setCursor(0, 0);
+  helpers.doKeys(']', '\'');
+  helpers.assertCursorAt(2, 0);
+});
+testVim('jumpToMark_next_repeat', function(cm, vim, helpers) {
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(3, 2);
+  helpers.doKeys('m', 'b');
+  cm.setCursor(4, 2);
+  helpers.doKeys('m', 'c');
+  cm.setCursor(0, 0);
+  helpers.doKeys('2', ']', '`');
+  helpers.assertCursorAt(3, 2);
+  cm.setCursor(0, 0);
+  helpers.doKeys('2', ']', '\'');
+  helpers.assertCursorAt(3, 1);
+});
+testVim('jumpToMark_next_sameline', function(cm, vim, helpers) {
+  cm.setCursor(2, 0);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(2, 4);
+  helpers.doKeys('m', 'b');
+  cm.setCursor(2, 2);
+  helpers.doKeys(']', '`');
+  helpers.assertCursorAt(2, 4);
+});
+testVim('jumpToMark_next_onlyprev', function(cm, vim, helpers) {
+  cm.setCursor(2, 0);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(4, 0);
+  helpers.doKeys(']', '`');
+  helpers.assertCursorAt(4, 0);
+});
+testVim('jumpToMark_next_nomark', function(cm, vim, helpers) {
+  cm.setCursor(2, 2);
+  helpers.doKeys(']', '`');
+  helpers.assertCursorAt(2, 2);
+  helpers.doKeys(']', '\'');
+  helpers.assertCursorAt(2, 0);
+});
+testVim('jumpToMark_next_linewise_over', function(cm, vim, helpers) {
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(3, 4);
+  helpers.doKeys('m', 'b');
+  cm.setCursor(2, 1);
+  helpers.doKeys(']', '\'');
+  helpers.assertCursorAt(3, 1);
+});
+testVim('jumpToMark_next_action', function(cm, vim, helpers) {
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 't');
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', ']', '`');
+  helpers.assertCursorAt(0, 0);
+  var actual = cm.getLine(0);
+  var expected = 'pop pop 0 1 2 3 4';
+  eq(actual, expected, "Deleting while jumping to the next mark failed.");
+});
+testVim('jumpToMark_next_line_action', function(cm, vim, helpers) {
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 't');
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', ']', '\'');
+  helpers.assertCursorAt(0, 1);
+  var actual = cm.getLine(0);
+  var expected = ' (a) [b] {c} '
+  eq(actual, expected, "Deleting while jumping to the next mark line failed.");
+});
+testVim('jumpToMark_prev', function(cm, vim, helpers) {
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 't');
+  cm.setCursor(4, 0);
+  helpers.doKeys('[', '`');
+  helpers.assertCursorAt(2, 2);
+  cm.setCursor(4, 0);
+  helpers.doKeys('[', '\'');
+  helpers.assertCursorAt(2, 0);
+});
+testVim('jumpToMark_prev_repeat', function(cm, vim, helpers) {
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(3, 2);
+  helpers.doKeys('m', 'b');
+  cm.setCursor(4, 2);
+  helpers.doKeys('m', 'c');
+  cm.setCursor(5, 0);
+  helpers.doKeys('2', '[', '`');
+  helpers.assertCursorAt(3, 2);
+  cm.setCursor(5, 0);
+  helpers.doKeys('2', '[', '\'');
+  helpers.assertCursorAt(3, 1);
+});
+testVim('jumpToMark_prev_sameline', function(cm, vim, helpers) {
+  cm.setCursor(2, 0);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(2, 4);
+  helpers.doKeys('m', 'b');
+  cm.setCursor(2, 2);
+  helpers.doKeys('[', '`');
+  helpers.assertCursorAt(2, 0);
+});
+testVim('jumpToMark_prev_onlynext', function(cm, vim, helpers) {
+  cm.setCursor(4, 4);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(2, 0);
+  helpers.doKeys('[', '`');
+  helpers.assertCursorAt(2, 0);
+});
+testVim('jumpToMark_prev_nomark', function(cm, vim, helpers) {
+  cm.setCursor(2, 2);
+  helpers.doKeys('[', '`');
+  helpers.assertCursorAt(2, 2);
+  helpers.doKeys('[', '\'');
+  helpers.assertCursorAt(2, 0);
+});
+testVim('jumpToMark_prev_linewise_over', function(cm, vim, helpers) {
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(3, 4);
+  helpers.doKeys('m', 'b');
+  cm.setCursor(3, 6);
+  helpers.doKeys('[', '\'');
+  helpers.assertCursorAt(2, 0);
+});
+testVim('delmark_single', function(cm, vim, helpers) {
+  cm.setCursor(1, 2);
+  helpers.doKeys('m', 't');
+  helpers.doEx('delmarks t');
+  cm.setCursor(0, 0);
+  helpers.doKeys('`', 't');
+  helpers.assertCursorAt(0, 0);
+});
+testVim('delmark_range', function(cm, vim, helpers) {
+  cm.setCursor(1, 2);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 'b');
+  cm.setCursor(3, 2);
+  helpers.doKeys('m', 'c');
+  cm.setCursor(4, 2);
+  helpers.doKeys('m', 'd');
+  cm.setCursor(5, 2);
+  helpers.doKeys('m', 'e');
+  helpers.doEx('delmarks b-d');
+  cm.setCursor(0, 0);
+  helpers.doKeys('`', 'a');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'b');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'c');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'd');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'e');
+  helpers.assertCursorAt(5, 2);
+});
+testVim('delmark_multi', function(cm, vim, helpers) {
+  cm.setCursor(1, 2);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 'b');
+  cm.setCursor(3, 2);
+  helpers.doKeys('m', 'c');
+  cm.setCursor(4, 2);
+  helpers.doKeys('m', 'd');
+  cm.setCursor(5, 2);
+  helpers.doKeys('m', 'e');
+  helpers.doEx('delmarks bcd');
+  cm.setCursor(0, 0);
+  helpers.doKeys('`', 'a');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'b');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'c');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'd');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'e');
+  helpers.assertCursorAt(5, 2);
+});
+testVim('delmark_multi_space', function(cm, vim, helpers) {
+  cm.setCursor(1, 2);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 'b');
+  cm.setCursor(3, 2);
+  helpers.doKeys('m', 'c');
+  cm.setCursor(4, 2);
+  helpers.doKeys('m', 'd');
+  cm.setCursor(5, 2);
+  helpers.doKeys('m', 'e');
+  helpers.doEx('delmarks b c d');
+  cm.setCursor(0, 0);
+  helpers.doKeys('`', 'a');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'b');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'c');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'd');
+  helpers.assertCursorAt(1, 2);
+  helpers.doKeys('`', 'e');
+  helpers.assertCursorAt(5, 2);
+});
+testVim('delmark_all', function(cm, vim, helpers) {
+  cm.setCursor(1, 2);
+  helpers.doKeys('m', 'a');
+  cm.setCursor(2, 2);
+  helpers.doKeys('m', 'b');
+  cm.setCursor(3, 2);
+  helpers.doKeys('m', 'c');
+  cm.setCursor(4, 2);
+  helpers.doKeys('m', 'd');
+  cm.setCursor(5, 2);
+  helpers.doKeys('m', 'e');
+  helpers.doEx('delmarks a b-de');
+  cm.setCursor(0, 0);
+  helpers.doKeys('`', 'a');
+  helpers.assertCursorAt(0, 0);
+  helpers.doKeys('`', 'b');
+  helpers.assertCursorAt(0, 0);
+  helpers.doKeys('`', 'c');
+  helpers.assertCursorAt(0, 0);
+  helpers.doKeys('`', 'd');
+  helpers.assertCursorAt(0, 0);
+  helpers.doKeys('`', 'e');
+  helpers.assertCursorAt(0, 0);
+});
+testVim('visual', function(cm, vim, helpers) {
+  helpers.doKeys('l', 'v', 'l', 'l');
+  helpers.assertCursorAt(0, 3);
+  eqPos(makeCursor(0, 1), cm.getCursor('anchor'));
+  helpers.doKeys('d');
+  eq('15', cm.getValue());
+}, { value: '12345' });
+testVim('visual_line', function(cm, vim, helpers) {
+  helpers.doKeys('l', 'V', 'l', 'j', 'j', 'd');
+  eq(' 4\n 5', cm.getValue());
+}, { value: ' 1\n 2\n 3\n 4\n 5' });
+testVim('visual_marks', function(cm, vim, helpers) {
+  helpers.doKeys('l', 'v', 'l', 'l', 'v');
+  // Test visual mode marks
+  cm.setCursor(0, 0);
+  helpers.doKeys('\'', '<');
+  helpers.assertCursorAt(0, 1);
+  helpers.doKeys('\'', '>');
+  helpers.assertCursorAt(0, 3);
+});
+testVim('visual_join', function(cm, vim, helpers) {
+  helpers.doKeys('l', 'V', 'l', 'j', 'j', 'J');
+  eq(' 1 2 3\n 4\n 5', cm.getValue());
+}, { value: ' 1\n 2\n 3\n 4\n 5' });
+testVim('visual_blank', function(cm, vim, helpers) {
+  helpers.doKeys('v', 'k');
+  eq(vim.visualMode, true);
+}, { value: '\n' });
+testVim('s_normal', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.doKeys('s');
+  helpers.doInsertModeKeys('Esc');
+  helpers.assertCursorAt(0, 0);
+  eq('ac', cm.getValue());
+}, { value: 'abc'});
+testVim('s_visual', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.doKeys('v', 's');
+  helpers.doInsertModeKeys('Esc');
+  helpers.assertCursorAt(0, 0);
+  eq('ac', cm.getValue());
+}, { value: 'abc'});
+testVim('S_normal', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.doKeys('j', 'S');
+  helpers.doInsertModeKeys('Esc');
+  helpers.assertCursorAt(1, 0);
+  eq('aa\n\ncc', cm.getValue());
+}, { value: 'aa\nbb\ncc'});
+testVim('S_visual', function(cm, vim, helpers) {
+  cm.setCursor(0, 1);
+  helpers.doKeys('v', 'j', 'S');
+  helpers.doInsertModeKeys('Esc');
+  helpers.assertCursorAt(0, 0);
+  eq('\ncc', cm.getValue());
+}, { value: 'aa\nbb\ncc'});
+testVim('/ and n/N', function(cm, vim, helpers) {
+  cm.openDialog = helpers.fakeOpenDialog('match');
+  helpers.doKeys('/');
+  helpers.assertCursorAt(0, 11);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(1, 6);
+  helpers.doKeys('N');
+  helpers.assertCursorAt(0, 11);
+
+  cm.setCursor(0, 0);
+  helpers.doKeys('2', '/');
+  helpers.assertCursorAt(1, 6);
+}, { value: 'match nope match \n nope Match' });
+testVim('/_case', function(cm, vim, helpers) {
+  cm.openDialog = helpers.fakeOpenDialog('Match');
+  helpers.doKeys('/');
+  helpers.assertCursorAt(1, 6);
+}, { value: 'match nope match \n nope Match' });
+testVim('/_nongreedy', function(cm, vim, helpers) {
+  cm.openDialog = helpers.fakeOpenDialog('aa');
+  helpers.doKeys('/');
+  helpers.assertCursorAt(0, 4);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(1, 3);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 0);
+}, { value: 'aaa aa \n a aa'});
+testVim('?_nongreedy', function(cm, vim, helpers) {
+  cm.openDialog = helpers.fakeOpenDialog('aa');
+  helpers.doKeys('?');
+  helpers.assertCursorAt(1, 3);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 4);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 0);
+}, { value: 'aaa aa \n a aa'});
+testVim('/_greedy', function(cm, vim, helpers) {
+  cm.openDialog = helpers.fakeOpenDialog('a+');
+  helpers.doKeys('/');
+  helpers.assertCursorAt(0, 4);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(1, 1);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(1, 3);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 0);
+}, { value: 'aaa aa \n a aa'});
+testVim('?_greedy', function(cm, vim, helpers) {
+  cm.openDialog = helpers.fakeOpenDialog('a+');
+  helpers.doKeys('?');
+  helpers.assertCursorAt(1, 3);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(1, 1);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 4);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 0);
+}, { value: 'aaa aa \n a aa'});
+testVim('/_greedy_0_or_more', function(cm, vim, helpers) {
+  cm.openDialog = helpers.fakeOpenDialog('a*');
+  helpers.doKeys('/');
+  helpers.assertCursorAt(0, 3);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 4);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 5);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(1, 0);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(1, 1);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 0);
+}, { value: 'aaa  aa\n aa'});
+testVim('?_greedy_0_or_more', function(cm, vim, helpers) {
+  cm.openDialog = helpers.fakeOpenDialog('a*');
+  helpers.doKeys('?');
+  helpers.assertCursorAt(1, 1);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(1, 0);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 5);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 4);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 3);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 0);
+}, { value: 'aaa  aa\n aa'});
+testVim('? and n/N', function(cm, vim, helpers) {
+  cm.openDialog = helpers.fakeOpenDialog('match');
+  helpers.doKeys('?');
+  helpers.assertCursorAt(1, 6);
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 11);
+  helpers.doKeys('N');
+  helpers.assertCursorAt(1, 6);
+
+  cm.setCursor(0, 0);
+  helpers.doKeys('2', '?');
+  helpers.assertCursorAt(0, 11);
+}, { value: 'match nope match \n nope Match' });
+testVim('*', function(cm, vim, helpers) {
+  cm.setCursor(0, 9);
+  helpers.doKeys('*');
+  helpers.assertCursorAt(0, 22);
+
+  cm.setCursor(0, 9);
+  helpers.doKeys('2', '*');
+  helpers.assertCursorAt(1, 8);
+}, { value: 'nomatch match nomatch match \nnomatch Match' });
+testVim('*_no_word', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('*');
+  helpers.assertCursorAt(0, 0);
+}, { value: ' \n match \n' });
+testVim('*_symbol', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('*');
+  helpers.assertCursorAt(1, 0);
+}, { value: ' /}\n/} match \n' });
+testVim('#', function(cm, vim, helpers) {
+  cm.setCursor(0, 9);
+  helpers.doKeys('#');
+  helpers.assertCursorAt(1, 8);
+
+  cm.setCursor(0, 9);
+  helpers.doKeys('2', '#');
+  helpers.assertCursorAt(0, 22);
+}, { value: 'nomatch match nomatch match \nnomatch Match' });
+testVim('*_seek', function(cm, vim, helpers) {
+  // Should skip over space and symbols.
+  cm.setCursor(0, 3);
+  helpers.doKeys('*');
+  helpers.assertCursorAt(0, 22);
+}, { value: '    :=  match nomatch match \nnomatch Match' });
+testVim('#', function(cm, vim, helpers) {
+  // Should skip over space and symbols.
+  cm.setCursor(0, 3);
+  helpers.doKeys('#');
+  helpers.assertCursorAt(1, 8);
+}, { value: '    :=  match nomatch match \nnomatch Match' });
+testVim('.', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('2', 'd', 'w');
+  helpers.doKeys('.');
+  eq('5 6', cm.getValue());
+}, { value: '1 2 3 4 5 6'});
+testVim('._repeat', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('2', 'd', 'w');
+  helpers.doKeys('3', '.');
+  eq('6', cm.getValue());
+}, { value: '1 2 3 4 5 6'});
+testVim('._insert', function(cm, vim, helpers) {
+  helpers.doKeys('i');
+  cm.replaceRange('test', cm.getCursor());
+  helpers.doInsertModeKeys('Esc');
+  helpers.doKeys('.');
+  eq('testestt', cm.getValue());
+  helpers.assertCursorAt(0, 6);
+}, { value: ''});
+testVim('._insert_repeat', function(cm, vim, helpers) {
+  helpers.doKeys('i');
+  cm.replaceRange('test', cm.getCursor());
+  cm.setCursor(0, 4);
+  helpers.doInsertModeKeys('Esc');
+  helpers.doKeys('2', '.');
+  eq('testesttestt', cm.getValue());
+  helpers.assertCursorAt(0, 10);
+}, { value: ''});
+testVim('._repeat_insert', function(cm, vim, helpers) {
+  helpers.doKeys('3', 'i');
+  cm.replaceRange('te', cm.getCursor());
+  cm.setCursor(0, 2);
+  helpers.doInsertModeKeys('Esc');
+  helpers.doKeys('.');
+  eq('tetettetetee', cm.getValue());
+  helpers.assertCursorAt(0, 10);
+}, { value: ''});
+testVim('._insert_o', function(cm, vim, helpers) {
+  helpers.doKeys('o');
+  cm.replaceRange('z', cm.getCursor());
+  cm.setCursor(1, 1);
+  helpers.doInsertModeKeys('Esc');
+  helpers.doKeys('.');
+  eq('\nz\nz', cm.getValue());
+  helpers.assertCursorAt(2, 0);
+}, { value: ''});
+testVim('._insert_o_repeat', function(cm, vim, helpers) {
+  helpers.doKeys('o');
+  cm.replaceRange('z', cm.getCursor());
+  helpers.doInsertModeKeys('Esc');
+  cm.setCursor(1, 0);
+  helpers.doKeys('2', '.');
+  eq('\nz\nz\nz', cm.getValue());
+  helpers.assertCursorAt(3, 0);
+}, { value: ''});
+testVim('._insert_o_indent', function(cm, vim, helpers) {
+  helpers.doKeys('o');
+  cm.replaceRange('z', cm.getCursor());
+  helpers.doInsertModeKeys('Esc');
+  cm.setCursor(1, 2);
+  helpers.doKeys('.');
+  eq('{\n  z\n  z', cm.getValue());
+  helpers.assertCursorAt(2, 2);
+}, { value: '{'});
+testVim('._insert_cw', function(cm, vim, helpers) {
+  helpers.doKeys('c', 'w');
+  cm.replaceRange('test', cm.getCursor());
+  helpers.doInsertModeKeys('Esc');
+  cm.setCursor(0, 3);
+  helpers.doKeys('2', 'l');
+  helpers.doKeys('.');
+  eq('test test word3', cm.getValue());
+  helpers.assertCursorAt(0, 8);
+}, { value: 'word1 word2 word3' });
+testVim('._insert_cw_repeat', function(cm, vim, helpers) {
+  // For some reason, repeat cw in desktop VIM will does not repeat insert mode
+  // changes. Will conform to that behavior.
+  helpers.doKeys('c', 'w');
+  cm.replaceRange('test', cm.getCursor());
+  helpers.doInsertModeKeys('Esc');
+  cm.setCursor(0, 4);
+  helpers.doKeys('l');
+  helpers.doKeys('2', '.');
+  eq('test test', cm.getValue());
+  helpers.assertCursorAt(0, 8);
+}, { value: 'word1 word2 word3' });
+testVim('._delete', function(cm, vim, helpers) {
+  cm.setCursor(0, 5);
+  helpers.doKeys('i');
+  helpers.doInsertModeKeys('Backspace', 'Esc');
+  helpers.doKeys('.');
+  eq('zace', cm.getValue());
+  helpers.assertCursorAt(0, 1);
+}, { value: 'zabcde'});
+testVim('._delete_repeat', function(cm, vim, helpers) {
+  cm.setCursor(0, 6);
+  helpers.doKeys('i');
+  helpers.doInsertModeKeys('Backspace', 'Esc');
+  helpers.doKeys('2', '.');
+  eq('zzce', cm.getValue());
+  helpers.assertCursorAt(0, 1);
+}, { value: 'zzabcde'});
+testVim('f;', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('f', 'x');
+  helpers.doKeys(';');
+  helpers.doKeys('2', ';');
+  eq(9, cm.getCursor().ch);
+}, { value: '01x3xx678x'});
+testVim('F;', function(cm, vim, helpers) {
+  cm.setCursor(0, 8);
+  helpers.doKeys('F', 'x');
+  helpers.doKeys(';');
+  helpers.doKeys('2', ';');
+  eq(2, cm.getCursor().ch);
+}, { value: '01x3xx6x8x'});
+testVim('t;', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('t', 'x');
+  helpers.doKeys(';');
+  helpers.doKeys('2', ';');
+  eq(8, cm.getCursor().ch);
+}, { value: '01x3xx678x'});
+testVim('T;', function(cm, vim, helpers) {
+  cm.setCursor(0, 9);
+  helpers.doKeys('T', 'x');
+  helpers.doKeys(';');
+  helpers.doKeys('2', ';');
+  eq(2, cm.getCursor().ch);
+}, { value: '0xx3xx678x'});
+testVim('f,', function(cm, vim, helpers) {
+  cm.setCursor(0, 6);
+  helpers.doKeys('f', 'x');
+  helpers.doKeys(',');
+  helpers.doKeys('2', ',');
+  eq(2, cm.getCursor().ch);
+}, { value: '01x3xx678x'});
+testVim('F,', function(cm, vim, helpers) {
+  cm.setCursor(0, 3);
+  helpers.doKeys('F', 'x');
+  helpers.doKeys(',');
+  helpers.doKeys('2', ',');
+  eq(9, cm.getCursor().ch);
+}, { value: '01x3xx678x'});
+testVim('t,', function(cm, vim, helpers) {
+  cm.setCursor(0, 6);
+  helpers.doKeys('t', 'x');
+  helpers.doKeys(',');
+  helpers.doKeys('2', ',');
+  eq(3, cm.getCursor().ch);
+}, { value: '01x3xx678x'});
+testVim('T,', function(cm, vim, helpers) {
+  cm.setCursor(0, 4);
+  helpers.doKeys('T', 'x');
+  helpers.doKeys(',');
+  helpers.doKeys('2', ',');
+  eq(8, cm.getCursor().ch);
+}, { value: '01x3xx67xx'});
+testVim('fd,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('f', '4');
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', ';');
+  eq('56789', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 9);
+  helpers.doKeys('d', ',');
+  eq('01239', cm.getValue());
+}, { value: '0123456789'});
+testVim('Fd,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 9);
+  helpers.doKeys('F', '4');
+  cm.setCursor(0, 9);
+  helpers.doKeys('d', ';');
+  eq('01239', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', ',');
+  eq('56789', cm.getValue());
+}, { value: '0123456789'});
+testVim('td,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('t', '4');
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', ';');
+  eq('456789', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 9);
+  helpers.doKeys('d', ',');
+  eq('012349', cm.getValue());
+}, { value: '0123456789'});
+testVim('Td,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 9);
+  helpers.doKeys('T', '4');
+  cm.setCursor(0, 9);
+  helpers.doKeys('d', ';');
+  eq('012349', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 0);
+  helpers.doKeys('d', ',');
+  eq('456789', cm.getValue());
+}, { value: '0123456789'});
+testVim('fc,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('f', '4');
+  cm.setCursor(0, 0);
+  helpers.doKeys('c', ';', 'Esc');
+  eq('56789', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 9);
+  helpers.doKeys('c', ',');
+  eq('01239', cm.getValue());
+}, { value: '0123456789'});
+testVim('Fc,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 9);
+  helpers.doKeys('F', '4');
+  cm.setCursor(0, 9);
+  helpers.doKeys('c', ';', 'Esc');
+  eq('01239', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 0);
+  helpers.doKeys('c', ',');
+  eq('56789', cm.getValue());
+}, { value: '0123456789'});
+testVim('tc,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('t', '4');
+  cm.setCursor(0, 0);
+  helpers.doKeys('c', ';', 'Esc');
+  eq('456789', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 9);
+  helpers.doKeys('c', ',');
+  eq('012349', cm.getValue());
+}, { value: '0123456789'});
+testVim('Tc,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 9);
+  helpers.doKeys('T', '4');
+  cm.setCursor(0, 9);
+  helpers.doKeys('c', ';', 'Esc');
+  eq('012349', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 0);
+  helpers.doKeys('c', ',');
+  eq('456789', cm.getValue());
+}, { value: '0123456789'});
+testVim('fy,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('f', '4');
+  cm.setCursor(0, 0);
+  helpers.doKeys('y', ';', 'P');
+  eq('012340123456789', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 9);
+  helpers.doKeys('y', ',', 'P');
+  eq('012345678456789', cm.getValue());
+}, { value: '0123456789'});
+testVim('Fy,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 9);
+  helpers.doKeys('F', '4');
+  cm.setCursor(0, 9);
+  helpers.doKeys('y', ';', 'p');
+  eq('012345678945678', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 0);
+  helpers.doKeys('y', ',', 'P');
+  eq('012340123456789', cm.getValue());
+}, { value: '0123456789'});
+testVim('ty,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('t', '4');
+  cm.setCursor(0, 0);
+  helpers.doKeys('y', ';', 'P');
+  eq('01230123456789', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 9);
+  helpers.doKeys('y', ',', 'p');
+  eq('01234567895678', cm.getValue());
+}, { value: '0123456789'});
+testVim('Ty,;', function(cm, vim, helpers) {
+  cm.setCursor(0, 9);
+  helpers.doKeys('T', '4');
+  cm.setCursor(0, 9);
+  helpers.doKeys('y', ';', 'p');
+  eq('01234567895678', cm.getValue());
+  helpers.doKeys('u');
+  cm.setCursor(0, 0);
+  helpers.doKeys('y', ',', 'P');
+  eq('01230123456789', cm.getValue());
+}, { value: '0123456789'});
+testVim('HML', function(cm, vim, helpers) {
+  var lines = 35;
+  var textHeight = cm.defaultTextHeight();
+  cm.setSize(600, lines*textHeight);
+  cm.setCursor(120, 0);
+  helpers.doKeys('H');
+  helpers.assertCursorAt(86, 2);
+  helpers.doKeys('L');
+  helpers.assertCursorAt(120, 4);
+  helpers.doKeys('M');
+  helpers.assertCursorAt(103,4);
+}, { value: (function(){
+  var lines = new Array(100);
+  var upper = '  xx\n';
+  var lower = '    xx\n';
+  upper = lines.join(upper);
+  lower = lines.join(lower);
+  return upper + lower;
+})()});
+
+var zVals = ['zb','zz','zt','z-','z.','z<CR>'].map(function(e, idx){
+  var lineNum = 250;
+  var lines = 35;
+  testVim(e, function(cm, vim, helpers) {
+    var k1 = e[0];
+    var k2 = e.substring(1);
+    var textHeight = cm.defaultTextHeight();
+    cm.setSize(600, lines*textHeight);
+    cm.setCursor(lineNum, 0);
+    helpers.doKeys(k1, k2);
+    zVals[idx] = cm.getScrollInfo().top;
+  }, { value: (function(){
+    return new Array(500).join('\n');
+  })()});
+});
+testVim('zb<zz', function(cm, vim, helpers){
+  eq(zVals[0]<zVals[1], true);
+});
+testVim('zz<zt', function(cm, vim, helpers){
+  eq(zVals[1]<zVals[2], true);
+});
+testVim('zb==z-', function(cm, vim, helpers){
+  eq(zVals[0], zVals[3]);
+});
+testVim('zz==z.', function(cm, vim, helpers){
+  eq(zVals[1], zVals[4]);
+});
+testVim('zt==z<CR>', function(cm, vim, helpers){
+  eq(zVals[2], zVals[5]);
+});
+
+var squareBracketMotionSandbox = ''+
+  '({\n'+//0
+  '  ({\n'+//11
+  '  /*comment {\n'+//2
+  '            */(\n'+//3
+  '#else                \n'+//4
+  '  /*       )\n'+//5
+  '#if        }\n'+//6
+  '  )}*/\n'+//7
+  ')}\n'+//8
+  '{}\n'+//9
+  '#else {{\n'+//10
+  '{}\n'+//11
+  '}\n'+//12
+  '{\n'+//13
+  '#endif\n'+//14
+  '}\n'+//15
+  '}\n'+//16
+  '#else';//17
+testVim('[[, ]]', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys(']', ']');
+  helpers.assertCursorAt(9,0);
+  helpers.doKeys('2', ']', ']');
+  helpers.assertCursorAt(13,0);
+  helpers.doKeys(']', ']');
+  helpers.assertCursorAt(17,0);
+  helpers.doKeys('[', '[');
+  helpers.assertCursorAt(13,0);
+  helpers.doKeys('2', '[', '[');
+  helpers.assertCursorAt(9,0);
+  helpers.doKeys('[', '[');
+  helpers.assertCursorAt(0,0);
+}, { value: squareBracketMotionSandbox});
+testVim('[], ][', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys(']', '[');
+  helpers.assertCursorAt(12,0);
+  helpers.doKeys('2', ']', '[');
+  helpers.assertCursorAt(16,0);
+  helpers.doKeys(']', '[');
+  helpers.assertCursorAt(17,0);
+  helpers.doKeys('[', ']');
+  helpers.assertCursorAt(16,0);
+  helpers.doKeys('2', '[', ']');
+  helpers.assertCursorAt(12,0);
+  helpers.doKeys('[', ']');
+  helpers.assertCursorAt(0,0);
+}, { value: squareBracketMotionSandbox});
+testVim('[{, ]}', function(cm, vim, helpers) {
+  cm.setCursor(4, 10);
+  helpers.doKeys('[', '{');
+  helpers.assertCursorAt(2,12);
+  helpers.doKeys('2', '[', '{');
+  helpers.assertCursorAt(0,1);
+  cm.setCursor(4, 10);
+  helpers.doKeys(']', '}');
+  helpers.assertCursorAt(6,11);
+  helpers.doKeys('2', ']', '}');
+  helpers.assertCursorAt(8,1);
+  cm.setCursor(0,1);
+  helpers.doKeys(']', '}');
+  helpers.assertCursorAt(8,1);
+  helpers.doKeys('[', '{');
+  helpers.assertCursorAt(0,1);
+}, { value: squareBracketMotionSandbox});
+testVim('[(, ])', function(cm, vim, helpers) {
+  cm.setCursor(4, 10);
+  helpers.doKeys('[', '(');
+  helpers.assertCursorAt(3,14);
+  helpers.doKeys('2', '[', '(');
+  helpers.assertCursorAt(0,0);
+  cm.setCursor(4, 10);
+  helpers.doKeys(']', ')');
+  helpers.assertCursorAt(5,11);
+  helpers.doKeys('2', ']', ')');
+  helpers.assertCursorAt(8,0);
+  helpers.doKeys('[', '(');
+  helpers.assertCursorAt(0,0);
+  helpers.doKeys(']', ')');
+  helpers.assertCursorAt(8,0);
+}, { value: squareBracketMotionSandbox});
+testVim('[*, ]*, [/, ]/', function(cm, vim, helpers) {
+  ['*', '/'].forEach(function(key){
+    cm.setCursor(7, 0);
+    helpers.doKeys('2', '[', key);
+    helpers.assertCursorAt(2,2);
+    helpers.doKeys('2', ']', key);
+    helpers.assertCursorAt(7,5);
+  });
+}, { value: squareBracketMotionSandbox});
+testVim('[#, ]#', function(cm, vim, helpers) {
+  cm.setCursor(10, 3);
+  helpers.doKeys('2', '[', '#');
+  helpers.assertCursorAt(4,0);
+  helpers.doKeys('5', ']', '#');
+  helpers.assertCursorAt(17,0);
+  cm.setCursor(10, 3);
+  helpers.doKeys(']', '#');
+  helpers.assertCursorAt(14,0);
+}, { value: squareBracketMotionSandbox});
+testVim('[m, ]m, [M, ]M', function(cm, vim, helpers) {
+  cm.setCursor(11, 0);
+  helpers.doKeys('[', 'm');
+  helpers.assertCursorAt(10,7);
+  helpers.doKeys('4', '[', 'm');
+  helpers.assertCursorAt(1,3);
+  helpers.doKeys('5', ']', 'm');
+  helpers.assertCursorAt(11,0);
+  helpers.doKeys('[', 'M');
+  helpers.assertCursorAt(9,1);
+  helpers.doKeys('3', ']', 'M');
+  helpers.assertCursorAt(15,0);
+  helpers.doKeys('5', '[', 'M');
+  helpers.assertCursorAt(7,3);
+}, { value: squareBracketMotionSandbox});
+
+// Ex mode tests
+testVim('ex_go_to_line', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doEx('4');
+  helpers.assertCursorAt(3, 0);
+}, { value: 'a\nb\nc\nd\ne\n'});
+testVim('ex_write', function(cm, vim, helpers) {
+  var tmp = CodeMirror.commands.save;
+  var written;
+  var actualCm;
+  CodeMirror.commands.save = function(cm) {
+    written = true;
+    actualCm = cm;
+  };
+  // Test that w, wr, wri ... write all trigger :write.
+  var command = 'write';
+  for (var i = 1; i < command.length; i++) {
+    written = false;
+    actualCm = null;
+    helpers.doEx(command.substring(0, i));
+    eq(written, true);
+    eq(actualCm, cm);
+  }
+  CodeMirror.commands.save = tmp;
+});
+testVim('ex_sort', function(cm, vim, helpers) {
+  helpers.doEx('sort');
+  eq('Z\na\nb\nc\nd', cm.getValue());
+}, { value: 'b\nZ\nd\nc\na'});
+testVim('ex_sort_reverse', function(cm, vim, helpers) {
+  helpers.doEx('sort!');
+  eq('d\nc\nb\na', cm.getValue());
+}, { value: 'b\nd\nc\na'});
+testVim('ex_sort_range', function(cm, vim, helpers) {
+  helpers.doEx('2,3sort');
+  eq('b\nc\nd\na', cm.getValue());
+}, { value: 'b\nd\nc\na'});
+testVim('ex_sort_oneline', function(cm, vim, helpers) {
+  helpers.doEx('2sort');
+  // Expect no change.
+  eq('b\nd\nc\na', cm.getValue());
+}, { value: 'b\nd\nc\na'});
+testVim('ex_sort_ignoreCase', function(cm, vim, helpers) {
+  helpers.doEx('sort i');
+  eq('a\nb\nc\nd\nZ', cm.getValue());
+}, { value: 'b\nZ\nd\nc\na'});
+testVim('ex_sort_unique', function(cm, vim, helpers) {
+  helpers.doEx('sort u');
+  eq('Z\na\nb\nc\nd', cm.getValue());
+}, { value: 'b\nZ\na\na\nd\na\nc\na'});
+testVim('ex_sort_decimal', function(cm, vim, helpers) {
+  helpers.doEx('sort d');
+  eq('d3\n s5\n6\n.9', cm.getValue());
+}, { value: '6\nd3\n s5\n.9'});
+testVim('ex_sort_decimal_negative', function(cm, vim, helpers) {
+  helpers.doEx('sort d');
+  eq('z-9\nd3\n s5\n6\n.9', cm.getValue());
+}, { value: '6\nd3\n s5\n.9\nz-9'});
+testVim('ex_sort_decimal_reverse', function(cm, vim, helpers) {
+  helpers.doEx('sort! d');
+  eq('.9\n6\n s5\nd3', cm.getValue());
+}, { value: '6\nd3\n s5\n.9'});
+testVim('ex_sort_hex', function(cm, vim, helpers) {
+  helpers.doEx('sort x');
+  eq(' s5\n6\n.9\n&0xB\nd3', cm.getValue());
+}, { value: '6\nd3\n s5\n&0xB\n.9'});
+testVim('ex_sort_octal', function(cm, vim, helpers) {
+  helpers.doEx('sort o');
+  eq('.8\n.9\nd3\n s5\n6', cm.getValue());
+}, { value: '6\nd3\n s5\n.9\n.8'});
+testVim('ex_sort_decimal_mixed', function(cm, vim, helpers) {
+  helpers.doEx('sort d');
+  eq('y\nz\nc1\nb2\na3', cm.getValue());
+}, { value: 'a3\nz\nc1\ny\nb2'});
+testVim('ex_sort_decimal_mixed_reverse', function(cm, vim, helpers) {
+  helpers.doEx('sort! d');
+  eq('a3\nb2\nc1\nz\ny', cm.getValue());
+}, { value: 'a3\nz\nc1\ny\nb2'});
+testVim('ex_substitute_same_line', function(cm, vim, helpers) {
+  cm.setCursor(1, 0);
+  helpers.doEx('s/one/two');
+  eq('one one\n two two', cm.getValue());
+}, { value: 'one one\n one one'});
+testVim('ex_substitute_global', function(cm, vim, helpers) {
+  cm.setCursor(1, 0);
+  helpers.doEx('%s/one/two');
+  eq('two two\n two two', cm.getValue());
+}, { value: 'one one\n one one'});
+testVim('ex_substitute_input_range', function(cm, vim, helpers) {
+  cm.setCursor(1, 0);
+  helpers.doEx('1,3s/\\d/0');
+  eq('0\n0\n0\n4', cm.getValue());
+}, { value: '1\n2\n3\n4' });
+testVim('ex_substitute_visual_range', function(cm, vim, helpers) {
+  cm.setCursor(1, 0);
+  // Set last visual mode selection marks '< and '> at lines 2 and 4
+  helpers.doKeys('V', '2', 'j', 'v');
+  helpers.doEx('\'<,\'>s/\\d/0');
+  eq('1\n0\n0\n0\n5', cm.getValue());
+}, { value: '1\n2\n3\n4\n5' });
+testVim('ex_substitute_capture', function(cm, vim, helpers) {
+  cm.setCursor(1, 0);
+  helpers.doEx('s/(\\d+)/$1$1/')
+  eq('a1111 a1212 a1313', cm.getValue());
+}, { value: 'a11 a12 a13' });
+testVim('ex_substitute_empty_query', function(cm, vim, helpers) {
+  // If the query is empty, use last query.
+  cm.setCursor(1, 0);
+  cm.openDialog = helpers.fakeOpenDialog('1');
+  helpers.doKeys('/');
+  helpers.doEx('s//b');
+  eq('abb ab2 ab3', cm.getValue());
+}, { value: 'a11 a12 a13' });
+testVim('ex_substitute_count', function(cm, vim, helpers) {
+  cm.setCursor(1, 0);
+  helpers.doEx('s/\\d/0/i 2');
+  eq('1\n0\n0\n4', cm.getValue());
+}, { value: '1\n2\n3\n4' });
+testVim('ex_substitute_count_with_range', function(cm, vim, helpers) {
+  cm.setCursor(1, 0);
+  helpers.doEx('1,3s/\\d/0/ 3');
+  eq('1\n2\n0\n0', cm.getValue());
+}, { value: '1\n2\n3\n4' });
+function testSubstituteConfirm(name, command, initialValue, expectedValue, keys, finalPos) {
+  testVim(name, function(cm, vim, helpers) {
+    var savedOpenDialog = cm.openDialog;
+    var savedKeyName = CodeMirror.keyName;
+    var onKeyDown;
+    var recordedCallback;
+    var closed = true; // Start out closed, set false on second openDialog.
+    function close() {
+      closed = true;
+    }
+    // First openDialog should save callback.
+    cm.openDialog = function(template, callback, options) {
+      recordedCallback = callback;
+    }
+    // Do first openDialog.
+    helpers.doKeys(':');
+    // Second openDialog should save keyDown handler.
+    cm.openDialog = function(template, callback, options) {
+      onKeyDown = options.onKeyDown;
+      closed = false;
+    };
+    // Return the command to Vim and trigger second openDialog.
+    recordedCallback(command);
+    // The event should really use keyCode, but here just mock it out and use
+    // key and replace keyName to just return key.
+    CodeMirror.keyName = function (e) { return e.key; }
+    keys = keys.toUpperCase();
+    for (var i = 0; i < keys.length; i++) {
+      is(!closed);
+      onKeyDown({ key: keys.charAt(i) }, '', close);
+    }
+    try {
+      eq(expectedValue, cm.getValue());
+      helpers.assertCursorAt(finalPos);
+      is(closed);
+    } catch(e) {
+      throw e
+    } finally {
+      // Restore overriden functions.
+      CodeMirror.keyName = savedKeyName;
+      cm.openDialog = savedOpenDialog;
+    }
+  }, { value: initialValue });
+};
+testSubstituteConfirm('ex_substitute_confirm_emptydoc',
+    '%s/x/b/c', '', '', '', makeCursor(0, 0));
+testSubstituteConfirm('ex_substitute_confirm_nomatch',
+    '%s/x/b/c', 'ba a\nbab', 'ba a\nbab', '', makeCursor(0, 0));
+testSubstituteConfirm('ex_substitute_confirm_accept',
+    '%s/a/b/c', 'ba a\nbab', 'bb b\nbbb', 'yyy', makeCursor(1, 1));
+testSubstituteConfirm('ex_substitute_confirm_random_keys',
+    '%s/a/b/c', 'ba a\nbab', 'bb b\nbbb', 'ysdkywerty', makeCursor(1, 1));
+testSubstituteConfirm('ex_substitute_confirm_some',
+    '%s/a/b/c', 'ba a\nbab', 'bb a\nbbb', 'yny', makeCursor(1, 1));
+testSubstituteConfirm('ex_substitute_confirm_all',
+    '%s/a/b/c', 'ba a\nbab', 'bb b\nbbb', 'a', makeCursor(1, 1));
+testSubstituteConfirm('ex_substitute_confirm_accept_then_all',
+    '%s/a/b/c', 'ba a\nbab', 'bb b\nbbb', 'ya', makeCursor(1, 1));
+testSubstituteConfirm('ex_substitute_confirm_quit',
+    '%s/a/b/c', 'ba a\nbab', 'bb a\nbab', 'yq', makeCursor(0, 3));
+testSubstituteConfirm('ex_substitute_confirm_last',
+    '%s/a/b/c', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3));
+testSubstituteConfirm('ex_substitute_confirm_oneline',
+    '1s/a/b/c', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3));
+testSubstituteConfirm('ex_substitute_confirm_range_accept',
+    '1,2s/a/b/c', 'aa\na \na\na', 'bb\nb \na\na', 'yyy', makeCursor(1, 0));
+testSubstituteConfirm('ex_substitute_confirm_range_some',
+    '1,3s/a/b/c', 'aa\na \na\na', 'ba\nb \nb\na', 'ynyy', makeCursor(2, 0));
+testSubstituteConfirm('ex_substitute_confirm_range_all',
+    '1,3s/a/b/c', 'aa\na \na\na', 'bb\nb \nb\na', 'a', makeCursor(2, 0));
+testSubstituteConfirm('ex_substitute_confirm_range_last',
+    '1,3s/a/b/c', 'aa\na \na\na', 'bb\nb \na\na', 'yyl', makeCursor(1, 0));
+//:noh should clear highlighting of search-results but allow to resume search through n
+testVim('ex_noh_clearSearchHighlight', function(cm, vim, helpers) {
+  cm.openDialog = helpers.fakeOpenDialog('match');
+  helpers.doKeys('?');
+  helpers.doEx('noh');
+  eq(vim.searchState_.getOverlay(),null,'match-highlighting wasn\'t cleared');
+  helpers.doKeys('n');
+  helpers.assertCursorAt(0, 11,'can\'t resume search after clearing highlighting');
+}, { value: 'match nope match \n nope Match' });
+// TODO: Reset key maps after each test.
+testVim('ex_map_key2key', function(cm, vim, helpers) {
+  helpers.doEx('map a x');
+  helpers.doKeys('a');
+  helpers.assertCursorAt(0, 0);
+  eq('bc', cm.getValue());
+}, { value: 'abc' });
+testVim('ex_map_key2key_to_colon', function(cm, vim, helpers) {
+  helpers.doEx('map ; :');
+  var dialogOpened = false;
+  cm.openDialog = function() {
+    dialogOpened = true;
+  }
+  helpers.doKeys(';');
+  eq(dialogOpened, true);
+});
+testVim('ex_map_ex2key:', function(cm, vim, helpers) {
+  helpers.doEx('map :del x');
+  helpers.doEx('del');
+  helpers.assertCursorAt(0, 0);
+  eq('bc', cm.getValue());
+}, { value: 'abc' });
+testVim('ex_map_ex2ex', function(cm, vim, helpers) {
+  helpers.doEx('map :del :w');
+  var tmp = CodeMirror.commands.save;
+  var written = false;
+  var actualCm;
+  CodeMirror.commands.save = function(cm) {
+    written = true;
+    actualCm = cm;
+  };
+  helpers.doEx('del');
+  CodeMirror.commands.save = tmp;
+  eq(written, true);
+  eq(actualCm, cm);
+});
+testVim('ex_map_key2ex', function(cm, vim, helpers) {
+  helpers.doEx('map a :w');
+  var tmp = CodeMirror.commands.save;
+  var written = false;
+  var actualCm;
+  CodeMirror.commands.save = function(cm) {
+    written = true;
+    actualCm = cm;
+  };
+  helpers.doKeys('a');
+  CodeMirror.commands.save = tmp;
+  eq(written, true);
+  eq(actualCm, cm);
+});
+// Testing registration of functions as ex-commands and mapping to <Key>-keys
+testVim('ex_api_test', function(cm, vim, helpers) {
+  var res=false;
+  var val='from';
+  CodeMirror.Vim.defineEx('extest','ext',function(cm,params){
+    if(params.args)val=params.args[0];
+    else res=true;
+  });
+  helpers.doEx(':ext to');
+  eq(val,'to','Defining ex-command failed');
+  CodeMirror.Vim.map('<C-CR><Space>',':ext');
+  helpers.doKeys('<C-CR>','<Space>');
+  is(res,'Mapping to key failed');
+});
+// For now, this test needs to be last because it messes up : for future tests.
+testVim('ex_map_key2key_from_colon', function(cm, vim, helpers) {
+  helpers.doEx('map : x');
+  helpers.doKeys(':');
+  helpers.assertCursorAt(0, 0);
+  eq('bc', cm.getValue());
+}, { value: 'abc' });
--- a/browser/devtools/sourceeditor/test/codemirror.html
+++ b/browser/devtools/sourceeditor/test/codemirror.html
@@ -8,22 +8,22 @@
     <!--<link rel="stylesheet" href="../doc/docs.css">-->
 
     <script src="chrome://browser/content/devtools/codemirror/codemirror.js"></script>
     <script src="chrome://browser/content/devtools/codemirror/searchcursor.js"></script>
     <script src="chrome://browser/content/devtools/codemirror/dialog.js"></script>
     <script src="chrome://browser/content/devtools/codemirror/matchbrackets.js"></script>
     <script src="chrome://browser/content/devtools/codemirror/comment.js"></script>
     <script src="chrome://browser/content/devtools/codemirror/javascript.js"></script>
+    <script src="chrome://browser/content/devtools/codemirror/vim.js"></script>
+    <script src="chrome://browser/content/devtools/codemirror/emacs.js"></script>
 
     <!--<script src="../addon/mode/overlay.js"></script>
     <script src="../addon/mode/multiplex.js"></script>
-    <script src="../mode/xml/xml.js"></script>
-    <script src="../keymap/vim.js"></script>
-    <script src="../keymap/emacs.js"></script>-->
+    <script src="../mode/xml/xml.js"></script>-->
 
     <style type="text/css">
       .ok {color: #090;}
       .fail {color: #e00;}
       .error {color: #c90;}
       .done {font-weight: bold;}
       #progress {
         background: #45d;
@@ -57,16 +57,18 @@
 
     <div id=testground></div>
 
     <script src="cm_driver.js"></script>
     <script src="cm_test.js"></script>
     <script src="cm_comment_test.js"></script>
     <script src="cm_mode_test.js"></script>
     <script src="cm_mode_javascript_test.js"></script>
+    <script src="cm_vim_test.js"></script>
+    <script src="cm_emacs_test.js"></script>
 
     <!--<script src="doc_test.js"></script>
     <script src="../mode/css/css.js"></script>
     <script src="../mode/css/test.js"></script>
     <script src="../mode/css/scss_test.js"></script>
     <script src="../mode/xml/xml.js"></script>
     <script src="../mode/htmlmixed/htmlmixed.js"></script>
     <script src="../mode/ruby/ruby.js"></script>
@@ -75,19 +77,17 @@
     <script src="../mode/markdown/markdown.js"></script>
     <script src="../mode/markdown/test.js"></script>
     <script src="../mode/gfm/gfm.js"></script>
     <script src="../mode/gfm/test.js"></script>
     <script src="../mode/stex/stex.js"></script>
     <script src="../mode/stex/test.js"></script>
     <script src="../mode/xquery/xquery.js"></script>
     <script src="../mode/xquery/test.js"></script>
-    <script src="../addon/mode/multiplex_test.js"></script>
-    <script src="vim_test.js"></script>
-    <script src="emacs_test.js"></script>-->
+    <script src="../addon/mode/multiplex_test.js"></script>-->
 
     <script>
       window.onload = runHarness;
       CodeMirror.on(window, 'hashchange', runHarness);
 
       function esc(str) {
         return str.replace(/[<&]/, function(ch) { return ch == "<" ? "&lt;" : "&amp;"; });
       }
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -192,16 +192,20 @@ StyleSheetEditor.prototype = {
       readOnly: this._state.readOnly,
       autoCloseBrackets: "{}()[]",
       extraKeys: this._getKeyBindings(),
       contextMenu: "sourceEditorContextMenu"
     };
     let sourceEditor = new Editor(config);
 
     sourceEditor.appendTo(inputElement).then(() => {
+      sourceEditor.on("save", () => {
+        this.saveToFile();
+      });
+
       sourceEditor.on("change", () => {
         this.updateStyleSheet();
       });
 
       this.sourceEditor = sourceEditor;
 
       if (this._focusOnSourceEditorReady) {
         this._focusOnSourceEditorReady = false;