Strawman `substituteKeyboardEvents` config option

Lets API consumers override the keyboard events abstraction with a
wrapper around saneKeyboardEvents() that can intercept events and ignore
them or do stuff like insert `ans` before a `+` is typed.
diff --git a/src/publicapi.js b/src/publicapi.js
index 2ccdeb2..78ce1b4 100644
--- a/src/publicapi.js
+++ b/src/publicapi.js
@@ -77,6 +77,7 @@
 
   MQ.L = L;
   MQ.R = R;
+  MQ.saneKeyboardEvents = saneKeyboardEvents;
 
   function config(currentOptions, newOptions) {
     if (newOptions && newOptions.handlers) {
diff --git a/src/services/textarea.js b/src/services/textarea.js
index dd2d2de..31ff8d0 100644
--- a/src/services/textarea.js
+++ b/src/services/textarea.js
@@ -66,11 +66,12 @@
       if (text) textarea.select();
     };
   };
+  Options.p.substituteKeyboardEvents = saneKeyboardEvents;
   _.editablesTextareaEvents = function() {
     var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor,
       textarea = ctrlr.textarea, textareaSpan = ctrlr.textareaSpan;
 
-    var keyboardEventsShim = saneKeyboardEvents(textarea, this);
+    var keyboardEventsShim = this.options.substituteKeyboardEvents(textarea, this);
     this.selectFn = function(text) { keyboardEventsShim.select(text); };
 
     this.container.prepend(textareaSpan)
diff --git a/test/unit/publicapi.test.js b/test/unit/publicapi.test.js
index 91ef1f0..894c917 100644
--- a/test/unit/publicapi.test.js
+++ b/test/unit/publicapi.test.js
@@ -776,6 +776,27 @@
     });
   });
 
+  suite('substituteKeyboardEvents', function() {
+    test('can intercept key events', function() {
+      var mq = MQ.MathField($('<span>').appendTo('#mock')[0], {
+        substituteKeyboardEvents: function(textarea, handlers) {
+          return MQ.saneKeyboardEvents(textarea, jQuery.extend({}, handlers, {
+            keystroke: function(_key, evt) {
+              key = _key;
+              return handlers.keystroke.apply(handlers, arguments);
+            }
+          }));
+        }
+      });
+      var key;
+
+      $(mq.el()).find('textarea').trigger({ type: 'keydown', which: '37' });
+      assert.equal(key, 'Left');
+
+      $(mq.el()).remove();
+    });
+  });
+
   suite('clickAt', function() {
     test('inserts at coordinates', function() {
       // Insert filler so that the page is taller than the window so this test is deterministic