text: /*************************************************
   * Base classes of edit tree-related objects
   *
   * Only doing tree node manipulation via these
   * adopt/ disown methods guarantees well-formedness
   * of the tree.
   ************************************************/
  
  // L = 'left'
  // R = 'right'
  //
  // the contract is that they can be used as object properties
  // and (-L) === R, and (-R) === L.
  var L = -1;
  var R = 1;
  
  function prayDirection(dir) {
    pray('a direction was passed', dir === L || dir === R);
  }
  
  /**
   * Tiny extension of jQuery adding directionalized DOM manipulation methods.
   *
   * Funny how Pjs v3 almost just works with `jQuery.fn.init`.
   *
   * jQuery features that don't work on $:
   *   - jQuery.*, like jQuery.ajax, obviously (Pjs doesn't and shouldn't
   *                                            copy constructor properties)
   *
   *   - jQuery(function), the shortcut for `jQuery(document).ready(function)`,
   *     because `jQuery.fn.init` is idiosyncratic and Pjs doing, essentially,
   *     `jQuery.fn.init.apply(this, arguments)` isn't quite right, you need:
   *
   *       _.init = function(s, c) { jQuery.fn.init.call(this, s, c, $(document)); };
   *
   *     if you actually give a shit (really, don't bother),
   *     see https://github.com/jquery/jquery/blob/1.7.2/src/core.js#L889
   *
   *   - jQuery(selector), because jQuery translates that to
   *     `jQuery(document).find(selector)`, but Pjs doesn't (should it?) let
   *     you override the result of a constructor call
   *       + note that because of the jQuery(document) shortcut-ness, there's also
   *         the 3rd-argument-needs-to-be-`$(document)` thing above, but the fix
   *         for that (as can be seen above) is really easy. This problem requires
   *         a way more intrusive fix
   *
   * And that's it! Everything else just magically works because jQuery internally
   * uses `this.constructor()` everywhere (hence calling `$`), but never ever does
   * `this.constructor.find` or anything like that, always doing `jQuery.find`.
   */
  var $ = P(jQuery, function(_) {
    _.insDirOf = function(dir, el) {
      return dir === L ?
        this.insertBefore(el.first()) : this.insertAfter(el.last());
    };
    _.insAtDirEnd = function(dir, el) {
      return dir === L ? this.prependTo(el) : this.appendTo(el);
    };
  });
  
  var Point = P(function(_) {
    _.parent = 0;
    _[L] = 0;
    _[R] = 0;
  
    _.init = function(parent, leftward, rightward) {
      this.parent = parent;
      this[L] = leftward;
      this[R] = rightward;
    };
  
    this.copy = function(pt) {
      return Point(pt.parent, pt[L], pt[R]);
    };
  });
  
  /**
   * MathQuill virtual-DOM tree-node abstract base class
   */
  var Node = P(function(_) {
    _[L] = 0;
    _[R] = 0
    _.parent = 0;
  
    var id = 0;
    function uniqueNodeId() { return id += 1; }
    this.byId = {};
  
    _.init = function() {
      this.id = uniqueNodeId();
      Node.byId[this.id] = this;
  
      this.ends = {};
      this.ends[L] = 0;
      this.ends[R] = 0;
    };
  
    _.dispose = function() { delete Node.byId[this.id]; };
  
    _.toString = function() { return '{{ MathQuill Node #'+this.id+' }}'; };
  
    _.jQ = $();
    _.jQadd = function(jQ) { return this.jQ = this.jQ.add(jQ); };
    _.jQize = function(jQ) {
      // jQuery-ifies this.html() and links up the .jQ of all corresponding Nodes
      var jQ = $(jQ || this.html());
  
      function jQadd(el) {
        if (el.getAttribute) {
          var cmdId = el.getAttribute('mathquill-command-id');
          var blockId = el.getAttribute('mathquill-block-id');
          if (cmdId) Node.byId[cmdId].jQadd(el);
          if (blockId) Node.byId[blockId].jQadd(el);
        }
        for (el = el.firstChild; el; el = el.nextSibling) {
          jQadd(el);
        }
      }
  
      for (var i = 0; i < jQ.length; i += 1) jQadd(jQ[i]);
      return jQ;
    };
  
    _.createDir = function(dir, cursor) {
      prayDirection(dir);
      var node = this;
      node.jQize();
      node.jQ.insDirOf(dir, cursor.jQ);
      cursor[dir] = node.adopt(cursor.parent, cursor[L], cursor[R]);
      return node;
    };
    _.createLeftOf = function(el) { return this.createDir(L, el); };
  
    _.selectChildren = function(leftEnd, rightEnd) {
      return Selection(leftEnd, rightEnd);
    };
  
    _.bubble = iterator(function(yield_) {
      for (var ancestor = this; ancestor; ancestor = ancestor.parent) {
        var result = yield_(ancestor);
        if (result === false) break;
      }
  
      return this;
    });
  
    _.postOrder = iterator(function(yield_) {
      (function recurse(descendant) {
        descendant.eachChild(recurse);
        yield_(descendant);
      })(this);
  
      return this;
    });
  
    _.isEmpty = function() {
      return this.ends[L] === 0 && this.ends[R] === 0;
    };
    
    _.isStyleBlock = function() {
      return false;
    };
  
    _.children = function() {
      return Fragment(this.ends[L], this.ends[R]);
    };
  
    _.eachChild = function() {
      var children = this.children();
      children.each.apply(children, arguments);
      return this;
    };
  
    _.foldChildren = function(fold, fn) {
      return this.children().fold(fold, fn);
    };
  
    _.withDirAdopt = function(dir, parent, withDir, oppDir) {
      Fragment(this, this).withDirAdopt(dir, parent, withDir, oppDir);
      return this;
    };
  
    _.adopt = function(parent, leftward, rightward) {
      Fragment(this, this).adopt(parent, leftward, rightward);
      return this;
    };
  
    _.disown = function() {
      Fragment(this, this).disown();
      return this;
    };
  
    _.remove = function() {
      this.jQ.remove();
      this.postOrder('dispose');
      return this.disown();
    };
  });
  
  function prayWellFormed(parent, leftward, rightward) {
    pray('a parent is always present', parent);
    pray('leftward is properly set up', (function() {
      // either it's empty and `rightward` is the left end child (possibly empty)
      if (!leftward) return parent.ends[L] === rightward;
  
      // or it's there and its [R] and .parent are properly set up
      return leftward[R] === rightward && leftward.parent === parent;
    })());
  
    pray('rightward is properly set up', (function() {
      // either it's empty and `leftward` is the right end child (possibly empty)
      if (!rightward) return parent.ends[R] === leftward;
  
      // or it's there and its [L] and .parent are properly set up
      return rightward[L] === leftward && rightward.parent === parent;
    })());
  }
  
  
  /**
   * An entity outside the virtual tree with one-way pointers (so it's only a
   * "view" of part of the tree, not an actual node/entity in the tree) that
   * delimits a doubly-linked list of sibling nodes.
   * It's like a fanfic love-child between HTML DOM DocumentFragment and the Range
   * classes: like DocumentFragment, its contents must be sibling nodes
   * (unlike Range, whose contents are arbitrary contiguous pieces of subtrees),
   * but like Range, it has only one-way pointers to its contents, its contents
   * have no reference to it and in fact may still be in the visible tree (unlike
   * DocumentFragment, whose contents must be detached from the visible tree
   * and have their 'parent' pointers set to the DocumentFragment).
   */
  var Fragment = P(function(_) {
    _.init = function(withDir, oppDir, dir) {
      if (dir === undefined) dir = L;
      prayDirection(dir);
  
      pray('no half-empty fragments', !withDir === !oppDir);
  
      this.ends = {};
  
      if (!withDir) return;
  
      pray('withDir is passed to Fragment', withDir instanceof Node);
      pray('oppDir is passed to Fragment', oppDir instanceof Node);
      pray('withDir and oppDir have the same parent',
           withDir.parent === oppDir.parent);
  
      this.ends[dir] = withDir;
      this.ends[-dir] = oppDir;
  
      // To build the jquery collection for a fragment, accumulate elements
      // into an array and then call jQ.add once on the result. jQ.add sorts the
      // collection according to document order each time it is called, so
      // building a collection by folding jQ.add directly takes more than
      // quadratic time in the number of elements.
      //
      // https://github.com/jquery/jquery/blob/2.1.4/src/traversing.js#L112
      var accum = this.fold([], function (accum, el) {
        accum.push.apply(accum, el.jQ.get());
        return accum;
      });
  
      this.jQ = this.jQ.add(accum);
    };
    _.jQ = $();
  
    // like Cursor::withDirInsertAt(dir, parent, withDir, oppDir)
    _.withDirAdopt = function(dir, parent, withDir, oppDir) {
      return (dir === L ? this.adopt(parent, withDir, oppDir)
                        : this.adopt(parent, oppDir, withDir));
    };
    _.adopt = function(parent, leftward, rightward) {
      prayWellFormed(parent, leftward, rightward);
  
      var self = this;
      self.disowned = false;
  
      var leftEnd = self.ends[L];
      if (!leftEnd) return this;
  
      var rightEnd = self.ends[R];
  
      if (leftward) {
        // NB: this is handled in the ::each() block
        // leftward[R] = leftEnd
      } else {
        parent.ends[L] = leftEnd;
      }
  
      if (rightward) {
        rightward[L] = rightEnd;
      } else {
        parent.ends[R] = rightEnd;
      }
  
      self.ends[R][R] = rightward;
  
      self.each(function(el) {
        el[L] = leftward;
        el.parent = parent;
        if (leftward) leftward[R] = el;
  
        leftward = el;
      });
  
      return self;
    };
  
    _.disown = function() {
      var self = this;
      var leftEnd = self.ends[L];
  
      // guard for empty and already-disowned fragments
      if (!leftEnd || self.disowned) return self;
  
      self.disowned = true;
  
      var rightEnd = self.ends[R]
      var parent = leftEnd.parent;
  
      prayWellFormed(parent, leftEnd[L], leftEnd);
      prayWellFormed(parent, rightEnd, rightEnd[R]);
  
      if (leftEnd[L]) {
        leftEnd[L][R] = rightEnd[R];
      } else {
        parent.ends[L] = rightEnd[R];
      }
  
      if (rightEnd[R]) {
        rightEnd[R][L] = leftEnd[L];
      } else {
        parent.ends[R] = leftEnd[L];
      }
  
      return self;
    };
  
    _.remove = function() {
      this.jQ.remove();
      this.each('postOrder', 'dispose');
      return this.disown();
    };
  
    _.each = iterator(function(yield_) {
      var self = this;
      var el = self.ends[L];
      if (!el) return self;
  
      for (; el !== self.ends[R][R]; el = el[R]) {
        var result = yield_(el);
        if (result === false) break;
      }
  
      return self;
    });
  
    _.fold = function(fold, fn) {
      this.each(function(el) {
        fold = fn.call(this, fold, el);
      });
  
      return fold;
    };
  });
  
  
  /**
   * Registry of LaTeX commands and commands created when typing
   * a single character.
   *
   * (Commands are all subclasses of Node.)
   */
  var LatexCmds = {}, CharCmds = {};
  