about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlona Enraght-Moony <code@alona.page>2024-10-19 19:24:54 +0000
committerAlona Enraght-Moony <code@alona.page>2024-10-19 19:24:54 +0000
commit9435afc0fbd3f41375b676230aca6a945741dfa7 (patch)
tree243a7292644b1be94a5c9604dc16e80d27f25baf
parentebb842328af5ef19c98cc361e1a27dc8f87ff8cd (diff)
downloadrust-9435afc0fbd3f41375b676230aca6a945741dfa7.tar.gz
rust-9435afc0fbd3f41375b676230aca6a945741dfa7.zip
rustdoc: Refractor footnote handling
-rw-r--r--src/librustdoc/html/markdown/footnotes.rs103
1 files changed, 67 insertions, 36 deletions
diff --git a/src/librustdoc/html/markdown/footnotes.rs b/src/librustdoc/html/markdown/footnotes.rs
index 47e248b891c..3f0e586b8e3 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<String, (Vec<Event<'a>>, u16)>,
+    footnotes: FxIndexMap<String, FootnoteDef<'a>>,
+}
+
+/// The definition of a single footnote.
+struct FootnoteDef<'a> {
+    content: Vec<Event<'a>>,
+    /// 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<Event<'a>>, u16) {
+    fn get_entry(&mut self, key: &str) -> (&mut Vec<Event<'a>>, 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<Item = SpannedEvent<'a>>> 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!(
-                        "<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{0}</a></sup>",
-                        (*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!("<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{0}</a></sup>", 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("<div class=\"footnotes\"><hr><ol>");
-                        for (mut content, id) in v {
-                            write!(ret, "<li id=\"fn{id}\">").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, "&nbsp;<a href=\"#fnref{id}\">↩</a>").unwrap();
-                            if is_paragraph {
-                                ret.push_str("</p>");
-                            }
-                            ret.push_str("</li>");
-                        }
-                        ret.push_str("</ol></div>");
-                        return Some((Event::Html(ret.into()), 0..0));
+                        // After all the markdown is emmited, emit an <hr> 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<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, I> {
         }
     }
 }
+
+fn collect_footnote_def<'a>(events: impl Iterator<Item = SpannedEvent<'a>>) -> Vec<Event<'a>> {
+    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<FootnoteDef<'_>>) -> String {
+    let mut ret = String::from("<div class=\"footnotes\"><hr><ol>");
+
+    // Footnotes must listed in order of id, so the numbers the
+    // browser generated for <li> are right.
+    footnotes.sort_by_key(|x| x.id);
+
+    for FootnoteDef { mut content, id } in footnotes {
+        write!(ret, "<li id=\"fn{id}\">").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, "&nbsp;<a href=\"#fnref{id}\">↩</a>").unwrap();
+        if is_paragraph {
+            ret.push_str("</p>");
+        }
+        ret.push_str("</li>");
+    }
+    ret.push_str("</ol></div>");
+
+    ret
+}