rustdoc/html/markdown/
footnotes.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! Markdown footnote handling.
use std::fmt::Write as _;

use pulldown_cmark::{CowStr, 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, 'b, I> {
    inner: I,
    footnotes: FxIndexMap<String, FootnoteDef<'a>>,
    existing_footnotes: &'b mut usize,
}

/// The definition of a single footnote.
struct FootnoteDef<'a> {
    content: Vec<Event<'a>>,
    /// The number that appears in the footnote reference and list.
    id: usize,
}

impl<'a, 'b, I: Iterator<Item = SpannedEvent<'a>>> Footnotes<'a, 'b, I> {
    pub(super) fn new(iter: I, existing_footnotes: &'b mut usize) -> Self {
        Footnotes { inner: iter, footnotes: FxIndexMap::default(), existing_footnotes }
    }

    fn get_entry(&mut self, key: &str) -> (&mut Vec<Event<'a>>, usize) {
        let new_id = self.footnotes.len() + 1 + *self.existing_footnotes;
        let key = key.to_owned();
        let FootnoteDef { content, id } =
            self.footnotes.entry(key).or_insert(FootnoteDef { content: Vec::new(), id: new_id });
        // Don't allow changing the ID of existing entrys, but allow changing the contents.
        (content, *id)
    }

    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 reference = format!(
            "<sup id=\"fnref{0}\"><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.
            id - *self.existing_footnotes
        );
        Event::Html(reference.into())
    }

    fn collect_footnote_def(&mut self) -> Vec<Event<'a>> {
        let mut content = Vec::new();
        while let Some((event, _)) = self.inner.next() {
            match event {
                Event::End(TagEnd::FootnoteDefinition) => break,
                Event::FootnoteReference(ref reference) => {
                    content.push(self.handle_footnote_reference(reference));
                }
                event => content.push(event),
            }
        }
        content
    }
}

impl<'a, 'b, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, 'b, I> {
    type Item = SpannedEvent<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            let next = self.inner.next();
            match next {
                Some((Event::FootnoteReference(ref reference), range)) => {
                    return Some((self.handle_footnote_reference(reference), range));
                }
                Some((Event::Start(Tag::FootnoteDefinition(def)), _)) => {
                    // When we see a footnote definition, collect the assocated content, and store
                    // that for rendering later.
                    let content = self.collect_footnote_def();
                    let (entry_content, _) = self.get_entry(&def);
                    *entry_content = content;
                }
                Some(e) => return Some(e),
                None => {
                    if !self.footnotes.is_empty() {
                        // 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();
                        *self.existing_footnotes += defs.len();
                        let defs_html = render_footnotes_defs(defs);
                        return Some((Event::Html(defs_html.into()), 0..0));
                    } else {
                        return None;
                    }
                }
            }
        }
    }
}

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
}