Skip to content

Commit 9435afc

Browse files
committed
rustdoc: Refractor footnote handling
1 parent ebb8423 commit 9435afc

File tree

1 file changed

+67
-36
lines changed

1 file changed

+67
-36
lines changed

src/librustdoc/html/markdown/footnotes.rs

+67-36
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,30 @@ use super::SpannedEvent;
1010
/// references.
1111
pub(super) struct Footnotes<'a, I> {
1212
inner: I,
13-
footnotes: FxIndexMap<String, (Vec<Event<'a>>, u16)>,
13+
footnotes: FxIndexMap<String, FootnoteDef<'a>>,
14+
}
15+
16+
/// The definition of a single footnote.
17+
struct FootnoteDef<'a> {
18+
content: Vec<Event<'a>>,
19+
/// The number that appears in the footnote reference and list.
20+
id: u16,
1421
}
1522

1623
impl<'a, I> Footnotes<'a, I> {
1724
pub(super) fn new(iter: I) -> Self {
1825
Footnotes { inner: iter, footnotes: FxIndexMap::default() }
1926
}
2027

21-
fn get_entry(&mut self, key: &str) -> &mut (Vec<Event<'a>>, u16) {
28+
fn get_entry(&mut self, key: &str) -> (&mut Vec<Event<'a>>, u16) {
2229
let new_id = self.footnotes.len() + 1;
2330
let key = key.to_owned();
24-
self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16))
31+
let FootnoteDef { content, id } = self
32+
.footnotes
33+
.entry(key)
34+
.or_insert(FootnoteDef { content: Vec::new(), id: new_id as u16 });
35+
// Don't allow changing the ID of existing entrys, but allow changing the contents.
36+
(content, *id)
2537
}
2638
}
2739

@@ -32,46 +44,28 @@ impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, I> {
3244
loop {
3345
match self.inner.next() {
3446
Some((Event::FootnoteReference(ref reference), range)) => {
35-
let entry = self.get_entry(reference);
36-
let reference = format!(
37-
"<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{0}</a></sup>",
38-
(*entry).1
39-
);
47+
// When we see a reference (to a footnote we may not know) the definition of,
48+
// reserve a number for it, and emit a link to that number.
49+
let (_, id) = self.get_entry(reference);
50+
let reference =
51+
format!("<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{0}</a></sup>", id);
4052
return Some((Event::Html(reference.into()), range));
4153
}
4254
Some((Event::Start(Tag::FootnoteDefinition(def)), _)) => {
43-
let mut content = Vec::new();
44-
for (event, _) in &mut self.inner {
45-
if let Event::End(TagEnd::FootnoteDefinition) = event {
46-
break;
47-
}
48-
content.push(event);
49-
}
50-
let entry = self.get_entry(&def);
51-
(*entry).0 = content;
55+
// When we see a footnote definition, collect the assocated content, and store
56+
// that for rendering later.
57+
let content = collect_footnote_def(&mut self.inner);
58+
let (entry_content, _) = self.get_entry(&def);
59+
*entry_content = content;
5260
}
5361
Some(e) => return Some(e),
5462
None => {
5563
if !self.footnotes.is_empty() {
56-
let mut v: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect();
57-
v.sort_by(|a, b| a.1.cmp(&b.1));
58-
let mut ret = String::from("<div class=\"footnotes\"><hr><ol>");
59-
for (mut content, id) in v {
60-
write!(ret, "<li id=\"fn{id}\">").unwrap();
61-
let mut is_paragraph = false;
62-
if let Some(&Event::End(TagEnd::Paragraph)) = content.last() {
63-
content.pop();
64-
is_paragraph = true;
65-
}
66-
html::push_html(&mut ret, content.into_iter());
67-
write!(ret, "&nbsp;<a href=\"#fnref{id}\">↩</a>").unwrap();
68-
if is_paragraph {
69-
ret.push_str("</p>");
70-
}
71-
ret.push_str("</li>");
72-
}
73-
ret.push_str("</ol></div>");
74-
return Some((Event::Html(ret.into()), 0..0));
64+
// After all the markdown is emmited, emit an <hr> then all the footnotes
65+
// in a list.
66+
let defs: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect();
67+
let defs_html = render_footnotes_defs(defs);
68+
return Some((Event::Html(defs_html.into()), 0..0));
7569
} else {
7670
return None;
7771
}
@@ -80,3 +74,40 @@ impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, I> {
8074
}
8175
}
8276
}
77+
78+
fn collect_footnote_def<'a>(events: impl Iterator<Item = SpannedEvent<'a>>) -> Vec<Event<'a>> {
79+
let mut content = Vec::new();
80+
for (event, _) in events {
81+
if let Event::End(TagEnd::FootnoteDefinition) = event {
82+
break;
83+
}
84+
content.push(event);
85+
}
86+
content
87+
}
88+
89+
fn render_footnotes_defs(mut footnotes: Vec<FootnoteDef<'_>>) -> String {
90+
let mut ret = String::from("<div class=\"footnotes\"><hr><ol>");
91+
92+
// Footnotes must listed in order of id, so the numbers the
93+
// browser generated for <li> are right.
94+
footnotes.sort_by_key(|x| x.id);
95+
96+
for FootnoteDef { mut content, id } in footnotes {
97+
write!(ret, "<li id=\"fn{id}\">").unwrap();
98+
let mut is_paragraph = false;
99+
if let Some(&Event::End(TagEnd::Paragraph)) = content.last() {
100+
content.pop();
101+
is_paragraph = true;
102+
}
103+
html::push_html(&mut ret, content.into_iter());
104+
write!(ret, "&nbsp;<a href=\"#fnref{id}\">↩</a>").unwrap();
105+
if is_paragraph {
106+
ret.push_str("</p>");
107+
}
108+
ret.push_str("</li>");
109+
}
110+
ret.push_str("</ol></div>");
111+
112+
ret
113+
}

0 commit comments

Comments
 (0)