diff --git a/src/Parser.js b/src/Parser.js index 9de048759..5f9f5725b 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -254,7 +254,18 @@ Parser.prototype.parseAtom = function(pos, mode) { // Lex the first token var lex = this.lexer.lex(currPos, mode); - if (lex.text === "^") { + if (lex.text === "\\limits" || lex.text === "\\nolimits") { + // We got a limit control + if (!base || base.result.type !== "op") { + throw new ParseError("Limit controls must follow a math operator", + this.lexer, currPos); + } + else { + var limits = lex.text == "\\limits"; + base.result.value.limits = limits; + currPos = lex.position; + } + } else if (lex.text === "^") { // We got a superscript start if (superscript) { throw new ParseError( diff --git a/test/katex-spec.js b/test/katex-spec.js index 36b4f91f4..927cd9d66 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -360,6 +360,40 @@ describe("A subscript and superscript tree-builder", function() { }); }); +describe("A parser with limit controls", function() { + it("should fail when the limit control is not preceded by an op node", function() { + expect("3\\nolimits_2^2").toNotParse(); + expect("\\sqrt\\limits_2^2").toNotParse(); + expect("45 +\\nolimits 45").toNotParse(); + }); + + it("should parse when the limit control directly follows an op node", function() { + expect("\\int\\limits_2^2 3").toParse(); + expect("\\sum\\nolimits_3^4 4").toParse(); + }); + + it("should parse when the limit control is in the sup/sub area of an op node", function() { + expect("\\int_2^2\\limits").toParse(); + expect("\\int^2\\nolimits_2").toParse(); + expect("\\int_2\\limits^2").toParse(); + }); + + it("should allow multiple limit controls in the sup/sub area of an op node", function() { + expect("\\int_2\\nolimits^2\\limits 3").toParse(); + expect("\\int\\nolimits\\limits_2^2").toParse(); + expect("\\int\\limits\\limits\\limits_2^2").toParse(); + }); + + it("should have the rightmost limit control determine the limits property " + + "of the preceding op node", function() { + var parsedInput = getParsed("\\int\\nolimits\\limits_2^2"); + expect(parsedInput[0].value.base.value.limits).toBe(true); + + parsedInput = getParsed("\\int\\limits_2\\nolimits^2"); + expect(parsedInput[0].value.base.value.limits).toBe(false); + }); +}); + describe("A group parser", function() { it("should not fail", function() { expect("{xy}").toParse(); diff --git a/test/screenshotter/images/LimitControls-chrome.png b/test/screenshotter/images/LimitControls-chrome.png new file mode 100644 index 000000000..0695620b4 Binary files /dev/null and b/test/screenshotter/images/LimitControls-chrome.png differ diff --git a/test/screenshotter/images/LimitControls-firefox.png b/test/screenshotter/images/LimitControls-firefox.png new file mode 100644 index 000000000..a71450984 Binary files /dev/null and b/test/screenshotter/images/LimitControls-firefox.png differ diff --git a/test/screenshotter/ss_data.json b/test/screenshotter/ss_data.json index deaf70c4c..78c1e85d2 100644 --- a/test/screenshotter/ss_data.json +++ b/test/screenshotter/ss_data.json @@ -20,6 +20,7 @@ "LeftRight": "http://localhost:7936/test/screenshotter/test.html?m=\\left( x^2 \\right) \\left\\{ x^{x^{x^{x^x}}} \\right.", "LeftRightListStyling": "http://localhost:7936/test/screenshotter/test.html?m=a+\\left(x+y\\right)-x", "LeftRightStyleSizing": "http://localhost:7936/test/screenshotter/test.html?m=+\\left\\{\\rule{0.1em}{1em}\\right.x^{+\\left\\{\\rule{0.1em}{1em}\\right.x^{+\\left\\{\\rule{0.1em}{1em}\\right.}}", + "LimitControls": "http://localhost:7936/test/screenshotter/test.html?m=\\displaystyle\\int\\limits_2^3 3x^2\\,dx + \\sum\\nolimits^n_{i=1}i", "NestedFractions": "http://localhost:7936/test/screenshotter/test.html?m=\\dfrac{\\frac{a}{b}}{\\frac{c}{d}}\\dfrac{\\dfrac{a}{b}}{\\dfrac{c}{d}}\\frac{\\frac{a}{b}}{\\frac{c}{d}}", "NullDelimiterInteraction": "http://localhost:7936/test/screenshotter/test.html?m=a \\bigl. + 2 \\quad \\left. + a \\right)", "OpLimits": "http://localhost:7936/test/screenshotter/test.html?m={\\sin_2^2 \\lim_2^2 \\int_2^2 \\sum_2^2}{\\displaystyle \\lim_2^2 \\int_2^2 \\intop_2^2 \\sum_2^2}",