rustdoc/html/markdown/
footnotes.rs
1use std::fmt::Write as _;
4use std::sync::atomic::{AtomicUsize, Ordering};
5use std::sync::{Arc, Weak};
6
7use pulldown_cmark::{CowStr, Event, Tag, TagEnd, html};
8use rustc_data_structures::fx::FxIndexMap;
9
10use super::SpannedEvent;
11
12pub(super) struct Footnotes<'a, I> {
15 inner: I,
16 footnotes: FxIndexMap<String, FootnoteDef<'a>>,
17 existing_footnotes: Arc<AtomicUsize>,
18 start_id: usize,
19}
20
21struct FootnoteDef<'a> {
23 content: Vec<Event<'a>>,
24 id: usize,
26}
27
28impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Footnotes<'a, I> {
29 pub(super) fn new(iter: I, existing_footnotes: Weak<AtomicUsize>) -> Self {
30 let existing_footnotes =
31 existing_footnotes.upgrade().expect("`existing_footnotes` was dropped");
32 let start_id = existing_footnotes.load(Ordering::Relaxed);
33 Footnotes { inner: iter, footnotes: FxIndexMap::default(), existing_footnotes, start_id }
34 }
35
36 fn get_entry(&mut self, key: &str) -> (&mut Vec<Event<'a>>, usize) {
37 let new_id = self.footnotes.len() + 1 + self.start_id;
38 let key = key.to_owned();
39 let FootnoteDef { content, id } =
40 self.footnotes.entry(key).or_insert(FootnoteDef { content: Vec::new(), id: new_id });
41 (content, *id)
43 }
44
45 fn handle_footnote_reference(&mut self, reference: &CowStr<'a>) -> Event<'a> {
46 let (_, id) = self.get_entry(reference);
49 let reference = format!(
50 "<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{1}</a></sup>",
51 id,
52 id - self.start_id
55 );
56 Event::Html(reference.into())
57 }
58
59 fn collect_footnote_def(&mut self) -> Vec<Event<'a>> {
60 let mut content = Vec::new();
61 while let Some((event, _)) = self.inner.next() {
62 match event {
63 Event::End(TagEnd::FootnoteDefinition) => break,
64 Event::FootnoteReference(ref reference) => {
65 content.push(self.handle_footnote_reference(reference));
66 }
67 event => content.push(event),
68 }
69 }
70 content
71 }
72}
73
74impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, I> {
75 type Item = SpannedEvent<'a>;
76
77 fn next(&mut self) -> Option<Self::Item> {
78 loop {
79 let next = self.inner.next();
80 match next {
81 Some((Event::FootnoteReference(ref reference), range)) => {
82 return Some((self.handle_footnote_reference(reference), range));
83 }
84 Some((Event::Start(Tag::FootnoteDefinition(def)), _)) => {
85 let content = self.collect_footnote_def();
88 let (entry_content, _) = self.get_entry(&def);
89 *entry_content = content;
90 }
91 Some(e) => return Some(e),
92 None => {
93 if !self.footnotes.is_empty() {
94 let defs: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect();
97 self.existing_footnotes.fetch_add(defs.len(), Ordering::Relaxed);
98 let defs_html = render_footnotes_defs(defs);
99 return Some((Event::Html(defs_html.into()), 0..0));
100 } else {
101 return None;
102 }
103 }
104 }
105 }
106 }
107}
108
109fn render_footnotes_defs(mut footnotes: Vec<FootnoteDef<'_>>) -> String {
110 let mut ret = String::from("<div class=\"footnotes\"><hr><ol>");
111
112 footnotes.sort_by_key(|x| x.id);
115
116 for FootnoteDef { mut content, id } in footnotes {
117 write!(ret, "<li id=\"fn{id}\">").unwrap();
118 let mut is_paragraph = false;
119 if let Some(&Event::End(TagEnd::Paragraph)) = content.last() {
120 content.pop();
121 is_paragraph = true;
122 }
123 html::push_html(&mut ret, content.into_iter());
124 write!(ret, " <a href=\"#fnref{id}\">↩</a>").unwrap();
125 if is_paragraph {
126 ret.push_str("</p>");
127 }
128 ret.push_str("</li>");
129 }
130 ret.push_str("</ol></div>");
131
132 ret
133}