From 26aed11841aebf63d1a496e32364b380fdd45175 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 7 Jul 2026 02:36:08 +1000 Subject: [PATCH] Fix false math-mode warning for \tag* with math content \tag*{...} (amsmath's unnumbered tag) incorrectly triggered an "open inline math \( used inside existing math mode" warning when its argument contained inline math, e.g. \tag*{\(\dagger\)} or \tag*{$abc$}. \tag{...} without the star already worked. The linter marks \tag's argument group as text-mode so nested math delimiters are allowed, but the literal * between \tag and { was tokenized separately and reset that flag before the argument group was reached. \tag now consumes an immediately following * so the text-mode flag survives to the argument group. --- .../languages/latex/linter/latex-linter.worker.ts | 14 +++++++++++++- .../languages/latex/latex-linter.test.ts | 12 ++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/services/web/frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker.ts b/services/web/frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker.ts index bb8589406b..20e1e9860c 100644 --- a/services/web/frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker.ts +++ b/services/web/frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker.ts @@ -1107,12 +1107,24 @@ const InterpretTokens = function ( seq === 'intertext' || seq === 'shortintertext' || seq === 'textnormal' || - seq === 'tag' || seq === 'reflectbox' || seq === 'textrm' ) { // next group will be in text mode regardless nextGroupMathMode = false + } else if (seq === 'tag') { + // next group will be in text mode regardless + nextGroupMathMode = false + // \tag* (unnumbered tag) - skip over the '*' so it doesn't reset + // nextGroupMathMode before the argument group is reached + const nextToken = Tokens[i + 1] + if ( + nextToken && + nextToken[1] === 'Text' && + text.substring(nextToken[2], nextToken[3]) === '*' + ) { + i = i + 1 + } } else if (seq === 'tikz') { // try to read any optional params [BAR]...., advance if found const newPos = readOptionalGeneric(TokeniseResult, i) diff --git a/services/web/test/frontend/features/source-editor/languages/latex/latex-linter.test.ts b/services/web/test/frontend/features/source-editor/languages/latex/latex-linter.test.ts index ea4c7e8e15..a6a78762c9 100644 --- a/services/web/test/frontend/features/source-editor/languages/latex/latex-linter.test.ts +++ b/services/web/test/frontend/features/source-editor/languages/latex/latex-linter.test.ts @@ -350,6 +350,18 @@ describe('LatexLinter', function () { assert.equal(errors.length, 0) }) + it('should accept math tag* commands', function () { + const { errors } = Parse( + '\\begin{align}\n\\pi=3 \\tag*{\\(\\dagger\\)}\n\\end{align}\n' + ) + assert.equal(errors.length, 0) + }) + + it('should accept math tag* commands with $ math mode', function () { + const { errors } = Parse('$\\tag*{$abc$}$\n') + assert.equal(errors.length, 0) + }) + it('should accept math \\def commands', function () { const { errors } = Parse( '\\def\\peb[#1]{{\\left\\lfloor #1\\right\\rfloor}}'