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>