PageSourceSearch

https://www.liczebnik.pl/js/mathquill.js

js liczebnik.pl collected 2026-10-02 23:27:36 UTC 187,744 bytes, 5,231 lines download raw bytes

vendor: 43,040 bytes, lines 1-1279
1/**
2 * MathQuill v0.10.1, by Han, Jeanine, and Mary
3 * http://mathquill.com | [email protected]
4 *
5 * This Source Code Form is subject to the terms of the
6 * Mozilla Public License, v. 2.0. If a copy of the MPL
7 * was not distributed with this file, You can obtain
8 * one at http://mozilla.org/MPL/2.0/.
9 */
10
11(function() {
12
13var jQuery = window.jQuery,
14  undefined,
15  mqCmdId = 'mathquill-command-id',
16  mqBlockId = 'mathquill-block-id',
17  min = Math.min,
18  max = Math.max;
19
20if (!jQuery) throw 'MathQuill requires jQuery 1.5.2+ to be loaded first';
21
22function noop() {}
23
24/**
25 * A utility higher-order function that makes defining variadic
26 * functions more convenient by letting you essentially define functions
27 * with the last argument as a splat, i.e. the last argument "gathers up"
28 * remaining arguments to the function:
29 *   var doStuff = variadic(function(first, rest) { return rest; });
30 *   doStuff(1, 2, 3); // => [2, 3]
31 */
32var __slice = [].slice;
33function variadic(fn) {
34  var numFixedArgs = fn.length - 1;
35  return function() {
36    var args = __slice.call(arguments, 0, numFixedArgs);
37    var varArg = __slice.call(arguments, numFixedArgs);
38    return fn.apply(this, args.concat([ varArg ]));
39  };
40}
41
42/**
43 * A utility higher-order function that makes combining object-oriented
44 * programming and functional programming techniques more convenient:
45 * given a method name and any number of arguments to be bound, returns
46 * a function that calls it's first argument's method of that name (if
47 * it exists) with the bound arguments and any additional arguments that
48 * are passed:
49 *   var sendMethod = send('method', 1, 2);
50 *   var obj = { method: function() { return Array.apply(this, arguments); } };
51 *   sendMethod(obj, 3, 4); // => [1, 2, 3, 4]
52 *   // or more specifically,
53 *   var obj2 = { method: function(one, two, three) { return one*two + three; } };
54 *   sendMethod(obj2, 3); // => 5
55 *   sendMethod(obj2, 4); // => 6
56 */
57var send = variadic(function(method, args) {
58  return variadic(function(obj, moreArgs) {
59    if (method in obj) return obj[method].apply(obj, args.concat(moreArgs));
60  });
61});
62
63/**
64 * A utility higher-order function that creates "implicit iterators"
65 * from "generators": given a function that takes in a sole argument,
66 * a "yield_" function, that calls "yield_" repeatedly with an object as
67 * a sole argument (presumably objects being iterated over), returns
68 * a function that calls it's first argument on each of those objects
69 * (if the first argument is a function, it is called repeatedly with
70 * each object as the first argument, otherwise it is stringified and
71 * the method of that name is called on each object (if such a method
72 * exists)), passing along all additional arguments:
73 *   var a = [
74 *     { method: function(list) { list.push(1); } },
75 *     { method: function(list) { list.push(2); } },
76 *     { method: function(list) { list.push(3); } }
77 *   ];
78 *   a.each = iterator(function(yield_) {
79 *     for (var i in this) yield_(this[i]);
80 *   });
81 *   var list = [];
82 *   a.each('method', list);
83 *   list; // => [1, 2, 3]
84 *   // Note that the for-in loop will yield 'each', but 'each' maps to
85 *   // the function object created by iterator() which does not have a
86 *   // .method() method, so that just fails silently.
87 */
88function iterator(generator) {
89  return variadic(function(fn, args) {
90    if (typeof fn !== 'function') fn = send(fn);
91    var yield_ = function(obj) { return fn.apply(obj, [ obj ].concat(args)); };
92    return generator.call(this, yield_);
93  });
94}
95
96/**
97 * sugar to make defining lots of commands easier.
98 * TODO: rethink this.
99 */
100function bind(cons /*, args... */) {
101  var args = __slice.call(arguments, 1);
102  return function() {
103    return cons.apply(this, args);
104  };
105}
106
107/**
108 * a development-only debug method.  This definition and all
109 * calls to `pray` will be stripped from the minified
110 * build of mathquill.
111 *
112 * This function must be called by name to be removed
113 * at compile time.  Do not define another function
114 * with the same name, and only call this function by
115 * name.
116 */
117function pray(message, cond) {
118  if (!cond) throw new Error('prayer failed: '+message);
119}
120var P = (function(prototype, ownProperty, undefined) {
121  // helper functions that also help minification
122  function isObject(o) { return typeof o === 'object'; }
123  function isFunction(f) { return typeof f === 'function'; }
124
125  // used to extend the prototypes of superclasses (which might not
126  // have `.Bare`s)
127  function SuperclassBare() {}
128
129  return function P(_superclass /* = Object */, definition) {
130    // handle the case where no superclass is given
131    if (definition === undefined) {
132      definition = _superclass;
133      _superclass = Object;
134    }
135
136    // C is the class to be returned.
137    //
138    // It delegates to instantiating an instance of `Bare`, so that it
139    // will always return a new instance regardless of the calling
140    // context.
141    //
142    //  TODO: the Chrome inspector shows all created objects as `C`
143    //        rather than `Object`.  Setting the .name property seems to
144    //        have no effect.  Is there a way to override this behavior?
145    function C() {
146      var self = new Bare;
147      if (isFunction(self.init)) self.init.apply(self, arguments);
148      return self;
149    }
150
151    // C.Bare is a class with a noop constructor.  Its prototype is the
152    // same as C, so that instances of C.Bare are also instances of C.
153    // New objects can be allocated without initialization by calling
154    // `new MyClass.Bare`.
155    function Bare() {}
156    C.Bare = Bare;
157
158    // Set up the prototype of the new class.
159    var _super = SuperclassBare[prototype] = _superclass[prototype];
160    var proto = Bare[prototype] = C[prototype] = C.p = new SuperclassBare;
161
162    // other variables, as a minifier optimization
163    var extensions;
164
165
166    // set the constructor property on the prototype, for convenience
167    proto.constructor = C;
168
169    C.extend = function(def) { return P(C, def); }
170
171    return (C.open = function(def) {
172      extensions = {};
173
174      if (isFunction(def)) {
175        // call the defining function with all the arguments you need
176        // extensions captures the return value.
177        extensions = def.call(C, proto, _super, C, _superclass);
178      }
179      else if (isObject(def)) {
180        // if you passed an object instead, we'll take it
181        extensions = def;
182      }
183
184      // ...and extend it
185      if (isObject(extensions)) {
186        for (var ext in extensions) {
187          if (ownProperty.call(extensions, ext)) {
188            proto[ext] = extensions[ext];
189          }
190        }
191      }
192
193      // if there's no init, we assume we're inheriting a non-pjs class, so
194      // we default to applying the superclass's constructor.
195      if (!isFunction(proto.init)) {
196        proto.init = _superclass;
197      }
198
199      return C;
200    })(definition);
201  }
202
203  // as a minifier optimization, we've closured in a few helper functions
204  // and the string 'prototype' (C[p] is much shorter than C.prototype)
205})('prototype', ({}).hasOwnProperty);
206/*************************************************
207 * Base classes of edit tree-related objects
208 *
209 * Only doing tree node manipulation via these
210 * adopt/ disown methods guarantees well-formedness
211 * of the tree.
212 ************************************************/
213
214// L = 'left'
215// R = 'right'
216//
217// the contract is that they can be used as object properties
218// and (-L) === R, and (-R) === L.
219var L = -1;
220var R = 1;
221
222function prayDirection(dir) {
223  pray('a direction was passed', dir === L || dir === R);
224}
225
226/**
227 * Tiny extension of jQuery adding directionalized DOM manipulation methods.
228 *
229 * Funny how Pjs v3 almost just works with `jQuery.fn.init`.
230 *
231 * jQuery features that don't work on $:
232 *   - jQuery.*, like jQuery.ajax, obviously (Pjs doesn't and shouldn't
233 *                                            copy constructor properties)
234 *
235 *   - jQuery(function), the shortcut for `jQuery(document).ready(function)`,
236 *     because `jQuery.fn.init` is idiosyncratic and Pjs doing, essentially,
237 *     `jQuery.fn.init.apply(this, arguments)` isn't quite right, you need:
238 *
239 *       _.init = function(s, c) { jQuery.fn.init.call(this, s, c, $(document)); };
240 *
241 *     if you actually give a shit (really, don't bother),
242 *     see https://github.com/jquery/jquery/blob/1.7.2/src/core.js#L889
243 *
244 *   - jQuery(selector), because jQuery translates that to
245 *     `jQuery(document).find(selector)`, but Pjs doesn't (should it?) let
246 *     you override the result of a constructor call
247 *       + note that because of the jQuery(document) shortcut-ness, there's also
248 *         the 3rd-argument-needs-to-be-`$(document)` thing above, but the fix
249 *         for that (as can be seen above) is really easy. This problem requires
250 *         a way more intrusive fix
251 *
252 * And that's it! Everything else just magically works because jQuery internally
253 * uses `this.constructor()` everywhere (hence calling `$`), but never ever does
254 * `this.constructor.find` or anything like that, always doing `jQuery.find`.
255 */
256var $ = P(jQuery, function(_) {
257  _.insDirOf = function(dir, el) {
258    return dir === L ?
259      this.insertBefore(el.first()) : this.insertAfter(el.last());
260  };
261  _.insAtDirEnd = function(dir, el) {
262    return dir === L ? this.prependTo(el) : this.appendTo(el);
263  };
264});
265
266var Point = P(function(_) {
267  _.parent = 0;
268  _[L] = 0;
269  _[R] = 0;
270
271  _.init = function(parent, leftward, rightward) {
272    this.parent = parent;
273    this[L] = leftward;
274    this[R] = rightward;
275  };
276
277  this.copy = function(pt) {
278    return Point(pt.parent, pt[L], pt[R]);
279  };
280});
281
282/**
283 * MathQuill virtual-DOM tree-node abstract base class
284 */
285var Node = P(function(_) {
286  _[L] = 0;
287  _[R] = 0
288  _.parent = 0;
289
290  var id = 0;
291  function uniqueNodeId() { return id += 1; }
292  this.byId = {};
293
294  _.init = function() {
295    this.id = uniqueNodeId();
296    Node.byId[this.id] = this;
297
298    this.ends = {};
299    this.ends[L] = 0;
300    this.ends[R] = 0;
301  };
302
303  _.dispose = function() { delete Node.byId[this.id]; };
304
305  _.toString = function() { return '{{ MathQuill Node #'+this.id+' }}'; };
306
307  _.jQ = $();
308  _.jQadd = function(jQ) { return this.jQ = this.jQ.add(jQ); };
309  _.jQize = function(jQ) {
310    // jQuery-ifies this.html() and links up the .jQ of all corresponding Nodes
311    var jQ = $(jQ || this.html());
312
313    function jQadd(el) {
314      if (el.getAttribute) {
315        var cmdId = el.getAttribute('mathquill-command-id');
316        var blockId = el.getAttribute('mathquill-block-id');
317        if (cmdId) Node.byId[cmdId].jQadd(el);
318        if (blockId) Node.byId[blockId].jQadd(el);
319      }
320      for (el = el.firstChild; el; el = el.nextSibling) {
321        jQadd(el);
322      }
323    }
324
325    for (var i = 0; i < jQ.length; i += 1) jQadd(jQ[i]);
326    return jQ;
327  };
328
329  _.createDir = function(dir, cursor) {
330    prayDirection(dir);
331    var node = this;
332    node.jQize();
333    node.jQ.insDirOf(dir, cursor.jQ);
334    cursor[dir] = node.adopt(cursor.parent, cursor[L], cursor[R]);
335    return node;
336  };
337  _.createLeftOf = function(el) { return this.createDir(L, el); };
338
339  _.selectChildren = function(leftEnd, rightEnd) {
340    return Selection(leftEnd, rightEnd);
341  };
342
343  _.bubble = iterator(function(yield_) {
344    for (var ancestor = this; ancestor; ancestor = ancestor.parent) {
345      var result = yield_(ancestor);
346      if (result === false) break;
347    }
348
349    return this;
350  });
351
352  _.postOrder = iterator(function(yield_) {
353    (function recurse(descendant) {
354      descendant.eachChild(recurse);
355      yield_(descendant);
356    })(this);
357
358    return this;
359  });
360
361  _.isEmpty = function() {
362    return this.ends[L] === 0 && this.ends[R] === 0;
363  };
364  
365  _.isStyleBlock = function() {
366    return false;
367  };
368
369  _.children = function() {
370    return Fragment(this.ends[L], this.ends[R]);
371  };
372
373  _.eachChild = function() {
374    var children = this.children();
375    children.each.apply(children, arguments);
376    return this;
377  };
378
379  _.foldChildren = function(fold, fn) {
380    return this.children().fold(fold, fn);
381  };
382
383  _.withDirAdopt = function(dir, parent, withDir, oppDir) {
384    Fragment(this, this).withDirAdopt(dir, parent, withDir, oppDir);
385    return this;
386  };
387
388  _.adopt = function(parent, leftward, rightward) {
389    Fragment(this, this).adopt(parent, leftward, rightward);
390    return this;
391  };
392
393  _.disown = function() {
394    Fragment(this, this).disown();
395    return this;
396  };
397
398  _.remove = function() {
399    this.jQ.remove();
400    this.postOrder('dispose');
401    return this.disown();
402  };
403});
404
405function prayWellFormed(parent, leftward, rightward) {
406  pray('a parent is always present', parent);
407  pray('leftward is properly set up', (function() {
408    // either it's empty and `rightward` is the left end child (possibly empty)
409    if (!leftward) return parent.ends[L] === rightward;
410
411    // or it's there and its [R] and .parent are properly set up
412    return leftward[R] === rightward && leftward.parent === parent;
413  })());
414
415  pray('rightward is properly set up', (function() {
416    // either it's empty and `leftward` is the right end child (possibly empty)
417    if (!rightward) return parent.ends[R] === leftward;
418
419    // or it's there and its [L] and .parent are properly set up
420    return rightward[L] === leftward && rightward.parent === parent;
421  })());
422}
423
424
425/**
426 * An entity outside the virtual tree with one-way pointers (so it's only a
427 * "view" of part of the tree, not an actual node/entity in the tree) that
428 * delimits a doubly-linked list of sibling nodes.
429 * It's like a fanfic love-child between HTML DOM DocumentFragment and the Range
430 * classes: like DocumentFragment, its contents must be sibling nodes
431 * (unlike Range, whose contents are arbitrary contiguous pieces of subtrees),
432 * but like Range, it has only one-way pointers to its contents, its contents
433 * have no reference to it and in fact may still be in the visible tree (unlike
434 * DocumentFragment, whose contents must be detached from the visible tree
435 * and have their 'parent' pointers set to the DocumentFragment).
436 */
437var Fragment = P(function(_) {
438  _.init = function(withDir, oppDir, dir) {
439    if (dir === undefined) dir = L;
440    prayDirection(dir);
441
442    pray('no half-empty fragments', !withDir === !oppDir);
443
444    this.ends = {};
445
446    if (!withDir) return;
447
448    pray('withDir is passed to Fragment', withDir instanceof Node);
449    pray('oppDir is passed to Fragment', oppDir instanceof Node);
450    pray('withDir and oppDir have the same parent',
451         withDir.parent === oppDir.parent);
452
453    this.ends[dir] = withDir;
454    this.ends[-dir] = oppDir;
455
456    // To build the jquery collection for a fragment, accumulate elements
457    // into an array and then call jQ.add once on the result. jQ.add sorts the
458    // collection according to document order each time it is called, so
459    // building a collection by folding jQ.add directly takes more than
460    // quadratic time in the number of elements.
461    //
462    // https://github.com/jquery/jquery/blob/2.1.4/src/traversing.js#L112
463    var accum = this.fold([], function (accum, el) {
464      accum.push.apply(accum, el.jQ.get());
465      return accum;
466    });
467
468    this.jQ = this.jQ.add(accum);
469  };
470  _.jQ = $();
471
472  // like Cursor::withDirInsertAt(dir, parent, withDir, oppDir)
473  _.withDirAdopt = function(dir, parent, withDir, oppDir) {
474    return (dir === L ? this.adopt(parent, withDir, oppDir)
475                      : this.adopt(parent, oppDir, withDir));
476  };
477  _.adopt = function(parent, leftward, rightward) {
478    prayWellFormed(parent, leftward, rightward);
479
480    var self = this;
481    self.disowned = false;
482
483    var leftEnd = self.ends[L];
484    if (!leftEnd) return this;
485
486    var rightEnd = self.ends[R];
487
488    if (leftward) {
489      // NB: this is handled in the ::each() block
490      // leftward[R] = leftEnd
491    } else {
492      parent.ends[L] = leftEnd;
493    }
494
495    if (rightward) {
496      rightward[L] = rightEnd;
497    } else {
498      parent.ends[R] = rightEnd;
499    }
500
501    self.ends[R][R] = rightward;
502
503    self.each(function(el) {
504      el[L] = leftward;
505      el.parent = parent;
506      if (leftward) leftward[R] = el;
507
508      leftward = el;
509    });
510
511    return self;
512  };
513
514  _.disown = function() {
515    var self = this;
516    var leftEnd = self.ends[L];
517
518    // guard for empty and already-disowned fragments
519    if (!leftEnd || self.disowned) return self;
520
521    self.disowned = true;
522
523    var rightEnd = self.ends[R]
524    var parent = leftEnd.parent;
525
526    prayWellFormed(parent, leftEnd[L], leftEnd);
527    prayWellFormed(parent, rightEnd, rightEnd[R]);
528
529    if (leftEnd[L]) {
530      leftEnd[L][R] = rightEnd[R];
531    } else {
532      parent.ends[L] = rightEnd[R];
533    }
534
535    if (rightEnd[R]) {
536      rightEnd[R][L] = leftEnd[L];
537    } else {
538      parent.ends[R] = leftEnd[L];
539    }
540
541    return self;
542  };
543
544  _.remove = function() {
545    this.jQ.remove();
546    this.each('postOrder', 'dispose');
547    return this.disown();
548  };
549
550  _.each = iterator(function(yield_) {
551    var self = this;
552    var el = self.ends[L];
553    if (!el) return self;
554
555    for (; el !== self.ends[R][R]; el = el[R]) {
556      var result = yield_(el);
557      if (result === false) break;
558    }
559
560    return self;
561  });
562
563  _.fold = function(fold, fn) {
564    this.each(function(el) {
565      fold = fn.call(this, fold, el);
566    });
567
568    return fold;
569  };
570});
571
572
573/**
574 * Registry of LaTeX commands and commands created when typing
575 * a single character.
576 *
577 * (Commands are all subclasses of Node.)
578 */
579var LatexCmds = {}, CharCmds = {};
580/********************************************
581 * Cursor and Selection "singleton" classes
582 *******************************************/
583
584/* The main thing that manipulates the Math DOM. Makes sure to manipulate the
585HTML DOM to match. */
586
587/* Sort of singletons, since there should only be one per editable math
588textbox, but any one HTML document can contain many such textboxes, so any one
589JS environment could actually contain many instances. */
590
591//A fake cursor in the fake textbox that the math is rendered in.
592var Cursor = P(Point, function(_) {
593  _.init = function(initParent, options) {
594    this.parent = initParent;
595    this.options = options;
596
597    var jQ = this.jQ = this._jQ = $('<span class="mq-cursor">&#8203;</span>');
598    //closured for setInterval
599    this.blink = function(){ jQ.toggleClass('mq-blink'); };
600
601    this.upDownCache = {};
602  };
603
604  _.show = function() {
605    this.jQ = this._jQ.removeClass('mq-blink');
606    if ('intervalId' in this) //already was shown, just restart interval
607      clearInterval(this.intervalId);
608    else { //was hidden and detached, insert this.jQ back into HTML DOM
609      if (this[R]) {
610        if (this.selection && this.selection.ends[L][L] === this[L])
611          this.jQ.insertBefore(this.selection.jQ);
612        else
613          this.jQ.insertBefore(this[R].jQ.first());
614      }
615      else
616        this.jQ.appendTo(this.parent.jQ);
617      this.parent.focus();
618    }
619    this.intervalId = setInterval(this.blink, 500);
620    return this;
621  };
622  _.hide = function() {
623    if ('intervalId' in this)
624      clearInterval(this.intervalId);
625    delete this.intervalId;
626    this.jQ.detach();
627    this.jQ = $();
628    return this;
629  };
630
631  _.withDirInsertAt = function(dir, parent, withDir, oppDir) {
632    var oldParent = this.parent;
633    this.parent = parent;
634    this[dir] = withDir;
635    this[-dir] = oppDir;
636    // by contract, .blur() is called after all has been said and done
637    // and the cursor has actually been moved
638    // FIXME pass cursor to .blur() so text can fix cursor pointers when removing itself
639    if (oldParent !== parent && oldParent.blur) oldParent.blur(this);
640  };
641  _.insDirOf = function(dir, el) {
642    prayDirection(dir);
643    this.jQ.insDirOf(dir, el.jQ);
644    this.withDirInsertAt(dir, el.parent, el[dir], el);
645    this.parent.jQ.addClass('mq-hasCursor');
646    return this;
647  };
648  _.insLeftOf = function(el) { return this.insDirOf(L, el); };
649  _.insRightOf = function(el) { return this.insDirOf(R, el); };
650
651  _.insAtDirEnd = function(dir, el) {
652    prayDirection(dir);
653    this.jQ.insAtDirEnd(dir, el.jQ);
654    this.withDirInsertAt(dir, el, 0, el.ends[dir]);
655    el.focus();
656    return this;
657  };
658  _.insAtLeftEnd = function(el) { return this.insAtDirEnd(L, el); };
659  _.insAtRightEnd = function(el) { return this.insAtDirEnd(R, el); };
660
661  /**
662   * jump up or down from one block Node to another:
663   * - cache the current Point in the node we're jumping from
664   * - check if there's a Point in it cached for the node we're jumping to
665   *   + if so put the cursor there,
666   *   + if not seek a position in the node that is horizontally closest to
667   *     the cursor's current position
668   */
669  _.jumpUpDown = function(from, to) {
670    var self = this;
671    self.upDownCache[from.id] = Point.copy(self);
672    var cached = self.upDownCache[to.id];
673    if (cached) {
674      cached[R] ? self.insLeftOf(cached[R]) : self.insAtRightEnd(cached.parent);
675    }
676    else {
677      var pageX = self.offset().left;
678      to.seek(pageX, self);
679    }
680  };
681  _.offset = function() {
682    //in Opera 11.62, .getBoundingClientRect() and hence jQuery::offset()
683    //returns all 0's on inline elements with negative margin-right (like
684    //the cursor) at the end of their parent, so temporarily remove the
685    //negative margin-right when calling jQuery::offset()
686    //Opera bug DSK-360043
687    //http://bugs.jquery.com/ticket/11523
688    //https://github.com/jquery/jquery/pull/717
689    var self = this, offset = self.jQ.removeClass('mq-cursor').offset();
690    self.jQ.addClass('mq-cursor');
691    return offset;
692  }
693  _.unwrapGramp = function() {
694    var gramp = this.parent.parent;
695    var greatgramp = gramp.parent;
696    var rightward = gramp[R];
697    var cursor = this;
698
699    var leftward = gramp[L];
700    gramp.disown().eachChild(function(uncle) {
701      if (uncle.isEmpty()) return;
702
703      uncle.children()
704        .adopt(greatgramp, leftward, rightward)
705        .each(function(cousin) {
706          cousin.jQ.insertBefore(gramp.jQ.first());
707        })
708      ;
709
710      leftward = uncle.ends[R];
711    });
712
713    if (!this[R]) { //then find something to be rightward to insLeftOf
714      if (this[L])
715        this[R] = this[L][R];
716      else {
717        while (!this[R]) {
718          this.parent = this.parent[R];
719          if (this.parent)
720            this[R] = this.parent.ends[L];
721          else {
722            this[R] = gramp[R];
723            this.parent = greatgramp;
724            break;
725          }
726        }
727      }
728    }
729    if (this[R])
730      this.insLeftOf(this[R]);
731    else
732      this.insAtRightEnd(greatgramp);
733
734    gramp.jQ.remove();
735
736    if (gramp[L].siblingDeleted) gramp[L].siblingDeleted(cursor.options, R);
737    if (gramp[R].siblingDeleted) gramp[R].siblingDeleted(cursor.options, L);
738  };
739  _.startSelection = function() {
740    var anticursor = this.anticursor = Point.copy(this);
741    var ancestors = anticursor.ancestors = {}; // a map from each ancestor of
742      // the anticursor, to its child that is also an ancestor; in other words,
743      // the anticursor's ancestor chain in reverse order
744    for (var ancestor = anticursor; ancestor.parent; ancestor = ancestor.parent) {
745      ancestors[ancestor.parent.id] = ancestor;
746    }
747  };
748  _.endSelection = function() {
749    delete this.anticursor;
750  };
751  _.select = function() {
752    var anticursor = this.anticursor;
753    if (this[L] === anticursor[L] && this.parent === anticursor.parent) return false;
754
755    // Find the lowest common ancestor (`lca`), and the ancestor of the cursor
756    // whose parent is the LCA (which'll be an end of the selection fragment).
757    for (var ancestor = this; ancestor.parent; ancestor = ancestor.parent) {
758      if (ancestor.parent.id in anticursor.ancestors) {
759        var lca = ancestor.parent;
760        break;
761      }
762    }
763    pray('cursor and anticursor in the same tree', lca);
764    // The cursor and the anticursor should be in the same tree, because the
765    // mousemove handler attached to the document, unlike the one attached to
766    // the root HTML DOM element, doesn't try to get the math tree node of the
767    // mousemove target, and Cursor::seek() based solely on coordinates stays
768    // within the tree of `this` cursor's root.
769
770    // The other end of the selection fragment, the ancestor of the anticursor
771    // whose parent is the LCA.
772    var antiAncestor = anticursor.ancestors[lca.id];
773
774    // Now we have two either Nodes or Points, guaranteed to have a common
775    // parent and guaranteed that if both are Points, they are not the same,
776    // and we have to figure out which is the left end and which the right end
777    // of the selection.
778    var leftEnd, rightEnd, dir = R;
779
780    // This is an extremely subtle algorithm.
781    // As a special case, `ancestor` could be a Point and `antiAncestor` a Node
782    // immediately to `ancestor`'s left.
783    // In all other cases,
784    // - both Nodes
785    // - `ancestor` a Point and `antiAncestor` a Node
786    // - `ancestor` a Node and `antiAncestor` a Point
787    // `antiAncestor[R] === rightward[R]` for some `rightward` that is
788    // `ancestor` or to its right, if and only if `antiAncestor` is to
789    // the right of `ancestor`.
790    if (ancestor[L] !== antiAncestor) {
791      for (var rightward = ancestor; rightward; rightward = rightward[R]) {
792        if (rightward[R] === antiAncestor[R]) {
793          dir = L;
794          leftEnd = ancestor;
795          rightEnd = antiAncestor;
796          break;
797        }
798      }
799    }
800    if (dir === R) {
801      leftEnd = antiAncestor;
802      rightEnd = ancestor;
803    }
804
805    // only want to select Nodes up to Points, can't select Points themselves
806    if (leftEnd instanceof Point) leftEnd = leftEnd[R];
807    if (rightEnd instanceof Point) rightEnd = rightEnd[L];
808
809    this.hide().selection = lca.selectChildren(leftEnd, rightEnd);
810    this.insDirOf(dir, this.selection.ends[dir]);
811    this.selectionChanged();
812    return true;
813  };
814
815  _.clearSelection = function() {
816    if (this.selection) {
817      this.selection.clear();
818      delete this.selection;
819      this.selectionChanged();
820    }
821    return this;
822  };
823  _.deleteSelection = function() {
824    if (!this.selection) return;
825
826    this[L] = this.selection.ends[L][L];
827    this[R] = this.selection.ends[R][R];
828    this.selection.remove();
829    this.selectionChanged();
830    delete this.selection;
831  };
832  _.replaceSelection = function() {
833    var seln = this.selection;
834    if (seln) {
835      this[L] = seln.ends[L][L];
836      this[R] = seln.ends[R][R];
837      delete this.selection;
838    }
839    return seln;
840  };
841  _.depth = function() {
842    var node = this;
843    var depth = 0;
844    while (node = node.parent) {
845      depth += (node instanceof MathBlock) ? 1 : 0;
846    }
847    return depth;
848  };
849  _.isTooDeep = function(offset) {
850    if (this.options.maxDepth !== undefined) {
851      return this.depth() + (offset || 0) > this.options.maxDepth;
852    }
853  };
854});
855
856var Selection = P(Fragment, function(_, super_) {
857  _.init = function() {
858    super_.init.apply(this, arguments);
859    this.jQ = this.jQ.wrapAll('<span class="mq-selection"></span>').parent();
860      //can't do wrapAll(this.jQ = $(...)) because wrapAll will clone it
861  };
862  _.adopt = function() {
863    this.jQ.replaceWith(this.jQ = this.jQ.children());
864    return super_.adopt.apply(this, arguments);
865  };
866  _.clear = function() {
867    // using the browser's native .childNodes property so that we
868    // don't discard text nodes.
869    this.jQ.replaceWith(this.jQ[0].childNodes);
870    return this;
871  };
872  _.join = function(methodName) {
873    return this.fold('', function(fold, child) {
874      return fold + child[methodName]();
875    });
876  };
877});
878/*********************************************
879 * Controller for a MathQuill instance,
880 * on which services are registered with
881 *
882 *   Controller.open(function(_) { ... });
883 *
884 ********************************************/
885
886var Controller = P(function(_) {
887  _.init = function(root, container, options) {
888    this.id = root.id;
889    this.data = {};
890
891    this.root = root;
892    this.container = container;
893    this.options = options;
894
895    root.controller = this;
896
897    this.cursor = root.cursor = Cursor(root, options);
898    // TODO: stop depending on root.cursor, and rm it
899  };
900
901  _.handle = function(name, dir) {
902    var handlers = this.options.handlers;
903    if (handlers && handlers.fns[name]) {
904      var mq = handlers.APIClasses[this.KIND_OF_MQ](this);
905      if (dir === L || dir === R) handlers.fns[name](dir, mq);
906      else handlers.fns[name](mq);
907    }
908  };
909
910  var notifyees = [];
911  this.onNotify = function(f) { notifyees.push(f); };
912  _.notify = function() {
913    for (var i = 0; i < notifyees.length; i += 1) {
914      notifyees[i].apply(this.cursor, arguments);
915    }
916    return this;
917  };
918});
919/*********************************************************
920 * The publicly exposed MathQuill API.
921 ********************************************************/
922
923var API = {}, Options = P(), optionProcessors = {}, Progenote = P(), EMBEDS = {};
924
925/**
926 * Interface Versioning (#459, #495) to allow us to virtually guarantee
927 * backcompat. v0.10.x introduces it, so for now, don't completely break the
928 * API for people who don't know about it, just complain with console.warn().
929 *
930 * The methods are shimmed in outro.js so that MQ.MathField.prototype etc can
931 * be accessed.
932 */
933function insistOnInterVer() {
934  if (window.console) console.warn(
935    'You are using the MathQuill API without specifying an interface version, ' +
936    'which will fail in v1.0.0. Easiest fix is to do the following before ' +
937    'doing anything else:\n' +
938    '\n' +
939    '    MathQuill = MathQuill.getInterface(1);\n' +
940    '    // now MathQuill.MathField() works like it used to\n' +
941    '\n' +
942    'See also the "`dev` branch (2014\u20132015) \u2192 v0.10.0 Migration Guide" at\n' +
943    '  https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide'
944  );
945}
946// globally exported API object
947function MathQuill(el) {
948  insistOnInterVer();
949  return MQ1(el);
950};
951MathQuill.prototype = Progenote.p;
952MathQuill.VERSION = "v0.10.1";
953MathQuill.interfaceVersion = function(v) {
954  // shim for #459-era interface versioning (ended with #495)
955  if (v !== 1) throw 'Only interface version 1 supported. You specified: ' + v;
956  insistOnInterVer = function() {
957    if (window.console) console.warn(
958      'You called MathQuill.interfaceVersion(1); to specify the interface ' +
959      'version, which will fail in v1.0.0. You can fix this easily by doing ' +
960      'this before doing anything else:\n' +
961      '\n' +
962      '    MathQuill = MathQuill.getInterface(1);\n' +
963      '    // now MathQuill.MathField() works like it used to\n' +
964      '\n' +
965      'See also the "`dev` branch (2014\u20132015) \u2192 v0.10.0 Migration Guide" at\n' +
966      '  https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide'
967    );
968  };
969  insistOnInterVer();
970  return MathQuill;
971};
972MathQuill.getInterface = getInterface;
973
974var MIN = getInterface.MIN = 1, MAX = getInterface.MAX = 2;
975function getInterface(v) {
976  if (!(MIN <= v && v <= MAX)) throw 'Only interface versions between ' +
977    MIN + ' and ' + MAX + ' supported. You specified: ' + v;
978
979  /**
980   * Function that takes an HTML element and, if it's the root HTML element of a
981   * static math or math or text field, returns an API object for it (else, null).
982   *
983   *   var mathfield = MQ.MathField(mathFieldSpan);
984   *   assert(MQ(mathFieldSpan).id === mathfield.id);
985   *   assert(MQ(mathFieldSpan).id === MQ(mathFieldSpan).id);
986   *
987   */
988  function MQ(el) {
989    if (!el || !el.nodeType) return null; // check that `el` is a HTML element, using the
990      // same technique as jQuery: https://github.com/jquery/jquery/blob/679536ee4b7a92ae64a5f58d90e9cc38c001e807/src/core/init.js#L92
991    var blockId = $(el).children('.mq-root-block').attr(mqBlockId);
992    var ctrlr = blockId && Node.byId[blockId].controller;
993    return ctrlr ? APIClasses[ctrlr.KIND_OF_MQ](ctrlr) : null;
994  };
995  var APIClasses = {};
996
997  MQ.L = L;
998  MQ.R = R;
999  MQ.saneKeyboardEvents = saneKeyboardEvents;
1000
1001  function config(currentOptions, newOptions) {
1002    if (newOptions && newOptions.handlers) {
1003      newOptions.handlers = { fns: newOptions.handlers, APIClasses: APIClasses };
1004    }
1005    for (var name in newOptions) if (newOptions.hasOwnProperty(name)) {
1006      var value = newOptions[name], processor = optionProcessors[name];
1007      currentOptions[name] = (processor ? processor(value) : value);
1008    }
1009  }
1010  MQ.config = function(opts) { config(Options.p, opts); return this; };
1011  MQ.registerEmbed = function(name, options) {
1012    if (!/^[a-z][a-z0-9]*$/i.test(name)) {
1013      throw 'Embed name must start with letter and be only letters and digits';
1014    }
1015    EMBEDS[name] = options;
1016  };
1017
1018  var AbstractMathQuill = APIClasses.AbstractMathQuill = P(Progenote, function(_) {
1019    _.init = function(ctrlr) {
1020      this.__controller = ctrlr;
1021      this.__options = ctrlr.options;
1022      this.id = ctrlr.id;
1023      this.data = ctrlr.data;
1024    };
1025    _.__mathquillify = function(classNames) {
1026      var ctrlr = this.__controller, root = ctrlr.root, el = ctrlr.container;
1027      ctrlr.createTextarea();
1028
1029      var contents = el.addClass(classNames).contents().detach();
1030      root.jQ =
1031        $('<span class="mq-root-block"/>').attr(mqBlockId, root.id).appendTo(el);
1032      this.latex(contents.text());
1033
1034      this.revert = function() {
1035        return el.empty().unbind('.mathquill')
1036        .removeClass('mq-editable-field mq-math-mode mq-text-mode')
1037        .append(contents);
1038      };
1039    };
1040    _.config = function(opts) { config(this.__options, opts); return this; };
1041    _.el = function() { return this.__controller.container[0]; };
1042    _.text = function() { return this.__controller.exportText(); };
1043    _.latex = function(latex) {
1044      if (arguments.length > 0) {
1045        this.__controller.renderLatexMath(latex);
1046        if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
1047        return this;
1048      }
1049      return this.__controller.exportLatex();
1050    };
1051    _.html = function() {
1052      return this.__controller.root.jQ.html()
1053        .replace(/ mathquill-(?:command|block)-id="?\d+"?/g, '')
1054        .replace(/<span class="?mq-cursor( mq-blink)?"?>.?<\/span>/i, '')
1055        .replace(/ mq-hasCursor|mq-hasCursor ?/, '')
1056        .replace(/ class=(""|(?= |>))/g, '');
1057    };
1058    _.reflow = function() {
1059      this.__controller.root.postOrder('reflow');
1060      return this;
1061    };
1062  });
1063  MQ.prototype = AbstractMathQuill.prototype;
1064
1065  APIClasses.EditableField = P(AbstractMathQuill, function(_, super_) {
1066    _.__mathquillify = function() {
1067      super_.__mathquillify.apply(this, arguments);
1068      this.__controller.editable = true;
1069      this.__controller.delegateMouseEvents();
1070      this.__controller.editablesTextareaEvents();
1071      return this;
1072    };
1073    _.focus = function() { this.__controller.textarea.focus(); return this; };
1074    _.blur = function() { this.__controller.textarea.blur(); return this; };
1075    _.write = function(latex) {
1076      this.__controller.writeLatex(latex);
1077      this.__controller.scrollHoriz();
1078      if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
1079      return this;
1080    };
1081    _.empty = function() {
1082      var root = this.__controller.root, cursor = this.__controller.cursor;
1083      root.eachChild('postOrder', 'dispose');
1084      root.ends[L] = root.ends[R] = 0;
1085      root.jQ.empty();
1086      delete cursor.selection;
1087      cursor.insAtRightEnd(root);
1088      return this;
1089    };
1090    _.cmd = function(cmd) {
1091      var ctrlr = this.__controller.notify(), cursor = ctrlr.cursor;
1092      if (/^\\[a-z]+$/i.test(cmd) && !cursor.isTooDeep()) {
1093        cmd = cmd.slice(1);
1094        var klass = LatexCmds[cmd];
1095        if (klass) {
1096          cmd = klass(cmd);
1097          if (cursor.selection) cmd.replaces(cursor.replaceSelection());
1098          cmd.createLeftOf(cursor.show());
1099          this.__controller.scrollHoriz();
1100        }
1101        else /* TODO: API needs better error reporting */;
1102      }
1103      else cursor.parent.write(cursor, cmd);
1104      if (ctrlr.blurred) cursor.hide().parent.blur();
1105      return this;
1106    };
1107    _.select = function() {
1108      var ctrlr = this.__controller;
1109      ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
1110      while (ctrlr.cursor[L]) ctrlr.selectLeft();
1111      return this;
1112    };
1113    _.clearSelection = function() {
1114      this.__controller.cursor.clearSelection();
1115      return this;
1116    };
1117
1118    _.moveToDirEnd = function(dir) {
1119      this.__controller.notify('move').cursor.insAtDirEnd(dir, this.__controller.root);
1120      return this;
1121    };
1122    _.moveToLeftEnd = function() { return this.moveToDirEnd(L); };
1123    _.moveToRightEnd = function() { return this.moveToDirEnd(R); };
1124
1125    _.keystroke = function(keys) {
1126      var keys = keys.replace(/^\s+|\s+$/g, '').split(/\s+/);
1127      for (var i = 0; i < keys.length; i += 1) {
1128        this.__controller.keystroke(keys[i], { preventDefault: noop });
1129      }
1130      return this;
1131    };
1132    _.typedText = function(text) {
1133      for (var i = 0; i < text.length; i += 1) this.__controller.typedText(text.charAt(i));
1134      return this;
1135    };
1136    _.dropEmbedded = function(pageX, pageY, options) {
1137      var clientX = pageX - $(window).scrollLeft();
1138      var clientY = pageY - $(window).scrollTop();
1139
1140      var el = document.elementFromPoint(clientX, clientY);
1141      this.__controller.seek($(el), pageX, pageY);
1142      var cmd = Embed().setOptions(options);
1143      cmd.createLeftOf(this.__controller.cursor);
1144    };
1145    _.clickAt = function(clientX, clientY, target) {
1146      target = target || document.elementFromPoint(clientX, clientY);
1147
1148      var ctrlr = this.__controller, root = ctrlr.root;
1149      if (!jQuery.contains(root.jQ[0], target)) target = root.jQ[0];
1150      ctrlr.seek($(target), clientX + pageXOffset, clientY + pageYOffset);
1151      if (ctrlr.blurred) this.focus();
1152      return this;
1153    };
1154    _.ignoreNextMousedown = function(fn) {
1155      this.__controller.cursor.options.ignoreNextMousedown = fn;
1156      return this;
1157    };
1158  });
1159  MQ.EditableField = function() { throw "wtf don't call me, I'm 'abstract'"; };
1160  MQ.EditableField.prototype = APIClasses.EditableField.prototype;
1161
1162  /**
1163   * Export the API functions that MathQuill-ify an HTML element into API objects
1164   * of each class. If the element had already been MathQuill-ified but into a
1165   * different kind (or it's not an HTML element), return null.
1166   */
1167  for (var kind in API) (function(kind, defAPIClass) {
1168    var APIClass = APIClasses[kind] = defAPIClass(APIClasses);
1169    MQ[kind] = function(el, opts) {
1170      var mq = MQ(el);
1171      if (mq instanceof APIClass || !el || !el.nodeType) return mq;
1172      var ctrlr = Controller(APIClass.RootBlock(), $(el), Options());
1173      ctrlr.KIND_OF_MQ = kind;
1174      return APIClass(ctrlr).__mathquillify(opts, v);
1175    };
1176    MQ[kind].prototype = APIClass.prototype;
1177  }(kind, API[kind]));
1178
1179  return MQ;
1180}
1181
1182MathQuill.noConflict = function() {
1183  window.MathQuill = origMathQuill;
1184  return MathQuill;
1185};
1186var origMathQuill = window.MathQuill;
1187window.MathQuill = MathQuill;
1188
1189function RootBlockMixin(_) {
1190  var names = 'moveOutOf deleteOutOf selectOutOf upOutOf downOutOf'.split(' ');
1191  for (var i = 0; i < names.length; i += 1) (function(name) {
1192    _[name] = function(dir) { this.controller.handle(name, dir); };
1193  }(names[i]));
1194  _.reflow = function() {
1195    this.controller.handle('reflow');
1196    this.controller.handle('edited');
1197    this.controller.handle('edit');
1198  };
1199}
1200/*************************************************
1201 * Sane Keyboard Events Shim
1202 *
1203 * An abstraction layer wrapping the textarea in
1204 * an object with methods to manipulate and listen
1205 * to events on, that hides all the nasty cross-
1206 * browser incompatibilities behind a uniform API.
1207 *
1208 * Design goal: This is a *HARD* internal
1209 * abstraction barrier. Cross-browser
1210 * inconsistencies are not allowed to leak through
1211 * and be dealt with by event handlers. All future
1212 * cross-browser issues that arise must be dealt
1213 * with here, and if necessary, the API updated.
1214 *
1215 * Organization:
1216 * - key values map and stringify()
1217 * - saneKeyboardEvents()
1218 *    + defer() and flush()
1219 *    + event handler logic
1220 *    + attach event handlers and export methods
1221 ************************************************/
1222
1223var saneKeyboardEvents = (function() {
1224  // The following [key values][1] map was compiled from the
1225  // [DOM3 Events appendix section on key codes][2] and
1226  // [a widely cited report on cross-browser tests of key codes][3],
1227  // except for 10: 'Enter', which I've empirically observed in Safari on iOS
1228  // and doesn't appear to conflict with any other known key codes.
1229  //
1230  // [1]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#keys-keyvalues
1231  // [2]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#fixed-virtual-key-codes
1232  // [3]: http://unixpapa.com/js/key.html
1233  var KEY_VALUES = {
1234    8: 'Backspace',
1235    9: 'Tab',
1236
1237    10: 'Enter', // for Safari on iOS
1238
1239    13: 'Enter',
1240
1241    16: 'Shift',
1242    17: 'Control',
1243    18: 'Alt',
1244    20: 'CapsLock',
1245
1246    27: 'Esc',
1247
1248    32: 'Spacebar',
1249
1250    33: 'PageUp',
1251    34: 'PageDown',
1252    35: 'End',
1253    36: 'Home',
1254
1255    37: 'Left',
1256    38: 'Up',
1257    39: 'Right',
1258    40: 'Down',
1259
1260    45: 'Insert',
1261
1262    46: 'Del',
1263
1264    144: 'NumLock'
1265  };
1266
1267  // To the extent possible, create a normalized string representation
1268  // of the key combo (i.e., key code and modifier keys).
1269  function stringify(evt) {
1270    var which = evt.which || evt.keyCode;
1271    var keyVal = KEY_VALUES[which];
1272    var key;
1273    var modifiers = [];
1274
1275    if (evt.ctrlKey) modifiers.push('Ctrl');
1276    if (evt.originalEvent && evt.originalEvent.metaKey) modifiers.push('Meta');
1277    if (evt.altKey) modifiers.push('Alt');
1278    if (evt.shiftKey) modifiers.push('Shift');
1279
1280    key = keyVal || String.fromCharCode(which);
1281
1282    if (!modifiers.length && !keyVal) return key;
1283
1284    modifiers.push(key);
1285    return modifiers.join('-');
1286  }
1287
1288  // create a keyboard events shim that calls callbacks at useful times
1289  // and exports useful public methods
1290  return function saneKeyboardEvents(el, handlers) {
1291    var isandroid = /.*android.*/i.test(navigator.userAgent);
1292    var androidcompose = false;
1293    var keydown = null;
1294    var keypress = null;
1295
1296    var textarea = jQuery(el);
1297    var target = jQuery(handlers.container || textarea);
1298
1299    // checkTextareaFor() is called after key or clipboard events to
1300    // say "Hey, I think something was just typed" or "pasted" etc,
1301    // so that at all subsequent opportune times (next event or timeout),
1302    // will check for expected typed or pasted text.
1303    // Need to check repeatedly because #135: in Safari 5.1 (at least),
1304    // after selecting something and then typing, the textarea is
1305    // incorrectly reported as selected during the input event (but not
1306    // subsequently).
1307    var checkTextarea = noop, timeoutId;
1308    function checkTextareaFor(checker) {
1309      checkTextarea = checker;
1310      clearTimeout(timeoutId);
1311      timeoutId = setTimeout(checker);
1312    }
1313    function checkTextareaOnce(checker) {
1314      checkTextareaFor(function(e) {
1315        checkTextarea = noop;
1316        clearTimeout(timeoutId);
1317        checker(e);
1318      });
1319    }
1320    target.bind('keydown keypress input keyup focusout paste', function(e) { checkTextarea(e); });
1321
1322
1323    // -*- public methods -*- //
1324    function select(text) {
1325      // check textarea at least once/one last time before munging (so
1326      // no race condition if selection happens after keypress/paste but
1327      // before checkTextarea), then never again ('cos it's been munged)
1328      checkTextarea();
1329      checkTextarea = noop;
1330      clearTimeout(timeoutId);
1331
1332      textarea.val(text);
1333      if (text && textarea[0].select) textarea[0].select();
1334      shouldBeSelected = !!text;
1335    }
1336    var shouldBeSelected = false;
1337
1338    // -*- helper subroutines -*- //
1339
1340    // Determine whether there's a selection in the textarea.
1341    // This will always return false in IE < 9, which don't support
1342    // HTMLTextareaElement::selection{Start,End}.
1343    function hasSelection() {
1344      var dom = textarea[0];
1345
1346      if (!('selectionStart' in dom)) return false;
1347      return dom.selectionStart !== dom.selectionEnd;
1348    }
1349
1350    function handleKey() {
1351      //handlers.keystroke(stringify(keydown), keydown);
1352      if (isandroid && keydown.which === 229) {
1353        androidcompose = true;
1354        textarea.val('\0');
1355      } else {
1356        handlers.keystroke(stringify(keydown), keydown);
1357      }
1358    }
1359
1360    // -*- event handlers -*- //
1361    function onKeydown(e) {
1362      keydown = e;
1363      keypress = null;
1364
1365      if (shouldBeSelected) checkTextareaOnce(function(e) {
1366        if (!(e && e.type === 'focusout') && textarea[0].select) {
1367          // re-select textarea in case it's an unrecognized key that clears
1368          // the selection, then never again, 'cos next thing might be blur
1369          textarea[0].select();
1370        }
1371      });
1372
1373      handleKey();
1374    }
1375
1376    function onKeypress(e) {
1377      // call the key handler for repeated keypresses.
1378      // This excludes keypresses that happen directly
1379      // after keydown.  In that case, there will be
1380      // no previous keypress, so we skip it here
1381      if (keydown && keypress) handleKey();
1382
1383      keypress = e;
1384
1385      checkTextareaFor(typedText);
1386    }
1387    function onKeyup(e) {
1388      // Handle case of no keypress event being sent
1389      if (!!keydown && !keypress) checkTextareaFor(typedText);
1390    }
1391    function typedText() {
1392      // If there is a selection, the contents of the textarea couldn't
1393      // possibly have just been typed in.
1394      // This happens in browsers like Firefox and Opera that fire
1395      // keypress for keystrokes that are not text entry and leave the
1396      // selection in the textarea alone, such as Ctrl-C.
1397      // Note: we assume that browsers that don't support hasSelection()
1398      // also never fire keypress on keystrokes that are not text entry.
1399      // This seems reasonably safe because:
1400      // - all modern browsers including IE 9+ support hasSelection(),
1401      //   making it extremely unlikely any browser besides IE < 9 won't
1402      // - as far as we know IE < 9 never fires keypress on keystrokes
1403      //   that aren't text entry, which is only as reliable as our
1404      //   tests are comprehensive, but the IE < 9 way to do
1405      //   hasSelection() is poorly documented and is also only as
1406      //   reliable as our tests are comprehensive
1407      // If anything like #40 or #71 is reported in IE < 9, see
1408      // b1318e5349160b665003e36d4eedd64101ceacd8
1409      //if (hasSelection()) return;
1410
1411      //var text = textarea.val();
1412      //if (text.length === 1) {
1413      if (androidcompose) {
1414        var text = textarea.val();
1415        androidcompose = false;
1416        textarea.val('');
1417        //handlers.typedText(text);
1418      //} // in Firefox, keys that don't type text, just clear seln, fire keypress
1419      // https://github.com/mathquill/mathquill/issues/293#issuecomment-40997668
1420      //else if (text && textarea[0].select) textarea[0].select(); // re-select if that's why we're here
1421 if (text.length == 0) {
1422          handlers.keystroke('Backspace', keydown);
1423        }
1424        else if (text.length >= 2) {
1425          text = text.substring(1);
1426          handlers.keystroke(text === ' ' ? 'Spacebar' : text, keydown);
1427          if (!keydown.isDefaultPrevented()) {
1428            for (var i = 0; i < text.length; i += 1) handlers.typedText(text.charAt(i));
1429          }
1430        }
1431      } else {
1432        // If there is a selection, the contents of the textarea couldn't
1433        // possibly have just been typed in.
1434        // This happens in browsers like Firefox and Opera that fire
1435        // keypress for keystrokes that are not text entry and leave the
1436        // selection in the textarea alone, such as Ctrl-C.
1437        // Note: we assume that browsers that don't support hasSelection()
1438        // also never fire keypress on keystrokes that are not text entry.
1439        // This seems reasonably safe because:
1440        // - all modern browsers including IE 9+ support hasSelection(),
1441        //   making it extremely unlikely any browser besides IE < 9 won't
1442        // - as far as we know IE < 9 never fires keypress on keystrokes
1443        //   that aren't text entry, which is only as reliable as our
1444        //   tests are comprehensive, but the IE < 9 way to do
1445        //   hasSelection() is poorly documented and is also only as
1446        //   reliable as our tests are comprehensive
1447        // If anything like #40 or #71 is reported in IE < 9, see
1448        // b1318e5349160b665003e36d4eedd64101ceacd8
1449        if (hasSelection()) return;
1450
1451        var text = textarea.val();
1452        if (text.length >= 1) {
1453          textarea.val('');
1454          for (var i = 0; i < text.length; i += 1) handlers.typedText(text.charAt(i));
1455        } // in Firefox, keys that don't type text, just clear seln, fire keypress
1456        // https://github.com/mathquill/mathquill/issues/293#issuecomment-40997668
1457        else if (text && textarea[0].select) textarea[0].select(); // re-select if that's why we're here
1458      }
1459    }
1460
1461    function onBlur() { keydown = keypress = null; }
1462
1463    function onPaste(e) {
1464      // browsers are dumb.
1465      //
1466      // In Linux, middle-click pasting causes onPaste to be called,
1467      // when the textarea is not necessarily focused.  We focus it
1468      // here to ensure that the pasted text actually ends up in the
1469      // textarea.
1470      //
1471      // It's pretty nifty that by changing focus in this handler,
1472      // we can change the target of the default action.  (This works
1473      // on keydown too, FWIW).
1474      //
1475      // And by nifty, we mean dumb (but useful sometimes).
1476      textarea.focus();
1477
1478      checkTextareaFor(pastedText);
1479    }
1480    function pastedText() {
1481      var text = textarea.val();
1482      textarea.val('');
1483      if (text) handlers.paste(text);
1484    }
1485
1486    // -*- attach event handlers -*- //
1487    target.bind({
1488      keydown: onKeydown,
1489      keypress: onKeypress,
1490      keyup: onKeyup,
1491      focusout: onBlur,
1492      cut: function() { checkTextareaOnce(function() { handlers.cut(); }); },
1493      copy: function() { checkTextareaOnce(function() { handlers.copy(); }); },
1494      paste: onPaste
1495    });
vendor: 102,424 bytes, lines 1496-4424
1496
1497    // -*- export public methods -*- //
1498    return {
1499      select: select
1500    };
1501  };
1502}());
1503var Parser = P(function(_, super_, Parser) {
1504  // The Parser object is a wrapper for a parser function.
1505  // Externally, you use one to parse a string by calling
1506  //   var result = SomeParser.parse('Me Me Me! Parse Me!');
1507  // You should never call the constructor, rather you should
1508  // construct your Parser from the base parsers and the
1509  // parser combinator methods.
1510
1511  function parseError(stream, message) {
1512    if (stream) {
1513      stream = "'"+stream+"'";
1514    }
1515    else {
1516      stream = 'EOF';
1517    }
1518
1519    throw 'Parse Error: '+message+' at '+stream;
1520  }
1521
1522  _.init = function(body) { this._ = body; };
1523
1524  _.parse = function(stream) {
1525    return this.skip(eof)._(''+stream, success, parseError);
1526
1527    function success(stream, result) { return result; }
1528  };
1529
1530  // -*- primitive combinators -*- //
1531  _.or = function(alternative) {
1532    pray('or is passed a parser', alternative instanceof Parser);
1533
1534    var self = this;
1535
1536    return Parser(function(stream, onSuccess, onFailure) {
1537      return self._(stream, onSuccess, failure);
1538
1539      function failure(newStream) {
1540        return alternative._(stream, onSuccess, onFailure);
1541      }
1542    });
1543  };
1544
1545  _.then = function(next) {
1546    var self = this;
1547
1548    return Parser(function(stream, onSuccess, onFailure) {
1549      return self._(stream, success, onFailure);
1550
1551      function success(newStream, result) {
1552        var nextParser = (next instanceof Parser ? next : next(result));
1553        pray('a parser is returned', nextParser instanceof Parser);
1554        return nextParser._(newStream, onSuccess, onFailure);
1555      }
1556    });
1557  };
1558
1559  // -*- optimized iterative combinators -*- //
1560  _.many = function() {
1561    var self = this;
1562
1563    return Parser(function(stream, onSuccess, onFailure) {
1564      var xs = [];
1565      while (self._(stream, success, failure));
1566      return onSuccess(stream, xs);
1567
1568      function success(newStream, x) {
1569        stream = newStream;
1570        xs.push(x);
1571        return true;
1572      }
1573
1574      function failure() {
1575        return false;
1576      }
1577    });
1578  };
1579
1580  _.times = function(min, max) {
1581    if (arguments.length < 2) max = min;
1582    var self = this;
1583
1584    return Parser(function(stream, onSuccess, onFailure) {
1585      var xs = [];
1586      var result = true;
1587      var failure;
1588
1589      for (var i = 0; i < min; i += 1) {
1590        result = self._(stream, success, firstFailure);
1591        if (!result) return onFailure(stream, failure);
1592      }
1593
1594      for (; i < max && result; i += 1) {
1595        result = self._(stream, success, secondFailure);
1596      }
1597
1598      return onSuccess(stream, xs);
1599
1600      function success(newStream, x) {
1601        xs.push(x);
1602        stream = newStream;
1603        return true;
1604      }
1605
1606      function firstFailure(newStream, msg) {
1607        failure = msg;
1608        stream = newStream;
1609        return false;
1610      }
1611
1612      function secondFailure(newStream, msg) {
1613        return false;
1614      }
1615    });
1616  };
1617
1618  // -*- higher-level combinators -*- //
1619  _.result = function(res) { return this.then(succeed(res)); };
1620  _.atMost = function(n) { return this.times(0, n); };
1621  _.atLeast = function(n) {
1622    var self = this;
1623    return self.times(n).then(function(start) {
1624      return self.many().map(function(end) {
1625        return start.concat(end);
1626      });
1627    });
1628  };
1629
1630  _.map = function(fn) {
1631    return this.then(function(result) { return succeed(fn(result)); });
1632  };
1633
1634  _.skip = function(two) {
1635    return this.then(function(result) { return two.result(result); });
1636  };
1637
1638  // -*- primitive parsers -*- //
1639  var string = this.string = function(str) {
1640    var len = str.length;
1641    var expected = "expected '"+str+"'";
1642
1643    return Parser(function(stream, onSuccess, onFailure) {
1644      var head = stream.slice(0, len);
1645
1646      if (head === str) {
1647        return onSuccess(stream.slice(len), head);
1648      }
1649      else {
1650        return onFailure(stream, expected);
1651      }
1652    });
1653  };
1654
1655  var regex = this.regex = function(re) {
1656    pray('regexp parser is anchored', re.toString().charAt(1) === '^');
1657
1658    var expected = 'expected '+re;
1659
1660    return Parser(function(stream, onSuccess, onFailure) {
1661      var match = re.exec(stream);
1662
1663      if (match) {
1664        var result = match[0];
1665        return onSuccess(stream.slice(result.length), result);
1666      }
1667      else {
1668        return onFailure(stream, expected);
1669      }
1670    });
1671  };
1672
1673  var succeed = Parser.succeed = function(result) {
1674    return Parser(function(stream, onSuccess) {
1675      return onSuccess(stream, result);
1676    });
1677  };
1678
1679  var fail = Parser.fail = function(msg) {
1680    return Parser(function(stream, _, onFailure) {
1681      return onFailure(stream, msg);
1682    });
1683  };
1684
1685  var letter = Parser.letter = regex(/^[a-z]/i);
1686  var letters = Parser.letters = regex(/^[a-z]*/i);
1687  var digit = Parser.digit = regex(/^[0-9]/);
1688  var digits = Parser.digits = regex(/^[0-9]*/);
1689  var whitespace = Parser.whitespace = regex(/^\s+/);
1690  var optWhitespace = Parser.optWhitespace = regex(/^\s*/);
1691
1692  var any = Parser.any = Parser(function(stream, onSuccess, onFailure) {
1693    if (!stream) return onFailure(stream, 'expected any character');
1694
1695    return onSuccess(stream.slice(1), stream.charAt(0));
1696  });
1697
1698  var all = Parser.all = Parser(function(stream, onSuccess, onFailure) {
1699    return onSuccess('', stream);
1700  });
1701
1702  var eof = Parser.eof = Parser(function(stream, onSuccess, onFailure) {
1703    if (stream) return onFailure(stream, 'expected EOF');
1704
1705    return onSuccess(stream, stream);
1706  });
1707});
1708Controller.open(function(_) {
1709  _.focusBlurEvents = function() {
1710    var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor;
1711    var blurTimeout;
1712    ctrlr.textarea.focus(function() {
1713      ctrlr.blurred = false;
1714      clearTimeout(blurTimeout);
1715      ctrlr.container.addClass('mq-focused');
1716      if (!cursor.parent)
1717        cursor.insAtRightEnd(root);
1718      if (cursor.selection) {
1719        cursor.selection.jQ.removeClass('mq-blur');
1720        ctrlr.selectionChanged(); //re-select textarea contents after tabbing away and back
1721      }
1722      else
1723        cursor.show();
1724    }).blur(function() {
1725      ctrlr.blurred = true;
1726      blurTimeout = setTimeout(function() { // wait for blur on window; if
1727        root.postOrder('intentionalBlur'); // none, intentional blur: #264
1728        cursor.clearSelection().endSelection();
1729        blur();
1730      });
1731      $(window).bind('blur', windowBlur);
1732    });
1733    function windowBlur() { // blur event also fired on window, just switching
1734      clearTimeout(blurTimeout); // tabs/windows, not intentional blur
1735      if (cursor.selection) cursor.selection.jQ.addClass('mq-blur');
1736      blur();
1737    }
1738    function blur() { // not directly in the textarea blur handler so as to be
1739      cursor.hide().parent.blur(); // synchronous with/in the same frame as
1740      ctrlr.container.removeClass('mq-focused'); // clearing/blurring selection
1741      $(window).unbind('blur', windowBlur);
1742    }
1743    ctrlr.blurred = true;
1744    cursor.hide().parent.blur();
1745  };
1746});
1747
1748/**
1749 * TODO: I wanted to move MathBlock::focus and blur here, it would clean
1750 * up lots of stuff like, TextBlock::focus is set to MathBlock::focus
1751 * and TextBlock::blur calls MathBlock::blur, when instead they could
1752 * use inheritance and super_.
1753 *
1754 * Problem is, there's lots of calls to .focus()/.blur() on nodes
1755 * outside Controller::focusBlurEvents(), such as .postOrder('blur') on
1756 * insertion, which if MathBlock::blur becomes Node::blur, would add the
1757 * 'blur' CSS class to all Symbol's (because .isEmpty() is true for all
1758 * of them).
1759 *
1760 * I'm not even sure there aren't other troublesome calls to .focus() or
1761 * .blur(), so this is TODO for now.
1762 */
1763/***********************************************
1764 * Export math in a human-readable text format
1765 * As you can see, only half-baked so far.
1766 **********************************************/
1767
1768Controller.open(function(_, super_) {
1769  _.exportText = function() {
1770    return this.root.foldChildren('', function(text, child) {
1771      return text + child.text();
1772    });
1773  };
1774});
1775/********************************************************
1776 * Deals with mouse events for clicking, drag-to-select
1777 *******************************************************/
1778
1779Controller.open(function(_) {
1780  Options.p.ignoreNextMousedown = noop;
1781  _.delegateMouseEvents = function() {
1782    var ultimateRootjQ = this.root.jQ;
1783    //drag-to-select event handling
1784    this.container.bind('mousedown.mathquill', function(e) {
1785      var rootjQ = $(e.target).closest('.mq-root-block');
1786      var root = Node.byId[rootjQ.attr(mqBlockId) || ultimateRootjQ.attr(mqBlockId)];
1787      var ctrlr = root.controller, cursor = ctrlr.cursor, blink = cursor.blink;
1788      var textareaSpan = ctrlr.textareaSpan, textarea = ctrlr.textarea;
1789
1790      e.preventDefault(); // doesn't work in IE\u22648, but it's a one-line fix:
1791      e.target.unselectable = true; // http://jsbin.com/yagekiji/1
1792
1793      if (cursor.options.ignoreNextMousedown(e)) return;
1794      else cursor.options.ignoreNextMousedown = noop;
1795
1796      var target;
1797      function mousemove(e) { target = $(e.target); }
1798      function docmousemove(e) {
1799        if (!cursor.anticursor) cursor.startSelection();
1800        ctrlr.seek(target, e.pageX, e.pageY).cursor.select();
1801        target = undefined;
1802      }
1803      // outside rootjQ, the MathQuill node corresponding to the target (if any)
1804      // won't be inside this root, so don't mislead Controller::seek with it
1805
1806      function mouseup(e) {
1807        cursor.blink = blink;
1808        if (!cursor.selection) {
1809          if (ctrlr.editable) {
1810            cursor.show();
1811          }
1812          else {
1813            textareaSpan.detach();
1814          }
1815        }
1816
1817        // delete the mouse handlers now that we're not dragging anymore
1818        rootjQ.unbind('mousemove', mousemove);
1819        $(e.target.ownerDocument).unbind('mousemove', docmousemove).unbind('mouseup', mouseup);
1820      }
1821
1822      if (ctrlr.blurred) {
1823        if (!ctrlr.editable) rootjQ.prepend(textareaSpan);
1824        textarea.focus();
1825      }
1826
1827      cursor.blink = noop;
1828      ctrlr.seek($(e.target), e.pageX, e.pageY).cursor.startSelection();
1829
1830      rootjQ.mousemove(mousemove);
1831      $(e.target.ownerDocument).mousemove(docmousemove).mouseup(mouseup);
1832      // listen on document not just body to not only hear about mousemove and
1833      // mouseup on page outside field, but even outside page, except iframes: https://github.com/mathquill/mathquill/commit/8c50028afcffcace655d8ae2049f6e02482346c5#commitcomment-6175800
1834    });
1835  }
1836});
1837
1838Controller.open(function(_) {
1839  _.seek = function(target, pageX, pageY) {
1840    var cursor = this.notify('select').cursor;
1841
1842    if (target) {
1843      var nodeId = target.attr(mqBlockId) || target.attr(mqCmdId);
1844      if (!nodeId) {
1845        var targetParent = target.parent();
1846        nodeId = targetParent.attr(mqBlockId) || targetParent.attr(mqCmdId);
1847      }
1848    }
1849    var node = nodeId ? Node.byId[nodeId] : this.root;
1850    pray('nodeId is the id of some Node that exists', node);
1851
1852    // don't clear selection until after getting node from target, in case
1853    // target was selection span, otherwise target will have no parent and will
1854    // seek from root, which is less accurate (e.g. fraction)
1855    cursor.clearSelection().show();
1856
1857    node.seek(pageX, cursor);
1858    this.scrollHoriz(); // before .selectFrom when mouse-selecting, so
1859                        // always hits no-selection case in scrollHoriz and scrolls slower
1860    return this;
1861  };
1862});
1863/*****************************************
1864 * Deals with the browser DOM events from
1865 * interaction with the typist.
1866 ****************************************/
1867
1868Controller.open(function(_) {
1869  _.keystroke = function(key, evt) {
1870    this.cursor.parent.keystroke(key, evt, this);
1871  };
1872});
1873
1874Node.open(function(_) {
1875  _.keystroke = function(key, e, ctrlr) {
1876    var cursor = ctrlr.cursor;
1877
1878    switch (key) {
1879    case 'Ctrl-Shift-Backspace':
1880    case 'Ctrl-Backspace':
1881      ctrlr.ctrlDeleteDir(L);
1882      break;
1883
1884    case 'Shift-Backspace':
1885    case 'Backspace':
1886      ctrlr.backspace();
1887      break;
1888
1889    // Tab or Esc -> go one block right if it exists, else escape right.
1890    case 'Esc':
1891    case 'Tab':
1892      ctrlr.escapeDir(R, key, e);
1893      return;
1894
1895    // Shift-Tab -> go one block left if it exists, else escape left.
1896    case 'Shift-Tab':
1897    case 'Shift-Esc':
1898      ctrlr.escapeDir(L, key, e);
1899      return;
1900
1901    // End -> move to the end of the current block.
1902    case 'End':
1903      ctrlr.notify('move').cursor.insAtRightEnd(cursor.parent);
1904      break;
1905
1906    // Ctrl-End -> move all the way to the end of the root block.
1907    case 'Ctrl-End':
1908      ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
1909      break;
1910
1911    // Shift-End -> select to the end of the current block.
1912    case 'Shift-End':
1913      while (cursor[R]) {
1914        ctrlr.selectRight();
1915      }
1916      break;
1917
1918    // Ctrl-Shift-End -> select to the end of the root block.
1919    case 'Ctrl-Shift-End':
1920      while (cursor[R] || cursor.parent !== ctrlr.root) {
1921        ctrlr.selectRight();
1922      }
1923      break;
1924
1925    // Home -> move to the start of the root block or the current block.
1926    case 'Home':
1927      ctrlr.notify('move').cursor.insAtLeftEnd(cursor.parent);
1928      break;
1929
1930    // Ctrl-Home -> move to the start of the current block.
1931    case 'Ctrl-Home':
1932      ctrlr.notify('move').cursor.insAtLeftEnd(ctrlr.root);
1933      break;
1934
1935    // Shift-Home -> select to the start of the current block.
1936    case 'Shift-Home':
1937      while (cursor[L]) {
1938        ctrlr.selectLeft();
1939      }
1940      break;
1941
1942    // Ctrl-Shift-Home -> move to the start of the root block.
1943    case 'Ctrl-Shift-Home':
1944      while (cursor[L] || cursor.parent !== ctrlr.root) {
1945        ctrlr.selectLeft();
1946      }
1947      break;
1948
1949    case 'Left': ctrlr.moveLeft(); break;
1950    case 'Shift-Left': ctrlr.selectLeft(); break;
1951    case 'Ctrl-Left': break;
1952
1953    case 'Right': ctrlr.moveRight(); break;
1954    case 'Shift-Right': ctrlr.selectRight(); break;
1955    case 'Ctrl-Right': break;
1956
1957    case 'Up': ctrlr.moveUp(); break;
1958    case 'Down': ctrlr.moveDown(); break;
1959
1960    case 'Shift-Up':
1961      if (cursor[L]) {
1962        while (cursor[L]) ctrlr.selectLeft();
1963      } else {
1964        ctrlr.selectLeft();
1965      }
1966
1967    case 'Shift-Down':
1968      if (cursor[R]) {
1969        while (cursor[R]) ctrlr.selectRight();
1970      }
1971      else {
1972        ctrlr.selectRight();
1973      }
1974
1975    case 'Ctrl-Up': break;
1976    case 'Ctrl-Down': break;
1977
1978    case 'Ctrl-Shift-Del':
1979    case 'Ctrl-Del':
1980      ctrlr.ctrlDeleteDir(R);
1981      break;
1982
1983    case 'Shift-Del':
1984    case 'Del':
1985      ctrlr.deleteForward();
1986      break;
1987
1988    case 'Meta-A':
1989    case 'Ctrl-A':
1990      ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
1991      while (cursor[L]) ctrlr.selectLeft();
1992      break;
1993
1994    default:
1995      return;
1996    }
1997    e.preventDefault();
1998    ctrlr.scrollHoriz();
1999  };
2000
2001  _.moveOutOf = // called by Controller::escapeDir, moveDir
2002  _.moveTowards = // called by Controller::moveDir
2003  _.deleteOutOf = // called by Controller::deleteDir
2004  _.deleteTowards = // called by Controller::deleteDir
2005  _.unselectInto = // called by Controller::selectDir
2006  _.selectOutOf = // called by Controller::selectDir
2007  _.selectTowards = // called by Controller::selectDir
2008    function() { pray('overridden or never called on this node'); };
2009});
2010
2011Controller.open(function(_) {
2012  this.onNotify(function(e) {
2013    if (e === 'move' || e === 'upDown') this.show().clearSelection();
2014  });
2015  _.escapeDir = function(dir, key, e) {
2016    prayDirection(dir);
2017    var cursor = this.cursor;
2018
2019    // only prevent default of Tab if not in the root editable
2020    if (cursor.parent !== this.root) e.preventDefault();
2021
2022    // want to be a noop if in the root editable (in fact, Tab has an unrelated
2023    // default browser action if so)
2024    if (cursor.parent === this.root) return;
2025
2026    cursor.parent.moveOutOf(dir, cursor);
2027    return this.notify('move');
2028  };
2029
2030  optionProcessors.leftRightIntoCmdGoes = function(updown) {
2031    if (updown && updown !== 'up' && updown !== 'down') {
2032      throw '"up" or "down" required for leftRightIntoCmdGoes option, '
2033            + 'got "'+updown+'"';
2034    }
2035    return updown;
2036  };
2037  _.moveDir = function(dir) {
2038    prayDirection(dir);
2039    var cursor = this.cursor, updown = cursor.options.leftRightIntoCmdGoes;
2040
2041    if (cursor.selection) {
2042      cursor.insDirOf(dir, cursor.selection.ends[dir]);
2043    }
2044    else if (cursor[dir]) cursor[dir].moveTowards(dir, cursor, updown);
2045    else cursor.parent.moveOutOf(dir, cursor, updown);
2046
2047    return this.notify('move');
2048  };
2049  _.moveLeft = function() { return this.moveDir(L); };
2050  _.moveRight = function() { return this.moveDir(R); };
2051
2052  /**
2053   * moveUp and moveDown have almost identical algorithms:
2054   * - first check left and right, if so insAtLeft/RightEnd of them
2055   * - else check the parent's 'upOutOf'/'downOutOf' property:
2056   *   + if it's a function, call it with the cursor as the sole argument and
2057   *     use the return value as if it were the value of the property
2058   *   + if it's a Node, jump up or down into it:
2059   *     - if there is a cached Point in the block, insert there
2060   *     - else, seekHoriz within the block to the current x-coordinate (to be
2061   *       as close to directly above/below the current position as possible)
2062   *   + unless it's exactly `true`, stop bubbling
2063   */
2064  _.moveUp = function() { return moveUpDown(this, 'up'); };
2065  _.moveDown = function() { return moveUpDown(this, 'down'); };
2066  function moveUpDown(self, dir) {
2067    var cursor = self.notify('upDown').cursor;
2068    var dirInto = dir+'Into', dirOutOf = dir+'OutOf';
2069    if (cursor[R][dirInto]) cursor.insAtLeftEnd(cursor[R][dirInto]);
2070    else if (cursor[L][dirInto]) cursor.insAtRightEnd(cursor[L][dirInto]);
2071    else {
2072      cursor.parent.bubble(function(ancestor) {
2073        var prop = ancestor[dirOutOf];
2074        if (prop) {
2075          if (typeof prop === 'function') prop = ancestor[dirOutOf](cursor);
2076          if (prop instanceof Node) cursor.jumpUpDown(ancestor, prop);
2077          if (prop !== true) return false;
2078        }
2079      });
2080    }
2081    return self;
2082  }
2083  this.onNotify(function(e) { if (e !== 'upDown') this.upDownCache = {}; });
2084
2085  this.onNotify(function(e) { if (e === 'edit') this.show().deleteSelection(); });
2086  _.deleteDir = function(dir) {
2087    prayDirection(dir);
2088    var cursor = this.cursor;
2089
2090    var hadSelection = cursor.selection;
2091    this.notify('edit'); // deletes selection if present
2092    if (!hadSelection) {
2093      if (cursor[dir]) cursor[dir].deleteTowards(dir, cursor);
2094      else cursor.parent.deleteOutOf(dir, cursor);
2095    }
2096
2097    if (cursor[L].siblingDeleted) cursor[L].siblingDeleted(cursor.options, R);
2098    if (cursor[R].siblingDeleted) cursor[R].siblingDeleted(cursor.options, L);
2099    cursor.parent.bubble('reflow');
2100
2101    return this;
2102  };
2103  _.ctrlDeleteDir = function(dir) {
2104    prayDirection(dir);
2105    var cursor = this.cursor;
2106    if (!cursor[dir] || cursor.selection) return this.deleteDir(dir);
2107
2108    this.notify('edit');
2109    if (dir === L) {
2110      Fragment(cursor.parent.ends[L], cursor[L]).remove();
2111    } else {
2112      Fragment(cursor[R], cursor.parent.ends[R]).remove();
2113    };
2114    cursor.insAtDirEnd(dir, cursor.parent);
2115
2116    if (cursor[L].siblingDeleted) cursor[L].siblingDeleted(cursor.options, R);
2117    if (cursor[R].siblingDeleted) cursor[R].siblingDeleted(cursor.options, L);
2118    cursor.parent.bubble('reflow');
2119
2120    return this;
2121  };
2122  _.backspace = function() { return this.deleteDir(L); };
2123  _.deleteForward = function() { return this.deleteDir(R); };
2124
2125  this.onNotify(function(e) { if (e !== 'select') this.endSelection(); });
2126  _.selectDir = function(dir) {
2127    var cursor = this.notify('select').cursor, seln = cursor.selection;
2128    prayDirection(dir);
2129
2130    if (!cursor.anticursor) cursor.startSelection();
2131
2132    var node = cursor[dir];
2133    if (node) {
2134      // "if node we're selecting towards is inside selection (hence retracting)
2135      // and is on the *far side* of the selection (hence is only node selected)
2136      // and the anticursor is *inside* that node, not just on the other side"
2137      if (seln && seln.ends[dir] === node && cursor.anticursor[-dir] !== node) {
2138        node.unselectInto(dir, cursor);
2139      }
2140      else node.selectTowards(dir, cursor);
2141    }
2142    else cursor.parent.selectOutOf(dir, cursor);
2143
2144    cursor.clearSelection();
2145    cursor.select() || cursor.show();
2146  };
2147  _.selectLeft = function() { return this.selectDir(L); };
2148  _.selectRight = function() { return this.selectDir(R); };
2149});
2150/*********************************************
2151 * Manage the MathQuill instance's textarea
2152 * (as owned by the Controller)
2153 ********************************************/
2154
2155Controller.open(function(_) {
2156  Options.p.substituteTextarea = function() {
2157    return $('<textarea autocapitalize=off autocomplete=off autocorrect=off ' +
2158               'spellcheck=false x-palm-disable-ste-all=true />')[0];
2159  };
2160  _.createTextarea = function() {
2161    var textareaSpan = this.textareaSpan = $('<span class="mq-textarea"></span>'),
2162      textarea = this.options.substituteTextarea();
2163    if (!textarea.nodeType) {
2164      throw 'substituteTextarea() must return a DOM element, got ' + textarea;
2165    }
2166    textarea = this.textarea = $(textarea).appendTo(textareaSpan);
2167
2168    var ctrlr = this;
2169    ctrlr.cursor.selectionChanged = function() { ctrlr.selectionChanged(); };
2170  };
2171  _.selectionChanged = function() {
2172    var ctrlr = this;
2173    forceIERedraw(ctrlr.container[0]);
2174
2175    // throttle calls to setTextareaSelection(), because setting textarea.value
2176    // and/or calling textarea.select() can have anomalously bad performance:
2177    // https://github.com/mathquill/mathquill/issues/43#issuecomment-1399080
2178    if (ctrlr.textareaSelectionTimeout === undefined) {
2179      ctrlr.textareaSelectionTimeout = setTimeout(function() {
2180        ctrlr.setTextareaSelection();
2181      });
2182    }
2183  };
2184  _.setTextareaSelection = function() {
2185    this.textareaSelectionTimeout = undefined;
2186    var latex = '';
2187    if (this.cursor.selection) {
2188      latex = this.cursor.selection.join('latex');
2189      if (this.options.statelessClipboard) {
2190        // FIXME: like paste, only this works for math fields; should ask parent
2191        latex = '$' + latex + '$';
2192      }
2193    }
2194    this.selectFn(latex);
2195  };
2196  _.staticMathTextareaEvents = function() {
2197    var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor,
2198      textarea = ctrlr.textarea, textareaSpan = ctrlr.textareaSpan;
2199
2200    this.container.prepend(jQuery('<span class="mq-selectable">')
2201      .text('$'+ctrlr.exportLatex()+'$'));
2202    ctrlr.blurred = true;
2203    textarea.bind('cut paste', false)
2204    .bind('copy', function() { ctrlr.setTextareaSelection(); })
2205    .focus(function() { ctrlr.blurred = false; }).blur(function() {
2206      if (cursor.selection) cursor.selection.clear();
2207      setTimeout(detach); //detaching during blur explodes in WebKit
2208    });
2209    function detach() {
2210      textareaSpan.detach();
2211      ctrlr.blurred = true;
2212    }
2213
2214    ctrlr.selectFn = function(text) {
2215      textarea.val(text);
2216      if (text) textarea.select();
2217    };
2218  };
2219  Options.p.substituteKeyboardEvents = saneKeyboardEvents;
2220  _.editablesTextareaEvents = function() {
2221    var ctrlr = this, textarea = ctrlr.textarea, textareaSpan = ctrlr.textareaSpan;
2222
2223    var keyboardEventsShim = this.options.substituteKeyboardEvents(textarea, this);
2224    this.selectFn = function(text) { keyboardEventsShim.select(text); };
2225    this.container.prepend(textareaSpan);
2226    this.focusBlurEvents();
2227  };
2228  _.typedText = function(ch) {
2229    if (ch === '\n') return this.handle('enter');
2230    var cursor = this.notify().cursor;
2231    cursor.parent.write(cursor, ch);
2232    this.scrollHoriz();
2233  };
2234  _.cut = function() {
2235    var ctrlr = this, cursor = ctrlr.cursor;
2236    if (cursor.selection) {
2237      setTimeout(function() {
2238        ctrlr.notify('edit'); // deletes selection if present
2239        cursor.parent.bubble('reflow');
2240      });
2241    }
2242  };
2243  _.copy = function() {
2244    this.setTextareaSelection();
2245  };
2246  _.paste = function(text) {
2247    // TODO: document `statelessClipboard` config option in README, after
2248    // making it work like it should, that is, in both text and math mode
2249    // (currently only works in math fields, so worse than pointless, it
2250    //  only gets in the way by \text{}-ifying pasted stuff and $-ifying
2251    //  cut/copied LaTeX)
2252    if (this.options.statelessClipboard) {
2253      if (text.slice(0,1) === '$' && text.slice(-1) === '$') {
2254        text = text.slice(1, -1);
2255      }
2256      else {
2257        text = '\\text{'+text+'}';
2258      }
2259    }
2260    // FIXME: this always inserts math or a TextBlock, even in a RootTextBlock
2261    this.writeLatex(text).cursor.show();
2262  };
2263});
2264// Parser MathBlock
2265var latexMathParser = (function() {
2266  function commandToBlock(cmd) { // can also take in a Fragment
2267    var block = MathBlock();
2268    cmd.adopt(block, 0, 0);
2269    return block;
2270  }
2271  function joinBlocks(blocks) {
2272    var firstBlock = blocks[0] || MathBlock();
2273
2274    for (var i = 1; i < blocks.length; i += 1) {
2275      blocks[i].children().adopt(firstBlock, firstBlock.ends[R], 0);
2276    }
2277
2278    return firstBlock;
2279  }
2280
2281  var string = Parser.string;
2282  var regex = Parser.regex;
2283  var letter = Parser.letter;
2284  var any = Parser.any;
2285  var optWhitespace = Parser.optWhitespace;
2286  var succeed = Parser.succeed;
2287  var fail = Parser.fail;
2288
2289  // Parsers yielding either MathCommands, or Fragments of MathCommands
2290  //   (either way, something that can be adopted by a MathBlock)
2291  var variable = letter.map(function(c) { return Letter(c); });
2292  var symbol = regex(/^[^${}\\_^]/).map(function(c) { return VanillaSymbol(c); });
2293
2294  var controlSequence =
2295    regex(/^[^\\a-eg-zA-Z]/) // hotfix #164; match MathBlock::write
2296    .or(string('\\').then(
2297      regex(/^[a-z]+/i)
2298      .or(regex(/^\s+/).result(' '))
2299      .or(any)
2300    )).then(function(ctrlSeq) {
2301      var cmdKlass = LatexCmds[ctrlSeq];
2302
2303      if (cmdKlass) {
2304        return cmdKlass(ctrlSeq).parser();
2305      }
2306      else {
2307        return fail('unknown command: \\'+ctrlSeq);
2308      }
2309    })
2310  ;
2311
2312  var command =
2313    controlSequence
2314    .or(variable)
2315    .or(symbol)
2316  ;
2317
2318  // Parsers yielding MathBlocks
2319  var mathGroup = string('{').then(function() { return mathSequence; }).skip(string('}'));
2320  var mathBlock = optWhitespace.then(mathGroup.or(command.map(commandToBlock)));
2321  var mathSequence = mathBlock.many().map(joinBlocks).skip(optWhitespace);
2322
2323  var optMathBlock =
2324    string('[').then(
2325      mathBlock.then(function(block) {
2326        return block.join('latex') !== ']' ? succeed(block) : fail();
2327      })
2328      .many().map(joinBlocks).skip(optWhitespace)
2329    ).skip(string(']'))
2330  ;
2331
2332  var latexMath = mathSequence;
2333
2334  latexMath.block = mathBlock;
2335  latexMath.optBlock = optMathBlock;
2336  return latexMath;
2337})();
2338
2339Controller.open(function(_, super_) {
2340  _.exportLatex = function() {
2341    return this.root.latex().replace(/(\\[a-z]+) (?![a-z])/ig,'$1');
2342  };
2343
2344  optionProcessors.maxDepth = function(depth) {
2345    return (typeof depth === 'number') ? depth : undefined;
2346  };
2347  _.writeLatex = function(latex) {
2348    var cursor = this.notify('edit').cursor;
2349
2350    var all = Parser.all;
2351    var eof = Parser.eof;
2352
2353    var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex);
2354
2355    if (block && !block.isEmpty() && block.prepareInsertionAt(cursor)) {
2356      block.children().adopt(cursor.parent, cursor[L], cursor[R]);
2357      var jQ = block.jQize();
2358      jQ.insertBefore(cursor.jQ);
2359      cursor[L] = block.ends[R];
2360      block.finalizeInsert(cursor.options, cursor);
2361      if (block.ends[R][R].siblingCreated) block.ends[R][R].siblingCreated(cursor.options, L);
2362      if (block.ends[L][L].siblingCreated) block.ends[L][L].siblingCreated(cursor.options, R);
2363      cursor.parent.bubble('reflow');
2364    }
2365
2366    return this;
2367  };
2368  _.renderLatexMath = function(latex) {
2369    var root = this.root;
2370    var cursor = this.cursor;
2371    var options = cursor.options;
2372    var jQ = root.jQ;
2373
2374    var all = Parser.all;
2375    var eof = Parser.eof;
2376
2377    var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex);
2378
2379    root.eachChild('postOrder', 'dispose');
2380    root.ends[L] = root.ends[R] = 0;
2381
2382    if (block && block.prepareInsertionAt(cursor)) {
2383      block.children().adopt(root, 0, 0);
2384      var html = block.join('html');
2385      jQ.html(html);
2386      root.jQize(jQ.children());
2387      root.finalizeInsert(cursor.options);
2388    }
2389    else {
2390      jQ.empty();
2391    }
2392
2393    delete cursor.selection;
2394    cursor.insAtRightEnd(root);
2395  };
2396  _.renderLatexText = function(latex) {
2397    var root = this.root, cursor = this.cursor;
2398
2399    root.jQ.children().slice(1).remove();
2400    root.eachChild('postOrder', 'dispose');
2401    root.ends[L] = root.ends[R] = 0;
2402    delete cursor.selection;
2403    cursor.show().insAtRightEnd(root);
2404
2405    var regex = Parser.regex;
2406    var string = Parser.string;
2407    var eof = Parser.eof;
2408    var all = Parser.all;
2409
2410    // Parser RootMathCommand
2411    var mathMode = string('$').then(latexMathParser)
2412      // because TeX is insane, math mode doesn't necessarily
2413      // have to end.  So we allow for the case that math mode
2414      // continues to the end of the stream.
2415      .skip(string('$').or(eof))
2416      .map(function(block) {
2417        // HACK FIXME: this shouldn't have to have access to cursor
2418        var rootMathCommand = RootMathCommand(cursor);
2419
2420        rootMathCommand.createBlocks();
2421        var rootMathBlock = rootMathCommand.ends[L];
2422        block.children().adopt(rootMathBlock, 0, 0);
2423
2424        return rootMathCommand;
2425      })
2426    ;
2427
2428    var escapedDollar = string('\\$').result('$');
2429    var textChar = escapedDollar.or(regex(/^[^$]/)).map(VanillaSymbol);
2430    var latexText = mathMode.or(textChar).many();
2431    var commands = latexText.skip(eof).or(all.result(false)).parse(latex);
2432
2433    if (commands) {
2434      for (var i = 0; i < commands.length; i += 1) {
2435        commands[i].adopt(root, root.ends[R], 0);
2436      }
2437
2438      root.jQize().appendTo(root.jQ);
2439
2440      root.finalizeInsert(cursor.options);
2441    }
2442  };
2443});
2444/***********************************************
2445 * Horizontal panning for editable fields that
2446 * overflow their width
2447 **********************************************/
2448
2449Controller.open(function(_) {
2450  _.scrollHoriz = function() {
2451    var cursor = this.cursor, seln = cursor.selection;
2452    var rootRect = this.root.jQ[0].getBoundingClientRect();
2453    if (!seln) {
2454      var x = cursor.jQ[0].getBoundingClientRect().left;
2455      if (x > rootRect.right - 20) var scrollBy = x - (rootRect.right - 20);
2456      else if (x < rootRect.left + 20) var scrollBy = x - (rootRect.left + 20);
2457      else return;
2458    }
2459    else {
2460      var rect = seln.jQ[0].getBoundingClientRect();
2461      var overLeft = rect.left - (rootRect.left + 20);
2462      var overRight = rect.right - (rootRect.right - 20);
2463      if (seln.ends[L] === cursor[R]) {
2464        if (overLeft < 0) var scrollBy = overLeft;
2465        else if (overRight > 0) {
2466          if (rect.left - overRight < rootRect.left + 20) var scrollBy = overLeft;
2467          else var scrollBy = overRight;
2468        }
2469        else return;
2470      }
2471      else {
2472        if (overRight > 0) var scrollBy = overRight;
2473        else if (overLeft < 0) {
2474          if (rect.right - overLeft > rootRect.right - 20) var scrollBy = overRight;
2475          else var scrollBy = overLeft;
2476        }
2477        else return;
2478      }
2479    }
2480    this.root.jQ.stop().animate({ scrollLeft: '+=' + scrollBy}, 100);
2481  };
2482});
2483/*************************************************
2484 * Abstract classes of math blocks and commands.
2485 ************************************************/
2486
2487/**
2488 * Math tree node base class.
2489 * Some math-tree-specific extensions to Node.
2490 * Both MathBlock's and MathCommand's descend from it.
2491 */
2492var MathElement = P(Node, function(_, super_) {
2493  _.finalizeInsert = function(options, cursor) { // `cursor` param is only for
2494      // SupSub::contactWeld, and is deliberately only passed in by writeLatex,
2495      // see ea7307eb4fac77c149a11ffdf9a831df85247693
2496    var self = this;
2497    self.postOrder('finalizeTree', options);
2498    self.postOrder('contactWeld', cursor);
2499
2500    // note: this order is important.
2501    // empty elements need the empty box provided by blur to
2502    // be present in order for their dimensions to be measured
2503    // correctly by 'reflow' handlers.
2504    self.postOrder('blur');
2505
2506    self.postOrder('reflow');
2507    if (self[R].siblingCreated) self[R].siblingCreated(options, L);
2508    if (self[L].siblingCreated) self[L].siblingCreated(options, R);
2509    self.bubble('reflow');
2510  };
2511  // If the maxDepth option is set, make sure
2512  // deeply nested content is truncated. Just return
2513  // false if the cursor is already too deep.
2514  _.prepareInsertionAt = function(cursor) {
2515    var maxDepth = cursor.options.maxDepth;
2516    if (maxDepth !== undefined) {
2517      var cursorDepth = cursor.depth();
2518      if (cursorDepth > maxDepth) {
2519        return false;
2520      }
2521      this.removeNodesDeeperThan(maxDepth-cursorDepth);
2522    }
2523    return true;
2524  };
2525  // Remove nodes that are more than `cutoff`
2526  // blocks deep from this node.
2527  _.removeNodesDeeperThan = function (cutoff) {
2528    var depth = 0;
2529    var queue = [[this, depth]];
2530    var current;
2531
2532    // Do a breadth-first search of this node's descendants
2533    // down to cutoff, removing anything deeper.
2534    while (queue.length) {
2535      current = queue.shift();
2536      current[0].children().each(function (child) {
2537        var i = (child instanceof MathBlock) ? 1 : 0;
2538        depth = current[1]+i;
2539
2540        if (depth <= cutoff) {
2541          queue.push([child, depth]);
2542        } else {
2543          (i ? child.children() : child).remove();
2544        }
2545      });
2546    }
2547  };
2548});
2549
2550/**
2551 * Commands and operators, like subscripts, exponents, or fractions.
2552 * Descendant commands are organized into blocks.
2553 */
2554var MathCommand = P(MathElement, function(_, super_) {
2555  _.init = function(ctrlSeq, htmlTemplate, textTemplate) {
2556    var cmd = this;
2557    super_.init.call(cmd);
2558
2559    if (!cmd.ctrlSeq) cmd.ctrlSeq = ctrlSeq;
2560    if (htmlTemplate) cmd.htmlTemplate = htmlTemplate;
2561    if (textTemplate) cmd.textTemplate = textTemplate;
2562  };
2563
2564  // obvious methods
2565  _.replaces = function(replacedFragment) {
2566    replacedFragment.disown();
2567    this.replacedFragment = replacedFragment;
2568  };
2569  _.isEmpty = function() {
2570    return this.foldChildren(true, function(isEmpty, child) {
2571      return isEmpty && child.isEmpty();
2572    });
2573  };
2574
2575  _.parser = function() {
2576    var block = latexMathParser.block;
2577    var self = this;
2578
2579    return block.times(self.numBlocks()).map(function(blocks) {
2580      self.blocks = blocks;
2581
2582      for (var i = 0; i < blocks.length; i += 1) {
2583        blocks[i].adopt(self, self.ends[R], 0);
2584      }
2585
2586      return self;
2587    });
2588  };
2589
2590  // createLeftOf(cursor) and the methods it calls
2591  _.createLeftOf = function(cursor) {
2592    var cmd = this;
2593    var replacedFragment = cmd.replacedFragment;
2594
2595    cmd.createBlocks();
2596    super_.createLeftOf.call(cmd, cursor);
2597    if (replacedFragment) {
2598      replacedFragment.adopt(cmd.ends[L], 0, 0);
2599      replacedFragment.jQ.appendTo(cmd.ends[L].jQ);
2600      cmd.placeCursor(cursor);
2601      cmd.prepareInsertionAt(cursor);
2602    }
2603    cmd.finalizeInsert(cursor.options);
2604    cmd.placeCursor(cursor);
2605  };
2606  _.createBlocks = function() {
2607    var cmd = this,
2608      numBlocks = cmd.numBlocks(),
2609      blocks = cmd.blocks = Array(numBlocks);
2610
2611    for (var i = 0; i < numBlocks; i += 1) {
2612      var newBlock = blocks[i] = MathBlock();
2613      newBlock.adopt(cmd, cmd.ends[R], 0);
2614    }
2615  };
2616  _.placeCursor = function(cursor) {
2617    //insert the cursor at the right end of the first empty child, searching
2618    //left-to-right, or if none empty, the right end child
2619    cursor.insAtRightEnd(this.foldChildren(this.ends[L], function(leftward, child) {
2620      return leftward.isEmpty() ? leftward : child;
2621    }));
2622  };
2623
2624  // editability methods: called by the cursor for editing, cursor movements,
2625  // and selection of the MathQuill tree, these all take in a direction and
2626  // the cursor
2627  _.moveTowards = function(dir, cursor, updown) {
2628    var updownInto = updown && this[updown+'Into'];
2629    cursor.insAtDirEnd(-dir, updownInto || this.ends[-dir]);
2630  };
2631  _.deleteTowards = function(dir, cursor) {
2632    if (this.isEmpty()) cursor[dir] = this.remove()[dir];
2633    else this.moveTowards(dir, cursor, null);
2634  };
2635  _.selectTowards = function(dir, cursor) {
2636    cursor[-dir] = this;
2637    cursor[dir] = this[dir];
2638  };
2639  _.selectChildren = function() {
2640    return Selection(this, this);
2641  };
2642  _.unselectInto = function(dir, cursor) {
2643    cursor.insAtDirEnd(-dir, cursor.anticursor.ancestors[this.id]);
2644  };
2645  _.seek = function(pageX, cursor) {
2646    function getBounds(node) {
2647      var bounds = {}
2648      bounds[L] = node.jQ.offset().left;
2649      bounds[R] = bounds[L] + node.jQ.outerWidth();
2650      return bounds;
2651    }
2652
2653    var cmd = this;
2654    var cmdBounds = getBounds(cmd);
2655
2656    if (pageX < cmdBounds[L]) return cursor.insLeftOf(cmd);
2657    if (pageX > cmdBounds[R]) return cursor.insRightOf(cmd);
2658
2659    var leftLeftBound = cmdBounds[L];
2660    cmd.eachChild(function(block) {
2661      var blockBounds = getBounds(block);
2662      if (pageX < blockBounds[L]) {
2663        // closer to this block's left bound, or the bound left of that?
2664        if (pageX - leftLeftBound < blockBounds[L] - pageX) {
2665          if (block[L]) cursor.insAtRightEnd(block[L]);
2666          else cursor.insLeftOf(cmd);
2667        }
2668        else cursor.insAtLeftEnd(block);
2669        return false;
2670      }
2671      else if (pageX > blockBounds[R]) {
2672        if (block[R]) leftLeftBound = blockBounds[R]; // continue to next block
2673        else { // last (rightmost) block
2674          // closer to this block's right bound, or the cmd's right bound?
2675          if (cmdBounds[R] - pageX < pageX - blockBounds[R]) {
2676            cursor.insRightOf(cmd);
2677          }
2678          else cursor.insAtRightEnd(block);
2679        }
2680      }
2681      else {
2682        block.seek(pageX, cursor);
2683        return false;
2684      }
2685    });
2686  }
2687
2688  // methods involved in creating and cross-linking with HTML DOM nodes
2689  /*
2690    They all expect an .htmlTemplate like
2691      '<span>&0</span>'
2692    or
2693      '<span><span>&0</span><span>&1</span></span>'
2694
2695    See html.test.js for more examples.
2696
2697    Requirements:
2698    - For each block of the command, there must be exactly one "block content
2699      marker" of the form '&<number>' where <number> is the 0-based index of the
2700      block. (Like the LaTeX \newcommand syntax, but with a 0-based rather than
2701      1-based index, because JavaScript because C because Dijkstra.)
2702    - The block content marker must be the sole contents of the containing
2703      element, there can't even be surrounding whitespace, or else we can't
2704      guarantee sticking to within the bounds of the block content marker when
2705      mucking with the HTML DOM.
2706    - The HTML not only must be well-formed HTML (of course), but also must
2707      conform to the XHTML requirements on tags, specifically all tags must
2708      either be self-closing (like '<br/>') or come in matching pairs.
2709      Close tags are never optional.
2710
2711    Note that &<number> isn't well-formed HTML; if you wanted a literal '&123',
2712    your HTML template would have to have '&amp;123'.
2713  */
2714  _.numBlocks = function() {
2715    var matches = this.htmlTemplate.match(/&\d+/g);
2716    return matches ? matches.length : 0;
2717  };
2718  _.html = function() {
2719    // Render the entire math subtree rooted at this command, as HTML.
2720    // Expects .createBlocks() to have been called already, since it uses the
2721    // .blocks array of child blocks.
2722    //
2723    // See html.test.js for example templates and intended outputs.
2724    //
2725    // Given an .htmlTemplate as described above,
2726    // - insert the mathquill-command-id attribute into all top-level tags,
2727    //   which will be used to set this.jQ in .jQize().
2728    //   This is straightforward:
2729    //     * tokenize into tags and non-tags
2730    //     * loop through top-level tokens:
2731    //         * add #cmdId attribute macro to top-level self-closing tags
2732    //         * else add #cmdId attribute macro to top-level open tags
2733    //             * skip the matching top-level close tag and all tag pairs
2734    //               in between
2735    // - for each block content marker,
2736    //     + replace it with the contents of the corresponding block,
2737    //       rendered as HTML
2738    //     + insert the mathquill-block-id attribute into the containing tag
2739    //   This is even easier, a quick regex replace, since block tags cannot
2740    //   contain anything besides the block content marker.
2741    //
2742    // Two notes:
2743    // - The outermost loop through top-level tokens should never encounter any
2744    //   top-level close tags, because we should have first encountered a
2745    //   matching top-level open tag, all inner tags should have appeared in
2746    //   matching pairs and been skipped, and then we should have skipped the
2747    //   close tag in question.
2748    // - All open tags should have matching close tags, which means our inner
2749    //   loop should always encounter a close tag and drop nesting to 0. If
2750    //   a close tag is missing, the loop will continue until i >= tokens.length
2751    //   and token becomes undefined. This will not infinite loop, even in
2752    //   production without pray(), because it will then TypeError on .slice().
2753
2754    var cmd = this;
2755    var blocks = cmd.blocks;
2756    var cmdId = ' mathquill-command-id=' + cmd.id;
2757    var tokens = cmd.htmlTemplate.match(/<[^<>]+>|[^<>]+/g);
2758
2759    pray('no unmatched angle brackets', tokens.join('') === this.htmlTemplate);
2760
2761    // add cmdId to all top-level tags
2762    for (var i = 0, token = tokens[0]; token; i += 1, token = tokens[i]) {
2763      // top-level self-closing tags
2764      if (token.slice(-2) === '/>') {
2765        tokens[i] = token.slice(0,-2) + cmdId + '/>';
2766      }
2767      // top-level open tags
2768      else if (token.charAt(0) === '<') {
2769        pray('not an unmatched top-level close tag', token.charAt(1) !== '/');
2770
2771        tokens[i] = token.slice(0,-1) + cmdId + '>';
2772
2773        // skip matching top-level close tag and all tag pairs in between
2774        var nesting = 1;
2775        do {
2776          i += 1, token = tokens[i];
2777          pray('no missing close tags', token);
2778          // close tags
2779          if (token.slice(0,2) === '</') {
2780            nesting -= 1;
2781          }
2782          // non-self-closing open tags
2783          else if (token.charAt(0) === '<' && token.slice(-2) !== '/>') {
2784            nesting += 1;
2785          }
2786        } while (nesting > 0);
2787      }
2788    }
2789    return tokens.join('').replace(/>&(\d+)/g, function($0, $1) {
2790      return ' mathquill-block-id=' + blocks[$1].id + '>' + blocks[$1].join('html');
2791    });
2792  };
2793
2794  // methods to export a string representation of the math tree
2795  _.latex = function() {
2796    return this.foldChildren(this.ctrlSeq, function(latex, child) {
2797      return latex + '{' + (child.latex() || ' ') + '}';
2798    });
2799  };
2800  _.textTemplate = [''];
2801  _.text = function() {
2802    var cmd = this, i = 0;
2803    return cmd.foldChildren(cmd.textTemplate[i], function(text, child) {
2804      i += 1;
2805      var child_text = child.text();
2806      if (text && cmd.textTemplate[i] === '('
2807          && child_text[0] === '(' && child_text.slice(-1) === ')')
2808        return text + child_text.slice(1, -1) + cmd.textTemplate[i];
2809      return text + child_text + (cmd.textTemplate[i] || '');
2810    });
2811  };
2812});
2813
2814/**
2815 * Lightweight command without blocks or children.
2816 */
2817var Symbol = P(MathCommand, function(_, super_) {
2818  _.init = function(ctrlSeq, html, text) {
2819    if (!text) text = ctrlSeq && ctrlSeq.length > 1 ? ctrlSeq.slice(1) : ctrlSeq;
2820
2821    super_.init.call(this, ctrlSeq, html, [ text ]);
2822  };
2823
2824  _.parser = function() { return Parser.succeed(this); };
2825  _.numBlocks = function() { return 0; };
2826
2827  _.replaces = function(replacedFragment) {
2828    replacedFragment.remove();
2829  };
2830  _.createBlocks = noop;
2831
2832  _.moveTowards = function(dir, cursor) {
2833    cursor.jQ.insDirOf(dir, this.jQ);
2834    cursor[-dir] = this;
2835    cursor[dir] = this[dir];
2836  };
2837  _.deleteTowards = function(dir, cursor) {
2838    cursor[dir] = this.remove()[dir];
2839  };
2840  _.seek = function(pageX, cursor) {
2841    // insert at whichever side the click was closer to
2842    if (pageX - this.jQ.offset().left < this.jQ.outerWidth()/2)
2843      cursor.insLeftOf(this);
2844    else
2845      cursor.insRightOf(this);
2846  };
2847
2848  _.latex = function(){ return this.ctrlSeq; };
2849  _.text = function(){ return this.textTemplate; };
2850  _.placeCursor = noop;
2851  _.isEmpty = function(){ return true; };
2852});
2853var VanillaSymbol = P(Symbol, function(_, super_) {
2854  _.init = function(ch, html) {
2855    super_.init.call(this, ch, '<span>'+(html || ch)+'</span>');
2856  };
2857});
2858var BinaryOperator = P(Symbol, function(_, super_) {
2859  _.init = function(ctrlSeq, html, text) {
2860    super_.init.call(this,
2861      ctrlSeq, '<span class="mq-binary-operator">'+html+'</span>', text
2862    );
2863  };
2864});
2865
2866/**
2867 * Children and parent of MathCommand's. Basically partitions all the
2868 * symbols and operators that descend (in the Math DOM tree) from
2869 * ancestor operators.
2870 */
2871var MathBlock = P(MathElement, function(_, super_) {
2872  _.join = function(methodName) {
2873    return this.foldChildren('', function(fold, child) {
2874      return fold + child[methodName]();
2875    });
2876  };
2877  _.html = function() { return this.join('html'); };
2878  _.latex = function() { return this.join('latex'); };
2879  _.text = function() {
2880    return (this.ends[L] === this.ends[R] && this.ends[L] !== 0) ?
2881      this.ends[L].text() :
2882      this.join('text')
2883    ;
2884  };
2885
2886  _.keystroke = function(key, e, ctrlr) {
2887    if (ctrlr.options.spaceBehavesLikeTab
2888        && (key === 'Spacebar' || key === 'Shift-Spacebar')) {
2889      e.preventDefault();
2890      ctrlr.escapeDir(key === 'Shift-Spacebar' ? L : R, key, e);
2891      return;
2892    }
2893    return super_.keystroke.apply(this, arguments);
2894  };
2895
2896  // editability methods: called by the cursor for editing, cursor movements,
2897  // and selection of the MathQuill tree, these all take in a direction and
2898  // the cursor
2899  _.moveOutOf = function(dir, cursor, updown) {
2900    var updownInto = updown && this.parent[updown+'Into'];
2901    if (!updownInto && this[dir]) cursor.insAtDirEnd(-dir, this[dir]);
2902    else cursor.insDirOf(dir, this.parent);
2903  };
2904  _.selectOutOf = function(dir, cursor) {
2905    cursor.insDirOf(dir, this.parent);
2906  };
2907  _.deleteOutOf = function(dir, cursor) {
2908    cursor.unwrapGramp();
2909  };
2910  _.seek = function(pageX, cursor) {
2911    var node = this.ends[R];
2912    if (!node || node.jQ.offset().left + node.jQ.outerWidth() < pageX) {
2913      return cursor.insAtRightEnd(this);
2914    }
2915    if (pageX < this.ends[L].jQ.offset().left) return cursor.insAtLeftEnd(this);
2916    while (pageX < node.jQ.offset().left) node = node[L];
2917    return node.seek(pageX, cursor);
2918  };
2919  _.chToCmd = function(ch, options) {
2920    var cons;
2921    // exclude f because it gets a dedicated command with more spacing
2922    if (ch.match(/^[a-eg-zA-Z]$/))
2923      return Letter(ch);
2924    else if (/^\d$/.test(ch))
2925      return Digit(ch);
2926    else if (options && options.typingSlashWritesDivisionSymbol && ch === '/')
2927      return LatexCmds['\u00f7'](ch);
2928    else if (options && options.typingAsteriskWritesTimesSymbol && ch === '*')
2929      return LatexCmds['\u00d7'](ch);
2930    else if (cons = CharCmds[ch] || LatexCmds[ch])
2931      return cons(ch);
2932    else
2933      return VanillaSymbol(ch);
2934  };
2935  _.write = function(cursor, ch) {
2936    var cmd = this.chToCmd(ch, cursor.options);
2937    if (cursor.selection) cmd.replaces(cursor.replaceSelection());
2938    if (!cursor.isTooDeep()) {
2939      cmd.createLeftOf(cursor.show());
2940    }
2941  };
2942
2943  _.focus = function() {
2944    this.jQ.addClass('mq-hasCursor');
2945    this.jQ.removeClass('mq-empty');
2946
2947    return this;
2948  };
2949  _.blur = function() {
2950    this.jQ.removeClass('mq-hasCursor');
2951    if (this.isEmpty())
2952      this.jQ.addClass('mq-empty');
2953
2954    return this;
2955  };
2956});
2957
2958Options.p.mouseEvents = true;
2959API.StaticMath = function(APIClasses) {
2960  return P(APIClasses.AbstractMathQuill, function(_, super_) {
2961    this.RootBlock = MathBlock;
2962    _.__mathquillify = function(opts, interfaceVersion) {
2963      this.config(opts);
2964      super_.__mathquillify.call(this, 'mq-math-mode');
2965      if (this.__options.mouseEvents) {
2966        this.__controller.delegateMouseEvents();
2967        this.__controller.staticMathTextareaEvents();
2968      }
2969      return this;
2970    };
2971    _.init = function() {
2972      super_.init.apply(this, arguments);
2973      this.__controller.root.postOrder(
2974        'registerInnerField', this.innerFields = [], APIClasses.MathField);
2975    };
2976    _.latex = function() {
2977      var returned = super_.latex.apply(this, arguments);
2978      if (arguments.length > 0) {
2979        this.__controller.root.postOrder(
2980          'registerInnerField', this.innerFields = [], APIClasses.MathField);
2981      }
2982      return returned;
2983    };
2984  });
2985};
2986
2987var RootMathBlock = P(MathBlock, RootBlockMixin);
2988API.MathField = function(APIClasses) {
2989  return P(APIClasses.EditableField, function(_, super_) {
2990    this.RootBlock = RootMathBlock;
2991    _.__mathquillify = function(opts, interfaceVersion) {
2992      this.config(opts);
2993      if (interfaceVersion > 1) this.__controller.root.reflow = noop;
2994      super_.__mathquillify.call(this, 'mq-editable-field mq-math-mode');
2995      delete this.__controller.root.reflow;
2996      return this;
2997    };
2998  });
2999};
3000/*************************************************
3001 * Abstract classes of text blocks
3002 ************************************************/
3003
3004/**
3005 * Blocks of plain text, with one or two TextPiece's as children.
3006 * Represents flat strings of typically serif-font Roman characters, as
3007 * opposed to hierchical, nested, tree-structured math.
3008 * Wraps a single HTMLSpanElement.
3009 */
3010var TextBlock = P(Node, function(_, super_) {
3011  _.ctrlSeq = '\\text';
3012
3013  _.replaces = function(replacedText) {
3014    if (replacedText instanceof Fragment)
3015      this.replacedText = replacedText.remove().jQ.text();
3016    else if (typeof replacedText === 'string')
3017      this.replacedText = replacedText;
3018  };
3019
3020  _.jQadd = function(jQ) {
3021    super_.jQadd.call(this, jQ);
3022    if (this.ends[L]) this.ends[L].jQadd(this.jQ[0].firstChild);
3023  };
3024
3025  _.createLeftOf = function(cursor) {
3026    var textBlock = this;
3027    super_.createLeftOf.call(this, cursor);
3028
3029    cursor.insAtRightEnd(textBlock);
3030
3031    if (textBlock.replacedText)
3032      for (var i = 0; i < textBlock.replacedText.length; i += 1)
3033        textBlock.write(cursor, textBlock.replacedText.charAt(i));
3034
3035    if (textBlock[R].siblingCreated) textBlock[R].siblingCreated(cursor.options, L);
3036    if (textBlock[L].siblingCreated) textBlock[L].siblingCreated(cursor.options, R);
3037    textBlock.bubble('reflow');
3038  };
3039
3040  _.parser = function() {
3041    var textBlock = this;
3042
3043    // TODO: correctly parse text mode
3044    var string = Parser.string;
3045    var regex = Parser.regex;
3046    var optWhitespace = Parser.optWhitespace;
3047    return optWhitespace
3048      .then(string('{')).then(regex(/^[^}]*/)).skip(string('}'))
3049      .map(function(text) {
3050        if (text.length === 0) return Fragment();
3051
3052        TextPiece(text).adopt(textBlock, 0, 0);
3053        return textBlock;
3054      })
3055    ;
3056  };
3057
3058  _.textContents = function() {
3059    return this.foldChildren('', function(text, child) {
3060      return text + child.text;
3061    });
3062  };
3063  _.text = function() { return '"' + this.textContents() + '"'; };
3064  _.latex = function() {
3065    var contents = this.textContents();
3066    if (contents.length === 0) return '';
3067    return '\\text{' + contents.replace(/\\/g, '\\backslash ').replace(/[{}]/g, '\\$&') + '}';
3068  };
3069  _.html = function() {
3070    return (
3071        '<span class="mq-text-mode" mathquill-command-id='+this.id+'>'
3072      +   this.textContents()
3073      + '</span>'
3074    );
3075  };
3076
3077  // editability methods: called by the cursor for editing, cursor movements,
3078  // and selection of the MathQuill tree, these all take in a direction and
3079  // the cursor
3080  _.moveTowards = function(dir, cursor) { cursor.insAtDirEnd(-dir, this); };
3081  _.moveOutOf = function(dir, cursor) { cursor.insDirOf(dir, this); };
3082  _.unselectInto = _.moveTowards;
3083
3084  // TODO: make these methods part of a shared mixin or something.
3085  _.selectTowards = MathCommand.prototype.selectTowards;
3086  _.deleteTowards = MathCommand.prototype.deleteTowards;
3087
3088  _.selectOutOf = function(dir, cursor) {
3089    cursor.insDirOf(dir, this);
3090  };
3091  _.deleteOutOf = function(dir, cursor) {
3092    // backspace and delete at ends of block don't unwrap
3093    if (this.isEmpty()) cursor.insRightOf(this);
3094  };
3095  _.write = function(cursor, ch) {
3096    cursor.show().deleteSelection();
3097
3098    if (ch !== '$') {
3099      if (!cursor[L]) TextPiece(ch).createLeftOf(cursor);
3100      else cursor[L].appendText(ch);
3101    }
3102    else if (this.isEmpty()) {
3103      cursor.insRightOf(this);
3104      VanillaSymbol('\\$','$').createLeftOf(cursor);
3105    }
3106    else if (!cursor[R]) cursor.insRightOf(this);
3107    else if (!cursor[L]) cursor.insLeftOf(this);
3108    else { // split apart
3109      var leftBlock = TextBlock();
3110      var leftPc = this.ends[L];
3111      leftPc.disown().jQ.detach();
3112      leftPc.adopt(leftBlock, 0, 0);
3113
3114      cursor.insLeftOf(this);
3115      super_.createLeftOf.call(leftBlock, cursor); // micro-optimization, not for correctness
3116    }
3117    this.bubble('reflow');
3118  };
3119
3120  _.seek = function(pageX, cursor) {
3121    cursor.hide();
3122    var textPc = fuseChildren(this);
3123
3124    // insert cursor at approx position in DOMTextNode
3125    var avgChWidth = this.jQ.width()/this.text.length;
3126    var approxPosition = Math.round((pageX - this.jQ.offset().left)/avgChWidth);
3127    if (approxPosition <= 0) cursor.insAtLeftEnd(this);
3128    else if (approxPosition >= textPc.text.length) cursor.insAtRightEnd(this);
3129    else cursor.insLeftOf(textPc.splitRight(approxPosition));
3130
3131    // move towards mousedown (pageX)
3132    var displ = pageX - cursor.show().offset().left; // displacement
3133    var dir = displ && displ < 0 ? L : R;
3134    var prevDispl = dir;
3135    // displ * prevDispl > 0 iff displacement direction === previous direction
3136    while (cursor[dir] && displ * prevDispl > 0) {
3137      cursor[dir].moveTowards(dir, cursor);
3138      prevDispl = displ;
3139      displ = pageX - cursor.offset().left;
3140    }
3141    if (dir*displ < -dir*prevDispl) cursor[-dir].moveTowards(-dir, cursor);
3142
3143    if (!cursor.anticursor) {
3144      // about to start mouse-selecting, the anticursor is gonna get put here
3145      this.anticursorPosition = cursor[L] && cursor[L].text.length;
3146      // ^ get it? 'cos if there's no cursor[L], it's 0... I'm a terrible person.
3147    }
3148    else if (cursor.anticursor.parent === this) {
3149      // mouse-selecting within this TextBlock, re-insert the anticursor
3150      var cursorPosition = cursor[L] && cursor[L].text.length;;
3151      if (this.anticursorPosition === cursorPosition) {
3152        cursor.anticursor = Point.copy(cursor);
3153      }
3154      else {
3155        if (this.anticursorPosition < cursorPosition) {
3156          var newTextPc = cursor[L].splitRight(this.anticursorPosition);
3157          cursor[L] = newTextPc;
3158        }
3159        else {
3160          var newTextPc = cursor[R].splitRight(this.anticursorPosition - cursorPosition);
3161        }
3162        cursor.anticursor = Point(this, newTextPc[L], newTextPc);
3163      }
3164    }
3165  };
3166
3167  _.blur = function(cursor) {
3168    MathBlock.prototype.blur.call(this);
3169    if (!cursor) return;
3170    if (this.textContents() === '') {
3171      this.remove();
3172      if (cursor[L] === this) cursor[L] = this[L];
3173      else if (cursor[R] === this) cursor[R] = this[R];
3174    }
3175    else fuseChildren(this);
3176  };
3177
3178  function fuseChildren(self) {
3179    self.jQ[0].normalize();
3180
3181    var textPcDom = self.jQ[0].firstChild;
3182    if (!textPcDom) return;
3183    pray('only node in TextBlock span is Text node', textPcDom.nodeType === 3);
3184    // nodeType === 3 has meant a Text node since ancient times:
3185    //   http://reference.sitepoint.com/javascript/Node/nodeType
3186
3187    var textPc = TextPiece(textPcDom.data);
3188    textPc.jQadd(textPcDom);
3189
3190    self.children().disown();
3191    return textPc.adopt(self, 0, 0);
3192  }
3193
3194  _.focus = MathBlock.prototype.focus;
3195});
3196
3197/**
3198 * Piece of plain text, with a TextBlock as a parent and no children.
3199 * Wraps a single DOMTextNode.
3200 * For convenience, has a .text property that's just a JavaScript string
3201 * mirroring the text contents of the DOMTextNode.
3202 * Text contents must always be nonempty.
3203 */
3204var TextPiece = P(Node, function(_, super_) {
3205  _.init = function(text) {
3206    super_.init.call(this);
3207    this.text = text;
3208  };
3209  _.jQadd = function(dom) { this.dom = dom; this.jQ = $(dom); };
3210  _.jQize = function() {
3211    return this.jQadd(document.createTextNode(this.text));
3212  };
3213  _.appendText = function(text) {
3214    this.text += text;
3215    this.dom.appendData(text);
3216  };
3217  _.prependText = function(text) {
3218    this.text = text + this.text;
3219    this.dom.insertData(0, text);
3220  };
3221  _.insTextAtDirEnd = function(text, dir) {
3222    prayDirection(dir);
3223    if (dir === R) this.appendText(text);
3224    else this.prependText(text);
3225  };
3226  _.splitRight = function(i) {
3227    var newPc = TextPiece(this.text.slice(i)).adopt(this.parent, this, this[R]);
3228    newPc.jQadd(this.dom.splitText(i));
3229    this.text = this.text.slice(0, i);
3230    return newPc;
3231  };
3232
3233  function endChar(dir, text) {
3234    return text.charAt(dir === L ? 0 : -1 + text.length);
3235  }
3236
3237  _.moveTowards = function(dir, cursor) {
3238    prayDirection(dir);
3239
3240    var ch = endChar(-dir, this.text)
3241
3242    var from = this[-dir];
3243    if (from) from.insTextAtDirEnd(ch, dir);
3244    else TextPiece(ch).createDir(-dir, cursor);
3245
3246    return this.deleteTowards(dir, cursor);
3247  };
3248
3249  _.latex = function() { return this.text; };
3250
3251  _.deleteTowards = function(dir, cursor) {
3252    if (this.text.length > 1) {
3253      if (dir === R) {
3254        this.dom.deleteData(0, 1);
3255        this.text = this.text.slice(1);
3256      }
3257      else {
3258        // note that the order of these 2 lines is annoyingly important
3259        // (the second line mutates this.text.length)
3260        this.dom.deleteData(-1 + this.text.length, 1);
3261        this.text = this.text.slice(0, -1);
3262      }
3263    }
3264    else {
3265      this.remove();
3266      this.jQ.remove();
3267      cursor[dir] = this[dir];
3268    }
3269  };
3270
3271  _.selectTowards = function(dir, cursor) {
3272    prayDirection(dir);
3273    var anticursor = cursor.anticursor;
3274
3275    var ch = endChar(-dir, this.text)
3276
3277    if (anticursor[dir] === this) {
3278      var newPc = TextPiece(ch).createDir(dir, cursor);
3279      anticursor[dir] = newPc;
3280      cursor.insDirOf(dir, newPc);
3281    }
3282    else {
3283      var from = this[-dir];
3284      if (from) from.insTextAtDirEnd(ch, dir);
3285      else {
3286        var newPc = TextPiece(ch).createDir(-dir, cursor);
3287        newPc.jQ.insDirOf(-dir, cursor.selection.jQ);
3288      }
3289
3290      if (this.text.length === 1 && anticursor[-dir] === this) {
3291        anticursor[-dir] = this[-dir]; // `this` will be removed in deleteTowards
3292      }
3293    }
3294
3295    return this.deleteTowards(dir, cursor);
3296  };
3297});
3298
3299LatexCmds.text =
3300LatexCmds.textnormal =
3301LatexCmds.textrm =
3302LatexCmds.textup =
3303LatexCmds.textmd = TextBlock;
3304
3305function makeTextBlock(latex, tagName, attrs) {
3306  return P(TextBlock, {
3307    ctrlSeq: latex,
3308    htmlTemplate: '<'+tagName+' '+attrs+'>&0</'+tagName+'>'
3309  });
3310}
3311
3312LatexCmds.em = LatexCmds.italic = LatexCmds.italics =
3313LatexCmds.emph = LatexCmds.textit = LatexCmds.textsl =
3314  makeTextBlock('\\textit', 'i', 'class="mq-text-mode"');
3315LatexCmds.strong = LatexCmds.bold = LatexCmds.textbf =
3316  makeTextBlock('\\textbf', 'b', 'class="mq-text-mode"');
3317LatexCmds.sf = LatexCmds.textsf =
3318  makeTextBlock('\\textsf', 'span', 'class="mq-sans-serif mq-text-mode"');
3319LatexCmds.tt = LatexCmds.texttt =
3320  makeTextBlock('\\texttt', 'span', 'class="mq-monospace mq-text-mode"');
3321LatexCmds.textsc =
3322  makeTextBlock('\\textsc', 'span', 'style="font-variant:small-caps" class="mq-text-mode"');
3323LatexCmds.uppercase =
3324  makeTextBlock('\\uppercase', 'span', 'style="text-transform:uppercase" class="mq-text-mode"');
3325LatexCmds.lowercase =
3326  makeTextBlock('\\lowercase', 'span', 'style="text-transform:lowercase" class="mq-text-mode"');
3327
3328
3329var RootMathCommand = P(MathCommand, function(_, super_) {
3330  _.init = function(cursor) {
3331    super_.init.call(this, '$');
3332    this.cursor = cursor;
3333  };
3334  _.htmlTemplate = '<span class="mq-math-mode">&0</span>';
3335  _.createBlocks = function() {
3336    super_.createBlocks.call(this);
3337
3338    this.ends[L].cursor = this.cursor;
3339    this.ends[L].write = function(cursor, ch) {
3340      if (ch !== '$')
3341        MathBlock.prototype.write.call(this, cursor, ch);
3342      else if (this.isEmpty()) {
3343        cursor.insRightOf(this.parent);
3344        this.parent.deleteTowards(dir, cursor);
3345        VanillaSymbol('\\$','$').createLeftOf(cursor.show());
3346      }
3347      else if (!cursor[R])
3348        cursor.insRightOf(this.parent);
3349      else if (!cursor[L])
3350        cursor.insLeftOf(this.parent);
3351      else
3352        MathBlock.prototype.write.call(this, cursor, ch);
3353    };
3354  };
3355  _.latex = function() {
3356    return '$' + this.ends[L].latex() + '$';
3357  };
3358});
3359
3360var RootTextBlock = P(RootMathBlock, function(_, super_) {
3361  _.keystroke = function(key) {
3362    if (key === 'Spacebar' || key === 'Shift-Spacebar') return;
3363    return super_.keystroke.apply(this, arguments);
3364  };
3365  _.write = function(cursor, ch) {
3366    cursor.show().deleteSelection();
3367    if (ch === '$')
3368      RootMathCommand(cursor).createLeftOf(cursor);
3369    else {
3370      var html;
3371      if (ch === '<') html = '&lt;';
3372      else if (ch === '>') html = '&gt;';
3373      VanillaSymbol(ch, html).createLeftOf(cursor);
3374    }
3375  };
3376});
3377API.TextField = function(APIClasses) {
3378  return P(APIClasses.EditableField, function(_, super_) {
3379    this.RootBlock = RootTextBlock;
3380    _.__mathquillify = function() {
3381      return super_.__mathquillify.call(this, 'mq-editable-field mq-text-mode');
3382    };
3383    _.latex = function(latex) {
3384      if (arguments.length > 0) {
3385        this.__controller.renderLatexText(latex);
3386        if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
3387        return this;
3388      }
3389      return this.__controller.exportLatex();
3390    };
3391  });
3392};
3393/****************************************
3394 * Input box to type backslash commands
3395 ***************************************/
3396
3397var LatexCommandInput =
3398CharCmds['\\'] = P(MathCommand, function(_, super_) {
3399  _.ctrlSeq = '\\';
3400  _.replaces = function(replacedFragment) {
3401    this._replacedFragment = replacedFragment.disown();
3402    this.isEmpty = function() { return false; };
3403  };
3404  _.htmlTemplate = '<span class="mq-latex-command-input mq-non-leaf">\\<span>&0</span></span>';
3405  _.textTemplate = ['\\'];
3406  _.createBlocks = function() {
3407    super_.createBlocks.call(this);
3408    this.ends[L].focus = function() {
3409      this.parent.jQ.addClass('mq-hasCursor');
3410      if (this.isEmpty())
3411        this.parent.jQ.removeClass('mq-empty');
3412
3413      return this;
3414    };
3415    this.ends[L].blur = function() {
3416      this.parent.jQ.removeClass('mq-hasCursor');
3417      if (this.isEmpty())
3418        this.parent.jQ.addClass('mq-empty');
3419
3420      return this;
3421    };
3422    this.ends[L].write = function(cursor, ch) {
3423      cursor.show().deleteSelection();
3424
3425      if (ch.match(/[a-z]/i)) VanillaSymbol(ch).createLeftOf(cursor);
3426      else {
3427        this.parent.renderCommand(cursor);
3428        if (ch !== '\\' || !this.isEmpty()) cursor.parent.write(cursor, ch);
3429      }
3430    };
3431    this.ends[L].keystroke = function(key, e, ctrlr) {
3432      if (key === 'Tab' || key === 'Enter' || key === 'Spacebar') {
3433        this.parent.renderCommand(ctrlr.cursor);
3434        e.preventDefault();
3435        return;
3436      }
3437      return super_.keystroke.apply(this, arguments);
3438    };
3439  };
3440  _.createLeftOf = function(cursor) {
3441    super_.createLeftOf.call(this, cursor);
3442
3443    if (this._replacedFragment) {
3444      var el = this.jQ[0];
3445      this.jQ =
3446        this._replacedFragment.jQ.addClass('mq-blur').bind(
3447          'mousedown mousemove', //FIXME: is monkey-patching the mousedown and mousemove handlers the right way to do this?
3448          function(e) {
3449            $(e.target = el).trigger(e);
3450            return false;
3451          }
3452        ).insertBefore(this.jQ).add(this.jQ);
3453    }
3454  };
3455  _.latex = function() {
3456    return '\\' + this.ends[L].latex() + ' ';
3457  };
3458  _.renderCommand = function(cursor) {
3459    this.jQ = this.jQ.last();
3460    this.remove();
3461    if (this[R]) {
3462      cursor.insLeftOf(this[R]);
3463    } else {
3464      cursor.insAtRightEnd(this.parent);
3465    }
3466
3467    var latex = this.ends[L].latex();
3468    if (!latex) latex = ' ';
3469    var cmd = LatexCmds[latex];
3470    if (cmd) {
3471      cmd = cmd(latex);
3472      if (this._replacedFragment) cmd.replaces(this._replacedFragment);
3473      cmd.createLeftOf(cursor);
3474    }
3475    else {
3476      cmd = TextBlock();
3477      cmd.replaces(latex);
3478      cmd.createLeftOf(cursor);
3479      cursor.insRightOf(cmd);
3480      if (this._replacedFragment)
3481        this._replacedFragment.remove();
3482    }
3483  };
3484});
3485
3486/***************************
3487 * Commands and Operators.
3488 **************************/
3489
3490var scale, // = function(jQ, x, y) { ... }
3491//will use a CSS 2D transform to scale the jQuery-wrapped HTML elements,
3492//or the filter matrix transform fallback for IE 5.5-8, or gracefully degrade to
3493//increasing the fontSize to match the vertical Y scaling factor.
3494
3495//ideas from http://github.com/louisremi/jquery.transform.js
3496//see also http://msdn.microsoft.com/en-us/library/ms533014(v=vs.85).aspx
3497
3498  forceIERedraw = noop,
3499  div = document.createElement('div'),
3500  div_style = div.style,
3501  transformPropNames = {
3502    transform:1,
3503    WebkitTransform:1,
3504    MozTransform:1,
3505    OTransform:1,
3506    msTransform:1
3507  },
3508  transformPropName;
3509
3510for (var prop in transformPropNames) {
3511  if (prop in div_style) {
3512    transformPropName = prop;
3513    break;
3514  }
3515}
3516
3517if (transformPropName) {
3518  scale = function(jQ, x, y) {
3519    jQ.css(transformPropName, 'scale('+x+','+y+')');
3520  };
3521}
3522else if ('filter' in div_style) { //IE 6, 7, & 8 fallback, see https://github.com/laughinghan/mathquill/wiki/Transforms
3523  forceIERedraw = function(el){ el.className = el.className; };
3524  scale = function(jQ, x, y) { //NOTE: assumes y > x
3525    x /= (1+(y-1)/2);
3526    jQ.css('fontSize', y + 'em');
3527    if (!jQ.hasClass('mq-matrixed-container')) {
3528      jQ.addClass('mq-matrixed-container')
3529      .wrapInner('<span class="mq-matrixed"></span>');
3530    }
3531    var innerjQ = jQ.children()
3532    .css('filter', 'progid:DXImageTransform.Microsoft'
3533        + '.Matrix(M11=' + x + ",SizingMethod='auto expand')"
3534    );
3535    function calculateMarginRight() {
3536      jQ.css('marginRight', (innerjQ.width()-1)*(x-1)/x + 'px');
3537    }
3538    calculateMarginRight();
3539    var intervalId = setInterval(calculateMarginRight);
3540    $(window).load(function() {
3541      clearTimeout(intervalId);
3542      calculateMarginRight();
3543    });
3544  };
3545}
3546else {
3547  scale = function(jQ, x, y) {
3548    jQ.css('fontSize', y + 'em');
3549  };
3550}
3551
3552var Style = P(MathCommand, function(_, super_) {
3553  _.init = function(ctrlSeq, tagName, attrs) {
3554    super_.init.call(this, ctrlSeq, '<'+tagName+' '+attrs+'>&0</'+tagName+'>');
3555  };
3556});
3557
3558//fonts
3559LatexCmds.mathrm = bind(Style, '\\mathrm', 'span', 'class="mq-roman mq-font"');
3560LatexCmds.mathit = bind(Style, '\\mathit', 'i', 'class="mq-font"');
3561LatexCmds.mathbf = bind(Style, '\\mathbf', 'b', 'class="mq-font"');
3562LatexCmds.mathsf = bind(Style, '\\mathsf', 'span', 'class="mq-sans-serif mq-font"');
3563LatexCmds.mathtt = bind(Style, '\\mathtt', 'span', 'class="mq-monospace mq-font"');
3564//text-decoration
3565LatexCmds.underline = bind(Style, '\\underline', 'span', 'class="mq-non-leaf mq-underline"');
3566LatexCmds.overline = LatexCmds.bar = bind(Style, '\\overline', 'span', 'class="mq-non-leaf mq-overline"');
3567LatexCmds.overrightarrow = bind(Style, '\\overrightarrow', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-right"');
3568LatexCmds.overleftarrow = bind(Style, '\\overleftarrow', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-left"');
3569LatexCmds.overleftrightarrow = bind(Style, '\\overleftrightarrow', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-both"');
3570LatexCmds.overarc = bind(Style, '\\overarc', 'span', 'class="mq-non-leaf mq-overarc"');
3571LatexCmds.dot = P(MathCommand, function(_, super_) {
3572    _.init = function() {
3573        super_.init.call(this, '\\dot', '<span class="mq-non-leaf"><span class="mq-dot-recurring-inner">'
3574            + '<span class="mq-dot-recurring">&#x2d9;</span>'
3575            + '<span class="mq-empty-box">&0</span>'
3576            + '</span></span>'
3577        );
3578    };
3579});
3580
3581// `\textcolor{color}{math}` will apply a color to the given math content, where
3582// `color` is any valid CSS Color Value (see [SitePoint docs][] (recommended),
3583// [Mozilla docs][], or [W3C spec][]).
3584//
3585// [SitePoint docs]: http://reference.sitepoint.com/css/colorvalues
3586// [Mozilla docs]: https://developer.mozilla.org/en-US/docs/CSS/color_value#Values
3587// [W3C spec]: http://dev.w3.org/csswg/css3-color/#colorunits
3588var TextColor = LatexCmds.textcolor = P(MathCommand, function(_, super_) {
3589  _.setColor = function(color) {
3590    this.color = color;
3591    this.htmlTemplate =
3592      '<span class="mq-textcolor" style="color:' + color + '">&0</span>';
3593  };
3594  _.latex = function() {
3595    return '\\textcolor{' + this.color + '}{' + this.blocks[0].latex() + '}';
3596  };
3597  _.parser = function() {
3598    var self = this;
3599    var optWhitespace = Parser.optWhitespace;
3600    var string = Parser.string;
3601    var regex = Parser.regex;
3602
3603    return optWhitespace
3604      .then(string('{'))
3605      .then(regex(/^[#\w\s.,()%-]*/))
3606      .skip(string('}'))
3607      .then(function(color) {
3608        self.setColor(color);
3609        return super_.parser.call(self);
3610      })
3611    ;
3612  };
3613  _.isStyleBlock = function() {
3614    return true;
3615  };
3616});
3617
3618// Very similar to the \textcolor command, but will add the given CSS class.
3619// Usage: \class{classname}{math}
3620// Note regex that whitelists valid CSS classname characters:
3621// https://github.com/mathquill/mathquill/pull/191#discussion_r4327442
3622var Class = LatexCmds['class'] = P(MathCommand, function(_, super_) {
3623  _.parser = function() {
3624    var self = this, string = Parser.string, regex = Parser.regex;
3625    return Parser.optWhitespace
3626      .then(string('{'))
3627      .then(regex(/^[-\w\s\\\xA0-\xFF]*/))
3628      .skip(string('}'))
3629      .then(function(cls) {
3630        self.cls = cls || '';
3631        self.htmlTemplate = '<span class="mq-class '+cls+'">&0</span>';
3632        return super_.parser.call(self);
3633      })
3634    ;
3635  };
3636  _.latex = function() {
3637    return '\\class{' + this.cls + '}{' + this.blocks[0].latex() + '}';
3638  };
3639  _.isStyleBlock = function() {
3640    return true;
3641  };
3642});
3643
3644var SupSub = P(MathCommand, function(_, super_) {
3645  _.ctrlSeq = '_{...}^{...}';
3646  _.createLeftOf = function(cursor) {
3647    if (!this.replacedFragment && !cursor[L] && cursor.options.supSubsRequireOperand) return;
3648    return super_.createLeftOf.apply(this, arguments);
3649  };
3650  _.contactWeld = function(cursor) {
3651    // Look on either side for a SupSub, if one is found compare my
3652    // .sub, .sup with its .sub, .sup. If I have one that it doesn't,
3653    // then call .addBlock() on it with my block; if I have one that
3654    // it also has, then insert my block's children into its block,
3655    // unless my block has none, in which case insert the cursor into
3656    // its block (and not mine, I'm about to remove myself) in the case
3657    // I was just typed.
3658    // TODO: simplify
3659
3660    // equiv. to [L, R].forEach(function(dir) { ... });
3661    for (var dir = L; dir; dir = (dir === L ? R : false)) {
3662      if (this[dir] instanceof SupSub) {
3663        // equiv. to 'sub sup'.split(' ').forEach(function(supsub) { ... });
3664        for (var supsub = 'sub'; supsub; supsub = (supsub === 'sub' ? 'sup' : false)) {
3665          var src = this[supsub], dest = this[dir][supsub];
3666          if (!src) continue;
3667          if (!dest) this[dir].addBlock(src.disown());
3668          else if (!src.isEmpty()) { // ins src children at -dir end of dest
3669            src.jQ.children().insAtDirEnd(-dir, dest.jQ);
3670            var children = src.children().disown();
3671            var pt = Point(dest, children.ends[R], dest.ends[L]);
3672            if (dir === L) children.adopt(dest, dest.ends[R], 0);
3673            else children.adopt(dest, 0, dest.ends[L]);
3674          }
3675          else var pt = Point(dest, 0, dest.ends[L]);
3676          this.placeCursor = (function(dest, src) { // TODO: don't monkey-patch
3677            return function(cursor) { cursor.insAtDirEnd(-dir, dest || src); };
3678          }(dest, src));
3679        }
3680        this.remove();
3681        if (cursor && cursor[L] === this) {
3682          if (dir === R && pt) {
3683            pt[L] ? cursor.insRightOf(pt[L]) : cursor.insAtLeftEnd(pt.parent);
3684          }
3685          else cursor.insRightOf(this[dir]);
3686        }
3687        break;
3688      }
3689    }
3690  };
3691  Options.p.charsThatBreakOutOfSupSub = '';
3692  _.finalizeTree = function() {
3693    this.ends[L].write = function(cursor, ch) {
3694      if (cursor.options.autoSubscriptNumerals && this === this.parent.sub) {
3695        if (ch === '_') return;
3696        var cmd = this.chToCmd(ch, cursor.options);
3697        if (cmd instanceof Symbol) cursor.deleteSelection();
3698        else cursor.clearSelection().insRightOf(this.parent);
3699        return cmd.createLeftOf(cursor.show());
3700      }
3701      if (cursor[L] && !cursor[R] && !cursor.selection
3702          && cursor.options.charsThatBreakOutOfSupSub.indexOf(ch) > -1) {
3703        cursor.insRightOf(this.parent);
3704      }
3705      MathBlock.p.write.apply(this, arguments);
3706    };
3707  };
3708  _.moveTowards = function(dir, cursor, updown) {
3709    if (cursor.options.autoSubscriptNumerals && !this.sup) {
3710      cursor.insDirOf(dir, this);
3711    }
3712    else super_.moveTowards.apply(this, arguments);
3713  };
3714  _.deleteTowards = function(dir, cursor) {
3715    if (cursor.options.autoSubscriptNumerals && this.sub) {
3716      var cmd = this.sub.ends[-dir];
3717      if (cmd instanceof Symbol) cmd.remove();
3718      else if (cmd) cmd.deleteTowards(dir, cursor.insAtDirEnd(-dir, this.sub));
3719
3720      // TODO: factor out a .removeBlock() or something
3721      if (this.sub.isEmpty()) {
3722        this.sub.deleteOutOf(L, cursor.insAtLeftEnd(this.sub));
3723        if (this.sup) cursor.insDirOf(-dir, this);
3724        // Note `-dir` because in e.g. x_1^2| want backspacing (leftward)
3725        // to delete the 1 but to end up rightward of x^2; with non-negated
3726        // `dir` (try it), the cursor appears to have gone "through" the ^2.
3727      }
3728    }
3729    else super_.deleteTowards.apply(this, arguments);
3730  };
3731  _.latex = function() {
3732    function latex(prefix, block) {
3733      var l = block && block.latex();
3734      return block ? prefix + (l.length === 1 ? l : '{' + (l || ' ') + '}') : '';
3735    }
3736    return latex('_', this.sub) + latex('^', this.sup);
3737  };
3738  _.addBlock = function(block) {
3739    if (this.supsub === 'sub') {
3740      this.sup = this.upInto = this.sub.upOutOf = block;
3741      block.adopt(this, this.sub, 0).downOutOf = this.sub;
3742      block.jQ = $('<span class="mq-sup"/>').append(block.jQ.children())
3743        .attr(mqBlockId, block.id).prependTo(this.jQ);
3744    }
3745    else {
3746      this.sub = this.downInto = this.sup.downOutOf = block;
3747      block.adopt(this, 0, this.sup).upOutOf = this.sup;
3748      block.jQ = $('<span class="mq-sub"></span>').append(block.jQ.children())
3749        .attr(mqBlockId, block.id).appendTo(this.jQ.removeClass('mq-sup-only'));
3750      this.jQ.append('<span style="display:inline-block;width:0">&#8203;</span>');
3751    }
3752    // like 'sub sup'.split(' ').forEach(function(supsub) { ... });
3753    for (var i = 0; i < 2; i += 1) (function(cmd, supsub, oppositeSupsub, updown) {
3754      cmd[supsub].deleteOutOf = function(dir, cursor) {
3755        cursor.insDirOf((this[dir] ? -dir : dir), this.parent);
3756        if (!this.isEmpty()) {
3757          var end = this.ends[dir];
3758          this.children().disown()
3759            .withDirAdopt(dir, cursor.parent, cursor[dir], cursor[-dir])
3760            .jQ.insDirOf(-dir, cursor.jQ);
3761          cursor[-dir] = end;
3762        }
3763        cmd.supsub = oppositeSupsub;
3764        delete cmd[supsub];
3765        delete cmd[updown+'Into'];
3766        cmd[oppositeSupsub][updown+'OutOf'] = insLeftOfMeUnlessAtEnd;
3767        delete cmd[oppositeSupsub].deleteOutOf;
3768        if (supsub === 'sub') $(cmd.jQ.addClass('mq-sup-only')[0].lastChild).remove();
3769        this.remove();
3770      };
3771    }(this, 'sub sup'.split(' ')[i], 'sup sub'.split(' ')[i], 'down up'.split(' ')[i]));
3772  };
3773  _.reflow = function() {
3774    var $block = this.jQ ;//mq-supsub
3775    var $prev = $block.prev() ;
3776
3777    if ( !$prev.length ) {
3778        //we cant normalize it without having prev. element (which is base)
3779        return ;
3780    }
3781
3782    var $sup = $block.children( '.mq-sup' );//mq-supsub -> mq-sup
3783    if ( $sup.length ) {
3784        var sup_fontsize = parseInt( $sup.css('font-size') ) ;
3785        var sup_bottom = $sup.offset().top + $sup.height() ;
3786        //we want that superscript overlaps top of base on 0.7 of its font-size
3787        //this way small superscripts like x^2 look ok, but big ones like x^(1/2/3) too
3788        var needed = sup_bottom - $prev.offset().top  - 0.7*sup_fontsize ;
3789        var cur_margin = parseInt( $sup.css('margin-bottom' ) ) ;
3790        //we lift it up with margin-bottom
3791        $sup.css( 'margin-bottom', cur_margin + needed ) ;
3792    }
3793  } ;
3794
3795});
3796
3797function insLeftOfMeUnlessAtEnd(cursor) {
3798  // cursor.insLeftOf(cmd), unless cursor at the end of block, and every
3799  // ancestor cmd is at the end of every ancestor block
3800  var cmd = this.parent, ancestorCmd = cursor;
3801  do {
3802    if (ancestorCmd[R]) return cursor.insLeftOf(cmd);
3803    ancestorCmd = ancestorCmd.parent.parent;
3804  } while (ancestorCmd !== cmd);
3805  cursor.insRightOf(cmd);
3806}
3807
3808LatexCmds.subscript =
3809LatexCmds._ = P(SupSub, function(_, super_) {
3810  _.supsub = 'sub';
3811  _.htmlTemplate =
3812      '<span class="mq-supsub mq-non-leaf">'
3813    +   '<span class="mq-sub">&0</span>'
3814    +   '<span style="display:inline-block;width:0">&#8203;</span>'
3815    + '</span>'
3816  ;
3817  _.textTemplate = [ '_' ];
3818  _.finalizeTree = function() {
3819    this.downInto = this.sub = this.ends[L];
3820    this.sub.upOutOf = insLeftOfMeUnlessAtEnd;
3821    super_.finalizeTree.call(this);
3822  };
3823});
3824
3825LatexCmds.superscript =
3826LatexCmds.supscript =
3827LatexCmds['^'] = P(SupSub, function(_, super_) {
3828  _.supsub = 'sup';
3829  _.htmlTemplate =
3830      '<span class="mq-supsub mq-non-leaf mq-sup-only">'
3831    +   '<span class="mq-sup">&0</span>'
3832    + '</span>'
3833  ;
3834  _.textTemplate = [ '^' ];
3835  _.finalizeTree = function() {
3836    this.upInto = this.sup = this.ends[R];
3837    this.sup.downOutOf = insLeftOfMeUnlessAtEnd;
3838    super_.finalizeTree.call(this);
3839  };
3840});
3841
3842var SummationNotation = P(MathCommand, function(_, super_) {
3843  _.init = function(ch, html) {
3844    var htmlTemplate =
3845      '<span class="mq-large-operator mq-non-leaf">'
3846    +   '<span class="mq-to"><span>&1</span></span>'
3847    +   '<big>'+html+'</big>'
3848    +   '<span class="mq-from"><span>&0</span></span>'
3849    + '</span>'
3850    ;
3851    Symbol.prototype.init.call(this, ch, htmlTemplate);
3852  };
3853  _.createLeftOf = function(cursor) {
3854    super_.createLeftOf.apply(this, arguments);
3855    if (cursor.options.sumStartsWithNEquals) {
3856      Letter('n').createLeftOf(cursor);
3857      Equality().createLeftOf(cursor);
3858    }
3859  };
3860  _.latex = function() {
3861    function simplify(latex) {
3862      return latex.length === 1 ? latex : '{' + (latex || ' ') + '}';
3863    }
3864    return this.ctrlSeq + '_' + simplify(this.ends[L].latex()) +
3865      '^' + simplify(this.ends[R].latex());
3866  };
3867  _.parser = function() {
3868    var string = Parser.string;
3869    var optWhitespace = Parser.optWhitespace;
3870    var succeed = Parser.succeed;
3871    var block = latexMathParser.block;
3872
3873    var self = this;
3874    var blocks = self.blocks = [ MathBlock(), MathBlock() ];
3875    for (var i = 0; i < blocks.length; i += 1) {
3876      blocks[i].adopt(self, self.ends[R], 0);
3877    }
3878
3879    return optWhitespace.then(string('_').or(string('^'))).then(function(supOrSub) {
3880      var child = blocks[supOrSub === '_' ? 0 : 1];
3881      return block.then(function(block) {
3882        block.children().adopt(child, child.ends[R], 0);
3883        return succeed(self);
3884      });
3885    }).many().result(self);
3886  };
3887  _.finalizeTree = function() {
3888    this.downInto = this.ends[L];
3889    this.upInto = this.ends[R];
3890    this.ends[L].upOutOf = this.ends[R];
3891    this.ends[R].downOutOf = this.ends[L];
3892  };
3893});
3894
3895LatexCmds['\u2211'] =
3896LatexCmds.sum =
3897LatexCmds.summation = bind(SummationNotation,'\\sum ','&sum;');
3898
3899LatexCmds['\u220f'] =
3900LatexCmds.prod =
3901LatexCmds.product = bind(SummationNotation,'\\prod ','&prod;');
3902
3903LatexCmds.coprod =
3904LatexCmds.coproduct = bind(SummationNotation,'\\coprod ','&#8720;');
3905
3906LatexCmds['\u222b'] =
3907LatexCmds['int'] =
3908LatexCmds.integral = P(SummationNotation, function(_, super_) {
3909  _.init = function() {
3910    var htmlTemplate =
3911      '<span class="mq-int mq-non-leaf">'
3912    +   '<big>&int;</big>'
3913    +   '<span class="mq-supsub mq-non-leaf">'
3914    +     '<span class="mq-sup"><span class="mq-sup-inner">&1</span></span>'
3915    +     '<span class="mq-sub">&0</span>'
3916    +     '<span style="display:inline-block;width:0">&#8203</span>'
3917    +   '</span>'
3918    + '</span>'
3919    ;
3920    Symbol.prototype.init.call(this, '\\int ', htmlTemplate);
3921  };
3922  // FIXME: refactor rather than overriding
3923  _.createLeftOf = MathCommand.p.createLeftOf;
3924});
3925
3926var Fraction =
3927LatexCmds.frac =
3928LatexCmds.dfrac =
3929LatexCmds.cfrac =
3930LatexCmds.fraction = P(MathCommand, function(_, super_) {
3931  _.ctrlSeq = '\\frac';
3932  _.htmlTemplate =
3933      '<span class="mq-fraction mq-non-leaf">'
3934    +   '<span class="mq-numerator">&0</span>'
3935    +   '<span class="mq-denominator">&1</span>'
3936    +   '<span style="display:inline-block;width:0">&#8203;</span>'
3937    + '</span>'
3938  ;
3939  _.textTemplate = ['(', ')/(', ')'];
3940  _.finalizeTree = function() {
3941    this.upInto = this.ends[R].upOutOf = this.ends[L];
3942    this.downInto = this.ends[L].downOutOf = this.ends[R];
3943  };
3944});
3945
3946var LiveFraction =
3947LatexCmds.over =
3948CharCmds['/'] = P(Fraction, function(_, super_) {
3949  _.createLeftOf = function(cursor) {
3950    if (!this.replacedFragment) {
3951      var leftward = cursor[L];
3952      while (leftward &&
3953        !(
3954          leftward instanceof BinaryOperator ||
3955          leftward instanceof (LatexCmds.text || noop) ||
3956          leftward instanceof SummationNotation ||
3957          leftward.ctrlSeq === '\\ ' ||
3958          /^[,;:]$/.test(leftward.ctrlSeq)
3959        ) //lookbehind for operator
3960      ) leftward = leftward[L];
3961
3962      if (leftward instanceof SummationNotation && leftward[R] instanceof SupSub) {
3963        leftward = leftward[R];
3964        if (leftward[R] instanceof SupSub && leftward[R].ctrlSeq != leftward.ctrlSeq)
3965          leftward = leftward[R];
3966      }
3967
3968      if (leftward !== cursor[L] && !cursor.isTooDeep(1)) {
3969        this.replaces(Fragment(leftward[R] || cursor.parent.ends[L], cursor[L]));
3970        cursor[L] = leftward;
3971      }
3972    }
3973    super_.createLeftOf.call(this, cursor);
3974  };
3975});
3976
3977var SquareRoot =
3978LatexCmds.sqrt =
3979LatexCmds['\u221a'] = P(MathCommand, function(_, super_) {
3980  _.ctrlSeq = '\\sqrt';
3981  _.htmlTemplate =
3982      '<span class="mq-non-leaf">'
3983    +   '<span class="mq-scaled mq-sqrt-prefix">&radic;</span>'
3984    +   '<span class="mq-non-leaf mq-sqrt-stem">&0</span>'
3985    + '</span>'
3986  ;
3987  _.textTemplate = ['sqrt(', ')'];
3988  _.parser = function() {
3989    return latexMathParser.optBlock.then(function(optBlock) {
3990      return latexMathParser.block.map(function(block) {
3991        var nthroot = NthRoot();
3992        nthroot.blocks = [ optBlock, block ];
3993        optBlock.adopt(nthroot, 0, 0);
3994        block.adopt(nthroot, optBlock, 0);
3995        return nthroot;
3996      });
3997    }).or(super_.parser.call(this));
3998  };
3999  _.reflow = function() {
4000    var block = this.ends[R].jQ;
4001    scale(block.prev(), 1, block.innerHeight()/+block.css('fontSize').slice(0,-2) - .1);
4002  };
4003});
4004
4005var Hat = LatexCmds.hat = P(MathCommand, function(_, super_) {
4006  _.ctrlSeq = '\\hat';
4007  _.htmlTemplate =
4008      '<span class="mq-non-leaf">'
4009    +   '<span class="mq-hat-prefix">^</span>'
4010    +   '<span class="mq-hat-stem">&0</span>'
4011    + '</span>'
4012  ;
4013  _.textTemplate = ['hat(', ')'];
4014});
4015
4016var NthRoot =
4017LatexCmds.nthroot = P(SquareRoot, function(_, super_) {
4018  _.htmlTemplate =
4019      '<sup class="mq-nthroot mq-non-leaf">&0</sup>'
4020    + '<span class="mq-scaled">'
4021    +   '<span class="mq-sqrt-prefix mq-scaled">&radic;</span>'
4022    +   '<span class="mq-sqrt-stem mq-non-leaf">&1</span>'
4023    + '</span>'
4024  ;
4025  _.textTemplate = ['sqrt[', '](', ')'];
4026  _.latex = function() {
4027    return '\\sqrt['+this.ends[L].latex()+']{'+this.ends[R].latex()+'}';
4028  };
4029});
4030
4031var DiacriticAbove = P(MathCommand, function(_, super_) {
4032  _.init = function(ctrlSeq, symbol, textTemplate) {
4033    var htmlTemplate =
4034      '<span class="mq-non-leaf">'
4035      +   '<span class="mq-diacritic-above">'+symbol+'</span>'
4036      +   '<span class="mq-diacritic-stem">&0</span>'
4037      + '</span>'
4038    ;
4039
4040    super_.init.call(this, ctrlSeq, htmlTemplate, textTemplate);
4041  };
4042});
4043LatexCmds.vec = bind(DiacriticAbove, '\\vec', '&rarr;', ['vec(', ')']);
4044LatexCmds.tilde = bind(DiacriticAbove, '\\tilde', '~', ['tilde(', ')']);
4045
4046function DelimsMixin(_, super_) {
4047  _.jQadd = function() {
4048    super_.jQadd.apply(this, arguments);
4049    this.delimjQs = this.jQ.children(':first').add(this.jQ.children(':last'));
4050    this.contentjQ = this.jQ.children(':eq(1)');
4051  };
4052  _.reflow = function() {
4053    var height = this.contentjQ.outerHeight()
4054                 / parseFloat(this.contentjQ.css('fontSize'));
4055    scale(this.delimjQs, min(1 + .2*(height - 1), 1.2), 1.2*height);
4056  };
4057}
4058
4059// Round/Square/Curly/Angle Brackets (aka Parens/Brackets/Braces)
4060//   first typed as one-sided bracket with matching "ghost" bracket at
4061//   far end of current block, until you type an opposing one
4062var Bracket = P(P(MathCommand, DelimsMixin), function(_, super_) {
4063  _.init = function(side, open, close, ctrlSeq, end) {
4064    super_.init.call(this, '\\left'+ctrlSeq, undefined, [open, close]);
4065    this.side = side;
4066    this.sides = {};
4067    this.sides[L] = { ch: open, ctrlSeq: ctrlSeq };
4068    this.sides[R] = { ch: close, ctrlSeq: end };
4069  };
4070  _.numBlocks = function() { return 1; };
4071  _.html = function() { // wait until now so that .side may
4072    this.htmlTemplate = // be set by createLeftOf or parser
4073        '<span class="mq-non-leaf">'
4074      +   '<span class="mq-scaled mq-paren'+(this.side === R ? ' mq-ghost' : '')+'">'
4075      +     this.sides[L].ch
4076      +   '</span>'
4077      +   '<span class="mq-non-leaf">&0</span>'
4078      +   '<span class="mq-scaled mq-paren'+(this.side === L ? ' mq-ghost' : '')+'">'
4079      +     this.sides[R].ch
4080      +   '</span>'
4081      + '</span>'
4082    ;
4083    return super_.html.call(this);
4084  };
4085  _.latex = function() {
4086    return '\\left'+this.sides[L].ctrlSeq+this.ends[L].latex()+'\\right'+this.sides[R].ctrlSeq;
4087  };
4088  _.matchBrack = function(opts, expectedSide, node) {
4089    // return node iff it's a matching 1-sided bracket of expected side (if any)
4090    return node instanceof Bracket && node.side && node.side !== -expectedSide
4091      && (!opts.restrictMismatchedBrackets
4092        || OPP_BRACKS[this.sides[this.side].ch] === node.sides[node.side].ch
4093        || { '(': ']', '[': ')' }[this.sides[L].ch] === node.sides[R].ch) && node;
4094  };
4095  _.closeOpposing = function(brack) {
4096    brack.side = 0;
4097    brack.sides[this.side] = this.sides[this.side]; // copy over my info (may be
4098    brack.delimjQs.eq(this.side === L ? 0 : 1) // mismatched, like [a, b))
4099      .removeClass('mq-ghost').html(this.sides[this.side].ch);
4100  };
4101  _.createLeftOf = function(cursor) {
4102    if (!this.replacedFragment) { // unless wrapping seln in brackets,
4103        // check if next to or inside an opposing one-sided bracket
4104      var opts = cursor.options;
4105      if (this.sides[L].ch === '|') { // check both sides if I'm a pipe
4106        var brack = this.matchBrack(opts, R, cursor[R])
4107                 || this.matchBrack(opts, L, cursor[L])
4108                 || this.matchBrack(opts, 0, cursor.parent.parent);
4109      }
4110      else {
4111        var brack = this.matchBrack(opts, -this.side, cursor[-this.side])
4112                 || this.matchBrack(opts, -this.side, cursor.parent.parent);
4113      }
4114    }
4115    if (brack) {
4116      var side = this.side = -brack.side; // may be pipe with .side not yet set
4117      this.closeOpposing(brack);
4118      if (brack === cursor.parent.parent && cursor[side]) { // move the stuff between
4119        Fragment(cursor[side], cursor.parent.ends[side], -side) // me and ghost outside
4120          .disown().withDirAdopt(-side, brack.parent, brack, brack[side])
4121          .jQ.insDirOf(side, brack.jQ);
4122      }
4123      brack.bubble('reflow');
4124    }
4125    else {
4126      brack = this, side = brack.side;
4127      if (brack.replacedFragment) brack.side = 0; // wrapping seln, don't be one-sided
4128      else if (cursor[-side]) { // elsewise, auto-expand so ghost is at far end
4129        brack.replaces(Fragment(cursor[-side], cursor.parent.ends[-side], side));
4130        cursor[-side] = 0;
4131      }
4132      super_.createLeftOf.call(brack, cursor);
4133    }
4134    if (side === L) cursor.insAtLeftEnd(brack.ends[L]);
4135    else cursor.insRightOf(brack);
4136  };
4137  _.placeCursor = noop;
4138  _.unwrap = function() {
4139    this.ends[L].children().disown().adopt(this.parent, this, this[R])
4140      .jQ.insertAfter(this.jQ);
4141    this.remove();
4142  };
4143  _.deleteSide = function(side, outward, cursor) {
4144    var parent = this.parent, sib = this[side], farEnd = parent.ends[side];
4145
4146    if (side === this.side) { // deleting non-ghost of one-sided bracket, unwrap
4147      this.unwrap();
4148      sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
4149      return;
4150    }
4151
4152    var opts = cursor.options, wasSolid = !this.side;
4153    this.side = -side;
4154    // if deleting like, outer close-brace of [(1+2)+3} where inner open-paren
4155    if (this.matchBrack(opts, side, this.ends[L].ends[this.side])) { // is ghost,
4156      this.closeOpposing(this.ends[L].ends[this.side]); // then become [1+2)+3
4157      var origEnd = this.ends[L].ends[side];
4158      this.unwrap();
4159      if (origEnd.siblingCreated) origEnd.siblingCreated(cursor.options, side);
4160      sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
4161    }
4162    else { // if deleting like, inner close-brace of ([1+2}+3) where outer
4163      if (this.matchBrack(opts, side, this.parent.parent)) { // open-paren is
4164        this.parent.parent.closeOpposing(this); // ghost, then become [1+2+3)
4165        this.parent.parent.unwrap();
4166      } // else if deleting outward from a solid pair, unwrap
4167      else if (outward && wasSolid) {
4168        this.unwrap();
4169        sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
4170        return;
4171      }
4172      else { // else deleting just one of a pair of brackets, become one-sided
4173        this.sides[side] = { ch: OPP_BRACKS[this.sides[this.side].ch],
4174                             ctrlSeq: OPP_BRACKS[this.sides[this.side].ctrlSeq] };
4175        this.delimjQs.removeClass('mq-ghost')
4176          .eq(side === L ? 0 : 1).addClass('mq-ghost').html(this.sides[side].ch);
4177      }
4178      if (sib) { // auto-expand so ghost is at far end
4179        var origEnd = this.ends[L].ends[side];
4180        Fragment(sib, farEnd, -side).disown()
4181          .withDirAdopt(-side, this.ends[L], origEnd, 0)
4182          .jQ.insAtDirEnd(side, this.ends[L].jQ.removeClass('mq-empty'));
4183        if (origEnd.siblingCreated) origEnd.siblingCreated(cursor.options, side);
4184        cursor.insDirOf(-side, sib);
4185      } // didn't auto-expand, cursor goes just outside or just inside parens
4186      else (outward ? cursor.insDirOf(side, this)
4187                    : cursor.insAtDirEnd(side, this.ends[L]));
4188    }
4189  };
4190  _.deleteTowards = function(dir, cursor) {
4191    this.deleteSide(-dir, false, cursor);
4192  };
4193  _.finalizeTree = function() {
4194    this.ends[L].deleteOutOf = function(dir, cursor) {
4195      this.parent.deleteSide(dir, true, cursor);
4196    };
4197    // FIXME HACK: after initial creation/insertion, finalizeTree would only be
4198    // called if the paren is selected and replaced, e.g. by LiveFraction
4199    this.finalizeTree = this.intentionalBlur = function() {
4200      this.delimjQs.eq(this.side === L ? 1 : 0).removeClass('mq-ghost');
4201      this.side = 0;
4202    };
4203  };
4204  _.siblingCreated = function(opts, dir) { // if something typed between ghost and far
4205    if (dir === -this.side) this.finalizeTree(); // end of its block, solidify
4206  };
4207});
4208
4209var OPP_BRACKS = {
4210  '(': ')',
4211  ')': '(',
4212  '[': ']',
4213  ']': '[',
4214  '{': '}',
4215  '}': '{',
4216  '\\{': '\\}',
4217  '\\}': '\\{',
4218  '&lang;': '&rang;',
4219  '&rang;': '&lang;',
4220  '\\langle ': '\\rangle ',
4221  '\\rangle ': '\\langle ',
4222  '|': '|',
4223  '\\lVert ' : '\\rVert ',
4224  '\\rVert ' : '\\lVert ',
4225};
4226
4227function bindCharBracketPair(open, ctrlSeq) {
4228  var ctrlSeq = ctrlSeq || open, close = OPP_BRACKS[open], end = OPP_BRACKS[ctrlSeq];
4229  CharCmds[open] = bind(Bracket, L, open, close, ctrlSeq, end);
4230  CharCmds[close] = bind(Bracket, R, open, close, ctrlSeq, end);
4231}
4232bindCharBracketPair('(');
4233bindCharBracketPair('[');
4234bindCharBracketPair('{', '\\{');
4235LatexCmds.langle = bind(Bracket, L, '&lang;', '&rang;', '\\langle ', '\\rangle ');
4236LatexCmds.rangle = bind(Bracket, R, '&lang;', '&rang;', '\\langle ', '\\rangle ');
4237CharCmds['|'] = bind(Bracket, L, '|', '|', '|', '|');
4238LatexCmds.lVert = bind(Bracket, L, '&#8741;', '&#8741;', '\\lVert ', '\\rVert ');
4239LatexCmds.rVert = bind(Bracket, R, '&#8741;', '&#8741;', '\\lVert ', '\\rVert ');
4240
4241LatexCmds.left = P(MathCommand, function(_) {
4242  _.parser = function() {
4243    var regex = Parser.regex;
4244    var string = Parser.string;
4245    var succeed = Parser.succeed;
4246    var optWhitespace = Parser.optWhitespace;
4247
4248    return optWhitespace.then(regex(/^(?:[([|]|\\\{|\\langle(?![a-zA-Z])|\\lVert(?![a-zA-Z]))/))
4249      .then(function(ctrlSeq) {
4250        var open = (ctrlSeq.charAt(0) === '\\' ? ctrlSeq.slice(1) : ctrlSeq);
4251	if (ctrlSeq=="\\langle") { open = '&lang;'; ctrlSeq = ctrlSeq + ' '; }
4252	if (ctrlSeq=="\\lVert") { open = '&#8741;'; ctrlSeq = ctrlSeq + ' '; }
4253        return latexMathParser.then(function (block) {
4254          return string('\\right').skip(optWhitespace)
4255            .then(regex(/^(?:[\])|]|\\\}|\\rangle(?![a-zA-Z])|\\rVert(?![a-zA-Z]))/)).map(function(end) {
4256              var close = (end.charAt(0) === '\\' ? end.slice(1) : end);
4257	      if (end=="\\rangle") { close = '&rang;'; end = end + ' '; }
4258	      if (end=="\\rVert") { close = '&#8741;'; end = end + ' '; }
4259              var cmd = Bracket(0, open, close, ctrlSeq, end);
4260              cmd.blocks = [ block ];
4261              block.adopt(cmd, 0, 0);
4262              return cmd;
4263            })
4264          ;
4265        });
4266      })
4267    ;
4268  };
4269});
4270
4271LatexCmds.right = P(MathCommand, function(_) {
4272  _.parser = function() {
4273    return Parser.fail('unmatched \\right');
4274  };
4275});
4276
4277var Binomial =
4278LatexCmds.binom =
4279LatexCmds.binomial = P(P(MathCommand, DelimsMixin), function(_, super_) {
4280  _.ctrlSeq = '\\binom';
4281  _.htmlTemplate =
4282      '<span class="mq-non-leaf">'
4283    +   '<span class="mq-paren mq-scaled">(</span>'
4284    +   '<span class="mq-non-leaf">'
4285    +     '<span class="mq-array mq-non-leaf">'
4286    +       '<span>&0</span>'
4287    +       '<span>&1</span>'
4288    +     '</span>'
4289    +   '</span>'
4290    +   '<span class="mq-paren mq-scaled">)</span>'
4291    + '</span>'
4292  ;
4293  _.textTemplate = ['choose(',',',')'];
4294});
4295
4296var Choose =
4297LatexCmds.choose = P(Binomial, function(_) {
4298  _.createLeftOf = LiveFraction.prototype.createLeftOf;
4299});
4300
4301LatexCmds.editable = // backcompat with before cfd3620 on #233
4302LatexCmds.MathQuillMathField = P(MathCommand, function(_, super_) {
4303  _.ctrlSeq = '\\MathQuillMathField';
4304  _.htmlTemplate =
4305      '<span class="mq-editable-field">'
4306    +   '<span class="mq-root-block">&0</span>'
4307    + '</span>'
4308  ;
4309  _.parser = function() {
4310    var self = this,
4311      string = Parser.string, regex = Parser.regex, succeed = Parser.succeed;
4312    return string('[').then(regex(/^[a-z][a-z0-9]*/i)).skip(string(']'))
4313      .map(function(name) { self.name = name; }).or(succeed())
4314      .then(super_.parser.call(self));
4315  };
4316  _.finalizeTree = function(options) {
4317    var ctrlr = Controller(this.ends[L], this.jQ, options);
4318    ctrlr.KIND_OF_MQ = 'MathField';
4319    ctrlr.editable = true;
4320    ctrlr.createTextarea();
4321    ctrlr.editablesTextareaEvents();
4322    ctrlr.cursor.insAtRightEnd(ctrlr.root);
4323    RootBlockMixin(ctrlr.root);
4324  };
4325  _.registerInnerField = function(innerFields, MathField) {
4326    innerFields.push(innerFields[this.name] = MathField(this.ends[L].controller));
4327  };
4328  _.latex = function(){ return this.ends[L].latex(); };
4329  _.text = function(){ return this.ends[L].text(); };
4330});
4331
4332// Embed arbitrary things
4333// Probably the closest DOM analogue would be an iframe?
4334// From MathQuill's perspective, it's a Symbol, it can be
4335// anywhere and the cursor can go around it but never in it.
4336// Create by calling public API method .dropEmbedded(),
4337// or by calling the global public API method .registerEmbed()
4338// and rendering LaTeX like \embed{registeredName} (see test).
4339var Embed = LatexCmds.embed = P(Symbol, function(_, super_) {
4340  _.setOptions = function(options) {
4341    function noop () { return ""; }
4342    this.text = options.text || noop;
4343    this.htmlTemplate = options.htmlString || "";
4344    this.latex = options.latex || noop;
4345    return this;
4346  };
4347  _.parser = function() {
4348    var self = this,
4349      string = Parser.string, regex = Parser.regex, succeed = Parser.succeed;
4350    return string('{').then(regex(/^[a-z][a-z0-9]*/i)).skip(string('}'))
4351      .then(function(name) {
4352        // the chars allowed in the optional data block are arbitrary other than
4353        // excluding curly braces and square brackets (which'd be too confusing)
4354        return string('[').then(regex(/^[-\w\s]*/)).skip(string(']'))
4355          .or(succeed()).map(function(data) {
4356            return self.setOptions(EMBEDS[name](data));
4357          })
4358        ;
4359      })
4360    ;
4361  };
4362});
4363/************************************
4364 * Symbols for Advanced Mathematics
4365 ***********************************/
4366
4367LatexCmds.notin =
4368LatexCmds.cong =
4369LatexCmds.equiv =
4370LatexCmds.oplus =
4371LatexCmds.otimes = P(BinaryOperator, function(_, super_) {
4372  _.init = function(latex) {
4373    super_.init.call(this, '\\'+latex+' ', '&'+latex+';');
4374  };
4375});
4376
4377LatexCmds['\u2260'] = LatexCmds.ne = LatexCmds.neq = bind(BinaryOperator,'\\ne ','&ne;');
4378
4379LatexCmds['\u2217'] = LatexCmds.ast = LatexCmds.star = LatexCmds.loast = LatexCmds.lowast =
4380  bind(BinaryOperator,'\\ast ','&lowast;');
4381
4382LatexCmds.therefor = LatexCmds.therefore =
4383  bind(BinaryOperator,'\\therefore ','&there4;');
4384
4385LatexCmds.cuz = // l33t
4386LatexCmds.because = bind(BinaryOperator,'\\because ','&#8757;');
4387
4388LatexCmds.prop = LatexCmds.propto = bind(BinaryOperator,'\\propto ','&prop;');
4389
4390LatexCmds['\u2248'] = LatexCmds.asymp = LatexCmds.approx = bind(BinaryOperator,'\\approx ','&asymp;');
4391
4392LatexCmds.isin = LatexCmds['in'] = bind(BinaryOperator,'\\in ','&isin;');
4393
4394LatexCmds.ni = LatexCmds.contains = bind(BinaryOperator,'\\ni ','&ni;');
4395
4396LatexCmds.notni = LatexCmds.niton = LatexCmds.notcontains = LatexCmds.doesnotcontain =
4397  bind(BinaryOperator,'\\not\\ni ','&#8716;');
4398
4399LatexCmds.sub = LatexCmds.subset = bind(BinaryOperator,'\\subset ','&sub;');
4400
4401LatexCmds.sup = LatexCmds.supset = LatexCmds.superset =
4402  bind(BinaryOperator,'\\supset ','&sup;');
4403
4404LatexCmds.nsub = LatexCmds.notsub =
4405LatexCmds.nsubset = LatexCmds.notsubset =
4406  bind(BinaryOperator,'\\not\\subset ','&#8836;');
4407
4408LatexCmds.nsup = LatexCmds.notsup =
4409LatexCmds.nsupset = LatexCmds.notsupset =
4410LatexCmds.nsuperset = LatexCmds.notsuperset =
4411  bind(BinaryOperator,'\\not\\supset ','&#8837;');
4412
4413LatexCmds.sube = LatexCmds.subeq = LatexCmds.subsete = LatexCmds.subseteq =
4414  bind(BinaryOperator,'\\subseteq ','&sube;');
4415
4416LatexCmds.supe = LatexCmds.supeq =
4417LatexCmds.supsete = LatexCmds.supseteq =
4418LatexCmds.supersete = LatexCmds.superseteq =
4419  bind(BinaryOperator,'\\supseteq ','&supe;');
4420
4421LatexCmds.nsube = LatexCmds.nsubeq =
4422LatexCmds.notsube = LatexCmds.notsubeq =
4423LatexCmds.nsubsete = LatexCmds.nsubseteq =
4424LatexCmds.notsubsete = LatexCmds.notsubseteq =
4425  bind(BinaryOperator,'\\not\\subseteq ','&#8840;');
4426
4427LatexCmds.nsupe = LatexCmds.nsupeq =
4428LatexCmds.notsupe = LatexCmds.notsupeq =
4429LatexCmds.nsupsete = LatexCmds.nsupseteq =
4430LatexCmds.notsupsete = LatexCmds.notsupseteq =
4431LatexCmds.nsupersete = LatexCmds.nsuperseteq =
4432LatexCmds.notsupersete = LatexCmds.notsuperseteq =
4433  bind(BinaryOperator,'\\not\\supseteq ','&#8841;');
4434
4435
4436//the canonical sets of numbers
4437LatexCmds.N = LatexCmds.naturals = LatexCmds.Naturals =
4438  bind(VanillaSymbol,'\\mathbb{N}','&#8469;');
4439
4440LatexCmds.P =
4441LatexCmds.primes = LatexCmds.Primes =
4442LatexCmds.projective = LatexCmds.Projective =
4443LatexCmds.probability = LatexCmds.Probability =
4444  bind(VanillaSymbol,'\\mathbb{P}','&#8473;');
4445
4446LatexCmds.Z = LatexCmds.integers = LatexCmds.Integers =
4447  bind(VanillaSymbol,'\\mathbb{Z}','&#8484;');
4448
4449LatexCmds.Q = LatexCmds.rationals = LatexCmds.Rationals =
4450  bind(VanillaSymbol,'\\mathbb{Q}','&#8474;');
4451
4452LatexCmds.R = LatexCmds.reals = LatexCmds.Reals =
4453  bind(VanillaSymbol,'\\mathbb{R}','&#8477;');
4454
4455LatexCmds.C =
4456LatexCmds.complex = LatexCmds.Complex =
4457LatexCmds.complexes = LatexCmds.Complexes =
4458LatexCmds.complexplane = LatexCmds.Complexplane = LatexCmds.ComplexPlane =
4459  bind(VanillaSymbol,'\\mathbb{C}','&#8450;');
4460
4461LatexCmds.H = LatexCmds.Hamiltonian = LatexCmds.quaternions = LatexCmds.Quaternions =
4462  bind(VanillaSymbol,'\\mathbb{H}','&#8461;');
4463
4464//spacing
4465LatexCmds.quad = LatexCmds.emsp = bind(VanillaSymbol,'\\quad ','    ');
4466LatexCmds.qquad = bind(VanillaSymbol,'\\qquad ','        ');
4467/* spacing special characters, gonna have to implement this in LatexCommandInput::onText somehow
4468case ',':
4469  return VanillaSymbol('\\, ',' ');
4470case ':':
4471  return VanillaSymbol('\\: ','  ');
4472case ';':
4473  return VanillaSymbol('\\; ','   ');
4474case '!':
4475  return Symbol('\\! ','<span style="margin-right:-.2em"></span>');
4476*/
4477
4478//binary operators
4479LatexCmds.diamond = bind(VanillaSymbol, '\\diamond ', '&#9671;');
4480LatexCmds.bigtriangleup = bind(VanillaSymbol, '\\bigtriangleup ', '&#9651;');
4481LatexCmds.ominus = bind(VanillaSymbol, '\\ominus ', '&#8854;');
4482LatexCmds.uplus = bind(VanillaSymbol, '\\uplus ', '&#8846;');
4483LatexCmds.bigtriangledown = bind(VanillaSymbol, '\\bigtriangledown ', '&#9661;');
4484LatexCmds.sqcap = bind(VanillaSymbol, '\\sqcap ', '&#8851;');
4485LatexCmds.triangleleft = bind(VanillaSymbol, '\\triangleleft ', '&#8882;');
4486LatexCmds.sqcup = bind(VanillaSymbol, '\\sqcup ', '&#8852;');
4487LatexCmds.triangleright = bind(VanillaSymbol, '\\triangleright ', '&#8883;');
4488//circledot is not a not real LaTex command see https://github.com/mathquill/mathquill/pull/552 for more details
4489LatexCmds.odot = LatexCmds.circledot = bind(VanillaSymbol, '\\odot ', '&#8857;');
4490LatexCmds.bigcirc = bind(VanillaSymbol, '\\bigcirc ', '&#9711;');
4491LatexCmds.dagger = bind(VanillaSymbol, '\\dagger ', '&#0134;');
4492LatexCmds.ddagger = bind(VanillaSymbol, '\\ddagger ', '&#135;');
4493LatexCmds.wr = bind(VanillaSymbol, '\\wr ', '&#8768;');
4494LatexCmds.amalg = bind(VanillaSymbol, '\\amalg ', '&#8720;');
4495
4496//relationship symbols
4497LatexCmds.models = bind(VanillaSymbol, '\\models ', '&#8872;');
4498LatexCmds.prec = bind(VanillaSymbol, '\\prec ', '&#8826;');
4499LatexCmds.succ = bind(VanillaSymbol, '\\succ ', '&#8827;');
4500LatexCmds.preceq = bind(VanillaSymbol, '\\preceq ', '&#8828;');
4501LatexCmds.succeq = bind(VanillaSymbol, '\\succeq ', '&#8829;');
4502LatexCmds.simeq = bind(VanillaSymbol, '\\simeq ', '&#8771;');
4503LatexCmds.mid = bind(VanillaSymbol, '\\mid ', '&#8739;');
4504LatexCmds.ll = bind(VanillaSymbol, '\\ll ', '&#8810;');
4505LatexCmds.gg = bind(VanillaSymbol, '\\gg ', '&#8811;');
4506LatexCmds.parallel = bind(VanillaSymbol, '\\parallel ', '&#8741;');
4507LatexCmds.nparallel = bind(VanillaSymbol, '\\nparallel ', '&#8742;');
4508LatexCmds.bowtie = bind(VanillaSymbol, '\\bowtie ', '&#8904;');
4509LatexCmds.sqsubset = bind(VanillaSymbol, '\\sqsubset ', '&#8847;');
4510LatexCmds.sqsupset = bind(VanillaSymbol, '\\sqsupset ', '&#8848;');
4511LatexCmds.smile = bind(VanillaSymbol, '\\smile ', '&#8995;');
4512LatexCmds.sqsubseteq = bind(VanillaSymbol, '\\sqsubseteq ', '&#8849;');
4513LatexCmds.sqsupseteq = bind(VanillaSymbol, '\\sqsupseteq ', '&#8850;');
4514LatexCmds.doteq = bind(VanillaSymbol, '\\doteq ', '&#8784;');
4515LatexCmds.frown = bind(VanillaSymbol, '\\frown ', '&#8994;');
4516LatexCmds.vdash = bind(VanillaSymbol, '\\vdash ', '&#8870;');
4517LatexCmds.dashv = bind(VanillaSymbol, '\\dashv ', '&#8867;');
4518LatexCmds.nless = bind(VanillaSymbol, '\\nless ', '&#8814;');
4519LatexCmds.ngtr = bind(VanillaSymbol, '\\ngtr ', '&#8815;');
4520
4521//arrows
4522LatexCmds.longleftarrow = bind(VanillaSymbol, '\\longleftarrow ', '&#8592;');
4523LatexCmds.longrightarrow = bind(VanillaSymbol, '\\longrightarrow ', '&#8594;');
4524LatexCmds.Longleftarrow = bind(VanillaSymbol, '\\Longleftarrow ', '&#8656;');
4525LatexCmds.Longrightarrow = bind(VanillaSymbol, '\\Longrightarrow ', '&#8658;');
4526LatexCmds.longleftrightarrow = bind(VanillaSymbol, '\\longleftrightarrow ', '&#8596;');
4527LatexCmds.updownarrow = bind(VanillaSymbol, '\\updownarrow ', '&#8597;');
4528LatexCmds.Longleftrightarrow = bind(VanillaSymbol, '\\Longleftrightarrow ', '&#8660;');
4529LatexCmds.Updownarrow = bind(VanillaSymbol, '\\Updownarrow ', '&#8661;');
4530LatexCmds.mapsto = bind(VanillaSymbol, '\\mapsto ', '&#8614;');
4531LatexCmds.nearrow = bind(VanillaSymbol, '\\nearrow ', '&#8599;');
4532LatexCmds.hookleftarrow = bind(VanillaSymbol, '\\hookleftarrow ', '&#8617;');
4533LatexCmds.hookrightarrow = bind(VanillaSymbol, '\\hookrightarrow ', '&#8618;');
4534LatexCmds.searrow = bind(VanillaSymbol, '\\searrow ', '&#8600;');
4535LatexCmds.leftharpoonup = bind(VanillaSymbol, '\\leftharpoonup ', '&#8636;');
4536LatexCmds.rightharpoonup = bind(VanillaSymbol, '\\rightharpoonup ', '&#8640;');
4537LatexCmds.swarrow = bind(VanillaSymbol, '\\swarrow ', '&#8601;');
4538LatexCmds.leftharpoondown = bind(VanillaSymbol, '\\leftharpoondown ', '&#8637;');
4539LatexCmds.rightharpoondown = bind(VanillaSymbol, '\\rightharpoondown ', '&#8641;');
4540LatexCmds.nwarrow = bind(VanillaSymbol, '\\nwarrow ', '&#8598;');
4541
4542//Misc
4543LatexCmds.ldots = bind(VanillaSymbol, '\\ldots ', '&#8230;');
4544LatexCmds.cdots = bind(VanillaSymbol, '\\cdots ', '&#8943;');
4545LatexCmds.vdots = bind(VanillaSymbol, '\\vdots ', '&#8942;');
4546LatexCmds.ddots = bind(VanillaSymbol, '\\ddots ', '&#8945;');
4547LatexCmds.surd = bind(VanillaSymbol, '\\surd ', '&#8730;');
4548LatexCmds.triangle = bind(VanillaSymbol, '\\triangle ', '&#9651;');
4549LatexCmds.ell = bind(VanillaSymbol, '\\ell ', '&#8467;');
4550LatexCmds.top = bind(VanillaSymbol, '\\top ', '&#8868;');
4551LatexCmds.flat = bind(VanillaSymbol, '\\flat ', '&#9837;');
4552LatexCmds.natural = bind(VanillaSymbol, '\\natural ', '&#9838;');
4553LatexCmds.sharp = bind(VanillaSymbol, '\\sharp ', '&#9839;');
4554LatexCmds.wp = bind(VanillaSymbol, '\\wp ', '&#8472;');
4555LatexCmds.bot = bind(VanillaSymbol, '\\bot ', '&#8869;');
4556LatexCmds.clubsuit = bind(VanillaSymbol, '\\clubsuit ', '&#9827;');
4557LatexCmds.diamondsuit = bind(VanillaSymbol, '\\diamondsuit ', '&#9826;');
4558LatexCmds.heartsuit = bind(VanillaSymbol, '\\heartsuit ', '&#9825;');
4559LatexCmds.spadesuit = bind(VanillaSymbol, '\\spadesuit ', '&#9824;');
4560//not real LaTex command see https://github.com/mathquill/mathquill/pull/552 for more details
4561LatexCmds.parallelogram = bind(VanillaSymbol, '\\parallelogram ', '&#9649;');
4562LatexCmds.square = bind(VanillaSymbol, '\\square ', '&#11036;');
4563
4564//variable-sized
4565LatexCmds.oint = bind(VanillaSymbol, '\\oint ', '&#8750;');
4566LatexCmds.bigcap = bind(VanillaSymbol, '\\bigcap ', '&#8745;');
4567LatexCmds.bigcup = bind(VanillaSymbol, '\\bigcup ', '&#8746;');
4568LatexCmds.bigsqcup = bind(VanillaSymbol, '\\bigsqcup ', '&#8852;');
4569LatexCmds.bigvee = bind(VanillaSymbol, '\\bigvee ', '&#8744;');
4570LatexCmds.bigwedge = bind(VanillaSymbol, '\\bigwedge ', '&#8743;');
4571LatexCmds.bigodot = bind(VanillaSymbol, '\\bigodot ', '&#8857;');
4572LatexCmds.bigotimes = bind(VanillaSymbol, '\\bigotimes ', '&#8855;');
4573LatexCmds.bigoplus = bind(VanillaSymbol, '\\bigoplus ', '&#8853;');
4574LatexCmds.biguplus = bind(VanillaSymbol, '\\biguplus ', '&#8846;');
4575
4576//delimiters
4577LatexCmds.lfloor = bind(VanillaSymbol, '\\lfloor ', '&#8970;');
4578LatexCmds.rfloor = bind(VanillaSymbol, '\\rfloor ', '&#8971;');
4579LatexCmds.lceil = bind(VanillaSymbol, '\\lceil ', '&#8968;');
4580LatexCmds.rceil = bind(VanillaSymbol, '\\rceil ', '&#8969;');
4581LatexCmds.opencurlybrace = LatexCmds.lbrace = bind(VanillaSymbol, '\\lbrace ', '{');
4582LatexCmds.closecurlybrace = LatexCmds.rbrace = bind(VanillaSymbol, '\\rbrace ', '}');
4583LatexCmds.lbrack = bind(VanillaSymbol, '[');
4584LatexCmds.rbrack = bind(VanillaSymbol, ']');
4585
4586//various symbols
4587LatexCmds.slash = bind(VanillaSymbol, '/');
4588LatexCmds.vert = bind(VanillaSymbol,'|');
4589LatexCmds.perp = LatexCmds.perpendicular = bind(VanillaSymbol,'\\perp ','&perp;');
4590LatexCmds.nabla = LatexCmds.del = bind(VanillaSymbol,'\\nabla ','&nabla;');
4591LatexCmds.hbar = bind(VanillaSymbol,'\\hbar ','&#8463;');
4592
4593LatexCmds.AA = LatexCmds.Angstrom = LatexCmds.angstrom =
4594  bind(VanillaSymbol,'\\text\\AA ','&#8491;');
4595
4596LatexCmds.ring = LatexCmds.circ = LatexCmds.circle =
4597  bind(VanillaSymbol,'\\circ ','&#8728;');
4598
4599LatexCmds.bull = LatexCmds.bullet = bind(VanillaSymbol,'\\bullet ','&bull;');
4600
4601LatexCmds.setminus = LatexCmds.smallsetminus =
4602  bind(VanillaSymbol,'\\setminus ','&#8726;');
4603
4604LatexCmds.not = //bind(Symbol,'\\not ','<span class="not">/</span>');
4605LatexCmds['\u00ac'] = LatexCmds.neg = bind(VanillaSymbol,'\\neg ','&not;');
4606
4607LatexCmds['\u2026'] = LatexCmds.dots = LatexCmds.ellip = LatexCmds.hellip =
4608LatexCmds.ellipsis = LatexCmds.hellipsis =
4609  bind(VanillaSymbol,'\\dots ','&hellip;');
4610
4611LatexCmds.converges =
4612LatexCmds.darr = LatexCmds.dnarr = LatexCmds.dnarrow = LatexCmds.downarrow 
vendor: 23,851 bytes, lines 4612-5231
4612=
4613  bind(VanillaSymbol,'\\downarrow ','&darr;');
4614
4615LatexCmds.dArr = LatexCmds.dnArr = LatexCmds.dnArrow = LatexCmds.Downarrow =
4616  bind(VanillaSymbol,'\\Downarrow ','&dArr;');
4617
4618LatexCmds.diverges = LatexCmds.uarr = LatexCmds.uparrow =
4619  bind(VanillaSymbol,'\\uparrow ','&uarr;');
4620
4621LatexCmds.uArr = LatexCmds.Uparrow = bind(VanillaSymbol,'\\Uparrow ','&uArr;');
4622
4623LatexCmds.to = bind(BinaryOperator,'\\to ','&rarr;');
4624
4625LatexCmds.rarr = LatexCmds.rightarrow = bind(VanillaSymbol,'\\rightarrow ','&rarr;');
4626
4627LatexCmds.implies = bind(BinaryOperator,'\\Rightarrow ','&rArr;');
4628
4629LatexCmds.rArr = LatexCmds.Rightarrow = bind(VanillaSymbol,'\\Rightarrow ','&rArr;');
4630
4631LatexCmds.gets = bind(BinaryOperator,'\\gets ','&larr;');
4632
4633LatexCmds.larr = LatexCmds.leftarrow = bind(VanillaSymbol,'\\leftarrow ','&larr;');
4634
4635LatexCmds.impliedby = bind(BinaryOperator,'\\Leftarrow ','&lArr;');
4636
4637LatexCmds.lArr = LatexCmds.Leftarrow = bind(VanillaSymbol,'\\Leftarrow ','&lArr;');
4638
4639LatexCmds.harr = LatexCmds.lrarr = LatexCmds.leftrightarrow =
4640  bind(VanillaSymbol,'\\leftrightarrow ','&harr;');
4641
4642LatexCmds.iff = bind(BinaryOperator,'\\Leftrightarrow ','&hArr;');
4643
4644LatexCmds.hArr = LatexCmds.lrArr = LatexCmds.Leftrightarrow =
4645  bind(VanillaSymbol,'\\Leftrightarrow ','&hArr;');
4646
4647LatexCmds.Re = LatexCmds.Real = LatexCmds.real = bind(VanillaSymbol,'\\Re ','&real;');
4648
4649LatexCmds.Im = LatexCmds.imag =
4650LatexCmds.image = LatexCmds.imagin = LatexCmds.imaginary = LatexCmds.Imaginary =
4651  bind(VanillaSymbol,'\\Im ','&image;');
4652
4653LatexCmds.part = LatexCmds.partial = bind(VanillaSymbol,'\\partial ','&part;');
4654
4655LatexCmds.infty = LatexCmds.infin = LatexCmds.infinity =
4656  bind(VanillaSymbol,'\\infty ','&infin;');
4657
4658LatexCmds.pounds = bind(VanillaSymbol,'\\pounds ','&pound;');
4659
4660LatexCmds.alef = LatexCmds.alefsym = LatexCmds.aleph = LatexCmds.alephsym =
4661  bind(VanillaSymbol,'\\aleph ','&alefsym;');
4662
4663LatexCmds.xist = //LOL
4664LatexCmds.xists = LatexCmds.exist = LatexCmds.exists =
4665  bind(VanillaSymbol,'\\exists ','&exist;');
4666
4667LatexCmds.nexists = LatexCmds.nexist =
4668      bind(VanillaSymbol, '\\nexists ', '&#8708;');
4669
4670LatexCmds.and = LatexCmds.land = LatexCmds.wedge =
4671  bind(BinaryOperator,'\\wedge ','&and;');
4672
4673LatexCmds.or = LatexCmds.lor = LatexCmds.vee = bind(BinaryOperator,'\\vee ','&or;');
4674
4675LatexCmds.o = LatexCmds.O =
4676LatexCmds.empty = LatexCmds.emptyset =
4677LatexCmds.oslash = LatexCmds.Oslash =
4678LatexCmds.nothing = LatexCmds.varnothing =
4679  bind(BinaryOperator,'\\varnothing ','&empty;');
4680
4681LatexCmds.cup = LatexCmds.union = bind(BinaryOperator,'\\cup ','&cup;');
4682
4683LatexCmds.cap = LatexCmds.intersect = LatexCmds.intersection =
4684  bind(BinaryOperator,'\\cap ','&cap;');
4685
4686// FIXME: the correct LaTeX would be ^\circ but we can't parse that
4687LatexCmds.deg = LatexCmds.degree = bind(VanillaSymbol,'\\degree ','&deg;');
4688
4689LatexCmds.ang = LatexCmds.angle = bind(VanillaSymbol,'\\angle ','&ang;');
4690LatexCmds.measuredangle = bind(VanillaSymbol,'\\measuredangle ','&#8737;');
4691/*********************************
4692 * Symbols for Basic Mathematics
4693 ********************************/
4694
4695var Digit = P(VanillaSymbol, function(_, super_) {
4696  _.createLeftOf = function(cursor) {
4697    if (cursor.options.autoSubscriptNumerals
4698        && cursor.parent !== cursor.parent.parent.sub
4699        && ((cursor[L] instanceof Variable && cursor[L].isItalic !== false)
4700            || (cursor[L] instanceof SupSub
4701                && cursor[L][L] instanceof Variable
4702                && cursor[L][L].isItalic !== false))) {
4703      LatexCmds._().createLeftOf(cursor);
4704      super_.createLeftOf.call(this, cursor);
4705      cursor.insRightOf(cursor.parent.parent);
4706    }
4707    else super_.createLeftOf.call(this, cursor);
4708  };
4709});
4710
4711var Variable = P(Symbol, function(_, super_) {
4712  _.init = function(ch, html) {
4713    super_.init.call(this, ch, '<var>'+(html || ch)+'</var>');
4714  };
4715  _.text = function() {
4716    var text = this.ctrlSeq;
4717    if (this.isPartOfOperator) {
4718      if (text[0] == '\\') {
4719        text = text.slice(1, text.length);
4720      }
4721      else if (text[text.length-1] == ' ') {
4722        text = text.slice (0, -1);
4723      }
4724    } else {
4725      if (this[L] && !(this[L] instanceof Variable)
4726          && !(this[L] instanceof BinaryOperator)
4727          && this[L].ctrlSeq !== '\\ ')
4728        text = '*' + text;
4729      if (this[R] && !(this[R] instanceof BinaryOperator)
4730          && !(this[R] instanceof SupSub))
4731        text += '*';
4732    }
4733    return text;
4734  };
4735});
4736
4737Options.p.autoCommands = { _maxLength: 0 };
4738optionProcessors.autoCommands = function(cmds) {
4739  if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) {
4740    throw '"'+cmds+'" not a space-delimited list of only letters';
4741  }
4742  var list = cmds.split(' '), dict = {}, maxLength = 0;
4743  for (var i = 0; i < list.length; i += 1) {
4744    var cmd = list[i];
4745    if (cmd.length < 2) {
4746      throw 'autocommand "'+cmd+'" not minimum length of 2';
4747    }
4748    if (LatexCmds[cmd] === OperatorName) {
4749      throw '"' + cmd + '" is a built-in operator name';
4750    }
4751    dict[cmd] = 1;
4752    maxLength = max(maxLength, cmd.length);
4753  }
4754  dict._maxLength = maxLength;
4755  return dict;
4756};
4757
4758var Letter = P(Variable, function(_, super_) {
4759  _.init = function(ch) { return super_.init.call(this, this.letter = ch); };
4760  _.createLeftOf = function(cursor) {
4761    super_.createLeftOf.apply(this, arguments);
4762    var autoCmds = cursor.options.autoCommands, maxLength = autoCmds._maxLength;
4763    if (maxLength > 0) {
4764      // want longest possible autocommand, so join together longest
4765      // sequence of letters
4766      var str = '', l = this, i = 0;
4767      // FIXME: l.ctrlSeq === l.letter checks if first or last in an operator name
4768      while (l instanceof Letter && l.ctrlSeq === l.letter && i < maxLength) {
4769        str = l.letter + str, l = l[L], i += 1;
4770      }
4771      // check for an autocommand, going thru substrings longest to shortest
4772      while (str.length) {
4773        if (autoCmds.hasOwnProperty(str)) {
4774          for (var i = 1, l = this; i < str.length; i += 1, l = l[L]);
4775          Fragment(l, this).remove();
4776          cursor[L] = l[L];
4777          return LatexCmds[str](str).createLeftOf(cursor);
4778        }
4779        str = str.slice(1);
4780      }
4781    }
4782  };
4783  _.italicize = function(bool) {
4784    this.isItalic = bool;
4785    this.isPartOfOperator = !bool;
4786    this.jQ.toggleClass('mq-operator-name', !bool);
4787    return this;
4788  };
4789  _.finalizeTree = _.siblingDeleted = _.siblingCreated = function(opts, dir) {
4790    // don't auto-un-italicize if the sibling to my right changed (dir === R or
4791    // undefined) and it's now a Letter, it will un-italicize everyone
4792    if (dir !== L && this[R] instanceof Letter) return;
4793    this.autoUnItalicize(opts);
4794  };
4795  _.autoUnItalicize = function(opts) {
4796    var autoOps = opts.autoOperatorNames;
4797    if (autoOps._maxLength === 0) return;
4798    // want longest possible operator names, so join together entire contiguous
4799    // sequence of letters
4800    var str = this.letter;
4801    for (var l = this[L]; l instanceof Letter; l = l[L]) str = l.letter + str;
4802    for (var r = this[R]; r instanceof Letter; r = r[R]) str += r.letter;
4803
4804    // removeClass and delete flags from all letters before figuring out
4805    // which, if any, are part of an operator name
4806    Fragment(l[R] || this.parent.ends[L], r[L] || this.parent.ends[R]).each(function(el) {
4807      el.italicize(true).jQ.removeClass('mq-first mq-last mq-followed-by-supsub');
4808      el.ctrlSeq = el.letter;
4809    });
4810
4811    // check for operator names: at each position from left to right, check
4812    // substrings from longest to shortest
4813    outer: for (var i = 0, first = l[R] || this.parent.ends[L]; i < str.length; i += 1, first = first[R]) {
4814      for (var len = min(autoOps._maxLength, str.length - i); len > 0; len -= 1) {
4815        var word = str.slice(i, i + len);
4816        if (autoOps.hasOwnProperty(word)) {
4817          for (var j = 0, letter = first; j < len; j += 1, letter = letter[R]) {
4818            letter.italicize(false);
4819            var last = letter;
4820          }
4821
4822          var isBuiltIn = BuiltInOpNames.hasOwnProperty(word);
4823          first.ctrlSeq = (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq;
4824          last.ctrlSeq += (isBuiltIn ? ' ' : '}');
4825          if (TwoWordOpNames.hasOwnProperty(word)) last[L][L][L].jQ.addClass('mq-last');
4826          if (!shouldOmitPadding(first[L])) first.jQ.addClass('mq-first');
4827          if (!shouldOmitPadding(last[R])) {
4828            if (last[R] instanceof SupSub) {
4829              var supsub = last[R]; // XXX monkey-patching, but what's the right thing here?
4830              // Have operatorname-specific code in SupSub? A CSS-like language to style the
4831              // math tree, but which ignores cursor and selection (which CSS can't)?
4832              var respace = supsub.siblingCreated = supsub.siblingDeleted = function() {
4833                supsub.jQ.toggleClass('mq-after-operator-name', !(supsub[R] instanceof Bracket));
4834              };
4835              respace();
4836            }
4837            else {
4838              last.jQ.toggleClass('mq-last', !(last[R] instanceof Bracket));
4839            }
4840          }
4841
4842          i += len - 1;
4843          first = last;
4844          continue outer;
4845        }
4846      }
4847    }
4848  };
4849  function shouldOmitPadding(node) {
4850    // omit padding if no node, or if node already has padding (to avoid double-padding)
4851    return !node || (node instanceof BinaryOperator) || (node instanceof SummationNotation);
4852  }
4853});
4854var BuiltInOpNames = {}; // the set of operator names like \sin, \cos, etc that
4855  // are built-into LaTeX, see Section 3.17 of the Short Math Guide: http://tinyurl.com/jm9okjc
4856  // MathQuill auto-unitalicizes some operator names not in that set, like 'hcf'
4857  // and 'arsinh', which must be exported as \operatorname{hcf} and
4858  // \operatorname{arsinh}. Note: over/under line/arrow \lim variants like
4859  // \varlimsup are not supported
4860var AutoOpNames = Options.p.autoOperatorNames = { _maxLength: 9 }; // the set
4861  // of operator names that MathQuill auto-unitalicizes by default; overridable
4862var TwoWordOpNames = { limsup: 1, liminf: 1, projlim: 1, injlim: 1 };
4863(function() {
4864  var mostOps = ('arg deg det dim exp gcd hom inf ker lg lim ln log max min sup'
4865                 + ' limsup liminf injlim projlim Pr').split(' ');
4866  for (var i = 0; i < mostOps.length; i += 1) {
4867    BuiltInOpNames[mostOps[i]] = AutoOpNames[mostOps[i]] = 1;
4868  }
4869
4870  var builtInTrigs = // why coth but not sech and csch, LaTeX?
4871    'sin cos tan arcsin arccos arctan sinh cosh tanh sec csc cot coth'.split(' ');
4872  for (var i = 0; i < builtInTrigs.length; i += 1) {
4873    BuiltInOpNames[builtInTrigs[i]] = 1;
4874  }
4875
4876  var autoTrigs = 'sin cos tan sec cosec csc cotan cot ctg'.split(' ');
4877  for (var i = 0; i < autoTrigs.length; i += 1) {
4878    AutoOpNames[autoTrigs[i]] =
4879    AutoOpNames['arc'+autoTrigs[i]] =
4880    AutoOpNames[autoTrigs[i]+'h'] =
4881    AutoOpNames['ar'+autoTrigs[i]+'h'] =
4882    AutoOpNames['arc'+autoTrigs[i]+'h'] = 1;
4883  }
4884
4885  // compat with some of the nonstandard LaTeX exported by MathQuill
4886  // before #247. None of these are real LaTeX commands so, seems safe
4887  var moreNonstandardOps = 'gcf hcf lcm proj span'.split(' ');
4888  for (var i = 0; i < moreNonstandardOps.length; i += 1) {
4889    AutoOpNames[moreNonstandardOps[i]] = 1;
4890  }
4891}());
4892optionProcessors.autoOperatorNames = function(cmds) {
4893  if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) {
4894    throw '"'+cmds+'" not a space-delimited list of only letters';
4895  }
4896  var list = cmds.split(' '), dict = {}, maxLength = 0;
4897  for (var i = 0; i < list.length; i += 1) {
4898    var cmd = list[i];
4899    if (cmd.length < 2) {
4900      throw '"'+cmd+'" not minimum length of 2';
4901    }
4902    dict[cmd] = 1;
4903    maxLength = max(maxLength, cmd.length);
4904  }
4905  dict._maxLength = maxLength;
4906  return dict;
4907};
4908var OperatorName = P(Symbol, function(_, super_) {
4909  _.init = function(fn) { this.ctrlSeq = fn; };
4910  _.createLeftOf = function(cursor) {
4911    var fn = this.ctrlSeq;
4912    for (var i = 0; i < fn.length; i += 1) {
4913      Letter(fn.charAt(i)).createLeftOf(cursor);
4914    }
4915  };
4916  _.parser = function() {
4917    var fn = this.ctrlSeq;
4918    var block = MathBlock();
4919    for (var i = 0; i < fn.length; i += 1) {
4920      Letter(fn.charAt(i)).adopt(block, block.ends[R], 0);
4921    }
4922    return Parser.succeed(block.children());
4923  };
4924});
4925for (var fn in AutoOpNames) if (AutoOpNames.hasOwnProperty(fn)) {
4926  LatexCmds[fn] = OperatorName;
4927}
4928LatexCmds.operatorname = P(MathCommand, function(_) {
4929  _.createLeftOf = noop;
4930  _.numBlocks = function() { return 1; };
4931  _.parser = function() {
4932    return latexMathParser.block.map(function(b) { return b.children(); });
4933  };
4934});
4935
4936LatexCmds.f = P(Letter, function(_, super_) {
4937  _.init = function() {
4938    Symbol.p.init.call(this, this.letter = 'f', '<var class="mq-f">f</var>');
4939  };
4940  _.italicize = function(bool) {
4941    this.jQ.html('f').toggleClass('mq-f', bool);
4942    return super_.italicize.apply(this, arguments);
4943  };
4944});
4945
4946// VanillaSymbol's
4947LatexCmds[' '] = LatexCmds.space = bind(VanillaSymbol, '\\ ', '&nbsp;');
4948
4949LatexCmds["'"] = LatexCmds.prime = bind(VanillaSymbol, "'", '&prime;');
4950LatexCmds['\u2033'] = LatexCmds.dprime = bind(VanillaSymbol, '\u2033', '&Prime;');
4951
4952LatexCmds.backslash = bind(VanillaSymbol,'\\backslash ','\\');
4953if (!CharCmds['\\']) CharCmds['\\'] = LatexCmds.backslash;
4954
4955LatexCmds.$ = bind(VanillaSymbol, '\\$', '$');
4956
4957// does not use Symbola font
4958var NonSymbolaSymbol = P(Symbol, function(_, super_) {
4959  _.init = function(ch, html) {
4960    super_.init.call(this, ch, '<span class="mq-nonSymbola">'+(html || ch)+'</span>');
4961  };
4962});
4963
4964LatexCmds['@'] = NonSymbolaSymbol;
4965LatexCmds['&'] = bind(NonSymbolaSymbol, '\\&', '&amp;');
4966LatexCmds['%'] = bind(NonSymbolaSymbol, '\\%', '%');
4967
4968//the following are all Greek to me, but this helped a lot: http://www.ams.org/STIX/ion/stixsig03.html
4969
4970//lowercase Greek letter variables
4971LatexCmds.alpha =
4972LatexCmds.beta =
4973LatexCmds.gamma =
4974LatexCmds.delta =
4975LatexCmds.zeta =
4976LatexCmds.eta =
4977LatexCmds.theta =
4978LatexCmds.iota =
4979LatexCmds.kappa =
4980LatexCmds.mu =
4981LatexCmds.nu =
4982LatexCmds.xi =
4983LatexCmds.rho =
4984LatexCmds.sigma =
4985LatexCmds.tau =
4986LatexCmds.chi =
4987LatexCmds.psi =
4988LatexCmds.omega = P(Variable, function(_, super_) {
4989  _.init = function(latex) {
4990    super_.init.call(this,'\\'+latex+' ','&'+latex+';');
4991  };
4992});
4993
4994//why can't anybody FUCKING agree on these
4995LatexCmds.phi = //W3C or Unicode?
4996  bind(Variable,'\\phi ','&#981;');
4997
4998LatexCmds.phiv = //Elsevier and 9573-13
4999LatexCmds.varphi = //AMS and LaTeX
5000  bind(Variable,'\\varphi ','&phi;');
5001
5002LatexCmds.epsilon = //W3C or Unicode?
5003  bind(Variable,'\\epsilon ','&#1013;');
5004
5005LatexCmds.epsiv = //Elsevier and 9573-13
5006LatexCmds.varepsilon = //AMS and LaTeX
5007  bind(Variable,'\\varepsilon ','&epsilon;');
5008
5009LatexCmds.piv = //W3C/Unicode and Elsevier and 9573-13
5010LatexCmds.varpi = //AMS and LaTeX
5011  bind(Variable,'\\varpi ','&piv;');
5012
5013LatexCmds.sigmaf = //W3C/Unicode
5014LatexCmds.sigmav = //Elsevier
5015LatexCmds.varsigma = //LaTeX
5016  bind(Variable,'\\varsigma ','&sigmaf;');
5017
5018LatexCmds.thetav = //Elsevier and 9573-13
5019LatexCmds.vartheta = //AMS and LaTeX
5020LatexCmds.thetasym = //W3C/Unicode
5021  bind(Variable,'\\vartheta ','&thetasym;');
5022
5023LatexCmds.upsilon = //AMS and LaTeX and W3C/Unicode
5024LatexCmds.upsi = //Elsevier and 9573-13
5025  bind(Variable,'\\upsilon ','&upsilon;');
5026
5027//these aren't even mentioned in the HTML character entity references
5028LatexCmds.gammad = //Elsevier
5029LatexCmds.Gammad = //9573-13 -- WTF, right? I dunno if this was a typo in the reference (see above)
5030LatexCmds.digamma = //LaTeX
5031  bind(Variable,'\\digamma ','&#989;');
5032
5033LatexCmds.kappav = //Elsevier
5034LatexCmds.varkappa = //AMS and LaTeX
5035  bind(Variable,'\\varkappa ','&#1008;');
5036
5037LatexCmds.rhov = //Elsevier and 9573-13
5038LatexCmds.varrho = //AMS and LaTeX
5039  bind(Variable,'\\varrho ','&#1009;');
5040
5041//Greek constants, look best in non-italicized Times New Roman
5042LatexCmds.pi = LatexCmds['\u03c0'] = bind(NonSymbolaSymbol,'\\pi ','&pi;');
5043LatexCmds.lambda = bind(NonSymbolaSymbol,'\\lambda ','&lambda;');
5044
5045//uppercase greek letters
5046
5047LatexCmds.Upsilon = //LaTeX
5048LatexCmds.Upsi = //Elsevier and 9573-13
5049LatexCmds.upsih = //W3C/Unicode "upsilon with hook"
5050LatexCmds.Upsih = //'cos it makes sense to me
5051  bind(Symbol,'\\Upsilon ','<var style="font-family: serif">&upsih;</var>'); //Symbola's 'upsilon with a hook' is a capital Y without hooks :(
5052
5053//other symbols with the same LaTeX command and HTML character entity reference
5054LatexCmds.Gamma =
5055LatexCmds.Delta =
5056LatexCmds.Theta =
5057LatexCmds.Lambda =
5058LatexCmds.Xi =
5059LatexCmds.Pi =
5060LatexCmds.Sigma =
5061LatexCmds.Phi =
5062LatexCmds.Psi =
5063LatexCmds.Omega =
5064LatexCmds.forall = P(VanillaSymbol, function(_, super_) {
5065  _.init = function(latex) {
5066    super_.init.call(this,'\\'+latex+' ','&'+latex+';');
5067  };
5068});
5069
5070// symbols that aren't a single MathCommand, but are instead a whole
5071// Fragment. Creates the Fragment from a LaTeX string
5072var LatexFragment = P(MathCommand, function(_) {
5073  _.init = function(latex) { this.latex = latex; };
5074  _.createLeftOf = function(cursor) {
5075    var block = latexMathParser.parse(this.latex);
5076    block.children().adopt(cursor.parent, cursor[L], cursor[R]);
5077    cursor[L] = block.ends[R];
5078    block.jQize().insertBefore(cursor.jQ);
5079    block.finalizeInsert(cursor.options, cursor);
5080    if (block.ends[R][R].siblingCreated) block.ends[R][R].siblingCreated(cursor.options, L);
5081    if (block.ends[L][L].siblingCreated) block.ends[L][L].siblingCreated(cursor.options, R);
5082    cursor.parent.bubble('reflow');
5083  };
5084  _.parser = function() {
5085    var frag = latexMathParser.parse(this.latex).children();
5086    return Parser.succeed(frag);
5087  };
5088});
5089
5090// for what seems to me like [stupid reasons][1], Unicode provides
5091// subscripted and superscripted versions of all ten Arabic numerals,
5092// as well as [so-called "vulgar fractions"][2].
5093// Nobody really cares about most of them, but some of them actually
5094// predate Unicode, dating back to [ISO-8859-1][3], apparently also
5095// known as "Latin-1", which among other things [Windows-1252][4]
5096// largely coincides with, so Microsoft Word sometimes inserts them
5097// and they get copy-pasted into MathQuill.
5098//
5099// (Irrelevant but funny story: though not a superset of Latin-1 aka
5100// ISO-8859-1, Windows-1252 **is** a strict superset of the "closely
5101// related but distinct"[3] "ISO 8859-1" -- see the lack of a dash
5102// after "ISO"? Completely different character set, like elephants vs
5103// elephant seals, or "Zombies" vs "Zombie Redneck Torture Family".
5104// What kind of idiot would get them confused.
5105// People in fact got them confused so much, it was so common to
5106// mislabel Windows-1252 text as ISO-8859-1, that most modern web
5107// browsers and email clients treat the MIME charset of ISO-8859-1
5108// as actually Windows-1252, behavior now standard in the HTML5 spec.)
5109//
5110// [1]: http://en.wikipedia.org/wiki/Unicode_subscripts_andsuper_scripts
5111// [2]: http://en.wikipedia.org/wiki/Number_Forms
5112// [3]: http://en.wikipedia.org/wiki/ISO/IEC_8859-1
5113// [4]: http://en.wikipedia.org/wiki/Windows-1252
5114LatexCmds['\u00b9'] = bind(LatexFragment, '^1');
5115LatexCmds['\u00b2'] = bind(LatexFragment, '^2');
5116LatexCmds['\u00b3'] = bind(LatexFragment, '^3');
5117LatexCmds['\u00bc'] = bind(LatexFragment, '\\frac14');
5118LatexCmds['\u00bd'] = bind(LatexFragment, '\\frac12');
5119LatexCmds['\u00be'] = bind(LatexFragment, '\\frac34');
5120
5121var PlusMinus = P(BinaryOperator, function(_) {
5122  _.init = VanillaSymbol.prototype.init;
5123
5124  _.contactWeld = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
5125    function determineOpClassType(node) {
5126      if (node[L]) {
5127        // If the left sibling is a binary operator or a separator (comma, semicolon, colon)
5128        // or an open bracket (open parenthesis, open square bracket)
5129        // consider the operator to be unary
5130        if (node[L] instanceof BinaryOperator || /^[,;:\(\[]$/.test(node[L].ctrlSeq)) {
5131          return '';
5132        }
5133      } else if (node.parent && node.parent.parent && node.parent.parent.isStyleBlock()) {
5134        //if we are in a style block at the leftmost edge, determine unary/binary based on
5135        //the style block
5136        //this allows style blocks to be transparent for unary/binary purposes
5137        return determineOpClassType(node.parent.parent);
5138      } else {
5139        return '';
5140      }
5141
5142      return 'mq-binary-operator';
5143    };
5144    
5145    if (dir === R) return; // ignore if sibling only changed on the right
5146    this.jQ[0].className = determineOpClassType(this);
5147    return this;
5148  };
5149});
5150
5151LatexCmds['+'] = bind(PlusMinus, '+', '+');
5152//yes, these are different dashes, I think one is an en dash and the other is a hyphen
5153LatexCmds['\u2013'] = LatexCmds['-'] = bind(PlusMinus, '-', '&minus;');
5154LatexCmds['\u00b1'] = LatexCmds.pm = LatexCmds.plusmn = LatexCmds.plusminus =
5155  bind(PlusMinus,'\\pm ','&plusmn;');
5156LatexCmds.mp = LatexCmds.mnplus = LatexCmds.minusplus =
5157  bind(PlusMinus,'\\mp ','&#8723;');
5158
5159CharCmds['*'] = LatexCmds.sdot = LatexCmds.cdot =
5160  bind(BinaryOperator, '\\cdot ', '&middot;', '*');
5161//semantically should be &sdot;, but &middot; looks better
5162
5163var Inequality = P(BinaryOperator, function(_, super_) {
5164  _.init = function(data, strict) {
5165    this.data = data;
5166    this.strict = strict;
5167    var strictness = (strict ? 'Strict' : '');
5168    super_.init.call(this, data['ctrlSeq'+strictness], data['html'+strictness],
5169                     data['text'+strictness]);
5170  };
5171  _.swap = function(strict) {
5172    this.strict = strict;
5173    var strictness = (strict ? 'Strict' : '');
5174    this.ctrlSeq = this.data['ctrlSeq'+strictness];
5175    this.jQ.html(this.data['html'+strictness]);
5176    this.textTemplate = [ this.data['text'+strictness] ];
5177  };
5178  _.deleteTowards = function(dir, cursor) {
5179    if (dir === L && !this.strict) {
5180      this.swap(true);
5181      this.bubble('reflow');
5182      return;
5183    }
5184    super_.deleteTowards.apply(this, arguments);
5185  };
5186});
5187
5188var less = { ctrlSeq: '\\le ', html: '&le;', text: '\u2264',
5189             ctrlSeqStrict: '<', htmlStrict: '&lt;', textStrict: '<' };
5190var greater = { ctrlSeq: '\\ge ', html: '&ge;', text: '\u2265',
5191                ctrlSeqStrict: '>', htmlStrict: '&gt;', textStrict: '>' };
5192
5193LatexCmds['<'] = LatexCmds.lt = bind(Inequality, less, true);
5194LatexCmds['>'] = LatexCmds.gt = bind(Inequality, greater, true);
5195LatexCmds['\u2264'] = LatexCmds.le = LatexCmds.leq = bind(Inequality, less, false);
5196LatexCmds['\u2265'] = LatexCmds.ge = LatexCmds.geq = bind(Inequality, greater, false);
5197
5198var Equality = P(BinaryOperator, function(_, super_) {
5199  _.init = function() {
5200    super_.init.call(this, '=', '=');
5201  };
5202  _.createLeftOf = function(cursor) {
5203    if (cursor[L] instanceof Inequality && cursor[L].strict) {
5204      cursor[L].swap(false);
5205      cursor[L].bubble('reflow');
5206      return;
5207    }
5208    super_.createLeftOf.apply(this, arguments);
5209  };
5210});
5211LatexCmds['='] = Equality;
5212
5213LatexCmds['\u00d7'] = LatexCmds.times = bind(BinaryOperator, '\\times ', '&times;', '[x]');
5214
5215LatexCmds['\u00f7'] = LatexCmds.div = LatexCmds.divide = LatexCmds.divides =
5216  bind(BinaryOperator,'\\div ','&divide;', '[/]');
5217
5218CharCmds['~'] = LatexCmds.sim = bind(BinaryOperator, '\\sim ', '~', '~');
5219var MQ1 = getInterface(1);
5220for (var key in MQ1) (function(key, val) {
5221  if (typeof val === 'function') {
5222    MathQuill[key] = function() {
5223      insistOnInterVer();
5224      return val.apply(this, arguments);
5225    };
5226    MathQuill[key].prototype = val.prototype;
5227  }
5228  else MathQuill[key] = val;
5229}(key, MQ1[key]));
5230
5231}());

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.