Skip to content

Restore backslash escapes before parsing TeX math content#1580

Merged
rhennigan merged 3 commits into
mainfrom
bugfix/unescaped-characters-in-tex-strings
Jul 16, 2026
Merged

Restore backslash escapes before parsing TeX math content#1580
rhennigan merged 3 commits into
mainfrom
bugfix/unescaped-characters-in-tex-strings

Conversation

@rhennigan

Copy link
Copy Markdown
Member

Summary

A markdown-escaped character inside a math block was mangled instead of rendered. $$\$123.45 / \$6.78$$ produced garbage where the dollar signs should be.

Before, on main:

In[1]:= Needs[ "Wolfram`Chatbook`" ]
In[2]:= FormatChatOutput[ "Here is some TeX: $$\$123.45 / \$6.78$$" ] // InputForm
<|"boxes" -> FormBox[RowBox[{"ï¸36ï¹123.45", "/", "ï¸36ï¹6.78"}], TraditionalForm],
  "errors" -> {}, "input" -> "$123.45 / $6.78", "state" -> "Boxes"|>

After:

<|"boxes" -> FormBox[RowBox[{"$123.45", "/", "$6.78"}], TraditionalForm],
  "errors" -> {}, "input" -> "\\$123.45 / \\$6.78", "state" -> "Boxes"|>

Root cause

reformatTextData0 rewrites markdown escapes like \$ into a private sentinel (esc, a \[EntityStart]…\[EntityEnd] pair) before the text is parsed into cells, then restores them afterward with $mdUnescapeRules. Those rules drop the backslash, which is right for body text — there the backslash only marks the escape, so \$ should render as a bare $.

Math content never takes that path. It is handed to makeTeXBoxes as a raw string, so the sentinel reached the TeX parser verbatim and came back as the mojibake above, once those private use characters were reinterpreted.

The two halves of the "before" output fail differently, which is worth noting since only one of them is visible to users:

  • "boxes" was mangled by the TeX parser, and no longer contained a sentinel for anything downstream to recognize.
  • "input" still held real sentinels, so the outer unescape sweep in reformatTextData0 restored them — to a bare $, silently stripping the TeX escape rather than showing mojibake.

Fix

Unescape math with the new $texUnescapeRules, which restores the sentinel to \<char> and keeps the backslash. Inside $$...$$ the escape belongs to TeX rather than markdown, so the backslash has to reach the parser: \$ is how TeX renders a literal dollar sign, and likewise \_ and \#. The remaining escaped characters (`, *, |) keep whatever meaning TeX assigns the sequence, which still beats passing the sentinel through.

The change sits on the general mathCell definition only. The digits-only and system-name definitions above it can't hold a sentinel, and the fallback to inlineCodeCell returns strings, which $mdUnescapeRules still catches on the way out.

Test plan

  • New Tests/Formatting.wlt — 5/5 pass. Formatting.wl had no test coverage before this.
  • Confirmed the tests actually catch the bug: both TeX tests were run against the pre-fix code and fail there, reproducing the exact mojibake above. Without that check a passing test proves nothing here.
  • TeX-Escaped-Dollar pins the reported case; TeX-Escaped-Underscore-And-Hash covers \_ and \# to show the rule generalizes.
  • Markdown-Escaped-Dollar is a guard, not a regression test: it passes both before and after, and pins the neighboring markdown path (\$$ in body text) that this change could otherwise have quietly broken. The two unescape rule sets are easy to confuse.
  • CodeInspector clean on both changed files.

🤖 Generated with Claude Code

rhennigan and others added 3 commits July 15, 2026 20:00
reformatTextData0 rewrites markdown escapes like \$ into a private sentinel
before the text is parsed into cells, then restores them afterward with
$mdUnescapeRules, which drops the backslash because in body text it only marks
the escape. Math content is handed to makeTeXBoxes as a raw string, so the
sentinel reached the TeX parser verbatim: "$$\$123.45 / \$6.78$$" came back as
boxes reading \[EntityStart]36\[EntityEnd]123.45, which displays as mojibake
once those private use characters are reinterpreted, and the template's "input"
was left as "$123.45 / $6.78" with the TeX escape stripped.

Unescape math with $texUnescapeRules instead, which keeps the backslash. Inside
$$...$$ the escape belongs to TeX rather than markdown, so the backslash has to
survive: \$ is how TeX renders a literal dollar sign, and likewise \_ and \#.
The remaining escaped characters keep whatever meaning TeX assigns the
sequence, which still beats passing the sentinel through.

The fix sits on the general mathCell definition only. The digits-only and
system-name definitions above it can't hold a sentinel, and the fallback to
inlineCodeCell returns strings, which $mdUnescapeRules still catches on the way
out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers the escape sentinel that reformatTextData0 substitutes for markdown
escapes reaching the TeX parser, which rendered "$$\$123.45 / \$6.78$$" as
mojibake rather than $123.45 / $6.78. Both TeX tests were confirmed to fail
against the code as it stood before the previous commit.

Also pins the neighboring markdown path, where the backslash is dropped rather
than kept, since the two unescape rule sets are easy to confuse and a fix on
one side could quietly change the other.

The file is new -- Formatting.wl had no test coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 16, 2026 00:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes TeX math rendering in FormatChatOutput when markdown-escaped characters (notably \$) appear inside $$...$$ blocks, by restoring backslash escapes before passing math content to the TeX parser. This aligns math handling with how markdown escapes are already round-tripped elsewhere in reformatTextData0, while preserving TeX’s need to see escapes like \$, \_, and \# intact.

Changes:

  • Add a dedicated $texUnescapeRules rule set to restore the escape sentinel back to \-prefixed sequences for TeX parsing.
  • Apply $texUnescapeRules to mathCell content before calling makeTeXBoxes.
  • Add a new Tests/Formatting.wlt file covering TeX-escaped $, _, #, plus a guard test for markdown-body \$ behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
Source/Chatbook/Formatting.wl Restores backslash escapes from the markdown sentinel before TeX parsing for mathCell strings.
Tests/Formatting.wlt Adds regression and guard tests validating correct TeX-escape handling vs. markdown-body unescaping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@rhennigan
rhennigan merged commit 2c34923 into main Jul 16, 2026
2 checks passed
@rhennigan
rhennigan deleted the bugfix/unescaped-characters-in-tex-strings branch July 16, 2026 00:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants