Skip to content

Custom syntax highlighting now works in Quarto Hub projects#187

Merged
gordonwoodhull merged 2 commits into
mainfrom
bugfix/bd-izfv-thread-user-grammars
May 12, 2026
Merged

Custom syntax highlighting now works in Quarto Hub projects#187
gordonwoodhull merged 2 commits into
mainfrom
bugfix/bd-izfv-thread-user-grammars

Conversation

@gordonwoodhull

@gordonwoodhull gordonwoodhull commented May 12, 2026

Copy link
Copy Markdown
Member

I uncovered a failing test when running hub e2e tests on Playwright; Claude's fix sounds reasonable.

  • crates/quarto/tests/smoke-all/highlighting/03-user-grammar/03-user-grammar-toml.qmd

Summary

  • Before: user-supplied tree-sitter grammars under _quarto/grammars/<lang>/ only highlighted code blocks in single-file documents. The moment a qmd file lived under a _quarto.yml ancestor, the hub-client routed through renderPageInProjectrender_project_active_page_to_response, which literally discarded the grammars handle (let _ = user_grammars;).
  • After: the project-render path threads user_grammars through RenderToHtmlRenderer to the per-page RenderContext, so CodeHighlightStage consults the same JsUserGrammars handle the single-file path was already wired for. RenderContext::user_grammar_provider (and the matching StageContext field) move from Option<Box<dyn UserGrammarProvider>> to Option<Rc<RefCell<dyn UserGrammarProvider>>> so one handle can be shared across every page a renderer touches.
  • Two-line e2e harness fixes folded in so the existing highlighting/03-user-grammar-toml.qmd smoke-all fixture can actually verify the fix in a browser: binary fixture loading (.wasm grammars were being read as utf-8 and corrupted before reaching Automerge), and getPreviewHtml now passes a VFS-derived userGrammars context so the re-render matches the live iframe.

bd-izfv. Plan + investigation: claude-notes/plans/2026-05-10-thread-user-grammars-renderer.md.

Test plan

  • cargo build --workspace
  • cargo nextest run --workspace — 8805 passed
  • cargo nextest run -p quarto-core --test render_to_html_user_grammars — passes; confirmed failing with the wiring temporarily reverted
  • cargo xtask verify — all 9 steps green
  • npx playwright test --grep "user-grammar" smoke-all.spec.ts — passes; iframe innerHTML at assertion time shows <pre class="sourceCode toml">…<span class="hl-string">"example"</span> (snippet recorded in the plan's "Phase 4 end-to-end verification artifact" section)

Full cargo xtask verify --e2e had 6 unrelated CI-load timeout flakes — the kind documented in bc562bd4's commit message — all of which passed when re-run in isolation. The user-grammar fixture itself was the only e2e test directly relevant to this change.

The hub-client's project-rendering path was dropping user_grammars
because RenderToHtmlRenderer constructed its own per-page
RenderContext with no API for attaching a provider. Result: any qmd
under a `_quarto.yml` ancestor rendered fenced blocks for
user-grammar languages (e.g. the highlighting/03-user-grammar-toml
fixture) unhighlighted, even though the single-file render path was
wired correctly.

Migrate `RenderContext::user_grammar_provider` and the matching
`StageContext` field from `Option<Box<dyn UserGrammarProvider>>` to
`Option<Rc<RefCell<dyn UserGrammarProvider>>>` so a single handle
can be shared across every page a renderer touches without being
consumed. Add `RenderToHtmlRenderer::with_user_grammars(...)` and
install the provider on each per-page `RenderContext`. Update the
WASM project entry point `render_project_active_page_to_response`
to wrap the incoming `JsUserGrammars` in `Rc<RefCell<_>>` and pass
it through instead of dropping it.

The pipeline-internal `.take()` becomes `.clone()` so the `Rc` is
shared with the `StageContext` rather than transferred — matters
for multi-page renders that reuse the same renderer.

Plan + investigation notes:
`claude-notes/plans/2026-05-10-thread-user-grammars-renderer.md`.

Tests: new `crates/quarto-core/tests/render_to_html_user_grammars.rs`
drives `RenderToHtmlRenderer` through the orchestrator with a stub
provider and asserts the rendered HTML carries the matching
`<span class="hl-...">` wrapper. Confirmed it fails at HEAD without
the wiring and passes after.

End-to-end harness fixes folded in (without these the
highlighting/03-user-grammar-toml.qmd e2e fixture can't actually
exercise the fix in a browser):

- smokeAllDiscovery.ts: detect binary fixture extensions (.wasm
  grammars + common image/font types), read as bytes, base64-
  encode, and tag `contentType: 'binary'` with a matching
  mimeType so the Automerge sync client routes them as binary
  documents instead of corrupting them through utf-8 decoding.
  Add `contentType`/`mimeType` fields on `DiscoveredTest` and
  forward them in smoke-all.spec.ts.
- previewExtraction.ts::getPreviewHtml: derive a userGrammars
  context from VFS state (vfsListFiles + vfsReadFile /
  vfsReadBinaryFile, with the /project/ prefix stripped to match
  discoverUserGrammars's project-relative shape) so the re-render
  matches what Preview.tsx → automergeSync produces in the live
  iframe.

End-to-end verification (snippet from the iframe at assertion time):

  <pre class="sourceCode toml" …><code><span class="hl-property">
    <span class="hl-type">name</span> <span class="hl-operator">=</span>
    <span class="hl-string">"example"</span></span> …
@gordonwoodhull gordonwoodhull merged commit 374f329 into main May 12, 2026
5 checks passed
@gordonwoodhull gordonwoodhull deleted the bugfix/bd-izfv-thread-user-grammars branch May 12, 2026 18:00
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