diff options
| author | Guillaume Gomez <guillaume1.gomez@gmail.com> | 2025-08-14 11:39:32 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-14 11:39:32 +0200 |
| commit | f676dd8729ed30b3fddaa409683bd09e33bca6f1 (patch) | |
| tree | 5885540baa4032946c8c94f21ea2e127e4ee3f6f /src | |
| parent | 2c1ac85679678dfe5cce7ea8037735b0349ceaf3 (diff) | |
| parent | 74aca53f5591f9ad4a5e74c42eb101534c3e7b12 (diff) | |
| download | rust-f676dd8729ed30b3fddaa409683bd09e33bca6f1.tar.gz rust-f676dd8729ed30b3fddaa409683bd09e33bca6f1.zip | |
Rollup merge of #140434 - a4lg:rustdoc-multi-footnote-refs, r=fmease,GuillaumeGomez
rustdoc: Allow multiple references to a single footnote Multiple references to a single footnote is a part of GitHub Flavored Markdown syntax (although not explicitly documented as well as regular footnotes, it is implemented in GitHub's fork of CommonMark) and not prohibited by rustdoc. cf. <https://github.com/github/cmark-gfm/blob/587a12bb54d95ac37241377e6ddc93ea0e45439b/test/extensions.txt#L762-L780> However, using it makes multiple `sup` elements with the same `id` attribute, which is invalid per the HTML specification. Still, not only this is a valid GitHub Flavored Markdown syntax, this is helpful on certain cases and actually tested (accidentally) in `tests/rustdoc/footnote-reference-in-footnote-def.rs`. This commit keeps track of the number of references per footnote and gives unique ID to each reference. It also emits *all* back links from a footnote to its references as "↩" (return symbol) plus a numeric list in superscript. As a known limitation, it assumes that all references to a footnote are rendered (this is not always true if a dangling footnote has one or more references but considered a reasonable compromise). Also note that, this commit is designed so that no HTML changes will occur unless multiple references to a single footnote is actually used.
Diffstat (limited to 'src')
| -rw-r--r-- | src/librustdoc/html/markdown/footnotes.rs | 33 |
1 files changed, 24 insertions, 9 deletions
diff --git a/src/librustdoc/html/markdown/footnotes.rs b/src/librustdoc/html/markdown/footnotes.rs index 7ee012c4da2..a81d8dd6035 100644 --- a/src/librustdoc/html/markdown/footnotes.rs +++ b/src/librustdoc/html/markdown/footnotes.rs @@ -23,6 +23,8 @@ struct FootnoteDef<'a> { content: Vec<Event<'a>>, /// The number that appears in the footnote reference and list. id: usize, + /// The number of footnote references. + num_refs: usize, } impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Footnotes<'a, I> { @@ -33,21 +35,25 @@ impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Footnotes<'a, I> { Footnotes { inner: iter, footnotes: FxIndexMap::default(), existing_footnotes, start_id } } - fn get_entry(&mut self, key: &str) -> (&mut Vec<Event<'a>>, usize) { + fn get_entry(&mut self, key: &str) -> (&mut Vec<Event<'a>>, usize, &mut usize) { let new_id = self.footnotes.len() + 1 + self.start_id; let key = key.to_owned(); - let FootnoteDef { content, id } = - self.footnotes.entry(key).or_insert(FootnoteDef { content: Vec::new(), id: new_id }); + let FootnoteDef { content, id, num_refs } = self + .footnotes + .entry(key) + .or_insert(FootnoteDef { content: Vec::new(), id: new_id, num_refs: 0 }); // Don't allow changing the ID of existing entries, but allow changing the contents. - (content, *id) + (content, *id, num_refs) } fn handle_footnote_reference(&mut self, reference: &CowStr<'a>) -> Event<'a> { // When we see a reference (to a footnote we may not know) the definition of, // reserve a number for it, and emit a link to that number. - let (_, id) = self.get_entry(reference); + let (_, id, num_refs) = self.get_entry(reference); + *num_refs += 1; + let fnref_suffix = if *num_refs <= 1 { "".to_owned() } else { format!("-{num_refs}") }; let reference = format!( - "<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{1}</a></sup>", + "<sup id=\"fnref{0}{fnref_suffix}\"><a href=\"#fn{0}\">{1}</a></sup>", id, // Although the ID count is for the whole page, the footnote reference // are local to the item so we make this ID "local" when displayed. @@ -85,7 +91,7 @@ impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, I> { // When we see a footnote definition, collect the associated content, and store // that for rendering later. let content = self.collect_footnote_def(); - let (entry_content, _) = self.get_entry(&def); + let (entry_content, _, _) = self.get_entry(&def); *entry_content = content; } Some(e) => return Some(e), @@ -113,7 +119,7 @@ fn render_footnotes_defs(mut footnotes: Vec<FootnoteDef<'_>>) -> String { // browser generated for <li> are right. footnotes.sort_by_key(|x| x.id); - for FootnoteDef { mut content, id } in footnotes { + for FootnoteDef { mut content, id, num_refs } in footnotes { write!(ret, "<li id=\"fn{id}\">").unwrap(); let mut is_paragraph = false; if let Some(&Event::End(TagEnd::Paragraph)) = content.last() { @@ -121,7 +127,16 @@ fn render_footnotes_defs(mut footnotes: Vec<FootnoteDef<'_>>) -> String { is_paragraph = true; } html::push_html(&mut ret, content.into_iter()); - write!(ret, " <a href=\"#fnref{id}\">↩</a>").unwrap(); + if num_refs <= 1 { + write!(ret, " <a href=\"#fnref{id}\">↩</a>").unwrap(); + } else { + // There are multiple references to single footnote. Make the first + // back link a single "a" element to make touch region larger. + write!(ret, " <a href=\"#fnref{id}\">↩ <sup>1</sup></a>").unwrap(); + for refid in 2..=num_refs { + write!(ret, " <sup><a href=\"#fnref{id}-{refid}\">{refid}</a></sup>").unwrap(); + } + } if is_paragraph { ret.push_str("</p>"); } |
