From ebb842328af5ef19c98cc361e1a27dc8f87ff8cd Mon Sep 17 00:00:00 2001 From: Alona Enraght-Moony Date: Sat, 19 Oct 2024 14:37:11 +0000 Subject: [PATCH 1/2] rustdoc: Extract footnote logic into it's own module. --- src/librustdoc/html/markdown.rs | 84 ++--------------------- src/librustdoc/html/markdown/footnotes.rs | 82 ++++++++++++++++++++++ 2 files changed, 87 insertions(+), 79 deletions(-) create mode 100644 src/librustdoc/html/markdown/footnotes.rs diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 315b7742a4cf8..7826a5d8394a7 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -37,7 +37,7 @@ use std::sync::OnceLock; use pulldown_cmark::{ BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, TagEnd, html, }; -use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Diag, DiagMessage}; use rustc_hir::def_id::LocalDefId; use rustc_middle::ty::TyCtxt; @@ -57,6 +57,7 @@ use crate::html::length_limit::HtmlWithLimit; use crate::html::render::small_url_encode; use crate::html::toc::{Toc, TocBuilder}; +mod footnotes; #[cfg(test)] mod tests; @@ -646,81 +647,6 @@ impl<'a, I: Iterator>> Iterator for SummaryLine<'a, I> { } } -/// Moves all footnote definitions to the end and add back links to the -/// references. -struct Footnotes<'a, I> { - inner: I, - footnotes: FxIndexMap>, u16)>, -} - -impl<'a, I> Footnotes<'a, I> { - fn new(iter: I) -> Self { - Footnotes { inner: iter, footnotes: FxIndexMap::default() } - } - - fn get_entry(&mut self, key: &str) -> &mut (Vec>, u16) { - let new_id = self.footnotes.len() + 1; - let key = key.to_owned(); - self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16)) - } -} - -impl<'a, I: Iterator>> Iterator for Footnotes<'a, I> { - type Item = SpannedEvent<'a>; - - fn next(&mut self) -> Option { - loop { - match self.inner.next() { - Some((Event::FootnoteReference(ref reference), range)) => { - let entry = self.get_entry(reference); - let reference = format!( - "{0}", - (*entry).1 - ); - return Some((Event::Html(reference.into()), range)); - } - Some((Event::Start(Tag::FootnoteDefinition(def)), _)) => { - let mut content = Vec::new(); - for (event, _) in &mut self.inner { - if let Event::End(TagEnd::FootnoteDefinition) = event { - break; - } - content.push(event); - } - let entry = self.get_entry(&def); - (*entry).0 = content; - } - Some(e) => return Some(e), - None => { - if !self.footnotes.is_empty() { - let mut v: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect(); - v.sort_by(|a, b| a.1.cmp(&b.1)); - let mut ret = String::from("

    "); - for (mut content, id) in v { - write!(ret, "
  1. ").unwrap(); - let mut is_paragraph = false; - if let Some(&Event::End(TagEnd::Paragraph)) = content.last() { - content.pop(); - is_paragraph = true; - } - html::push_html(&mut ret, content.into_iter()); - write!(ret, " ").unwrap(); - if is_paragraph { - ret.push_str("

    "); - } - ret.push_str("
  2. "); - } - ret.push_str("
"); - return Some((Event::Html(ret.into()), 0..0)); - } else { - return None; - } - } - } - } - } -} - /// A newtype that represents a relative line number in Markdown. /// /// In other words, this represents an offset from the first line of Markdown @@ -1408,7 +1334,7 @@ impl Markdown<'_> { let mut s = String::with_capacity(md.len() * 3 / 2); let p = HeadingLinks::new(p, None, ids, heading_offset); - let p = Footnotes::new(p); + let p = footnotes::Footnotes::new(p); let p = LinkReplacer::new(p.map(|(ev, _)| ev), links); let p = TableWrapper::new(p); let p = CodeBlocks::new(p, codes, edition, playground); @@ -1443,7 +1369,7 @@ impl MarkdownWithToc<'_> { { let p = HeadingLinks::new(p, Some(&mut toc), ids, HeadingOffset::H1); - let p = Footnotes::new(p); + let p = footnotes::Footnotes::new(p); let p = TableWrapper::new(p.map(|(ev, _)| ev)); let p = CodeBlocks::new(p, codes, edition, playground); html::push_html(&mut s, p); @@ -1476,7 +1402,7 @@ impl MarkdownItemInfo<'_> { let mut s = String::with_capacity(md.len() * 3 / 2); let p = HeadingLinks::new(p, None, ids, HeadingOffset::H1); - let p = Footnotes::new(p); + let p = footnotes::Footnotes::new(p); let p = TableWrapper::new(p.map(|(ev, _)| ev)); let p = p.filter(|event| { !matches!(event, Event::Start(Tag::Paragraph) | Event::End(TagEnd::Paragraph)) diff --git a/src/librustdoc/html/markdown/footnotes.rs b/src/librustdoc/html/markdown/footnotes.rs new file mode 100644 index 0000000000000..47e248b891c32 --- /dev/null +++ b/src/librustdoc/html/markdown/footnotes.rs @@ -0,0 +1,82 @@ +//! Markdown footnote handling. +use std::fmt::Write as _; + +use pulldown_cmark::{Event, Tag, TagEnd, html}; +use rustc_data_structures::fx::FxIndexMap; + +use super::SpannedEvent; + +/// Moves all footnote definitions to the end and add back links to the +/// references. +pub(super) struct Footnotes<'a, I> { + inner: I, + footnotes: FxIndexMap>, u16)>, +} + +impl<'a, I> Footnotes<'a, I> { + pub(super) fn new(iter: I) -> Self { + Footnotes { inner: iter, footnotes: FxIndexMap::default() } + } + + fn get_entry(&mut self, key: &str) -> &mut (Vec>, u16) { + let new_id = self.footnotes.len() + 1; + let key = key.to_owned(); + self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16)) + } +} + +impl<'a, I: Iterator>> Iterator for Footnotes<'a, I> { + type Item = SpannedEvent<'a>; + + fn next(&mut self) -> Option { + loop { + match self.inner.next() { + Some((Event::FootnoteReference(ref reference), range)) => { + let entry = self.get_entry(reference); + let reference = format!( + "{0}", + (*entry).1 + ); + return Some((Event::Html(reference.into()), range)); + } + Some((Event::Start(Tag::FootnoteDefinition(def)), _)) => { + let mut content = Vec::new(); + for (event, _) in &mut self.inner { + if let Event::End(TagEnd::FootnoteDefinition) = event { + break; + } + content.push(event); + } + let entry = self.get_entry(&def); + (*entry).0 = content; + } + Some(e) => return Some(e), + None => { + if !self.footnotes.is_empty() { + let mut v: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect(); + v.sort_by(|a, b| a.1.cmp(&b.1)); + let mut ret = String::from("

    "); + for (mut content, id) in v { + write!(ret, "
  1. ").unwrap(); + let mut is_paragraph = false; + if let Some(&Event::End(TagEnd::Paragraph)) = content.last() { + content.pop(); + is_paragraph = true; + } + html::push_html(&mut ret, content.into_iter()); + write!(ret, " ").unwrap(); + if is_paragraph { + ret.push_str("

    "); + } + ret.push_str("
  2. "); + } + ret.push_str("
"); + return Some((Event::Html(ret.into()), 0..0)); + } else { + return None; + } + } + } + } + } +} From 9435afc0fbd3f41375b676230aca6a945741dfa7 Mon Sep 17 00:00:00 2001 From: Alona Enraght-Moony Date: Sat, 19 Oct 2024 19:24:54 +0000 Subject: [PATCH 2/2] rustdoc: Refractor footnote handling --- src/librustdoc/html/markdown/footnotes.rs | 103 ++++++++++++++-------- 1 file changed, 67 insertions(+), 36 deletions(-) diff --git a/src/librustdoc/html/markdown/footnotes.rs b/src/librustdoc/html/markdown/footnotes.rs index 47e248b891c32..3f0e586b8e372 100644 --- a/src/librustdoc/html/markdown/footnotes.rs +++ b/src/librustdoc/html/markdown/footnotes.rs @@ -10,7 +10,14 @@ use super::SpannedEvent; /// references. pub(super) struct Footnotes<'a, I> { inner: I, - footnotes: FxIndexMap>, u16)>, + footnotes: FxIndexMap>, +} + +/// The definition of a single footnote. +struct FootnoteDef<'a> { + content: Vec>, + /// The number that appears in the footnote reference and list. + id: u16, } impl<'a, I> Footnotes<'a, I> { @@ -18,10 +25,15 @@ impl<'a, I> Footnotes<'a, I> { Footnotes { inner: iter, footnotes: FxIndexMap::default() } } - fn get_entry(&mut self, key: &str) -> &mut (Vec>, u16) { + fn get_entry(&mut self, key: &str) -> (&mut Vec>, u16) { let new_id = self.footnotes.len() + 1; let key = key.to_owned(); - self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16)) + let FootnoteDef { content, id } = self + .footnotes + .entry(key) + .or_insert(FootnoteDef { content: Vec::new(), id: new_id as u16 }); + // Don't allow changing the ID of existing entrys, but allow changing the contents. + (content, *id) } } @@ -32,46 +44,28 @@ impl<'a, I: Iterator>> Iterator for Footnotes<'a, I> { loop { match self.inner.next() { Some((Event::FootnoteReference(ref reference), range)) => { - let entry = self.get_entry(reference); - let reference = format!( - "{0}", - (*entry).1 - ); + // 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 reference = + format!("{0}", id); return Some((Event::Html(reference.into()), range)); } Some((Event::Start(Tag::FootnoteDefinition(def)), _)) => { - let mut content = Vec::new(); - for (event, _) in &mut self.inner { - if let Event::End(TagEnd::FootnoteDefinition) = event { - break; - } - content.push(event); - } - let entry = self.get_entry(&def); - (*entry).0 = content; + // When we see a footnote definition, collect the assocated content, and store + // that for rendering later. + let content = collect_footnote_def(&mut self.inner); + let (entry_content, _) = self.get_entry(&def); + *entry_content = content; } Some(e) => return Some(e), None => { if !self.footnotes.is_empty() { - let mut v: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect(); - v.sort_by(|a, b| a.1.cmp(&b.1)); - let mut ret = String::from("

    "); - for (mut content, id) in v { - write!(ret, "
  1. ").unwrap(); - let mut is_paragraph = false; - if let Some(&Event::End(TagEnd::Paragraph)) = content.last() { - content.pop(); - is_paragraph = true; - } - html::push_html(&mut ret, content.into_iter()); - write!(ret, " ").unwrap(); - if is_paragraph { - ret.push_str("

    "); - } - ret.push_str("
  2. "); - } - ret.push_str("
"); - return Some((Event::Html(ret.into()), 0..0)); + // After all the markdown is emmited, emit an
then all the footnotes + // in a list. + let defs: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect(); + let defs_html = render_footnotes_defs(defs); + return Some((Event::Html(defs_html.into()), 0..0)); } else { return None; } @@ -80,3 +74,40 @@ impl<'a, I: Iterator>> Iterator for Footnotes<'a, I> { } } } + +fn collect_footnote_def<'a>(events: impl Iterator>) -> Vec> { + let mut content = Vec::new(); + for (event, _) in events { + if let Event::End(TagEnd::FootnoteDefinition) = event { + break; + } + content.push(event); + } + content +} + +fn render_footnotes_defs(mut footnotes: Vec>) -> String { + let mut ret = String::from("

    "); + + // Footnotes must listed in order of id, so the numbers the + // browser generated for
  1. are right. + footnotes.sort_by_key(|x| x.id); + + for FootnoteDef { mut content, id } in footnotes { + write!(ret, "
  2. ").unwrap(); + let mut is_paragraph = false; + if let Some(&Event::End(TagEnd::Paragraph)) = content.last() { + content.pop(); + is_paragraph = true; + } + html::push_html(&mut ret, content.into_iter()); + write!(ret, " ").unwrap(); + if is_paragraph { + ret.push_str("

    "); + } + ret.push_str("
  3. "); + } + ret.push_str("
"); + + ret +}