Merge pull request #604 from mathquill/fix.autocmds-eating-op-names

Fix auto-commands eating letters from operator names
diff --git a/README.md b/README.md
index 956bee5..ff01a2d 100644
--- a/README.md
+++ b/README.md
@@ -280,10 +280,11 @@
 `autoOperatorNames`, a list of the same form (space-delimited letters-only each
 length>=2), and overrides the set of operator names that automatically become
 non-italicized when typing the letters without typing a backslash first, like
-`sin`, `log`, etc. (Defaults to [the LaTeX built-in operator names][Wikia], but
-with additional trig operators like `sech`, `arcsec`, `arsinh`, etc.)
+`sin`, `log`, etc. Defaults to the LaTeX built-in operator names ([Section
+3.17 of the Short Math Guide][3.17]) plus some missing trig operators like
+`sech`, `arcsec`, `arsinh`, etc.
 
-[Wikia]: http://latex.wikia.com/wiki/List_of_LaTeX_symbols#Named_operators:_sin.2C_cos.2C_etc.
+[3.17]: http://tinyurl.com/jm9okjc
 
 `substituteTextarea`, a function that creates a focusable DOM element, called
 when setting up a math field. It defaults to `<textarea autocorrect=off .../>`,
diff --git a/src/commands/math/basicSymbols.js b/src/commands/math/basicSymbols.js
index 398407b..a8578ae 100644
--- a/src/commands/math/basicSymbols.js
+++ b/src/commands/math/basicSymbols.js
@@ -104,7 +104,7 @@
     // removeClass and delete flags from all letters before figuring out
     // which, if any, are part of an operator name
     Fragment(l[R] || this.parent.ends[L], r[L] || this.parent.ends[R]).each(function(el) {
-      el.italicize(true).jQ.removeClass('mq-first mq-last');
+      el.italicize(true).jQ.removeClass('mq-first mq-last mq-followed-by-supsub');
       el.ctrlSeq = el.letter;
     });
 
@@ -123,8 +123,21 @@
           first.ctrlSeq = (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq;
           last.ctrlSeq += (isBuiltIn ? ' ' : '}');
           if (TwoWordOpNames.hasOwnProperty(word)) last[L][L][L].jQ.addClass('mq-last');
-          if (nonOperatorSymbol(first[L])) first.jQ.addClass('mq-first');
-          if (nonOperatorSymbol(last[R])) last.jQ.addClass('mq-last');
+          if (!shouldOmitPadding(first[L])) first.jQ.addClass('mq-first');
+          if (!shouldOmitPadding(last[R])) {
+            if (last[R] instanceof SupSub) {
+              var supsub = last[R]; // XXX monkey-patching, but what's the right thing here?
+              // Have operatorname-specific code in SupSub? A CSS-like language to style the
+              // math tree, but which ignores cursor and selection (which CSS can't)?
+              var respace = supsub.siblingCreated = supsub.siblingDeleted = function() {
+                supsub.jQ.toggleClass('mq-after-operator-name', !(supsub[R] instanceof Bracket));
+              };
+              respace();
+            }
+            else {
+              last.jQ.toggleClass('mq-last', !(last[R] instanceof Bracket));
+            }
+          }
 
           i += len - 1;
           first = last;
@@ -133,12 +146,13 @@
       }
     }
   };
-  function nonOperatorSymbol(node) {
-    return node instanceof Symbol && !(node instanceof BinaryOperator);
+  function shouldOmitPadding(node) {
+    // omit padding if no node, or if node already has padding (to avoid double-padding)
+    return !node || (node instanceof BinaryOperator) || (node instanceof SummationNotation);
   }
 });
 var BuiltInOpNames = {}; // the set of operator names like \sin, \cos, etc that
-  // are built-into LaTeX: http://latex.wikia.com/wiki/List_of_LaTeX_symbols#Named_operators:_sin.2C_cos.2C_etc.
+  // are built-into LaTeX, see Section 3.17 of the Short Math Guide: http://tinyurl.com/jm9okjc
   // MathQuill auto-unitalicizes some operator names not in that set, like 'hcf'
   // and 'arsinh', which must be exported as \operatorname{hcf} and
   // \operatorname{arsinh}. Note: over/under line/arrow \lim variants like
@@ -408,8 +422,11 @@
 
   _.contactWeld = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
     if (dir === R) return; // ignore if sibling only changed on the right
+    // If the left sibling is a binary operator or a separator (comma, semicolon, colon)
+    // or an open bracket (open parenthesis, open square bracket)
+    // consider the operator to be unary, otherwise binary
     this.jQ[0].className =
-      (!this[L] || this[L] instanceof BinaryOperator ? '' : 'mq-binary-operator');
+      (!this[L] || this[L] instanceof BinaryOperator || /^[,;:\(\[]$/.test(this[L].ctrlSeq) ? '' : 'mq-binary-operator');
     return this;
   };
 });
diff --git a/src/css/math.less b/src/css/math.less
index fcaa23d..fd828b0 100644
--- a/src/css/math.less
+++ b/src/css/math.less
@@ -87,7 +87,7 @@
   }
 
   big {
-    font-size: 125%;
+    font-size: 200%;
   }
 
   .mq-roman {
@@ -129,6 +129,7 @@
   //   its contents, rather than always being as wide as the subscript.
   //   See also .fraction
   .mq-supsub {
+    text-align: left;
     font-size: 90%;
     vertical-align: -.5em;
     &.mq-limit {
@@ -215,7 +216,7 @@
   var.mq-operator-name.mq-first {
     padding-left: .2em;
   }
-  var.mq-operator-name.mq-last {
+  var.mq-operator-name.mq-last, .mq-supsub.mq-after-operator-name {
     padding-right: .2em;
   }
 
@@ -291,6 +292,8 @@
   }
 
   .mq-large-operator {
+    vertical-align: -.2em;
+    padding: .2em;
     text-align: center;
 
     .mq-from, big, .mq-to  {
diff --git a/test/unit/css.test.js b/test/unit/css.test.js
index 223895f..639cdf9 100644
--- a/test/unit/css.test.js
+++ b/test/unit/css.test.js
@@ -52,5 +52,58 @@
     var mqF = $(mq.el()).find('.mq-f');
     var testVal = parseFloat(mqF.css('margin-right')) - parseFloat(mqF.css('margin-left'));
     assert.ok(testVal > 0, 'this should be truthy') ;
+
+    $(mq.el()).remove();
+  });
+
+  test('unary PlusMinus before separator', function () {
+    var mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
+    mq.latex('(-1,-1-1)-1,(+1;+1+1)+1,(\\pm1,\\pm1\\pm1)\\pm1');
+    var spans = $(mq.el()).find('.mq-root-block').find('span');
+    assert.equal(spans.length, 35, 'PlusMinus expression parsed incorrectly');
+
+    function isBinaryOperator(i) { return $(spans[i]).hasClass('mq-binary-operator'); }
+    function assertBinaryOperator(i, s) { assert.ok(isBinaryOperator(i), '"' + s + '" should be binary'); }
+    function assertUnaryOperator(i, s) { assert.ok(!isBinaryOperator(i), '"' + s + '" should be unary'); }
+
+    assertUnaryOperator(1, '(-');
+    assertUnaryOperator(4, '(-1,-');
+    assertBinaryOperator(6, '(-1,-1-');
+    assertBinaryOperator(9, '(-1,-1-1)-');
+    assertUnaryOperator(13, '(-1,-1-1)-1,(+');
+    assertUnaryOperator(16, '(-1,-1-1)-1,(+1;+');
+    assertBinaryOperator(18, '(-1,-1-1)-1,(+1;+1+');
+    assertBinaryOperator(21, '(-1,-1-1)-1,(+1;+1+1)+');
+    assertUnaryOperator(25, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm');
+    assertUnaryOperator(28, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm1,\pm');
+    assertBinaryOperator(30, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm1,\pm1\pm');
+    assertBinaryOperator(33, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm1,\pm1\pm1)\pm');
+
+    $(mq.el()).remove();
+  });
+
+  test('operator name spacing e.g. sin x', function() {
+    var mq = MathQuill.MathField($('<span></span>').appendTo(mock)[0]);
+
+    mq.typedText('sin');
+    var n = jQuery('#mock var.mq-operator-name:last');
+    assert.equal(n.text(), 'n');
+    assert.ok(!n.is('.mq-last'));
+
+    mq.typedText('x');
+    assert.ok(n.is('.mq-last'));
+
+    mq.keystroke('Left').typedText('(');
+    assert.ok(!n.is('.mq-last'));
+
+    mq.keystroke('Backspace').typedText('^');
+    assert.ok(!n.is('.mq-last'));
+    var supsub = jQuery('#mock .mq-supsub');
+    assert.ok(supsub.is('.mq-after-operator-name'));
+
+    mq.typedText('2').keystroke('Tab').typedText('(');
+    assert.ok(!supsub.is('.mq-after-operator-name'));
+
+    $(mq.el()).empty();
   });
 });
diff --git a/test/visual.html b/test/visual.html
index 0429dd0..1c6f44f 100644
--- a/test/visual.html
+++ b/test/visual.html
@@ -77,7 +77,7 @@
   <td><span class="mathquill-static-math">\sqrt{\MathQuillMathField{x^2+y^2}}</span>

 </table>

 

-<p>Clicks/mousedown to drag should work anywhere in the blue box: <div class="math-container" style="border: solid 1px lightblue; height: 5em; width: 15em; line-height: 5em; text-align: center"><span class="mathquill-math-field">a_2 x^2 + a_1 x + a_0 = 0</span></div>

+<p>Clicks/mousedown to drag should work anywhere in the blue box: <div class="math-container" style="border: solid 1px lightblue; height: 5em; width: 15em; line-height: 5em; text-align: center"><span class="mathquill-math-field">x_{very\ long\ thing}^2 + a_0 = 0</span></div>

 

 <h3>Redrawing</h3>

 <p>

@@ -187,7 +187,18 @@
 <tr><td><span class="mathquill-static-math">^{\frac{as}{ }df}</span><td><span>^{\frac{as}{ }df}</span>

 <tr><td><span class="mathquill-static-math">e^{i\pi}+1=0</span><td><span>e^{i\pi}+1=0</span>

 <tr><td><span class="mathquill-static-math">\sqrt[n]{1}</span><td><span>\sqrt[n]{1}</span>

-<tr><td><span class="mathquill-static-math">\theta + \sin 2\deg</span><td><span>\theta + \sin 2\deg</span>

+<tr><td><span class="mathquill-static-math">\sin ^2x+\sin ^2\left(x\right)+\sin ^2(x)</span></td><td><span>\sin ^2x+\sin ^2\left(x\right)+\sin ^2(x)</span>

+<tr><td><span class="mathquill-static-math">12a\sin b</span></td><td><span>12a\sin b</span>

+<tr><td><span class="mathquill-static-math">1a^2 \sin b</span></td><td><span>1a^2 \sin b</span>

+<tr><td><span class="mathquill-static-math">a + \sin b</span></td><td><span>a + \sin b</span>

+<tr><td><span class="mathquill-static-math">a + b</span></td><td><span>a + b</span>

+<tr><td><span class="mathquill-static-math">\sum\sin</span></td><td><span>\sum\sin</span>

+<tr><td><span class="mathquill-static-math">\sum a</span></td><td><span>\sum a</span>

+<tr><td><span class="mathquill-static-math">(\sin)</span></td><td><span>(\sin)</span>

+<tr><td><span class="mathquill-static-math">\left(\sin\right)</span></td><td><span>\left(\sin\right)</span>

+<tr><td><span class="mathquill-static-math">(x)\sin(x)</span></td><td><span>(x)\sin(x)</span>

+<tr><td><span class="mathquill-static-math">\left(x\right)\sin\left(x\right)</span></td><td><span>\left(x\right)\sin\left(x\right)</span>

+<tr><td><span class="mathquill-static-math">a \sin b</span></td><td><span>a \sin b</span>

 <tr><td><span class="mathquill-static-math">-1 + +-2</span><td><span>-1 + +-2</span>

 <tr><td><span class="mathquill-static-math">\left ( n+1 \right ) + \frac{1}{\frac{n}{k}} + \binom{n}{k}</span><td><span>\left ( n+1 \right ) + \frac{1}{\frac{n}{k}} + \binom{n}{k}</span>

 <tr><td><span class="mathquill-static-math">x_{\frac{1}{\frac{2}{3}}}^{\frac{\frac{1}{2}}{3}}</span><td><span>x_{\frac{1}{\frac{2}{3}}}^{\frac{\frac{1}{2}}{3}}</span>