feat: add the texMathDollars preference, replacing the math option (#5446) by Jocs · Pull Request #5450 · marktext/marktext · GitHub
Skip to content

feat: add the texMathDollars preference, replacing the math option (#5446) - #5450

Merged
Jocs merged 6 commits into
developfrom
feat/muya-tex-math-dollars-preference
Sep 19, 2026
Merged

Jocs merged 6 commits into
developfrom
feat/muya-tex-math-dollars-preference

Conversation

@Jocs

@Jocs Jocs commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

Task 2 of #5446, on top of Task 1 (#5449).

What math was

IMuyaOptions.math was a half-finished master switch:

  • it gated only the marked path — import, HTML export, clipboard;
  • the live editor ignored it entirely, so $x+y$ rendered as KaTeX no matter what;
  • MarkText never set it — no schema entry, no UI, no setOptions call.

So the option that was supposed to control dollar math could not turn it off, and where it did apply it made the editor disagree with its own export.

What it is now

texMathDollars, named after tex_math_dollars, with the live-editor gate it never had, and a preference under Preferences → Markdown → Extensions next to the other pandoc-named toggles — same more link treatment as superSubScript and footnote.

Default on, as in pandoc's own markdown dialect.

It covers $…$ and $$…$$ together, because that is the granularity pandoc gives the extension. That is the question #4231 stalled on — one setting or two? — answered by adopting an existing definition instead of inventing one. It also answers the three standing requests for a switch: #2002 (2020), #4231, #5243 (which reports binary-patching app.asar to get it).

Where the gate sits

off
inline $…$, $$…$$ not tokenized (tryChunks skips inline_math)
$$ on its own line not a math block (consumeBeginRules skips multiple_math)
marked path (import / export / clipboard) math extension not registered — unchanged, this half already worked
$$ + Enter stays a paragraph
quick-insert Display Math, front-menu Turn Into, Paragraph → Math Block not listed, and not built even if reached by shortcut

The last two rows are not parsing, but they produce $$…$$ that this editor would then read back as plain text — so they follow the switch. canInsertLabel is the single rule behind them: the menus that list blocks ask it, and so does the builder every insertion path funnels through, so the ⌥⌘M shortcut and the desktop menu cannot slip past a list they never consult. texMathDollars is already in PARSE_AFFECTING_OPTIONS, so toggling it re-parses the open document, and the new spec pins that the round trip is lossless: a demoted math block keeps its source and comes back as a math block when the option returns.

Making texMathDollars required on ITokenizerFacOptions is deliberate — it is what made the compiler point at the three places that hand-build that bag (getTOC, the backspace token scan, one spec) rather than leaving them silently gate-less.

Commits

  1. refactor(muya) — rename math → texMathDollars, including the four duplicate default sets. No behaviour change.
  2. feat(muya) — the live-editor gate, the two production paths, and texMathDollarsToggle.spec.ts.
  3. feat(desktop) — schema, defaults, store, types, the toggle, the setOptions watch, 11 locales.
  4. docs — the PREFERENCES.md row (prettier re-wrapped the table).
  5. feat(muya) — canInsertLabel as the one rule every menu and the block builder consult. It also found a latent crash: replaceBlockByLabel passed a declined build straight to block.replaceWith.
  6. feat(desktop) — Paragraph → Math Block disappears with the option, and reappears without a restart.

Known gap

The inline format toolbar's $ button still wraps a selection in $…$ with the option off. That is an explicit formatting command rather than a block insertion, and it leaves text the editor reads back verbatim, so it is left alone here.

Verification

muya 252 files / 1880 tests, CommonMark 0.31 + GFM 0.29 conformance 1347, desktop unit 820 (locale parity included), lint, typecheck, check-circular, and the muya Playwright suite. The two tests/inline/{shortcuts,format-toolbar} failures on my machine reproduce identically on develop (system-Chrome keyboard/focus flakes) — 6 there, 5 here.

🤖 Generated with Claude Code

Jocs and others added 4 commits September 19, 2026 01:37
`math` named a dialect rather than a syntax, and it only ever gated the
marked path. Name it after the pandoc extension it actually implements,
`tex_math_dollars`, so the gate it is about to grow over the live editor
has a name that says what it covers: `$…$` and `$$…$$`.

Pure rename — the four duplicate default sets (muya options, the marked
lex options, MarkdownToState, renderToStaticHTML) move together, so no
behaviour changes here.

Task 2 of #5446.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`math` gated only the marked path, so the option could never actually
turn dollar math off: the inline lexer rendered `$x+y$` as KaTeX no
matter what, and the editor disagreed with its own export. Thread the
flag into the tokenizer the way `superSubScript` and `footnote` already
travel, and skip the `inline_math` chunk rule and the `multiple_math`
begin rule when it is off.

Also stop offering the syntax where it can no longer be read back: a
`$$` paragraph does not convert on Enter, and the quick-insert menu
drops its Display Math entry. Both would otherwise write `$$…$$` that
the same editor then parses as plain text.

Making `texMathDollars` required on ITokenizerFacOptions is deliberate —
it is how the compiler finds the three call sites that hand-build that
bag (getTOC, the backspace token scan, and one spec).

Task 2 of #5446.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three reports have asked to turn `$…$` off — #2002, #4231, #5243, the
last of them patching app.asar to get it. The switch now exists, under
Preferences → Markdown → Extensions next to the other pandoc-named
toggles, linking at the pandoc manual the same way they do.

Default on, as in pandoc's own `markdown` dialect, so nothing changes
for anyone who does not go looking for it.

Note it covers both `$…$` and `$$…$$`, because that is the granularity
pandoc gives `tex_math_dollars` — this is the question #4231 stalled on,
answered by adopting an existing definition rather than inventing one.

Task 2 of #5446.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Task 2 of #5446.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Website preview ready: https://pr-5450-marktext-website.ransixi.workers.dev

Built from f0f4228ef96822ec657e5fa235058d22f654a386 · Worker: marktext-website · Version: 77fb2aac-863f-47df-85dd-8a91884005d5 · Alias: pr-5450

@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Build artifacts for PR #5450:

Run: https://github.com/marktext/marktext/actions/runs/35434378659

Artifact Size Link
marktext-macos-arm64 287.5 MB Download
marktext-linux 638.2 MB Download
marktext-windows-x64 287.6 MB Download
marktext-macos-x64 297.8 MB Download
marktext-windows-arm64 279.4 MB Download

Jocs and others added 2 commits September 19, 2026 14:28
…5446)

Hiding the quick-insert entry was not enough. The paragraph front menu
kept offering "Math Block", the ⌥⌘M shortcut and the desktop Paragraph
menu never consult a list at all, and all three still inserted a block
that serializes to `$$…$$` — which the editor then reads back as text.

`canInsertLabel` is now the single rule, read by the menus that list
blocks and by the builder every insertion path funnels through, so an
entry point that forgets to ask still cannot produce the block.

`replaceBlockByLabel` also has to survive a builder that declines:
it passed the null straight to `block.replaceWith`, which threw on
`firstContentInDescendant`. `insertBlockBelowByLabel` already guarded.

Task 2 of #5446.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…off (#5446)

Paragraph → Math Block offered a block the editor can no longer read
back, so it goes away with the option rather than staying as a dead
entry. The menu already rebuilds on a preference change for theme and
language; math joins that list, so the item appears and disappears
without a restart.

Task 2 of #5446.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Jocs
Jocs merged commit 3e9e91c into develop Sep 19, 2026
18 checks passed
@Jocs
Jocs deleted the feat/muya-tex-math-dollars-preference branch September 19, 2026 11:06
@Jocs
Jocs restored the feat/muya-tex-math-dollars-preference branch September 19, 2026 11:20
@Jocs
Jocs deleted the feat/muya-tex-math-dollars-preference branch September 19, 2026 11:24
Jocs added a commit that referenced this pull request Sep 21, 2026
…5483) (#5484)

* feat(desktop): follow the math preferences in source mode (#5483)

The `markdown-math` mode has never read a preference. `codeMirror/index.ts`
registers it at module load and `sourceCode.vue` asked for it by name, so the
source view delegated `$…$` to stex whether or not `texMathDollars` was on.

That went unnoticed because the preference ships enabled, but #5446 added
three more math syntaxes and they ship disabled — the same shortcut would be
wrong in the opposite direction for them. So the mode now takes its regions
from the mode spec, which is where the delimiters that follow can join.

Visible change, and the intended one: with `texMathDollars` off, the source
view stops treating `$` as a delimiter, matching what the editor has done
since #5450. That is what #2002 and #5243 asked for — the latter reported
patching app.asar to get it.

The factory's defaults reproduce the old behaviour, so a caller still passing
the bare string `'markdown-math'` is unaffected. A fresh CodeMirror instance
reads the preferences at mount (`index.vue` builds the source view behind a
`v-if`, so every entry is a new one); the watch covers a preference changed
while the view is already open, mirroring the one #5111 added for
`sourceCodeLineNumbers`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(desktop): apply pandoc's dollar-math constraints to the source view guard (#5483)

The inline `$…$` open guard said it mirrored Muya's `inline_math` rule, and
pointed at `src/muya/lib/parser/rules.js` — a path that belongs to the engine
this editor no longer uses. It drifted further when #5449 gave the editor
pandoc's three `tex_math_dollars` constraints, which the guard never gained.

So the source view still opened a math span on `Revenue rose from $13B to
$24B.` while the editor read it as prose — the currency case #2002 and #5243
reported, on the very input #5446 used to describe the problem.

The guard now carries the same three constraints: a non-space right after the
opener, a non-space right before the closer, and no digit right after it. It
still requires a closer on the line, which is this view's own rule rather than
pandoc's: CodeMirror tokenizes per line and the multiplexer's state survives
across them, so an unclosed opener would render the rest of the file as LaTeX.

The guard only decides whether to enter stex. The region's closer is the plain
string `$` and leaves on the next one without re-checking; a closer regexp
would need the text before it, which `StringStream.match` does not expose.

The existing currency case passed throughout: the old guard opened its span at
`$5` and closed it before `only`, which is what the assertion looked for. It
now checks the span contents instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(desktop): highlight the backslash TeX math delimiters in source mode (#5483)

`\(…\)`, `\[…\]`, `\\(…\\)` and `\\[…\\]` render as formulas in the WYSIWYG
view since #5481 but were plain prose in the source view — and worse than
plain: `markdown-math` exists because the markdown mode reads the underscores
in `\(\text{F}_\text{A}\)` as emphasis (#4121), which it was still doing for
these.

Each pair now gets a stex region, riding the preference that governs it.

Notes:

* The double-backslash regions are registered first. The multiplexer takes the
  first spec matching at the cursor, and the literal `\(` matches at the second
  character of `\\(`.
* The inline forms need their own open guards, not `INLINE_MATH_OPEN`: part of
  that one requires the character before the closer not to be a backslash, and
  for `\)` it is one. The content rules come from Muya's own regexes, including
  the two escape rules that differ between the extensions, so both views draw
  the same span. Display math legitimately spans lines, so like `$$` it carries
  no guard.
* Known limit, commented at the guards: `\\(x\)` — a double-backslash opener
  closed with a single-backslash closer — is read as math when only the single
  extension is on. The multiplexer searches a regexp against the rest of the
  line, so a guard cannot see the backslash in front of the opener. The shape
  is invalid in both dialects, and the well-formed `\\(x\\)` is unaffected.
* `- \[TODO\] item` becomes a formula once the extension is on. That is pandoc's
  documented drawback — the extension "precludes escaping `(` and `[`" — the
  editor agrees, and it is why both preferences ship disabled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(desktop): trim the markdown-math mode to the comments that earn it

The file had grown to 42% comments, several of them restating the code or
recording archaeology (why the outer mode is gfm, what `setMode` used to
resolve to) rather than anything a reader needs.

The three delimiter pairs also carried three copies of the same two region
literals. Folding them into one helper removes the duplication and, with it,
the comments that existed to explain it — including the note about display
regions preceding inline ones, which was never load-bearing.

What stays is what the code cannot say: why this mode exists at all, the
constraints each open guard encodes and where they come from, why the
backslash guards cannot reuse the dollar one, the ordering the multiplexer
demands, and the one shape it reads differently from the editor.

156 lines to 111; no behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(desktop): stop a backslash math region claiming the other one's block (#5483)

With only `texMathSingleBackslash` on, a `\\[…\\]` block highlighted as math —
the very syntax that preference does not cover. The editor reads `\\` there as
an escaped backslash and finds no formula, so the two views disagreed exactly
where this change set out to make them agree.

The display openers span lines, so unlike the inline ones they carry no closer
lookahead. `\[` was a plain string, and `\\[` contains it one character in: the
multiplexer found it at that offset and opened a region that ran to the
matching `\]`, swallowing the block whichever way the double-backslash
preference was set.

Every opener now refuses a preceding backslash. That needs the openers to be
regexps, and regexps could not see what precedes them: `indexOf` in our
vendored `multiplexingMode` searched `string.slice(from)`, hiding the text
before the cursor. It now searches the whole line from an offset, which keeps
lookbehind working — at the cost of `^` in an opener anchoring to the start of
the line rather than to the cursor, noted there. No opener in this codebase is
anchored.

The same guard retires the limitation the previous commit documented: `\\(x\)`
is no longer read as single-backslash math either.

The regression test sets its own preferences rather than leaning on the test
before it — with the extension off the assertion is vacuous, which is how a
first version of it passed against the unfixed code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(desktop): set the math delimiters back from the formula in source mode (#5483)

`$`, `\(`, `\[` and their closers carried the multiplexer's `delimStyle`
classes but no rule ever painted them, so a delimiter was as loud as the
formula it wrapped — unlike the WYSIWYG view, which greys its own markers.

They now keep the surrounding colour at reduced opacity. Fading what is
inherited rather than naming a hue is what makes this work across the three
CodeMirror themes the app ships and the thirty-odd MarkText palettes behind
them; no single colour could sit on both `#ffffff` and `#161616`.

0.65 is the lowest value that clears 3:1 — the threshold this sort of
de-emphasised punctuation is held to — in every shipped theme. Measured:
light 7.0, graphite 6.85, ulysses 6.72, tokyo-night-light 6.0, oxocarbon-dark
7.28, synthwave-84 6.57, material-dark 5.43, one-dark 3.67. one-dark sets the
floor because its body text only reaches 6.57:1, so anything fainter
disappears there long before it does anywhere else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(desktop): give GitHub inline math its own source-view region (#5483)

`` $`e=mc^2`$ `` opens on `$`, so the dollar rule claimed the span and handed
the backticks to stex as part of the formula — the same mis-reading #5457 fixed
in the editor, still on show in the source view, and unaffected by the
`texMathGfm` preference either way.

It now has its own region, offered ahead of the dollar ones because its opener
is the longer of the two. With the extension off the dollar rule keeps the span
exactly as before, which is what `tex_math_dollars` alone prescribes and what
the editor does.

The three delimiter pairs were building their regions through one helper that
took a display pair and an inline pair together; gfm has no display half, so
the helper splits into `displayRegion` and `inlineRegion` and each extension
pushes what it actually has.

Out of scope, and unchanged: ```` ```math ```` is a fenced code block rather
than a delimiter pair, so it reaches stex through a different route than a
multiplexer region.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: trim the source-mode math comments to what the code cannot say

144 comment lines across the four files this PR touches, down to 102. What
went was restatement and detail a reader does not need to act on: how
`StringStream.match` hides context, why display openers need the guard more
than inline ones, that inner modes are imported eagerly.

Two comments had drifted from the code they describe — later edits inserted
the gfm opener between the backslash comment and its constants, and the
delimiter-contrast note between the currency comment and its test. Both are
back above what they explain, and one had picked up doubled backslashes on the
way through a script.

Comments only; no behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wangqkoe pushed a commit to Wangqkoe/marktext that referenced this pull request Oct 21, 2026
…arktext#5446) (marktext#5450)

`math` was a half-finished master switch: it gated only the marked path
(import, HTML export, clipboard), the live editor ignored it entirely,
and MarkText never set it — no schema entry, no UI, no setOptions call.
So the option that was supposed to control dollar math could not turn it
off, and where it did apply it made the editor disagree with its export.

It is now `texMathDollars`, named after the pandoc extension it
implements, with the live-editor gate it never had and a preference
under Preferences → Markdown → Extensions beside the other pandoc-named
toggles. Default on, as in pandoc's own `markdown` dialect.

It covers `$…$` and `$$…$$` together, because that is the granularity
pandoc gives the extension — the question marktext#4231 stalled on, answered by
adopting an existing definition rather than inventing one. This is also
the switch marktext#2002, marktext#4231 and marktext#5243 asked for, the last of them patching
app.asar to get it.

With the option off the syntax is neither read nor offered: the inline
and begin rules skip it, `$$` + Enter stays a paragraph, and the menus
that list blocks — quick insert, the front menu's Turn Into, and the
desktop Paragraph menu — drop Math Block. `canInsertLabel` is the single
rule behind that, consulted by those menus and by the builder every
insertion path funnels through, so the ⌥⌘M shortcut cannot slip past a
list it never reads.

Task 2 of marktext#5446.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 3e9e91c)
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.

1 participant