feat(editor): the built-in themes colour Markdown (#676) - #684
Merged
Conversation
added 3 commits
August 19, 2026 22:20
Both themes shipped `rules: []` and inherited Monaco's vs/vs-dark, which were written for source code: headings, list markers and table pipes all arrived as one blue `keyword`, links and horizontal rules got no colour at all, and bold and italic got a font style and nothing else. Twelve rules, in the app's own six colours — the ones the preview and the chrome resolve through --color-*. Two of them exist to undo Monaco's trie inheritance, which would otherwise paint every table header like a heading and every fenced block like inline code. Headings and lists still share a colour: the tokenizer emits one `keyword` for both, and a theme cannot invent a distinction the grammar never made.
Monaco appends the grammar's `tokenPostfix` to every token it emits, so Markdown's are `keyword.md`, `string.md`, `comment.md`. The rules here were spelled bare, which puts them at the root of the token trie — where they repainted the Python, Rust and JSON inside fenced code blocks too, with a six-colour prose palette instead of the defaults tuned for code. Scoping them to `.md` also means a prefix no longer stands in for its children (`keyword.table.left.md` walks `keyword` first), so the table tokens are spelled out — and that is the chance to fix the other half: the table frame and `>` had gone muted, which put them nearer the body text than the blue they replaced. All block markup now reads as markup.
Monaco's defaults paint a tag in a Markdown document as source code — a maroon tag name, a red attribute — which made the one construct that is not Markdown the loudest thing on the line. It takes the muted colour, which is what the escape hatch deserves.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
The editor's own two themes now colour Markdown. Follow-up to #678, which fixed imported VS Code themes and left the built-ins as they were.
Before, both
defineThemecalls shippedrules: []and inherited Monaco'svs/vs-dark— palettes written for source code, which had never heard of this document. Measured against the base themes, that gave four colours, with the wrong things sharing them:#, list marker, table dividerkeyword#0000FF#0969daaccentkeyword.table.*#0000FF— same as a heading#656d76mutedkeyword.table.header#0000FF— the whole row read as a heading#1f2328text>comment#008000#656d76mutedstring#A31515#1a7f37codevariable#001188#1a7f37codevariable.source#1f2328textstring.link#8250dflinkmeta.separator#656d76mutedstrong,emphasis#9a6700, style keptThe colours are the app's own six, the ones
styles.cssexposes as--color-accent-fg,--color-success-fg,--color-attention-fg,--color-done-fg,--color-fg-mutedand--color-fg-default, so the editor now reads as part of the same app as the preview rather than as a code editor someone embedded.Mechanism
Monaco resolves a token against a trie, and that is what made two of the twelve rules necessary.
keyword.table.headeris the text of a header cell and inherits whateverkeywordsays — so colouring headings blue painted every table header blue too.variable.sourceis the body of a fenced block and inherits inline code's colour, which turned whole blocks green. Both rules exist to undo an inheritance rather than to add a colour, andeditorTheme.test.tsfails if either is dropped.Neither can say "leave it alone": a Monaco rule must name a colour, and
FontStyle.NotSethas no foreground equivalent. They name the app's own--color-fg-default, which is why a header cell sits a hair off the body cells below it (#1f2328against the base theme's#000000) — in step with the preview instead of with Monaco.Bold and italic carry no
fontStyle, which Monaco reads as NotSet and merges by leaving the base theme'sstrong: bold/emphasis: italicstanding. Spellingregularwould have flattened them.Headings and list markers still share one colour. The tokenizer emits a bare
keywordfor#, for setext underlines, for list bullets and for the---|---divider alike, so nothing downstream can tell them apart. That ceiling is the grammar, not the theme, and moving it means owning a fork of Monaco's Markdown monarch definition — a separate piece of work, and still only a regex's guess about what will render.Scope
Imported VS Code themes are untouched: they take their own
tokenColorsthroughtheme.ts, including the scope renaming #678 added.The editor's default text colour is left as Monaco's (
#000000/#D4D4D4) rather than moved to--color-fg-default. Repainting every character of every document is a bigger change than colouring Markdown syntax, and the difference is under 3% luminance.No new setting. The palette follows the light/dark theme the app is already on, the same as every other colour in the app.
Tests
scripts/editorTheme.test.ts, five behaviour tests overmarkdownTokenRules:monaco-editor's ownmarkdown.js, since a rule keyed on a name nothing emits is silently inert. That is exactly the defect How can I highlight markdown formatting (bold/italics etc.) with different colours? #676 was, one layer over, and it is the failure mode this change is most exposed to.--color-*value parsed out ofstyles.cssfor that appearance. The duplication is one-way and unavoidable (defineThemetakes hex strings and never sees a stylesheet), so this is what keeps the two from drifting.Revert
rules: markdownTokenRules(…)torules: []and the suite goes red on the first two.Verification
The before/after table was measured in Chromium against the dev server with
window.__TAURI_INTERNALS__stubbed, by typing a document that exercises every rule and readinggetComputedStyle().coloroff the rendered token spans — light and dark. What that does not cover is how the palette reads on a real display at length; it is six colours that the rest of the app already uses on the same two backgrounds, but nobody has written a long document in it yet.Revised after review
Two defects in the first version, both found by looking at a real document side by side with the old build.
The palette was leaking into every other language. Monaco appends the grammar's
tokenPostfixto every token (monarchLexer.js), so Markdown emitskeyword.md/string.md/comment.mdwhile Python emitskeyword.pythonand so on. The rules were spelled bare, which places them at the root of the token trie — so a six-colour prose palette repainted the Python, Rust and JSON inside fenced code blocks, over Monaco's defaults which are tuned for code. Every rule is now.md-scoped, and a test fails on any rule that is not.Measured after the fix, in a ```python block: the comment is back to
#608B4E, `assert` to `#569CD6`, the string to `#CE9178` — Monaco's own vs-dark values, i.e. exactly what shipped before this PR.Muting the table frame cost more than it bought. The first version made table pipes and
>--color-fg-muted, on the theory that the frame is noise and the cells should read. Against the body text that is a step backwards, which is measurable — CIEDE2000 against the editor's text colour, light theme:#0000FF, ΔE 39.7#656d76, ΔE 33.0#0969da, ΔE 42.4>#008000, ΔE 43.1#656d76, ΔE 33.0#0969da, ΔE 42.4---rule#656d76, ΔE 33.0#0969da, ΔE 42.4All block markup —
#, list markers, the table frame and divider,>, horizontal rules — now takes one colour. Distinguishing kinds of markup matters less than distinguishing markup from prose, and the tokenizer cannot tell a heading from a list marker anyway.Two constructs still move closer to the text colour, deliberately: a table header cell (ΔE 39.7 → 9.0) and the body of a fenced block (30.0 → 9.0). Both are content that the old theme painted as though it were syntax.
The four semantic families stay apart. Worst pair, CIEDE2000: structure↔link ΔE 17.8 light / 21.2 dark. For comparison the old palette's worst pair was link↔emphasis at ΔE 0.0 — both were simply the body text colour. (This is also why the editor's link colour stays purple rather than moving to the accent: that change would make the tightest pair zero.)