Skip to content

Syntax-highlight fenced code blocks and fix their skin styling#41

Merged
JeroenDeDauw merged 4 commits into
masterfrom
feature/syntax-highlight-fenced-code
Jul 16, 2026
Merged

Syntax-highlight fenced code blocks and fix their skin styling#41
JeroenDeDauw merged 4 commits into
masterfrom
feature/syntax-highlight-fenced-code

Conversation

@JeroenDeDauw

@JeroenDeDauw JeroenDeDauw commented Jul 16, 2026

Copy link
Copy Markdown
Member

Fixes #37
Fixes #36

A fenced code block whose info string names a language (a python fence, say) is now highlighted server-side by
Extension:SyntaxHighlight (Pygments) when that extension is installed, matching how a wikitext <syntaxhighlight>
block renders. Without it — or for a block with no info string, an unknown language, an over-size block or a
highlighter error — the block keeps the default <pre><code class="language-…"> rendering, so this is a silent
progressive enhancement with no hard dependency and no new configuration.

Delegation goes through a pure CodeHighlighter port with a SyntaxHighlight adapter in Persistence and a no-op
fallback, selected in the composition root by ExtensionRegistry. Highlight attempts are capped per render
(mirroring the template-expansion cap) because each block can shell out to Pygments and there is no
expensive-function budget in the ContentHandler context. The required ResourceLoader modules (ext.pygments,
ext.pygments.view) are registered on the ParserOutput only when a block was actually highlighted.

Those plain-path blocks were also mis-styled, which the second commit fixes. Skins style bare code for inline use
(background, border, padding), and since code is inline that background paints once per line box — so a block
rendered as CommonMark's pre > code showed a pill behind every line, inside the box pre already draws. Wikitext
never nests the two elements, so no skin resets it. This matters most on wikis without SyntaxHighlight
installed, where every fenced block takes the plain path. The fix is the extension's first style module, loaded on
every markdown page: it is tiny, it also covers indented code blocks, and it gives future markdown-specific styling
a home.

CI gains a step cloning SyntaxHighlight so the new integration tests run (they skip when it is absent), and
phpstan/psalm scan its source to resolve the adapter's class reference.

Possible follow-up: a tracking category for pages whose fenced languages could not be highlighted.

AI-authored — Claude Code, Opus 4.8 (max); detailed spec from @JeroenDeDauw, executed autonomously with no redirections; diff not yet human-reviewed; 395 PHPUnit tests + phpstan/psalm/phpcs green locally, verified end-to-end in a dev wiki, all CI checks green.

Production notes

Design and spec came from a Fable 5 (max)-orchestrated session; implementation, tests and verification by Opus 4.8 (max).

Two notes on the styling fix. The intended browser check was not possible (the Playwright profile was locked by another process), so it was verified instead by asserting the served CSS: the module's stylesheet link is in the page head, load.php returns the reset rule, the rendered page's plain blocks really are .mw-parser-output pre > code, and the selector's specificity (0,1,2) beats the skin's bare code rules (0,0,1), which carry no !important — so the reset wins regardless of load order. A pixel-level confirmation is still worth a glance on review.

A third commit fixes CI rather than the extension: the SyntaxHighlight clone added by the first commit passed only while the MediaWiki cache was cold. The cache keeps extensions/ despite its !mediawiki/extensions/ exclusion, so once warm it restored SyntaxHighlight and the clone failed on a non-empty destination — this branch's second CI run went red for that reason alone. The clone now removes the directory first, which is correct with a cold or warm cache and needs no cache key bump.

One spec deviation on the highlighting side: empty or whitespace-only code is guarded before calling SyntaxHighlight, since it would otherwise wrap empty input in a non-empty highlight div (isGood, non-empty value) rather than returning something the "value is empty" check would catch.

Fixes #37

A fenced code block whose info string names a language (```python) is now
highlighted server-side by Extension:SyntaxHighlight (Pygments) when that
extension is installed, matching how a wikitext <syntaxhighlight> block renders.
Without it -- or for a block with no info string, an unknown language, an
over-size block or a highlighter error -- the block keeps the default
<pre><code class="language-..."> rendering, so this is a silent progressive
enhancement with no hard dependency and no new configuration.

Delegation goes through a pure CodeHighlighter port with a SyntaxHighlight
adapter in Persistence and a no-op fallback, selected in the composition root by
ExtensionRegistry. Highlight attempts are capped per render (mirroring the
template-expansion cap) because each block can shell out to Pygments and there
is no expensive-function budget in the ContentHandler context. The required
ResourceLoader modules (ext.pygments, ext.pygments.view) are registered on the
ParserOutput only when a block was actually highlighted.

Possible follow-up: a tracking category for pages whose fenced languages could
not be highlighted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.25%. Comparing base (e4ef238) to head (a03b281).

Additional details and impacted files
@@             Coverage Diff              @@
##             master      #41      +/-   ##
============================================
- Coverage     84.56%   83.25%   -1.32%     
- Complexity      379      401      +22     
============================================
  Files            37       40       +3     
  Lines           985     1039      +54     
============================================
+ Hits            833      865      +32     
- Misses          152      174      +22     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

JeroenDeDauw and others added 2 commits July 16, 2026 18:33
Fixes #36

Skins style bare `code` for inline use, giving it a background, a border
and padding. Since `code` is an inline element, that background paints
once per line box, so a code block showed a pill behind every one of its
lines inside the box `pre` already draws. Wikitext never nests the two
elements, so no skin resets this, while CommonMark renders every code
block as `pre > code`.

Ships the extension's first style module, loaded on every markdown page:
it is tiny, it also covers indented code blocks, and it gives future
markdown-specific styling a home.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The MediaWiki cache turned out to keep extensions/, despite the
`!mediawiki/extensions/` exclusion, so once the cache was warm the
restored tree already contained SyntaxHighlight and the clone step
failed on a non-empty destination. It only passed the first time round
because the cache was still cold.

Remove the directory before cloning, so the step is correct with a cold
or a warm cache without needing a cache key bump, and drop the comment
claiming the cache excludes extensions/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JeroenDeDauw JeroenDeDauw changed the title Syntax-highlight fenced code blocks via Extension:SyntaxHighlight Syntax-highlight fenced code blocks and fix their skin styling Jul 16, 2026
@JeroenDeDauw
JeroenDeDauw marked this pull request as ready for review July 16, 2026 16:53
Renames ext.nativeMarkdown.content to ext.nativeMarkdown.styles and moves
its CSS from modules/ to resources/, matching both Karsten's independent
fix in #39 and the house convention WikibaseFacetedSearch and Maps follow.
Sets remoteExtPath to NativeMarkdown/resources so it agrees with
localBasePath, which matters as soon as the CSS references a file.

Drops the border-radius reset: it does nothing once the border is gone and
the background is transparent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JeroenDeDauw
JeroenDeDauw merged commit c0c6295 into master Jul 16, 2026
12 checks passed
@JeroenDeDauw
JeroenDeDauw deleted the feature/syntax-highlight-fenced-code branch July 16, 2026 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants