Add support for cross-document references#79
Conversation
📝 WalkthroughWalkthroughThis PR implements cross-reference support for the merged single-file Typst builder. In Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Implement :doc:/:ref:/:confval:/etc. cross-references in the Typst builder, following the same approach as Sphinx's LaTeX builder: all documents are merged into a single doctree, and Sphinx's standard env.resolve_references() runs against that merged tree right after merging. This lets Sphinx's own domains handle path normalization, label lookup, title substitution, and intersphinx exactly as they do for any other builder. Builder.get_target_uri()/get_relative_uri() map a docname onto the single merged Typst document, mirroring the LaTeX builder's "%docname" convention. The writer's job is then only to translate the refid/refuri that Sphinx resolves into a namespaced Typst label (<docname:id>); the namespace keeps ids from colliding across documents now that they all share one output file. A small per-document "#metadata(none) <label>" anchor - the same trick HTML uses for an extra empty anchor per additional id on an element - gives whole-document :doc: references something to link to even when the target document has no sections, and covers section/signature ids beyond the first, since Typst only allows one label per element. docutils' PropagateTargets transform forwards an explicit label (e.g. RST's ``.. _label:``) placed directly before a section, a paragraph, a directive with a signature (``.. confval::``), or another target, onto whatever follows it: it clears the target node's own ids and points its refid at the merged-in id instead. visit_target mirrors Sphinx's LaTeX writer (visit_target in sphinx/writers/latex.py) to handle this correctly: walk past any chain of adjacent forwarding targets to find what the id actually landed on. When that's a section, visit_title/depart_title already emit a namespaced anchor for every one of the section's ids (including ones forwarded here), so visit_target skips it to avoid emitting the label twice. Anything else - a plain paragraph, or a ``desc`` node whose own id handling in depart_desc_signature only looks at its signature's ids, not ids forwarded onto the parent ``desc`` - has no other code path that would emit the anchor, so visit_target writes it directly. Includes test coverage for basic cross-references, :ref: to named targets, relative paths (including bare relative paths resolved from the referring document's directory), nested documents, edge cases such as documents without sections and confval-style targets, and explicit labels placed directly before a plain paragraph or a confval directive.
|
Updated to fix some problems we saw with real-world documents. |
Implement :doc:/:ref:/:confval:/etc. cross-references in the Typst
builder, following the same approach as Sphinx's LaTeX builder: all
documents are merged into a single doctree, and Sphinx's standard
env.resolve_references() runs against that merged tree right after
merging. This lets Sphinx's own domains handle path normalization,
label lookup, title substitution, and intersphinx exactly as they do
for any other builder.
Builder.get_target_uri()/get_relative_uri() map a docname onto the
single merged Typst document, mirroring the LaTeX builder's
"%docname" convention. The writer's job is then only to translate the
refid/refuri that Sphinx resolves into a namespaced Typst label
(docname:id); the namespace keeps ids from colliding across
documents now that they all share one output file. A small
per-document "#metadata(none) " anchor - the same trick HTML
uses for an extra empty anchor per additional id on an element - gives
whole-document :doc: references something to link to even when the
target document has no sections, and covers section/signature ids
beyond the first, since Typst only allows one label per element.
docutils' PropagateTargets transform forwards an explicit label (e.g.
RST's
.. _label:) placed directly before a section, a paragraph,a directive with a signature (
.. confval::), or another target,onto whatever follows it: it clears the target node's own ids and
points its refid at the merged-in id instead. visit_target mirrors
Sphinx's LaTeX writer (visit_target in sphinx/writers/latex.py) to
handle this correctly: walk past any chain of adjacent forwarding
targets to find what the id actually landed on. When that's a
section, visit_title/depart_title already emit a namespaced anchor
for every one of the section's ids (including ones forwarded here), so
visit_target skips it to avoid emitting the label twice. Anything else - a plain paragraph, or a
descnode whose own id handling indepart_desc_signature only looks at its signature's ids, not ids
forwarded onto the parent
desc- has no other code path that wouldemit the anchor, so visit_target writes it directly.
Includes test coverage for basic cross-references, :ref: to named
targets, relative paths (including bare relative paths resolved from
the referring document's directory), nested documents, edge cases such
as documents without sections and confval-style targets, and explicit
labels placed directly before a plain paragraph or a confval directive.