From 0e04fff0ea80fa5cbe96b8354db38bd334aea83a Mon Sep 17 00:00:00 2001
From: Ananya Sen
Date: Mon, 16 Jul 2012 16:04:05 -0700
Subject: upgrade to codemirror 2.3

Signed-off-by: Ananya Sen <Ananya.Sen@motorola.com>

Conflicts:

	js/code-editor/codemirror-ninja/theme/lesser-dark-ninja.css

Signed-off-by: Ananya Sen <Ananya.Sen@motorola.com>
---
 imports/codemirror/LICENSE                         |   2 +-
 imports/codemirror/keymap/emacs.js                 |   4 +-
 imports/codemirror/keymap/vim.js                   | 927 +++++++++++++++------
 imports/codemirror/lib/codemirror.css              |  64 +-
 imports/codemirror/lib/codemirror.js               | 619 +++++++++-----
 imports/codemirror/lib/util/closetag.js            |  28 -
 imports/codemirror/lib/util/foldcode.js            |  36 +-
 imports/codemirror/lib/util/formatting.js          |   9 +-
 imports/codemirror/lib/util/loadmode.js            |  51 ++
 imports/codemirror/lib/util/match-highlighter.js   |   2 +-
 imports/codemirror/lib/util/multiplex.js           |  72 ++
 imports/codemirror/lib/util/overlay.js             |   5 +-
 imports/codemirror/lib/util/pig-hint.js            | 123 +++
 imports/codemirror/lib/util/search.js              |  32 +-
 imports/codemirror/lib/util/searchcursor.js        |   2 +-
 imports/codemirror/mode/clike/clike.js             |  39 +-
 imports/codemirror/mode/clike/scala.html           | 765 +++++++++++++++++
 .../codemirror/mode/coffeescript/coffeescript.js   |   6 +
 imports/codemirror/mode/coffeescript/index.html    |   6 +
 imports/codemirror/mode/diff/diff.css              |   3 -
 imports/codemirror/mode/diff/diff.js               |  29 +-
 imports/codemirror/mode/diff/index.html            |  13 +-
 imports/codemirror/mode/ecl/ecl.js                 |   2 +-
 imports/codemirror/mode/erlang/erlang.js           | 251 ++++++
 imports/codemirror/mode/erlang/index.html          |  61 ++
 imports/codemirror/mode/gfm/gfm.js                 |  42 +-
 .../codemirror/mode/htmlembedded/htmlembedded.js   |   2 +-
 imports/codemirror/mode/htmlmixed/htmlmixed.js     |   4 +-
 imports/codemirror/mode/javascript/javascript.js   |   9 +-
 imports/codemirror/mode/less/index.html            |  25 +-
 imports/codemirror/mode/less/less.js               |  42 +-
 imports/codemirror/mode/markdown/markdown.js       |   2 +-
 imports/codemirror/mode/php/php.js                 |   2 +-
 imports/codemirror/mode/pig/index.html             |  42 +
 imports/codemirror/mode/pig/pig.js                 | 172 ++++
 imports/codemirror/mode/python/python.js           |  30 +-
 imports/codemirror/mode/rst/rst.js                 |   2 +-
 imports/codemirror/mode/ruby/ruby.js               |  11 +-
 imports/codemirror/mode/scheme/scheme.js           | 120 ++-
 imports/codemirror/mode/shell/index.html           |  50 ++
 imports/codemirror/mode/shell/shell.js             | 103 +++
 imports/codemirror/mode/stex/stex.js               |  10 +-
 imports/codemirror/mode/stex/test.html             |  12 +
 imports/codemirror/mode/tiddlywiki/tiddlywiki.css  |  35 +-
 imports/codemirror/mode/tiddlywiki/tiddlywiki.js   |  28 +-
 imports/codemirror/mode/tiki/index.html            |  82 ++
 imports/codemirror/mode/tiki/tiki.css              |  26 +
 imports/codemirror/mode/tiki/tiki.js               | 316 +++++++
 imports/codemirror/mode/xml/xml.js                 |  69 +-
 imports/codemirror/mode/xmlpure/index.html         |  58 --
 imports/codemirror/mode/xmlpure/xmlpure.js         | 490 -----------
 imports/codemirror/theme/ambiance.css              |  81 ++
 imports/codemirror/theme/blackboard.css            |  25 +
 imports/codemirror/theme/erlang-dark.css           |  21 +
 imports/codemirror/theme/lesser-dark.css           |   7 +-
 imports/codemirror/theme/night.css                 |   2 +-
 imports/codemirror/theme/vibrant-ink.css           |  27 +
 imports/codemirror/version.txt                     |   2 +-
 58 files changed, 3858 insertions(+), 1242 deletions(-)
 create mode 100644 imports/codemirror/lib/util/loadmode.js
 create mode 100644 imports/codemirror/lib/util/multiplex.js
 create mode 100644 imports/codemirror/lib/util/pig-hint.js
 create mode 100644 imports/codemirror/mode/clike/scala.html
 delete mode 100644 imports/codemirror/mode/diff/diff.css
 create mode 100644 imports/codemirror/mode/erlang/erlang.js
 create mode 100644 imports/codemirror/mode/erlang/index.html
 create mode 100644 imports/codemirror/mode/pig/index.html
 create mode 100644 imports/codemirror/mode/pig/pig.js
 create mode 100644 imports/codemirror/mode/shell/index.html
 create mode 100644 imports/codemirror/mode/shell/shell.js
 create mode 100644 imports/codemirror/mode/tiki/index.html
 create mode 100644 imports/codemirror/mode/tiki/tiki.css
 create mode 100644 imports/codemirror/mode/tiki/tiki.js
 delete mode 100644 imports/codemirror/mode/xmlpure/index.html
 delete mode 100644 imports/codemirror/mode/xmlpure/xmlpure.js
 create mode 100644 imports/codemirror/theme/ambiance.css
 create mode 100644 imports/codemirror/theme/blackboard.css
 create mode 100644 imports/codemirror/theme/erlang-dark.css
 create mode 100644 imports/codemirror/theme/vibrant-ink.css

(limited to 'imports/codemirror')

diff --git a/imports/codemirror/LICENSE b/imports/codemirror/LICENSE
index f62410e6..3916e96b 100644
--- a/imports/codemirror/LICENSE
+++ b/imports/codemirror/LICENSE
@@ -1,4 +1,4 @@
-Copyright (C) 2011 by Marijn Haverbeke <marijnh@gmail.com>
+Copyright (C) 2012 by Marijn Haverbeke <marijnh@gmail.com>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/imports/codemirror/keymap/emacs.js b/imports/codemirror/keymap/emacs.js
index 8fd3564e..2a57e2ff 100644
--- a/imports/codemirror/keymap/emacs.js
+++ b/imports/codemirror/keymap/emacs.js
@@ -18,12 +18,12 @@
     "Alt-Y": function(cm) {cm.replaceSelection(popFromRing());},
     "Ctrl-/": "undo", "Shift-Ctrl--": "undo", "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
     "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace",
-    "Ctrl-Z": "undo", "Cmd-Z": "undo",
+    "Ctrl-Z": "undo", "Cmd-Z": "undo", "Alt-/": "autocomplete",
     fallthrough: ["basic", "emacsy"]
   };
 
   CodeMirror.keyMap["emacs-Ctrl-X"] = {
     "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": "undo", "K": "close",
-    auto: "emacs", catchall: function(cm) {/*ignore*/}
+    auto: "emacs", nofallthrough: true
   };
 })();
diff --git a/imports/codemirror/keymap/vim.js b/imports/codemirror/keymap/vim.js
index f8fa5e07..2aa6e0f7 100644
--- a/imports/codemirror/keymap/vim.js
+++ b/imports/codemirror/keymap/vim.js
@@ -1,21 +1,96 @@
+// Supported keybindings:
+//
+// Cursor movement:
+// h, j, k, l
+// e, E, w, W, b, B
+// Ctrl-f, Ctrl-b
+// Ctrl-n, Ctrl-p
+// $, ^, 0
+// G
+// ge, gE
+// gg
+// f<char>, F<char>, t<char>, T<char>
+// Ctrl-o, Ctrl-i TODO (FIXME - Ctrl-O wont work in Chrome)
+// /, ?, n, N TODO (does not work)
+// #, * TODO
+//
+// Entering insert mode:
+// i, I, a, A, o, O
+// s
+// ce, cb (without support for number of actions like c3e - TODO)
+// cc
+// S, C TODO
+// cf<char>, cF<char>, ct<char>, cT<char>
+//
+// Deleting text:
+// x, X
+// J
+// dd, D
+// de, db (without support for number of actions like d3e - TODO)
+// df<char>, dF<char>, dt<char>, dT<char>
+//
+// Yanking and pasting:
+// yy, Y
+// p, P
+// p'<char> TODO - test
+// y'<char> TODO - test
+// m<char> TODO - test
+//
+// Changing text in place:
+// ~
+// r<char>
+//
+// Visual mode:
+// v, V TODO
+//
+// Misc:
+// . TODO
+//
+
 (function() {
   var count = "";
   var sdir = "f";
   var buf = "";
   var yank = 0;
   var mark = [];
+  var reptTimes = 0;
   function emptyBuffer() { buf = ""; }
-  function pushInBuffer(str) { buf += str; };
-  function pushCountDigit(digit) { return function(cm) {count += digit;} }
-  function popCount() { var i = parseInt(count); count = ""; return i || 1; }
+  function pushInBuffer(str) { buf += str; }
+  function pushCountDigit(digit) { return function(cm) {count += digit;}; }
+  function popCount() { var i = parseInt(count, 10); count = ""; return i || 1; }
+  function iterTimes(func) {
+    for (var i = 0, c = popCount(); i < c; ++i) func(i, i == c - 1);
+  }
   function countTimes(func) {
     if (typeof func == "string") func = CodeMirror.commands[func];
-    return function(cm) { for (var i = 0, c = popCount(); i < c; ++i) func(cm); }
+    return function(cm) { iterTimes(function () { func(cm); }); };
   }
 
   function iterObj(o, f) {
     for (var prop in o) if (o.hasOwnProperty(prop)) f(prop, o[prop]);
   }
+  function iterList(l, f) {
+    for (var i in l) f(l[i]);
+  }
+  function toLetter(ch) {
+    // T -> t, Shift-T -> T, '*' -> *, "Space" -> " "
+    if (ch.slice(0, 6) == "Shift-") {
+      return ch.slice(0, 1);
+    } else {
+      if (ch == "Space") return " ";
+      if (ch.length == 3 && ch[0] == "'" && ch[2] == "'") return ch[1];
+      return ch.toLowerCase();
+    }
+  }
+  var SPECIAL_SYMBOLS = "~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;\"\'1234567890";
+  function toCombo(ch) {
+    // t -> T, T -> Shift-T, * -> '*', " " -> "Space"
+    if (ch == " ") return "Space";
+    var specialIdx = SPECIAL_SYMBOLS.indexOf(ch);
+    if (specialIdx != -1) return "'" + ch + "'";
+    if (ch.toLowerCase() == ch) return ch.toUpperCase();
+    return "Shift-" + ch.toUpperCase();
+  }
 
   var word = [/\w/, /[^\w\s]/], bigWord = [/\S/];
   function findWord(line, pos, dir, regexps) {
@@ -37,62 +112,172 @@
     }
     return {from: Math.min(start, end), to: Math.max(start, end)};
   }
-  function moveToWord(cm, regexps, dir, where) {
-    var cur = cm.getCursor(), ch = cur.ch, line = cm.getLine(cur.line), word;
-    while (true) {
-      word = findWord(line, ch, dir, regexps);
-      ch = word[where == "end" ? "to" : "from"];
-      if (ch == cur.ch && word.from != word.to) ch = word[dir < 0 ? "from" : "to"];
-      else break;
+  function moveToWord(cm, regexps, dir, times, where) {
+    var cur = cm.getCursor();
+
+    for (var i = 0; i < times; i++) {
+      var line = cm.getLine(cur.line), startCh = cur.ch, word;
+      while (true) {
+        // If we're at start/end of line, start on prev/next respectivly
+        if (cur.ch == line.length && dir > 0) {
+          cur.line++;
+          cur.ch = 0;
+          line = cm.getLine(cur.line);
+        } else if (cur.ch == 0 && dir < 0) {
+          cur.line--;
+          cur.ch = line.length;
+          line = cm.getLine(cur.line);
+        }
+        if (!line) break;
+
+        // On to the actual searching
+        word = findWord(line, cur.ch, dir, regexps);
+        cur.ch = word[where == "end" ? "to" : "from"];
+        if (startCh == cur.ch && word.from != word.to) cur.ch = word[dir < 0 ? "from" : "to"];
+        else break;
+      }
     }
-    cm.setCursor(cur.line, word[where == "end" ? "to" : "from"], true);
+    return cur;
   }
   function joinLineNext(cm) {
     var cur = cm.getCursor(), ch = cur.ch, line = cm.getLine(cur.line);
-    CodeMirror.commands.goLineEnd(cm); 
+    CodeMirror.commands.goLineEnd(cm);
     if (cur.line != cm.lineCount()) {
       CodeMirror.commands.goLineEnd(cm);
       cm.replaceSelection(" ", "end");
       CodeMirror.commands.delCharRight(cm);
-    } 
-  }
-  function editCursor(mode) {
-    if (mode == "vim-insert") {  
-      // put in your cursor css changing code
-    } else if (mode == "vim") {
-      // put in your cursor css changing code
     }
   }
-  function delTillMark(cm, cHar) { 
-    var i = mark[cHar], l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
+  function delTillMark(cm, cHar) {
+    var i = mark[cHar];
+    if (i === undefined) {
+      // console.log("Mark not set"); // TODO - show in status bar
+      return;
+    }
+    var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
     cm.setCursor(start);
     for (var c = start; c <= end; c++) {
-      pushInBuffer("\n"+cm.getLine(start)); 
+      pushInBuffer("\n"+cm.getLine(start));
       cm.removeLine(start);
     }
   }
-  function yankTillMark(cm, cHar) { 
-    var i = mark[cHar], l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
+  function yankTillMark(cm, cHar) {
+    var i = mark[cHar];
+    if (i === undefined) {
+      // console.log("Mark not set"); // TODO - show in status bar
+      return;
+    }
+    var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
     for (var c = start; c <= end; c++) {
       pushInBuffer("\n"+cm.getLine(c));
     }
     cm.setCursor(start);
   }
+  function goLineStartText(cm) {
+    // Go to the start of the line where the text begins, or the end for whitespace-only lines
+    var cur = cm.getCursor(), firstNonWS = cm.getLine(cur.line).search(/\S/);
+    cm.setCursor(cur.line, firstNonWS == -1 ? line.length : firstNonWS, true);
+  }
 
+  function charIdxInLine(cm, cHar, motion_options) {
+    // Search for cHar in line.
+    // motion_options: {forward, inclusive}
+    // If inclusive = true, include it too.
+    // If forward = true, search forward, else search backwards.
+    // If char is not found on this line, do nothing
+    var cur = cm.getCursor(), line = cm.getLine(cur.line), idx;
+    var ch = toLetter(cHar), mo = motion_options;
+    if (mo.forward) {
+      idx = line.indexOf(ch, cur.ch + 1);
+      if (idx != -1 && mo.inclusive) idx += 1;
+    } else {
+      idx = line.lastIndexOf(ch, cur.ch);
+      if (idx != -1 && !mo.inclusive) idx += 1;
+    }
+    return idx;
+  }
+
+  function moveTillChar(cm, cHar, motion_options) {
+    // Move to cHar in line, as found by charIdxInLine.
+    var idx = charIdxInLine(cm, cHar, motion_options), cur = cm.getCursor();
+    if (idx != -1) cm.setCursor({line: cur.line, ch: idx});
+  }
+
+  function delTillChar(cm, cHar, motion_options) {
+    // delete text in this line, untill cHar is met,
+    // as found by charIdxInLine.
+    // If char is not found on this line, do nothing
+    var idx = charIdxInLine(cm, cHar, motion_options);
+    var cur = cm.getCursor();
+    if (idx !== -1) {
+      if (motion_options.forward) {
+        cm.replaceRange("", {line: cur.line, ch: cur.ch}, {line: cur.line, ch: idx});
+      } else {
+        cm.replaceRange("", {line: cur.line, ch: idx}, {line: cur.line, ch: cur.ch});
+      }
+    }
+  }
+
+  function enterInsertMode(cm) {
+    // enter insert mode: switch mode and cursor
+    popCount();
+    cm.setOption("keyMap", "vim-insert");
+  }
+
+  // main keymap
   var map = CodeMirror.keyMap.vim = {
-    "0": function(cm) {count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm);},
-    "A": function(cm) {popCount(); cm.setCursor(cm.getCursor().line, cm.getCursor().ch+1, true); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");},
-    "Shift-A": function(cm) {popCount(); CodeMirror.commands.goLineEnd(cm); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");},
-    "I": function(cm) {popCount(); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");},
-    "Shift-I": function(cm) {popCount(); CodeMirror.commands.goLineStartSmart(cm); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");},
-    "O": function(cm) {popCount(); CodeMirror.commands.goLineEnd(cm); cm.replaceSelection("\n", "end"); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");},
-    "Shift-O": function(cm) {popCount(); CodeMirror.commands.goLineStart(cm); cm.replaceSelection("\n", "start"); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");},
-    "G": function(cm) {cm.setOption("keyMap", "vim-prefix-g");},
-    "D": function(cm) {cm.setOption("keyMap", "vim-prefix-d"); emptyBuffer();},
+    // Pipe (|); TODO: should be *screen* chars, so need a util function to turn tabs into spaces?
+    "'|'": function(cm) {
+      cm.setCursor(cm.getCursor().line, popCount() - 1, true);
+    },
+    "A": function(cm) {
+      cm.setCursor(cm.getCursor().line, cm.getCursor().ch+1, true);
+      enterInsertMode(cm);
+    },
+    "Shift-A": function(cm) { CodeMirror.commands.goLineEnd(cm); enterInsertMode(cm);},
+    "I": function(cm) { enterInsertMode(cm);},
+    "Shift-I": function(cm) { goLineStartText(cm); enterInsertMode(cm);},
+    "O": function(cm) {
+      CodeMirror.commands.goLineEnd(cm);
+      CodeMirror.commands.newlineAndIndent(cm);
+      enterInsertMode(cm);
+    },
+    "Shift-O": function(cm) {
+      CodeMirror.commands.goLineStart(cm);
+      cm.replaceSelection("\n", "start");
+      cm.indentLine(cm.getCursor().line);
+      enterInsertMode(cm);
+    },
+    "G": function(cm) { cm.setOption("keyMap", "vim-prefix-g");},
+    "Shift-D": function(cm) {
+      // commented out verions works, but I left original, cause maybe
+      // I don't know vim enouth to see what it does
+      /* var cur = cm.getCursor();
+      var f = {line: cur.line, ch: cur.ch}, t = {line: cur.line};
+      pushInBuffer(cm.getRange(f, t));
+      */
+      emptyBuffer();
+      mark["Shift-D"] = cm.getCursor(false).line;
+      cm.setCursor(cm.getCursor(true).line);
+      delTillMark(cm,"Shift-D"); mark = [];
+    },
+
+    "S": function (cm) {
+      countTimes(function (_cm) {
+        CodeMirror.commands.delCharRight(_cm);
+      })(cm);
+      enterInsertMode(cm);
+    },
     "M": function(cm) {cm.setOption("keyMap", "vim-prefix-m"); mark = [];},
     "Y": function(cm) {cm.setOption("keyMap", "vim-prefix-y"); emptyBuffer(); yank = 0;},
-    "/": function(cm) {var f = CodeMirror.commands.find; f && f(cm); sdir = "f"},
-    "Shift-/": function(cm) {
+    "Shift-Y": function(cm) {
+      emptyBuffer();
+      mark["Shift-D"] = cm.getCursor(false).line;
+      cm.setCursor(cm.getCursor(true).line);
+      yankTillMark(cm,"Shift-D"); mark = [];
+    },
+    "/": function(cm) {var f = CodeMirror.commands.find; f && f(cm); sdir = "f";},
+    "'?'": function(cm) {
       var f = CodeMirror.commands.find;
       if (f) { f(cm); CodeMirror.commands.findPrev(cm); sdir = "r"; }
     },
@@ -104,244 +289,478 @@
       var fn = CodeMirror.commands.findNext;
       if (fn) sdir != "r" ? CodeMirror.commands.findPrev(cm) : fn.findNext(cm);
     },
-    "Shift-G": function(cm) {count == "" ? cm.setCursor(cm.lineCount()) : cm.setCursor(parseInt(count)-1); popCount(); CodeMirror.commands.goLineStart(cm);},
-    catchall: function(cm) {/*ignore*/}
+    "Shift-G": function(cm) {
+      count == "" ? cm.setCursor(cm.lineCount()) : cm.setCursor(parseInt(count, 10)-1);
+      popCount();
+      CodeMirror.commands.goLineStart(cm);
+    },
+    nofallthrough: true, style: "fat-cursor"
   };
-  // Add bindings for number keys
-  for (var i = 1; i < 10; ++i) map[i] = pushCountDigit(i);
+
+  // standard mode switching
+  iterList(["d", "t", "T", "f", "F", "c", "r"],
+      function (ch) {
+        CodeMirror.keyMap.vim[toCombo(ch)] = function (cm) {
+          cm.setOption("keyMap", "vim-prefix-" + ch);
+          emptyBuffer();
+        };
+      });
+
+  function addCountBindings(keyMap) {
+    // Add bindings for number keys
+    keyMap["0"] = function(cm) {
+      count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm);
+    };
+    for (var i = 1; i < 10; ++i) keyMap[i] = pushCountDigit(i);
+  }
+  addCountBindings(CodeMirror.keyMap.vim);
+
+  // main num keymap
   // Add bindings that are influenced by number keys
-  iterObj({"H": "goColumnLeft", "L": "goColumnRight", "J": "goLineDown", "K": "goLineUp",
-		       "Left": "goColumnLeft", "Right": "goColumnRight", "Down": "goLineDown", "Up": "goLineUp",
-           "Backspace": "goCharLeft", "Space": "goCharRight",
-           "B": function(cm) {moveToWord(cm, word, -1, "end");},
-           "E": function(cm) {moveToWord(cm, word, 1, "end");},
-           "W": function(cm) {moveToWord(cm, word, 1, "start");},
-           "Shift-B": function(cm) {moveToWord(cm, bigWord, -1, "end");},
-           "Shift-E": function(cm) {moveToWord(cm, bigWord, 1, "end");},
-           "Shift-W": function(cm) {moveToWord(cm, bigWord, 1, "start");},
-           "X": function(cm) {CodeMirror.commands.delCharRight(cm)},
-           "P": function(cm) {
-		  var cur = cm.getCursor().line;
-		  if (buf!= "") {
-                    CodeMirror.commands.goLineEnd(cm); 
-		    cm.replaceSelection(buf, "end");
-		  }
-		  cm.setCursor(cur+1);
-	        },
-           "Shift-X": function(cm) {CodeMirror.commands.delCharLeft(cm)},
-           "Shift-J": function(cm) {joinLineNext(cm)},
-           "Shift-`": function(cm) {
-                        var cur = cm.getCursor(), cHar = cm.getRange({line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1});
-			cHar = cHar != cHar.toLowerCase() ? cHar.toLowerCase() : cHar.toUpperCase();
-                        cm.replaceRange(cHar, {line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1});
-			cm.setCursor(cur.line, cur.ch+1);
-	              },
-           "Ctrl-B": function(cm) {CodeMirror.commands.goPageUp(cm)},
-           "Ctrl-F": function(cm) {CodeMirror.commands.goPageDown(cm)},
-	   "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
-           "U": "undo", "Ctrl-R": "redo", "Shift-4": "goLineEnd"},
-          function(key, cmd) { map[key] = countTimes(cmd); });
+  iterObj({
+    "Left": "goColumnLeft", "Right": "goColumnRight",
+    "Down": "goLineDown", "Up": "goLineUp", "Backspace": "goCharLeft",
+    "Space": "goCharRight",
+    "X": function(cm) {CodeMirror.commands.delCharRight(cm);},
+    "P": function(cm) {
+      var cur = cm.getCursor().line;
+      if (buf!= "") {
+        if (buf[0] == "\n") CodeMirror.commands.goLineEnd(cm);
+        cm.replaceRange(buf, cm.getCursor());
+      }
+    },
+    "Shift-X": function(cm) {CodeMirror.commands.delCharLeft(cm);},
+    "Shift-J": function(cm) {joinLineNext(cm);},
+    "Shift-P": function(cm) {
+      var cur = cm.getCursor().line;
+      if (buf!= "") {
+        CodeMirror.commands.goLineUp(cm);
+        CodeMirror.commands.goLineEnd(cm);
+        cm.replaceSelection(buf, "end");
+      }
+      cm.setCursor(cur+1);
+    },
+    "'~'": function(cm) {
+      var cur = cm.getCursor(), cHar = cm.getRange({line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1});
+      cHar = cHar != cHar.toLowerCase() ? cHar.toLowerCase() : cHar.toUpperCase();
+      cm.replaceRange(cHar, {line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1});
+      cm.setCursor(cur.line, cur.ch+1);
+    },
+    "Ctrl-B": function(cm) {CodeMirror.commands.goPageUp(cm);},
+    "Ctrl-F": function(cm) {CodeMirror.commands.goPageDown(cm);},
+    "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
+    "U": "undo", "Ctrl-R": "redo"
+  }, function(key, cmd) { map[key] = countTimes(cmd); });
+
+  // empty key maps
+  iterList([
+      "vim-prefix-d'",
+      "vim-prefix-y'",
+      "vim-prefix-df",
+      "vim-prefix-dF",
+      "vim-prefix-dt",
+      "vim-prefix-dT",
+      "vim-prefix-c",
+      "vim-prefix-cf",
+      "vim-prefix-cF",
+      "vim-prefix-ct",
+      "vim-prefix-cT",
+      "vim-prefix-",
+      "vim-prefix-f",
+      "vim-prefix-F",
+      "vim-prefix-t",
+      "vim-prefix-T",
+      "vim-prefix-r",
+      "vim-prefix-m"
+      ],
+      function (prefix) {
+        CodeMirror.keyMap[prefix] = {
+          auto: "vim",
+          nofallthrough: true,
+          style: "fat-cursor"
+        };
+      });
 
   CodeMirror.keyMap["vim-prefix-g"] = {
-    "E": countTimes(function(cm) { moveToWord(cm, word, -1, "start");}),
-    "Shift-E": countTimes(function(cm) { moveToWord(cm, bigWord, -1, "start");}),
-    auto: "vim", 
-    catchall: function(cm) {/*ignore*/}
+    "E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, word, -1, 1, "start"));}),
+    "Shift-E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, bigWord, -1, 1, "start"));}),
+    "G": function (cm) { cm.setCursor({line: 0, ch: cm.getCursor().ch});},
+    auto: "vim", nofallthrough: true, style: "fat-cursor"
   };
 
-  CodeMirror.keyMap["vim-prefix-m"] = {
-    "A": function(cm) {mark["A"] = cm.getCursor().line;},
-    "Shift-A": function(cm) {mark["Shift-A"] = cm.getCursor().line;},
-    "B": function(cm) {mark["B"] = cm.getCursor().line;},
-    "Shift-B": function(cm) {mark["Shift-B"] = cm.getCursor().line;},
-    "C": function(cm) {mark["C"] = cm.getCursor().line;},
-    "Shift-C": function(cm) {mark["Shift-C"] = cm.getCursor().line;},
-    "D": function(cm) {mark["D"] = cm.getCursor().line;},
-    "Shift-D": function(cm) {mark["Shift-D"] = cm.getCursor().line;},
-    "E": function(cm) {mark["E"] = cm.getCursor().line;},
-    "Shift-E": function(cm) {mark["Shift-E"] = cm.getCursor().line;},
-    "F": function(cm) {mark["F"] = cm.getCursor().line;},
-    "Shift-F": function(cm) {mark["Shift-F"] = cm.getCursor().line;},
-    "G": function(cm) {mark["G"] = cm.getCursor().line;},
-    "Shift-G": function(cm) {mark["Shift-G"] = cm.getCursor().line;},
-    "H": function(cm) {mark["H"] = cm.getCursor().line;},
-    "Shift-H": function(cm) {mark["Shift-H"] = cm.getCursor().line;},
-    "I": function(cm) {mark["I"] = cm.getCursor().line;},
-    "Shift-I": function(cm) {mark["Shift-I"] = cm.getCursor().line;},
-    "J": function(cm) {mark["J"] = cm.getCursor().line;},
-    "Shift-J": function(cm) {mark["Shift-J"] = cm.getCursor().line;},
-    "K": function(cm) {mark["K"] = cm.getCursor().line;},
-    "Shift-K": function(cm) {mark["Shift-K"] = cm.getCursor().line;},
-    "L": function(cm) {mark["L"] = cm.getCursor().line;},
-    "Shift-L": function(cm) {mark["Shift-L"] = cm.getCursor().line;},
-    "M": function(cm) {mark["M"] = cm.getCursor().line;},
-    "Shift-M": function(cm) {mark["Shift-M"] = cm.getCursor().line;},
-    "N": function(cm) {mark["N"] = cm.getCursor().line;},
-    "Shift-N": function(cm) {mark["Shift-N"] = cm.getCursor().line;},
-    "O": function(cm) {mark["O"] = cm.getCursor().line;},
-    "Shift-O": function(cm) {mark["Shift-O"] = cm.getCursor().line;},
-    "P": function(cm) {mark["P"] = cm.getCursor().line;},
-    "Shift-P": function(cm) {mark["Shift-P"] = cm.getCursor().line;},
-    "Q": function(cm) {mark["Q"] = cm.getCursor().line;},
-    "Shift-Q": function(cm) {mark["Shift-Q"] = cm.getCursor().line;},
-    "R": function(cm) {mark["R"] = cm.getCursor().line;},
-    "Shift-R": function(cm) {mark["Shift-R"] = cm.getCursor().line;},
-    "S": function(cm) {mark["S"] = cm.getCursor().line;},
-    "Shift-S": function(cm) {mark["Shift-S"] = cm.getCursor().line;},
-    "T": function(cm) {mark["T"] = cm.getCursor().line;},
-    "Shift-T": function(cm) {mark["Shift-T"] = cm.getCursor().line;},
-    "U": function(cm) {mark["U"] = cm.getCursor().line;},
-    "Shift-U": function(cm) {mark["Shift-U"] = cm.getCursor().line;},
-    "V": function(cm) {mark["V"] = cm.getCursor().line;},
-    "Shift-V": function(cm) {mark["Shift-V"] = cm.getCursor().line;},
-    "W": function(cm) {mark["W"] = cm.getCursor().line;},
-    "Shift-W": function(cm) {mark["Shift-W"] = cm.getCursor().line;},
-    "X": function(cm) {mark["X"] = cm.getCursor().line;},
-    "Shift-X": function(cm) {mark["Shift-X"] = cm.getCursor().line;},
-    "Y": function(cm) {mark["Y"] = cm.getCursor().line;},
-    "Shift-Y": function(cm) {mark["Shift-Y"] = cm.getCursor().line;},
-    "Z": function(cm) {mark["Z"] = cm.getCursor().line;},
-    "Shift-Z": function(cm) {mark["Shift-Z"] = cm.getCursor().line;},
-    auto: "vim", 
-    catchall: function(cm) {/*ignore*/}
-  }
-  
   CodeMirror.keyMap["vim-prefix-d"] = {
-    "D": countTimes(function(cm) { pushInBuffer("\n"+cm.getLine(cm.getCursor().line)); cm.removeLine(cm.getCursor().line); }),
-    "'": function(cm) {cm.setOption("keyMap", "vim-prefix-d'"); emptyBuffer();},
-    auto: "vim", 
-    catchall: function(cm) {/*ignore*/}
+    "D": countTimes(function(cm) {
+      pushInBuffer("\n"+cm.getLine(cm.getCursor().line));
+      cm.removeLine(cm.getCursor().line);
+      cm.setOption("keyMap", "vim");
+    }),
+    "'": function(cm) {
+      cm.setOption("keyMap", "vim-prefix-d'");
+      emptyBuffer();
+    },
+    "B": function(cm) {
+      var cur = cm.getCursor();
+      var line = cm.getLine(cur.line);
+      var index = line.lastIndexOf(" ", cur.ch);
+
+      pushInBuffer(line.substring(index, cur.ch));
+      cm.replaceRange("", {line: cur.line, ch: index}, cur);
+      cm.setOption("keyMap", "vim");
+    },
+    nofallthrough: true, style: "fat-cursor"
   };
+  // FIXME - does not work for bindings like "d3e"
+  addCountBindings(CodeMirror.keyMap["vim-prefix-d"]);
 
-  CodeMirror.keyMap["vim-prefix-d'"] = {
-    "A": function(cm) {delTillMark(cm,"A");},
-    "Shift-A": function(cm) {delTillMark(cm,"Shift-A");},
-    "B": function(cm) {delTillMark(cm,"B");},
-    "Shift-B": function(cm) {delTillMark(cm,"Shift-B");},
-    "C": function(cm) {delTillMark(cm,"C");},
-    "Shift-C": function(cm) {delTillMark(cm,"Shift-C");},
-    "D": function(cm) {delTillMark(cm,"D");},
-    "Shift-D": function(cm) {delTillMark(cm,"Shift-D");},
-    "E": function(cm) {delTillMark(cm,"E");},
-    "Shift-E": function(cm) {delTillMark(cm,"Shift-E");},
-    "F": function(cm) {delTillMark(cm,"F");},
-    "Shift-F": function(cm) {delTillMark(cm,"Shift-F");},
-    "G": function(cm) {delTillMark(cm,"G");},
-    "Shift-G": function(cm) {delTillMark(cm,"Shift-G");},
-    "H": function(cm) {delTillMark(cm,"H");},
-    "Shift-H": function(cm) {delTillMark(cm,"Shift-H");},
-    "I": function(cm) {delTillMark(cm,"I");},
-    "Shift-I": function(cm) {delTillMark(cm,"Shift-I");},
-    "J": function(cm) {delTillMark(cm,"J");},
-    "Shift-J": function(cm) {delTillMark(cm,"Shift-J");},
-    "K": function(cm) {delTillMark(cm,"K");},
-    "Shift-K": function(cm) {delTillMark(cm,"Shift-K");},
-    "L": function(cm) {delTillMark(cm,"L");},
-    "Shift-L": function(cm) {delTillMark(cm,"Shift-L");},
-    "M": function(cm) {delTillMark(cm,"M");},
-    "Shift-M": function(cm) {delTillMark(cm,"Shift-M");},
-    "N": function(cm) {delTillMark(cm,"N");},
-    "Shift-N": function(cm) {delTillMark(cm,"Shift-N");},
-    "O": function(cm) {delTillMark(cm,"O");},
-    "Shift-O": function(cm) {delTillMark(cm,"Shift-O");},
-    "P": function(cm) {delTillMark(cm,"P");},
-    "Shift-P": function(cm) {delTillMark(cm,"Shift-P");},
-    "Q": function(cm) {delTillMark(cm,"Q");},
-    "Shift-Q": function(cm) {delTillMark(cm,"Shift-Q");},
-    "R": function(cm) {delTillMark(cm,"R");},
-    "Shift-R": function(cm) {delTillMark(cm,"Shift-R");},
-    "S": function(cm) {delTillMark(cm,"S");},
-    "Shift-S": function(cm) {delTillMark(cm,"Shift-S");},
-    "T": function(cm) {delTillMark(cm,"T");},
-    "Shift-T": function(cm) {delTillMark(cm,"Shift-T");},
-    "U": function(cm) {delTillMark(cm,"U");},
-    "Shift-U": function(cm) {delTillMark(cm,"Shift-U");},
-    "V": function(cm) {delTillMark(cm,"V");},
-    "Shift-V": function(cm) {delTillMark(cm,"Shift-V");},
-    "W": function(cm) {delTillMark(cm,"W");},
-    "Shift-W": function(cm) {delTillMark(cm,"Shift-W");},
-    "X": function(cm) {delTillMark(cm,"X");},
-    "Shift-X": function(cm) {delTillMark(cm,"Shift-X");},
-    "Y": function(cm) {delTillMark(cm,"Y");},
-    "Shift-Y": function(cm) {delTillMark(cm,"Shift-Y");},
-    "Z": function(cm) {delTillMark(cm,"Z");},
-    "Shift-Z": function(cm) {delTillMark(cm,"Shift-Z");},
-    auto: "vim", 
-    catchall: function(cm) {/*ignore*/}
+  CodeMirror.keyMap["vim-prefix-c"] = {
+    "B": function (cm) {
+      countTimes("delWordLeft")(cm);
+      enterInsertMode(cm);
+    },
+    "C": function (cm) {
+      iterTimes(function (i, last) {
+        CodeMirror.commands.deleteLine(cm);
+        if (i) {
+          CodeMirror.commands.delCharRight(cm);
+          if (last) CodeMirror.commands.deleteLine(cm);
+        }
+      });
+      enterInsertMode(cm);
+    },
+    nofallthrough: true, style: "fat-cursor"
   };
 
-  CodeMirror.keyMap["vim-prefix-y'"] = {
-    "A": function(cm) {yankTillMark(cm,"A");},
-    "Shift-A": function(cm) {yankTillMark(cm,"Shift-A");},
-    "B": function(cm) {yankTillMark(cm,"B");},
-    "Shift-B": function(cm) {yankTillMark(cm,"Shift-B");},
-    "C": function(cm) {yankTillMark(cm,"C");},
-    "Shift-C": function(cm) {yankTillMark(cm,"Shift-C");},
-    "D": function(cm) {yankTillMark(cm,"D");},
-    "Shift-D": function(cm) {yankTillMark(cm,"Shift-D");},
-    "E": function(cm) {yankTillMark(cm,"E");},
-    "Shift-E": function(cm) {yankTillMark(cm,"Shift-E");},
-    "F": function(cm) {yankTillMark(cm,"F");},
-    "Shift-F": function(cm) {yankTillMark(cm,"Shift-F");},
-    "G": function(cm) {yankTillMark(cm,"G");},
-    "Shift-G": function(cm) {yankTillMark(cm,"Shift-G");},
-    "H": function(cm) {yankTillMark(cm,"H");},
-    "Shift-H": function(cm) {yankTillMark(cm,"Shift-H");},
-    "I": function(cm) {yankTillMark(cm,"I");},
-    "Shift-I": function(cm) {yankTillMark(cm,"Shift-I");},
-    "J": function(cm) {yankTillMark(cm,"J");},
-    "Shift-J": function(cm) {yankTillMark(cm,"Shift-J");},
-    "K": function(cm) {yankTillMark(cm,"K");},
-    "Shift-K": function(cm) {yankTillMark(cm,"Shift-K");},
-    "L": function(cm) {yankTillMark(cm,"L");},
-    "Shift-L": function(cm) {yankTillMark(cm,"Shift-L");},
-    "M": function(cm) {yankTillMark(cm,"M");},
-    "Shift-M": function(cm) {yankTillMark(cm,"Shift-M");},
-    "N": function(cm) {yankTillMark(cm,"N");},
-    "Shift-N": function(cm) {yankTillMark(cm,"Shift-N");},
-    "O": function(cm) {yankTillMark(cm,"O");},
-    "Shift-O": function(cm) {yankTillMark(cm,"Shift-O");},
-    "P": function(cm) {yankTillMark(cm,"P");},
-    "Shift-P": function(cm) {yankTillMark(cm,"Shift-P");},
-    "Q": function(cm) {yankTillMark(cm,"Q");},
-    "Shift-Q": function(cm) {yankTillMark(cm,"Shift-Q");},
-    "R": function(cm) {yankTillMark(cm,"R");},
-    "Shift-R": function(cm) {yankTillMark(cm,"Shift-R");},
-    "S": function(cm) {yankTillMark(cm,"S");},
-    "Shift-S": function(cm) {yankTillMark(cm,"Shift-S");},
-    "T": function(cm) {yankTillMark(cm,"T");},
-    "Shift-T": function(cm) {yankTillMark(cm,"Shift-T");},
-    "U": function(cm) {yankTillMark(cm,"U");},
-    "Shift-U": function(cm) {yankTillMark(cm,"Shift-U");},
-    "V": function(cm) {yankTillMark(cm,"V");},
-    "Shift-V": function(cm) {yankTillMark(cm,"Shift-V");},
-    "W": function(cm) {yankTillMark(cm,"W");},
-    "Shift-W": function(cm) {yankTillMark(cm,"Shift-W");},
-    "X": function(cm) {yankTillMark(cm,"X");},
-    "Shift-X": function(cm) {yankTillMark(cm,"Shift-X");},
-    "Y": function(cm) {yankTillMark(cm,"Y");},
-    "Shift-Y": function(cm) {yankTillMark(cm,"Shift-Y");},
-    "Z": function(cm) {yankTillMark(cm,"Z");},
-    "Shift-Z": function(cm) {yankTillMark(cm,"Shift-Z");},
-    auto: "vim", 
-    catchall: function(cm) {/*ignore*/}
+  iterList(["vim-prefix-d", "vim-prefix-c", "vim-prefix-"], function (prefix) {
+    iterList(["f", "F", "T", "t"],
+      function (ch) {
+        CodeMirror.keyMap[prefix][toCombo(ch)] = function (cm) {
+          cm.setOption("keyMap", prefix + ch);
+          emptyBuffer();
+        };
+      });
+  });
+
+  var MOTION_OPTIONS = {
+    "t": {inclusive: false, forward: true},
+    "f": {inclusive: true,  forward: true},
+    "T": {inclusive: false, forward: false},
+    "F": {inclusive: true,  forward: false}
   };
 
+  function setupPrefixBindingForKey(m) {
+    CodeMirror.keyMap["vim-prefix-m"][m] = function(cm) {
+      mark[m] = cm.getCursor().line;
+    };
+    CodeMirror.keyMap["vim-prefix-d'"][m] = function(cm) {
+      delTillMark(cm,m);
+    };
+    CodeMirror.keyMap["vim-prefix-y'"][m] = function(cm) {
+      yankTillMark(cm,m);
+    };
+    CodeMirror.keyMap["vim-prefix-r"][m] = function (cm) {
+      var cur = cm.getCursor();
+      cm.replaceRange(toLetter(m),
+          {line: cur.line, ch: cur.ch},
+          {line: cur.line, ch: cur.ch + 1});
+      CodeMirror.commands.goColumnLeft(cm);
+    };
+    // all commands, related to motions till char in line
+    iterObj(MOTION_OPTIONS, function (ch, options) {
+      CodeMirror.keyMap["vim-prefix-" + ch][m] = function(cm) {
+        moveTillChar(cm, m, options);
+      };
+      CodeMirror.keyMap["vim-prefix-d" + ch][m] = function(cm) {
+        delTillChar(cm, m, options);
+      };
+      CodeMirror.keyMap["vim-prefix-c" + ch][m] = function(cm) {
+        delTillChar(cm, m, options);
+        enterInsertMode(cm);
+      };
+    });
+  }
+  for (var i = 65; i < 65 + 26; i++) { // uppercase alphabet char codes
+    var ch = String.fromCharCode(i);
+    setupPrefixBindingForKey(toCombo(ch));
+    setupPrefixBindingForKey(toCombo(ch.toLowerCase()));
+  }
+  iterList(SPECIAL_SYMBOLS, function (ch) {
+    setupPrefixBindingForKey(toCombo(ch));
+  });
+  setupPrefixBindingForKey("Space");
+
   CodeMirror.keyMap["vim-prefix-y"] = {
     "Y": countTimes(function(cm) { pushInBuffer("\n"+cm.getLine(cm.getCursor().line+yank)); yank++; }),
     "'": function(cm) {cm.setOption("keyMap", "vim-prefix-y'"); emptyBuffer();},
-    auto: "vim", 
-    catchall: function(cm) {/*ignore*/}
+    nofallthrough: true, style: "fat-cursor"
   };
 
   CodeMirror.keyMap["vim-insert"] = {
+    // TODO: override navigation keys so that Esc will cancel automatic indentation from o, O, i_<CR>
     "Esc": function(cm) {
-	     cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true); 
-	     cm.setOption("keyMap", "vim");
-	     editCursor("vim");
-           },
-    "Ctrl-N": function(cm) {/* Code to bring up autocomplete hint */},
-    "Ctrl-P": function(cm) {/* Code to bring up autocomplete hint */},
+      cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
+      cm.setOption("keyMap", "vim");
+    },
+    "Ctrl-N": "autocomplete",
+    "Ctrl-P": "autocomplete",
     fallthrough: ["default"]
   };
+
+  function findMatchedSymbol(cm, cur, symb) {
+    var line = cur.line;
+    var symb = symb ? symb : cm.getLine(line)[cur.ch];
+
+    // Are we at the opening or closing char
+    var forwards = ['(', '[', '{'].indexOf(symb) != -1;
+
+    var reverseSymb = (function(sym) {
+      switch (sym) {
+        case '(' : return ')';
+        case '[' : return ']';
+        case '{' : return '}';
+        case ')' : return '(';
+        case ']' : return '[';
+        case '}' : return '{';
+        default : return null;
+      }
+    })(symb);
+
+    // Couldn't find a matching symbol, abort
+    if (reverseSymb == null) return cur;
+
+    // Tracking our imbalance in open/closing symbols. An opening symbol wii be
+    // the first thing we pick up if moving forward, this isn't true moving backwards
+    var disBal = forwards ? 0 : 1;
+
+    while (true) {
+      if (line == cur.line) {
+        // First pass, do some special stuff
+        var currLine =  forwards ? cm.getLine(line).substr(cur.ch).split('') : cm.getLine(line).substr(0,cur.ch).split('').reverse();
+      } else {
+        var currLine =  forwards ? cm.getLine(line).split('') : cm.getLine(line).split('').reverse();
+      }
+
+      for (var index = 0;  index < currLine.length; index++) {
+        if (currLine[index] == symb) disBal++;
+        else if (currLine[index] == reverseSymb) disBal--;
+
+        if (disBal == 0) {
+          if (forwards && cur.line == line) return {line: line, ch: index + cur.ch};
+          else if (forwards) return {line: line, ch: index};
+          else return {line: line, ch: currLine.length - index - 1 };
+        }
+      }
+
+      if (forwards) line++;
+      else line--;
+    }
+  }
+
+  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};
+  }
+
+  // These are our motion commands to be used for navigation and selection with
+  // certian other commands. All should return a cursor object.
+  var motionList = ['B', 'E', 'J', 'K', 'H', 'L', 'W', 'Shift-W', "'^'", "'$'", "'%'", 'Esc'];
+
+  motions = {
+    'B': function(cm, times) { return moveToWord(cm, word, -1, times); },
+    'Shift-B': function(cm, times) { return moveToWord(cm, bigWord, -1, times); },
+    'E': function(cm, times) { return moveToWord(cm, word, 1, times, 'end'); },
+    'Shift-E': function(cm, times) { return moveToWord(cm, bigWord, 1, times, 'end'); },
+    'J': function(cm, times) {
+      var cur = cm.getCursor();
+      return {line: cur.line+times, ch : cur.ch};
+    },
+
+    'K': function(cm, times) {
+      var cur = cm.getCursor();
+      return {line: cur.line-times, ch: cur.ch};
+    },
+
+    'H': function(cm, times) {
+      var cur = cm.getCursor();
+      return {line: cur.line, ch: cur.ch-times};
+    },
+
+    'L': function(cm, times) {
+      var cur = cm.getCursor();
+      return {line: cur.line, ch: cur.ch+times};
+    },
+    'W': function(cm, times) { return moveToWord(cm, word, 1, times); },
+    'Shift-W': function(cm, times) { return moveToWord(cm, bigWord, 1, times); },
+    "'^'": function(cm, times) {
+      var cur = cm.getCursor();
+      var line = cm.getLine(cur.line).split('');
+
+      // Empty line :o
+      if (line.length == 0) return cur;
+
+      for (var index = 0;  index < line.length; index++) {
+        if (line[index].match(/[^\s]/)) return {line: cur.line, ch: index};
+      }
+    },
+    "'$'": function(cm) {
+      var cur = cm.getCursor();
+      var line = cm.getLine(cur.line);
+      return {line: cur.line, ch: line.length};
+    },
+    "'%'": function(cm) { return findMatchedSymbol(cm, cm.getCursor()); },
+    "Esc" : function(cm) {
+      cm.setOption('vim');
+      reptTimes = 0;
+
+      return cm.getCursor();
+    }
+  };
+
+  // Map our movement actions each operator and non-operational movement
+  motionList.forEach(function(key, index, array) {
+    CodeMirror.keyMap['vim-prefix-d'][key] = function(cm) {
+      // Get our selected range
+      var start = cm.getCursor();
+      var end = motions[key](cm, reptTimes ? reptTimes : 1);
+
+      // Set swap var if range is of negative length
+      if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
+
+      // Take action, switching start and end if swap var is set
+      pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
+      cm.replaceRange("", swap ? end : start, swap ? start : end);
+
+      // And clean up
+      reptTimes = 0;
+      cm.setOption("keyMap", "vim");
+    };
+
+    CodeMirror.keyMap['vim-prefix-c'][key] = function(cm) {
+      var start = cm.getCursor();
+      var end = motions[key](cm, reptTimes ? reptTimes : 1);
+
+      if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
+      pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
+      cm.replaceRange("", swap ? end : start, swap ? start : end);
+
+      reptTimes = 0;
+      cm.setOption('keyMap', 'vim-insert');
+    };
+
+    CodeMirror.keyMap['vim-prefix-y'][key] = function(cm) {
+      var start = cm.getCursor();
+      var end = motions[key](cm, reptTimes ? reptTimes : 1);
+
+      if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
+      pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
+
+      reptTimes = 0;
+      cm.setOption("keyMap", "vim");
+    };
+
+    CodeMirror.keyMap['vim'][key] = function(cm) {
+      var cur = motions[key](cm, reptTimes ? reptTimes : 1);
+      cm.setCursor(cur.line, cur.ch);
+
+      reptTimes = 0;
+    };
+  });
+
+  var nums = [1,2,3,4,5,6,7,8,9];
+  nums.forEach(function(key, index, array) {
+    CodeMirror.keyMap['vim'][key] = function (cm) {
+      reptTimes = (reptTimes * 10) + key;
+    };
+    CodeMirror.keyMap['vim-prefix-d'][key] = function (cm) {
+      reptTimes = (reptTimes * 10) + key;
+    };
+    CodeMirror.keyMap['vim-prefix-y'][key] = function (cm) {
+      reptTimes = (reptTimes * 10) + key;
+    };
+    CodeMirror.keyMap['vim-prefix-c'][key] = function (cm) {
+      reptTimes = (reptTimes * 10) + key;
+    };
+  });
+
+  // Create our keymaps for each operator and make xa and xi where x is an operator
+  // change to the corrosponding keymap
+  var operators = ['d', 'y', 'c'];
+  operators.forEach(function(key, index, array) {
+    CodeMirror.keyMap['vim-prefix-'+key+'a'] = {
+      auto: 'vim', nofallthrough: true, style: "fat-cursor"
+    };
+    CodeMirror.keyMap['vim-prefix-'+key+'i'] = {
+      auto: 'vim', nofallthrough: true, style: "fat-cursor"
+    };
+
+    CodeMirror.keyMap['vim-prefix-'+key]['A'] = function(cm) {
+      reptTimes = 0;
+      cm.setOption('keyMap', 'vim-prefix-' + key + 'a');
+    };
+
+    CodeMirror.keyMap['vim-prefix-'+key]['I'] = function(cm) {
+      reptTimes = 0;
+      cm.setOption('keyMap', 'vim-prefix-' + key + 'i');
+    };
+  });
+
+  function regexLastIndexOf(string, pattern, startIndex) {
+    for (var i = startIndex == null ? string.length : startIndex; i >= 0; --i)
+      if (pattern.test(string.charAt(i))) return i;
+    return -1;
+  }
+
+  // Create our text object functions. They work similar to motions but they
+  // return a start cursor as well
+  var textObjectList = ['W', 'Shift-[', 'Shift-9', '['];
+  var textObjects = {
+    'W': function(cm, inclusive) {
+      var cur = cm.getCursor();
+      var line = cm.getLine(cur.line);
+
+      var line_to_char = new String(line.substring(0, cur.ch));
+      var start = regexLastIndexOf(line_to_char, /[^a-zA-Z0-9]/) + 1;
+      var end = motions["E"](cm, 1) ;
+
+      end.ch += inclusive ? 1 : 0 ;
+      return {start: {line: cur.line, ch: start}, end: end };
+    },
+    'Shift-[': function(cm, inclusive) { return selectCompanionObject(cm, '}', inclusive); },
+    'Shift-9': function(cm, inclusive) { return selectCompanionObject(cm, ')', inclusive); },
+    '[': function(cm, inclusive) { return selectCompanionObject(cm, ']', inclusive); }
+  };
+
+  // One function to handle all operation upon text objects. Kinda funky but it works
+  // better than rewriting this code six times
+  function textObjectManipulation(cm, object, remove, insert, inclusive) {
+    // Object is the text object, delete object if remove is true, enter insert
+    // mode if insert is true, inclusive is the difference between a and i
+    var tmp = textObjects[object](cm, inclusive);
+    var start = tmp.start;
+    var end = tmp.end;
+
+    if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true ;
+
+    pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
+    if (remove) cm.replaceRange("", swap ? end : start, swap ? start : end);
+    if (insert) cm.setOption('keyMap', 'vim-insert');
+  }
+
+  // And finally build the keymaps up from the text objects
+  for (var i = 0; i < textObjectList.length; ++i) {
+    var object = textObjectList[i];
+    (function(object) {
+      CodeMirror.keyMap['vim-prefix-di'][object] = function(cm) { textObjectManipulation(cm, object, true, false, false); };
+      CodeMirror.keyMap['vim-prefix-da'][object] = function(cm) { textObjectManipulation(cm, object, true, false, true); };
+      CodeMirror.keyMap['vim-prefix-yi'][object] = function(cm) { textObjectManipulation(cm, object, false, false, false); };
+      CodeMirror.keyMap['vim-prefix-ya'][object] = function(cm) { textObjectManipulation(cm, object, false, false, true); };
+      CodeMirror.keyMap['vim-prefix-ci'][object] = function(cm) { textObjectManipulation(cm, object, true, true, false); };
+      CodeMirror.keyMap['vim-prefix-ca'][object] = function(cm) { textObjectManipulation(cm, object, true, true, true); };
+    })(object)
+  }
 })();
diff --git a/imports/codemirror/lib/codemirror.css b/imports/codemirror/lib/codemirror.css
index 2d79f4aa..191ac25a 100644
--- a/imports/codemirror/lib/codemirror.css
+++ b/imports/codemirror/lib/codemirror.css
@@ -1,10 +1,16 @@
 .CodeMirror {
   line-height: 1em;
   font-family: monospace;
+
+  /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */
+  position: relative;
+  /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */
+  overflow: hidden;
 }
 
 .CodeMirror-scroll {
-  overflow: auto;
+  overflow-x: auto;
+  overflow-y: hidden;
   height: 300px;
   /* This is needed to prevent an IE[67] bug where the scrolled content
      is visible outside of the scrolling box. */
@@ -12,6 +18,37 @@
   outline: none;
 }
 
+/* Vertical scrollbar */
+.CodeMirror-scrollbar {
+  float: right;
+  overflow-x: hidden;
+  overflow-y: scroll;
+
+  /* This corrects for the 1px gap introduced to the left of the scrollbar
+     by the rule for .CodeMirror-scrollbar-inner. */
+  margin-left: -1px;
+}
+.CodeMirror-scrollbar-inner {
+  /* This needs to have a nonzero width in order for the scrollbar to appear
+     in Firefox and IE9. */
+  width: 1px;
+}
+.CodeMirror-scrollbar.cm-sb-overlap {
+  /* Ensure that the scrollbar appears in Lion, and that it overlaps the content
+     rather than sitting to the right of it. */
+  position: absolute;
+  z-index: 1;
+  float: none;
+  right: 0;
+  min-width: 12px;
+}
+.CodeMirror-scrollbar.cm-sb-nonoverlap {
+  min-width: 12px;
+}
+.CodeMirror-scrollbar.cm-sb-ie7 {
+  min-width: 18px;
+}
+
 .CodeMirror-gutter {
   position: absolute; left: 0; top: 0;
   z-index: 10;
@@ -29,6 +66,11 @@
 .CodeMirror-lines {
   padding: .4em;
   white-space: pre;
+  cursor: text;
+}
+.CodeMirror-lines * {
+  /* Necessary for throw-scrolling to decelerate properly on Safari. */
+  pointer-events: none;
 }
 
 .CodeMirror pre {
@@ -42,11 +84,14 @@
   padding: 0; margin: 0;
   white-space: pre;
   word-wrap: normal;
+  line-height: inherit;
+  color: inherit;
 }
 
 .CodeMirror-wrap pre {
   word-wrap: break-word;
   white-space: pre-wrap;
+  word-break: normal;
 }
 .CodeMirror-wrap .CodeMirror-scroll {
   overflow-x: hidden;
@@ -61,8 +106,19 @@
   position: absolute;
   visibility: hidden;
   border-left: 1px solid black;
-  border-right:none;
-  width:0;
+  border-right: none;
+  width: 0;
+}
+.cm-keymap-fat-cursor pre.CodeMirror-cursor {
+  width: auto;
+  border: 0;
+  background: transparent;
+  background: rgba(0, 200, 0, .4);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800);
+}
+/* Kludge to turn off filter in ie9+, which also accepts rgba */
+.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) {
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
 }
 .CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
 .CodeMirror-focused pre.CodeMirror-cursor {
@@ -98,7 +154,7 @@ div.CodeMirror-selected { background: #d9d9d9; }
 .cm-s-default span.cm-bracket {color: #cc7;}
 .cm-s-default span.cm-tag {color: #170;}
 .cm-s-default span.cm-attribute {color: #00c;}
-.cm-s-default span.cm-header {color: #a0a;}
+.cm-s-default span.cm-header {color: blue;}
 .cm-s-default span.cm-quote {color: #090;}
 .cm-s-default span.cm-hr {color: #999;}
 .cm-s-default span.cm-link {color: #00c;}
diff --git a/imports/codemirror/lib/codemirror.js b/imports/codemirror/lib/codemirror.js
index 5434a8dd..250d5b18 100644
--- a/imports/codemirror/lib/codemirror.js
+++ b/imports/codemirror/lib/codemirror.js
@@ -1,4 +1,4 @@
-// CodeMirror version 2.23
+// CodeMirror version 2.3
 //
 // All functions that need access to the editor's state live inside
 // the CodeMirror function. Below that, at the bottom of the file,
@@ -23,14 +23,19 @@ var CodeMirror = (function() {
       '<div style="overflow: hidden; position: relative; width: 3px; height: 0px;">' + // Wraps and hides input textarea
         '<textarea style="position: absolute; padding: 0; width: 1px; height: 1em" wrap="off" ' +
           'autocorrect="off" autocapitalize="off"></textarea></div>' +
+      '<div class="CodeMirror-scrollbar">' + // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself.
+        '<div class="CodeMirror-scrollbar-inner">' + // The empty scrollbar content, used solely for managing the scrollbar thumb.
+      '</div></div>' + // This must be before the scroll area because it's float-right.
       '<div class="CodeMirror-scroll" tabindex="-1">' +
         '<div style="position: relative">' + // Set to the height of the text, causes scrolling
           '<div style="position: relative">' + // Moved around its parent to cover visible view
             '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
             // Provides positioning relative to (visible) text origin
             '<div class="CodeMirror-lines"><div style="position: relative; z-index: 0">' +
+              // Used to measure text size
               '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden;"></div>' +
               '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
+              '<pre class="CodeMirror-cursor" style="visibility: hidden">&#160;</pre>' + // Used to force a width
               '<div style="position: relative; z-index: -1"></div><div></div>' + // DIVs containing the selection and the actual code
             '</div></div></div></div></div>';
     if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
@@ -39,12 +44,13 @@ var CodeMirror = (function() {
         scroller = wrapper.lastChild, code = scroller.firstChild,
         mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
         lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
-        cursor = measure.nextSibling, selectionDiv = cursor.nextSibling,
-        lineDiv = selectionDiv.nextSibling;
-    themeChanged();
+        cursor = measure.nextSibling, widthForcer = cursor.nextSibling,
+        selectionDiv = widthForcer.nextSibling, lineDiv = selectionDiv.nextSibling,
+        scrollbar = inputDiv.nextSibling, scrollbarInner = scrollbar.firstChild;
+    themeChanged(); keyMapChanged();
     // Needed to hide big blue blinking cursor on Mobile Safari
     if (ios) input.style.width = "0px";
-    if (!webkit) lineSpace.draggable = true;
+    if (!webkit) scroller.draggable = true;
     lineSpace.style.outline = "none";
     if (options.tabindex != null) input.tabIndex = options.tabindex;
     if (options.autofocus) focusInput();
@@ -52,6 +58,17 @@ var CodeMirror = (function() {
     // Needed to handle Tab key in KHTML
     if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute";
 
+    // Check for OS X >= 10.7. If so, we need to force a width on the scrollbar, and 
+    // make it overlap the content. (But we only do this if the scrollbar doesn't already
+    // have a natural width. If the mouse is plugged in or the user sets the system pref
+    // to always show scrollbars, the scrollbar shouldn't overlap.)
+    if (mac_geLion) {
+      scrollbar.className += (overlapScrollbars() ? " cm-sb-overlap" : " cm-sb-nonoverlap");
+    } else if (ie_lt8) {
+      // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
+      scrollbar.className += " cm-sb-ie7";
+    }
+
     // Check for problem with IE innerHTML not working when we have a
     // P (or similar) parent node.
     try { stringWidth("x"); }
@@ -75,7 +92,7 @@ var CodeMirror = (function() {
     var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
     // Selection-related flags. shiftSelecting obviously tracks
     // whether the user is holding shift.
-    var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText,
+    var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, lastScrollLeft = 0, draggingText,
         overwrite = false, suppressEdits = false;
     // Variables used by startOperation/endOperation to track what
     // happened during the operation.
@@ -88,7 +105,7 @@ var CodeMirror = (function() {
     var bracketHighlighted;
     // Tracks the maximum line length so that the horizontal scrollbar
     // can be kept static when scrolling.
-    var maxLine = "", maxWidth;
+    var maxLine = "", updateMaxLine = false, maxLineChanged = true;
     var tabCache = {};
 
     // Initialize the content.
@@ -98,18 +115,16 @@ var CodeMirror = (function() {
     // Register our event handlers.
     connect(scroller, "mousedown", operation(onMouseDown));
     connect(scroller, "dblclick", operation(onDoubleClick));
-    connect(lineSpace, "dragstart", onDragStart);
     connect(lineSpace, "selectstart", e_preventDefault);
     // Gecko browsers fire contextmenu *after* opening the menu, at
     // which point we can't mess with it anymore. Context menu is
     // handled in onMouseDown for Gecko.
     if (!gecko) connect(scroller, "contextmenu", onContextMenu);
-    connect(scroller, "scroll", function() {
-      lastScrollPos = scroller.scrollTop;
-      updateDisplay([]);
-      if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
-      if (options.onScroll) options.onScroll(instance);
-    });
+    connect(scroller, "scroll", onScroll);
+    connect(scrollbar, "scroll", onScroll);
+    connect(scrollbar, "mousedown", function() {setTimeout(focusInput, 0);});
+    connect(scroller, "mousewheel", onMouseWheel);
+    connect(scroller, "DOMMouseScroll", onMouseWheel);
     connect(window, "resize", function() {updateDisplay(true);});
     connect(input, "keyup", operation(onKeyUp));
     connect(input, "input", fastPoll);
@@ -118,9 +133,16 @@ var CodeMirror = (function() {
     connect(input, "focus", onFocus);
     connect(input, "blur", onBlur);
 
-    connect(scroller, "dragenter", e_stop);
-    connect(scroller, "dragover", e_stop);
-    connect(scroller, "drop", operation(onDrop));
+    if (options.dragDrop) {
+      connect(scroller, "dragstart", onDragStart);
+      function drag_(e) {
+        if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
+        e_stop(e);
+      }
+      connect(scroller, "dragenter", drag_);
+      connect(scroller, "dragover", drag_);
+      connect(scroller, "drop", operation(onDrop));
+    }
     connect(scroller, "paste", function(){focusInput(); fastPoll();});
     connect(input, "paste", fastPoll);
     connect(input, "cut", operation(function(){
@@ -160,6 +182,7 @@ var CodeMirror = (function() {
         else if (option == "theme") themeChanged();
         else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
         else if (option == "tabSize") updateDisplay(true);
+        else if (option == "keyMap") keyMapChanged();
         if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") {
           gutterChanged();
           updateDisplay(true);
@@ -307,15 +330,20 @@ var CodeMirror = (function() {
       },
       scrollTo: function(x, y) {
         if (x != null) scroller.scrollLeft = x;
-        if (y != null) scroller.scrollTop = y;
+        if (y != null) scrollbar.scrollTop = y;
         updateDisplay([]);
       },
+      getScrollInfo: function() {
+        return {x: scroller.scrollLeft, y: scrollbar.scrollTop,
+                height: scrollbar.scrollHeight, width: scroller.scrollWidth};
+      },
 
       operation: function(f){return operation(f)();},
+      compoundChange: function(f){return compoundChange(f);},
       refresh: function(){
         updateDisplay(true);
-        if (scroller.scrollHeight > lastScrollPos)
-          scroller.scrollTop = lastScrollPos;
+        if (scrollbar.scrollHeight > lastScrollTop)
+          scrollbar.scrollTop = lastScrollTop;
       },
       getInputField: function(){return input;},
       getWrapperElement: function(){return wrapper;},
@@ -336,12 +364,22 @@ var CodeMirror = (function() {
                   splitLines(code), top, top);
       updateInput = true;
     }
-    function getValue(code) {
+    function getValue() {
       var text = [];
       doc.iter(0, doc.size, function(line) { text.push(line.text); });
       return text.join("\n");
     }
 
+    function onScroll(e) {
+      if (lastScrollTop != scrollbar.scrollTop || lastScrollLeft != scroller.scrollLeft) {
+        lastScrollTop = scrollbar.scrollTop;
+        lastScrollLeft = scroller.scrollLeft;
+        updateDisplay([]);
+        if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
+        if (options.onScroll) options.onScroll(instance);
+      }
+    }
+
     function onMouseDown(e) {
       setShift(e_prop(e, "shiftKey"));
       // Check whether this is a click in a widget
@@ -364,6 +402,8 @@ var CodeMirror = (function() {
         return;
       case 2:
         if (start) setCursor(start.line, start.ch, true);
+        setTimeout(focusInput, 20);
+        e_preventDefault(e);
         return;
       }
       // For button 1, if it was clicked inside the editor
@@ -385,23 +425,25 @@ var CodeMirror = (function() {
       } else { lastClick = {time: now, pos: start}; }
 
       var last = start, going;
-      if (dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
+      if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
           !posLess(start, sel.from) && !posLess(sel.to, start)) {
         // Let the drag handler handle this.
-        if (webkit) lineSpace.draggable = true;
-        var up = connect(document, "mouseup", operation(function(e2) {
-          if (webkit) lineSpace.draggable = false;
+        if (webkit) scroller.draggable = true;
+        function dragEnd(e2) {
+          if (webkit) scroller.draggable = false;
           draggingText = false;
-          up();
+          up(); drop();
           if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
             e_preventDefault(e2);
             setCursor(start.line, start.ch, true);
             focusInput();
           }
-        }), true);
+        }
+        var up = connect(document, "mouseup", operation(dragEnd), true);
+        var drop = connect(scroller, "drop", operation(dragEnd), true);
         draggingText = true;
         // IE's approach to draggable
-        if (lineSpace.dragDrop) lineSpace.dragDrop();
+        if (scroller.dragDrop) scroller.dragDrop();
         return;
       }
       e_preventDefault(e);
@@ -447,6 +489,7 @@ var CodeMirror = (function() {
       selectWordAt(start);
     }
     function onDrop(e) {
+      if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
       e.preventDefault();
       var pos = posFromMouse(e, true), files = e.dataTransfer.files;
       if (!pos || options.readOnly) return;
@@ -456,28 +499,31 @@ var CodeMirror = (function() {
           reader.onload = function() {
             text[i] = reader.result;
             if (++read == n) {
-	      pos = clipPos(pos);
-	      operation(function() {
+              pos = clipPos(pos);
+              operation(function() {
                 var end = replaceRange(text.join(""), pos, pos);
                 setSelectionUser(pos, end);
               })();
-	    }
+            }
           };
           reader.readAsText(file);
         }
         var n = files.length, text = Array(n), read = 0;
         for (var i = 0; i < n; ++i) loadFile(files[i], i);
-      }
-      else {
+      } else {
+        // Don't do a replace if the drop happened inside of the selected text.
+        if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return;
         try {
           var text = e.dataTransfer.getData("Text");
           if (text) {
-            var curFrom = sel.from, curTo = sel.to;
-            setSelectionUser(pos, pos);
-            if (draggingText) replaceRange("", curFrom, curTo);
-            replaceSelection(text);
-	    focusInput();
-	  }
+            compoundChange(function() {
+              var curFrom = sel.from, curTo = sel.to;
+              setSelectionUser(pos, pos);
+              if (draggingText) replaceRange("", curFrom, curTo);
+              replaceSelection(text);
+              focusInput();
+            });
+          }
         }
         catch(e){}
       }
@@ -487,7 +533,7 @@ var CodeMirror = (function() {
       e.dataTransfer.setData("Text", txt);
       
       // Use dummy image instead of default browsers image.
-      if (gecko || chrome) {
+      if (gecko || chrome || opera) {
         var img = document.createElement('img');
         img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image
         e.dataTransfer.setDragImage(img, 0, 0);
@@ -529,25 +575,33 @@ var CodeMirror = (function() {
       if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name;
       if (e_prop(e, "metaKey")) name = "Cmd-" + name;
 
+      var stopped = false;
+      function stop() { stopped = true; }
+
       if (e_prop(e, "shiftKey")) {
         handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap,
-                            function(b) {return doHandleBinding(b, true)