]> git.lizzy.rs Git - rust.git/blobdiff - src/librustdoc/html/markdown.rs
Rollup merge of #41135 - japaric:unstable-docs, r=steveklabnik
[rust.git] / src / librustdoc / html / markdown.rs
index 0b098fb14f1904c2f43a8879ab061d8a8cbe10d3..1e687d63f58755d732d1e71878c4ca45262d90df 100644 (file)
 //! of `fmt::Display`. Example usage:
 //!
 //! ```rust,ignore
-//! use rustdoc::html::markdown::{Markdown, MarkdownOutputStyle};
+//! use rustdoc::html::markdown::Markdown;
 //!
 //! let s = "My *markdown* _text_";
-//! let html = format!("{}", Markdown(s, MarkdownOutputStyle::Fancy));
+//! let html = format!("{}", Markdown(s));
 //! // ... something using html
 //! ```
 
@@ -27,7 +27,7 @@
 
 use std::ascii::AsciiExt;
 use std::cell::RefCell;
-use std::collections::HashMap;
+use std::collections::{HashMap, VecDeque};
 use std::default::Default;
 use std::fmt::{self, Write};
 use std::str;
 use html::render::derive_id;
 use html::toc::TocBuilder;
 use html::highlight;
-use html::escape::Escape;
 use test;
 
-use pulldown_cmark::{self, Event, Parser, Tag};
-
-#[derive(Copy, Clone)]
-pub enum MarkdownOutputStyle {
-    Compact,
-    Fancy,
-}
-
-impl MarkdownOutputStyle {
-    pub fn is_compact(&self) -> bool {
-        match *self {
-            MarkdownOutputStyle::Compact => true,
-            _ => false,
-        }
-    }
-
-    pub fn is_fancy(&self) -> bool {
-        match *self {
-            MarkdownOutputStyle::Fancy => true,
-            _ => false,
-        }
-    }
-}
+use pulldown_cmark::{html, Event, Tag, Parser};
+use pulldown_cmark::{Options, OPTION_ENABLE_FOOTNOTES, OPTION_ENABLE_TABLES};
 
 /// A unit struct which has the `fmt::Display` trait implemented. When
 /// formatted, this struct will emit the HTML corresponding to the rendered
 /// version of the contained markdown string.
 // The second parameter is whether we need a shorter version or not.
-pub struct Markdown<'a>(pub &'a str, pub MarkdownOutputStyle);
+pub struct Markdown<'a>(pub &'a str);
 /// A unit struct like `Markdown`, that renders the markdown with a
 /// table of contents.
 pub struct MarkdownWithToc<'a>(pub &'a str);
 /// A unit struct like `Markdown`, that renders the markdown escaping HTML tags.
 pub struct MarkdownHtml<'a>(pub &'a str);
+/// A unit struct like `Markdown`, that renders only the first paragraph.
+pub struct MarkdownSummaryLine<'a>(pub &'a str);
 
 /// Returns Some(code) if `s` is a line that should be stripped from
 /// documentation but used in example code. `code` is the portion of
@@ -90,12 +70,21 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
     }
 }
 
-/// Returns a new string with all consecutive whitespace collapsed into
-/// single spaces.
+/// Convert chars from a title for an id.
 ///
-/// Any leading or trailing whitespace will be trimmed.
-fn collapse_whitespace(s: &str) -> String {
-    s.split_whitespace().collect::<Vec<_>>().join(" ")
+/// "Hello, world!" -> "hello-world"
+fn slugify(c: char) -> Option<char> {
+    if c.is_alphanumeric() || c == '-' || c == '_' {
+        if c.is_ascii() {
+            Some(c.to_ascii_lowercase())
+        } else {
+            Some(c)
+        }
+    } else if c.is_whitespace() && c.is_ascii() {
+        Some('-')
+    } else {
+        None
+    }
 }
 
 // Information about the playground if a URL has been specified, containing an
@@ -104,103 +93,50 @@ fn collapse_whitespace(s: &str) -> String {
     RefCell::new(None)
 });
 
-macro_rules! event_loop_break {
-    ($parser:expr, $toc_builder:expr, $shorter:expr, $buf:expr, $escape:expr, $id:expr,
-     $($end_event:pat)|*) => {{
-        fn inner(id: &mut Option<&mut String>, s: &str) {
-            if let Some(ref mut id) = *id {
-                id.push_str(s);
-            }
-        }
-        while let Some(event) = $parser.next() {
-            match event {
-                $($end_event)|* => break,
-                Event::Text(ref s) => {
-                    debug!("Text");
-                    inner($id, s);
-                    if $escape {
-                        $buf.push_str(&format!("{}", Escape(s)));
-                    } else {
-                        $buf.push_str(s);
-                    }
-                }
-                Event::SoftBreak => {
-                    debug!("SoftBreak");
-                    if !$buf.is_empty() {
-                        $buf.push(' ');
-                    }
-                }
-                x => {
-                    looper($parser, &mut $buf, Some(x), $toc_builder, $shorter, $id);
-                }
-            }
-        }
-    }}
-}
-
-struct ParserWrapper<'a> {
-    parser: Parser<'a>,
-    // The key is the footnote reference. The value is the footnote definition and the id.
-    footnotes: HashMap<String, (String, u16)>,
+/// Adds syntax highlighting and playground Run buttons to rust code blocks.
+struct CodeBlocks<'a, I: Iterator<Item = Event<'a>>> {
+    inner: I,
 }
 
-impl<'a> ParserWrapper<'a> {
-    pub fn new(s: &'a str) -> ParserWrapper<'a> {
-        ParserWrapper {
-            parser: Parser::new_ext(s, pulldown_cmark::OPTION_ENABLE_TABLES |
-                                       pulldown_cmark::OPTION_ENABLE_FOOTNOTES),
-            footnotes: HashMap::new(),
+impl<'a, I: Iterator<Item = Event<'a>>> CodeBlocks<'a, I> {
+    fn new(iter: I) -> Self {
+        CodeBlocks {
+            inner: iter,
         }
     }
+}
 
-    pub fn next(&mut self) -> Option<Event<'a>> {
-        self.parser.next()
-    }
+impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'a, I> {
+    type Item = Event<'a>;
 
-    pub fn get_entry(&mut self, key: &str) -> &mut (String, u16) {
-        let new_id = self.footnotes.keys().count() + 1;
-        let key = key.to_owned();
-        self.footnotes.entry(key).or_insert((String::new(), new_id as u16))
-    }
-}
+    fn next(&mut self) -> Option<Self::Item> {
+        let event = self.inner.next();
+        if let Some(Event::Start(Tag::CodeBlock(lang))) = event {
+            if !LangString::parse(&lang).rust {
+                return Some(Event::Start(Tag::CodeBlock(lang)));
+            }
+        } else {
+            return event;
+        }
 
-pub fn render(w: &mut fmt::Formatter,
-              s: &str,
-              print_toc: bool,
-              shorter: MarkdownOutputStyle) -> fmt::Result {
-    fn code_block(parser: &mut ParserWrapper, buffer: &mut String, lang: &str) {
-        debug!("CodeBlock");
         let mut origtext = String::new();
-        while let Some(event) = parser.next() {
+        for event in &mut self.inner {
             match event {
-                Event::End(Tag::CodeBlock(_)) => break,
+                Event::End(Tag::CodeBlock(..)) => break,
                 Event::Text(ref s) => {
                     origtext.push_str(s);
                 }
                 _ => {}
             }
         }
-        let origtext = origtext.trim_left();
-        debug!("docblock: ==============\n{:?}\n=======", origtext);
-
         let lines = origtext.lines().filter(|l| {
             stripped_filtered_line(*l).is_none()
         });
         let text = lines.collect::<Vec<&str>>().join("\n");
-        let block_info = if lang.is_empty() {
-            LangString::all_false()
-        } else {
-            LangString::parse(lang)
-        };
-        if !block_info.rust {
-            buffer.push_str(&format!("<pre><code class=\"language-{}\">{}</code></pre>",
-                            lang, text));
-            return
-        }
         PLAYGROUND.with(|play| {
             // insert newline to clearly separate it from the
             // previous block so we can shorten the html output
-            buffer.push('\n');
+            let mut s = String::from("\n");
             let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
                 if url.is_empty() {
                     return None;
@@ -210,7 +146,7 @@ fn code_block(parser: &mut ParserWrapper, buffer: &mut String, lang: &str) {
                 }).collect::<Vec<&str>>().join("\n");
                 let krate = krate.as_ref().map(|s| &**s);
                 let test = test::maketest(&test, krate, false,
-                                          &Default::default());
+                                        &Default::default());
                 let channel = if test.contains("#![feature(") {
                     "&amp;version=nightly"
                 } else {
@@ -239,368 +175,186 @@ fn dont_escape(c: u8) -> bool {
                     url, test_escaped, channel
                 ))
             });
-            buffer.push_str(&highlight::render_with_highlighting(
-                            &text,
-                            Some("rust-example-rendered"),
-                            None,
-                            playground_button.as_ref().map(String::as_str)));
-        });
-    }
-
-    fn heading(parser: &mut ParserWrapper, buffer: &mut String,
-               toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle, level: i32) {
-        debug!("Heading");
-        let mut ret = String::new();
-        let mut id = String::new();
-        event_loop_break!(parser, toc_builder, shorter, ret, true, &mut Some(&mut id),
-                          Event::End(Tag::Header(_)));
-        ret = ret.trim_right().to_owned();
-
-        let id = id.chars().filter_map(|c| {
-            if c.is_alphanumeric() || c == '-' || c == '_' {
-                if c.is_ascii() {
-                    Some(c.to_ascii_lowercase())
-                } else {
-                    Some(c)
-                }
-            } else if c.is_whitespace() && c.is_ascii() {
-                Some('-')
-            } else {
-                None
-            }
-        }).collect::<String>();
-
-        let id = derive_id(id);
-
-        let sec = toc_builder.as_mut().map_or("".to_owned(), |builder| {
-            format!("{} ", builder.push(level as u32, ret.clone(), id.clone()))
-        });
-
-        // Render the HTML
-        buffer.push_str(&format!("<h{lvl} id=\"{id}\" class=\"section-header\">\
-                                  <a href=\"#{id}\">{sec}{}</a></h{lvl}>",
-                                 ret, lvl = level, id = id, sec = sec));
+            s.push_str(&highlight::render_with_highlighting(
+                        &text,
+                        Some("rust-example-rendered"),
+                        None,
+                        playground_button.as_ref().map(String::as_str)));
+            Some(Event::Html(s.into()))
+        })
     }
+}
 
-    fn inline_code(parser: &mut ParserWrapper, buffer: &mut String,
-                   toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
-                   id: &mut Option<&mut String>) {
-        debug!("InlineCode");
-        let mut content = String::new();
-        event_loop_break!(parser, toc_builder, shorter, content, false, id, Event::End(Tag::Code));
-        buffer.push_str(&format!("<code>{}</code>",
-                                 Escape(&collapse_whitespace(content.trim_right()))));
-    }
+/// Make headings links with anchor ids and build up TOC.
+struct HeadingLinks<'a, 'b, I: Iterator<Item = Event<'a>>> {
+    inner: I,
+    toc: Option<&'b mut TocBuilder>,
+    buf: VecDeque<Event<'a>>,
+}
 
-    fn link(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
-            shorter: MarkdownOutputStyle, url: &str, title: &str,
-            id: &mut Option<&mut String>) {
-        debug!("Link");
-        let mut content = String::new();
-        event_loop_break!(parser, toc_builder, shorter, content, true, id,
-                          Event::End(Tag::Link(_, _)));
-        if title.is_empty() {
-            buffer.push_str(&format!("<a href=\"{}\">{}</a>", url, content));
-        } else {
-            buffer.push_str(&format!("<a href=\"{}\" title=\"{}\">{}</a>",
-                                     url, Escape(title), content));
+impl<'a, 'b, I: Iterator<Item = Event<'a>>> HeadingLinks<'a, 'b, I> {
+    fn new(iter: I, toc: Option<&'b mut TocBuilder>) -> Self {
+        HeadingLinks {
+            inner: iter,
+            toc: toc,
+            buf: VecDeque::new(),
         }
     }
+}
 
-    fn image(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
-            shorter: MarkdownOutputStyle, url: &str, mut title: String,
-            id: &mut Option<&mut String>) {
-        debug!("Image");
-        event_loop_break!(parser, toc_builder, shorter, title, true, id,
-                          Event::End(Tag::Image(_, _)));
-        buffer.push_str(&format!("<img src=\"{}\" alt=\"{}\">", url, title));
-    }
-
-    fn paragraph(parser: &mut ParserWrapper, buffer: &mut String,
-                 toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
-                 id: &mut Option<&mut String>) {
-        debug!("Paragraph");
-        let mut content = String::new();
-        event_loop_break!(parser, toc_builder, shorter, content, true, id,
-                          Event::End(Tag::Paragraph));
-        buffer.push_str(&format!("<p>{}</p>", content.trim_right()));
-    }
-
-    fn table_cell(parser: &mut ParserWrapper, buffer: &mut String,
-                  toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
-        debug!("TableCell");
-        let mut content = String::new();
-        event_loop_break!(parser, toc_builder, shorter, content, true, &mut None,
-                          Event::End(Tag::TableHead) |
-                              Event::End(Tag::Table(_)) |
-                              Event::End(Tag::TableRow) |
-                              Event::End(Tag::TableCell));
-        buffer.push_str(&format!("<td>{}</td>", content.trim()));
-    }
+impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for HeadingLinks<'a, 'b, I> {
+    type Item = Event<'a>;
 
-    fn table_row(parser: &mut ParserWrapper, buffer: &mut String,
-                 toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
-        debug!("TableRow");
-        let mut content = String::new();
-        while let Some(event) = parser.next() {
-            match event {
-                Event::End(Tag::TableHead) |
-                    Event::End(Tag::Table(_)) |
-                    Event::End(Tag::TableRow) => break,
-                Event::Start(Tag::TableCell) => {
-                    table_cell(parser, &mut content, toc_builder, shorter);
-                }
-                x => {
-                    looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
-                }
-            }
+    fn next(&mut self) -> Option<Self::Item> {
+        if let Some(e) = self.buf.pop_front() {
+            return Some(e);
         }
-        buffer.push_str(&format!("<tr>{}</tr>", content));
-    }
 
-    fn table_head(parser: &mut ParserWrapper, buffer: &mut String,
-                  toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
-        debug!("TableHead");
-        let mut content = String::new();
-        while let Some(event) = parser.next() {
-            match event {
-                Event::End(Tag::TableHead) | Event::End(Tag::Table(_)) => break,
-                Event::Start(Tag::TableCell) => {
-                    table_cell(parser, &mut content, toc_builder, shorter);
-                }
-                x => {
-                    looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
+        let event = self.inner.next();
+        if let Some(Event::Start(Tag::Header(level))) = event {
+            let mut id = String::new();
+            for event in &mut self.inner {
+                match event {
+                    Event::End(Tag::Header(..)) => break,
+                    Event::Text(ref text) => id.extend(text.chars().filter_map(slugify)),
+                    _ => {},
                 }
+                self.buf.push_back(event);
             }
-        }
-        if !content.is_empty() {
-            buffer.push_str(&format!("<thead><tr>{}</tr></thead>", content.replace("td>", "th>")));
-        }
-    }
+            let id = derive_id(id);
 
-    fn table(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
-             shorter: MarkdownOutputStyle) {
-        debug!("Table");
-        let mut content = String::new();
-        let mut rows = String::new();
-        while let Some(event) = parser.next() {
-            match event {
-                Event::End(Tag::Table(_)) => break,
-                Event::Start(Tag::TableHead) => {
-                    table_head(parser, &mut content, toc_builder, shorter);
-                }
-                Event::Start(Tag::TableRow) => {
-                    table_row(parser, &mut rows, toc_builder, shorter);
-                }
-                _ => {}
+            if let Some(ref mut builder) = self.toc {
+                let mut html_header = String::new();
+                html::push_html(&mut html_header, self.buf.iter().cloned());
+                let sec = builder.push(level as u32, html_header, id.clone());
+                self.buf.push_front(Event::InlineHtml(format!("{} ", sec).into()));
             }
-        }
-        buffer.push_str(&format!("<table>{}{}</table>",
-                                 content,
-                                 if shorter.is_compact() || rows.is_empty() {
-                                     String::new()
-                                 } else {
-                                     format!("<tbody>{}</tbody>", rows)
-                                 }));
-    }
 
-    fn blockquote(parser: &mut ParserWrapper, buffer: &mut String,
-                  toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
-        debug!("BlockQuote");
-        let mut content = String::new();
-        event_loop_break!(parser, toc_builder, shorter, content, true, &mut None,
-                          Event::End(Tag::BlockQuote));
-        buffer.push_str(&format!("<blockquote>{}</blockquote>", content.trim_right()));
-    }
+            self.buf.push_back(Event::InlineHtml(format!("</a></h{}>", level).into()));
 
-    fn list_item(parser: &mut ParserWrapper, buffer: &mut String,
-                 toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
-        debug!("ListItem");
-        let mut content = String::new();
-        while let Some(event) = parser.next() {
-            match event {
-                Event::End(Tag::Item) => break,
-                Event::Text(ref s) => {
-                    content.push_str(&format!("{}", Escape(s)));
-                }
-                x => {
-                    looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
-                }
-            }
+            let start_tags = format!("<h{level} id=\"{id}\" class=\"section-header\">\
+                                      <a href=\"#{id}\">",
+                                     id = id,
+                                     level = level);
+            return Some(Event::InlineHtml(start_tags.into()));
         }
-        buffer.push_str(&format!("<li>{}</li>", content));
+        event
     }
+}
 
-    fn list(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
-            shorter: MarkdownOutputStyle) {
-        debug!("List");
-        let mut content = String::new();
-        while let Some(event) = parser.next() {
-            match event {
-                Event::End(Tag::List(_)) => break,
-                Event::Start(Tag::Item) => {
-                    list_item(parser, &mut content, toc_builder, shorter);
-                }
-                x => {
-                    looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
-                }
-            }
+/// Extracts just the first paragraph.
+struct SummaryLine<'a, I: Iterator<Item = Event<'a>>> {
+    inner: I,
+    started: bool,
+    depth: u32,
+}
+
+impl<'a, I: Iterator<Item = Event<'a>>> SummaryLine<'a, I> {
+    fn new(iter: I) -> Self {
+        SummaryLine {
+            inner: iter,
+            started: false,
+            depth: 0,
         }
-        buffer.push_str(&format!("<ul>{}</ul>", content));
     }
+}
 
-    fn emphasis(parser: &mut ParserWrapper, buffer: &mut String,
-                toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
-                id: &mut Option<&mut String>) {
-        debug!("Emphasis");
-        let mut content = String::new();
-        event_loop_break!(parser, toc_builder, shorter, content, false, id,
-                          Event::End(Tag::Emphasis));
-        buffer.push_str(&format!("<em>{}</em>", content));
-    }
+impl<'a, I: Iterator<Item = Event<'a>>> Iterator for SummaryLine<'a, I> {
+    type Item = Event<'a>;
 
-    fn strong(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
-              shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
-        debug!("Strong");
-        let mut content = String::new();
-        event_loop_break!(parser, toc_builder, shorter, content, false, id,
-                          Event::End(Tag::Strong));
-        buffer.push_str(&format!("<strong>{}</strong>", content));
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.started && self.depth == 0 {
+            return None;
+        }
+        if !self.started {
+            self.started = true;
+        }
+        let event = self.inner.next();
+        match event {
+            Some(Event::Start(..)) => self.depth += 1,
+            Some(Event::End(..)) => self.depth -= 1,
+            _ => {}
+        }
+        event
     }
+}
 
-    fn footnote(parser: &mut ParserWrapper, buffer: &mut String,
-                toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
-                id: &mut Option<&mut String>) {
-        debug!("FootnoteDefinition");
-        let mut content = String::new();
-        event_loop_break!(parser, toc_builder, shorter, content, true, id,
-                          Event::End(Tag::FootnoteDefinition(_)));
-        buffer.push_str(&content);
-    }
+/// Moves all footnote definitions to the end and add back links to the
+/// references.
+struct Footnotes<'a, I: Iterator<Item = Event<'a>>> {
+    inner: I,
+    footnotes: HashMap<String, (Vec<Event<'a>>, u16)>,
+}
 
-    fn rule(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
-            shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
-        debug!("Rule");
-        let mut content = String::new();
-        event_loop_break!(parser, toc_builder, shorter, content, true, id,
-                          Event::End(Tag::Rule));
-        buffer.push_str("<hr>");
+impl<'a, I: Iterator<Item = Event<'a>>> Footnotes<'a, I> {
+    fn new(iter: I) -> Self {
+        Footnotes {
+            inner: iter,
+            footnotes: HashMap::new(),
+        }
     }
+    fn get_entry(&mut self, key: &str) -> &mut (Vec<Event<'a>>, u16) {
+        let new_id = self.footnotes.keys().count() + 1;
+        let key = key.to_owned();
+        self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16))
+    }
+}
 
-    fn looper<'a>(parser: &'a mut ParserWrapper, buffer: &mut String, next_event: Option<Event<'a>>,
-                  toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
-                  id: &mut Option<&mut String>) -> bool {
-        if let Some(event) = next_event {
-            match event {
-                Event::Start(Tag::CodeBlock(lang)) => {
-                    code_block(parser, buffer, &*lang);
-                }
-                Event::Start(Tag::Header(level)) => {
-                    heading(parser, buffer, toc_builder, shorter, level);
-                }
-                Event::Start(Tag::Code) => {
-                    inline_code(parser, buffer, toc_builder, shorter, id);
-                }
-                Event::Start(Tag::Paragraph) => {
-                    paragraph(parser, buffer, toc_builder, shorter, id);
-                }
-                Event::Start(Tag::Link(ref url, ref t)) => {
-                    link(parser, buffer, toc_builder, shorter, url, t.as_ref(), id);
-                }
-                Event::Start(Tag::Image(ref url, ref t)) => {
-                    image(parser, buffer, toc_builder, shorter, url, t.as_ref().to_owned(), id);
-                }
-                Event::Start(Tag::Table(_)) => {
-                    table(parser, buffer, toc_builder, shorter);
-                }
-                Event::Start(Tag::BlockQuote) => {
-                    blockquote(parser, buffer, toc_builder, shorter);
-                }
-                Event::Start(Tag::List(_)) => {
-                    list(parser, buffer, toc_builder, shorter);
-                }
-                Event::Start(Tag::Emphasis) => {
-                    emphasis(parser, buffer, toc_builder, shorter, id);
-                }
-                Event::Start(Tag::Strong) => {
-                    strong(parser, buffer, toc_builder, shorter, id);
-                }
-                Event::Start(Tag::Rule) => {
-                    rule(parser, buffer, toc_builder, shorter, id);
-                }
-                Event::Start(Tag::FootnoteDefinition(ref def)) => {
-                    debug!("FootnoteDefinition");
-                    let mut content = String::new();
-                    let def = def.as_ref();
-                    footnote(parser, &mut content, toc_builder, shorter, id);
-                    let entry = parser.get_entry(def);
-                    let cur_id = (*entry).1;
-                    (*entry).0.push_str(&format!("<li id=\"ref{}\">{}&nbsp;<a href=\"#supref{0}\" \
-                                                  rev=\"footnote\">↩</a></p></li>",
-                                                 cur_id,
-                                                 if content.ends_with("</p>") {
-                                                     &content[..content.len() - 4]
-                                                 } else {
-                                                     &content
-                                                 }));
-                }
-                Event::FootnoteReference(ref reference) => {
-                    debug!("FootnoteReference");
-                    let entry = parser.get_entry(reference.as_ref());
-                    buffer.push_str(&format!("<sup id=\"supref{0}\"><a href=\"#ref{0}\">{0}</a>\
-                                              </sup>",
-                                             (*entry).1));
-                }
-                Event::HardBreak => {
-                    debug!("HardBreak");
-                    if shorter.is_fancy() {
-                        buffer.push_str("<br>");
-                    } else if !buffer.is_empty() {
-                        buffer.push(' ');
+impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
+    type Item = Event<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        loop {
+            match self.inner.next() {
+                Some(Event::FootnoteReference(ref reference)) => {
+                    let entry = self.get_entry(&reference);
+                    let reference = format!("<sup id=\"supref{0}\"><a href=\"#ref{0}\">{0}\
+                                             </a></sup>",
+                                            (*entry).1);
+                    return Some(Event::Html(reference.into()));
+                }
+                Some(Event::Start(Tag::FootnoteDefinition(def))) => {
+                    let mut content = Vec::new();
+                    for event in &mut self.inner {
+                        if let Event::End(Tag::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("<div class=\"footnotes\"><hr><ol>");
+                        for (mut content, id) in v {
+                            write!(ret, "<li id=\"ref{}\">", id).unwrap();
+                            let mut is_paragraph = false;
+                            if let Some(&Event::End(Tag::Paragraph)) = content.last() {
+                                content.pop();
+                                is_paragraph = true;
+                            }
+                            html::push_html(&mut ret, content.into_iter());
+                            write!(ret,
+                                   "&nbsp;<a href=\"#supref{}\" rev=\"footnote\">↩</a>",
+                                   id).unwrap();
+                            if is_paragraph {
+                                ret.push_str("</p>");
+                            }
+                            ret.push_str("</li>");
+                        }
+                        ret.push_str("</ol></div>");
+                        return Some(Event::Html(ret.into()));
+                    } else {
+                        return None;
                     }
                 }
-                Event::Html(h) | Event::InlineHtml(h) => {
-                    debug!("Html/InlineHtml");
-                    buffer.push_str(&*h);
-                }
-                _ => {}
             }
-            shorter.is_fancy()
-        } else {
-            false
         }
     }
-
-    let mut toc_builder = if print_toc {
-        Some(TocBuilder::new())
-    } else {
-        None
-    };
-    let mut buffer = String::new();
-    let mut parser = ParserWrapper::new(s);
-    loop {
-        let next_event = parser.next();
-        if !looper(&mut parser, &mut buffer, next_event, &mut toc_builder, shorter, &mut None) {
-            break
-        }
-    }
-    if !parser.footnotes.is_empty() {
-        let mut v: Vec<_> = parser.footnotes.values().collect();
-        v.sort_by(|a, b| a.1.cmp(&b.1));
-        buffer.push_str(&format!("<div class=\"footnotes\"><hr><ol>{}</ol></div>",
-                                 v.iter()
-                                  .map(|s| s.0.as_str())
-                                  .collect::<Vec<_>>()
-                                  .join("")));
-    }
-    let mut ret = toc_builder.map_or(Ok(()), |builder| {
-        write!(w, "<nav id=\"TOC\">{}</nav>", builder.into_toc())
-    });
-
-    if ret.is_ok() {
-        ret = w.write_str(&buffer);
-    }
-    ret
 }
 
 pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
@@ -747,17 +501,45 @@ fn parse(string: &str) -> LangString {
 
 impl<'a> fmt::Display for Markdown<'a> {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
-        let Markdown(md, shorter) = *self;
+        let Markdown(md) = *self;
         // This is actually common enough to special-case
         if md.is_empty() { return Ok(()) }
-        render(fmt, md, false, shorter)
+
+        let mut opts = Options::empty();
+        opts.insert(OPTION_ENABLE_TABLES);
+        opts.insert(OPTION_ENABLE_FOOTNOTES);
+
+        let p = Parser::new_ext(md, opts);
+
+        let mut s = String::with_capacity(md.len() * 3 / 2);
+
+        html::push_html(&mut s,
+                        Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, None))));
+
+        fmt.write_str(&s)
     }
 }
 
 impl<'a> fmt::Display for MarkdownWithToc<'a> {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
         let MarkdownWithToc(md) = *self;
-        render(fmt, md, true, MarkdownOutputStyle::Fancy)
+
+        let mut opts = Options::empty();
+        opts.insert(OPTION_ENABLE_TABLES);
+        opts.insert(OPTION_ENABLE_FOOTNOTES);
+
+        let p = Parser::new_ext(md, opts);
+
+        let mut s = String::with_capacity(md.len() * 3 / 2);
+
+        let mut toc = TocBuilder::new();
+
+        html::push_html(&mut s,
+                        Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, Some(&mut toc)))));
+
+        write!(fmt, "<nav id=\"TOC\">{}</nav>", toc.into_toc())?;
+
+        fmt.write_str(&s)
     }
 }
 
@@ -766,7 +548,41 @@ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
         let MarkdownHtml(md) = *self;
         // This is actually common enough to special-case
         if md.is_empty() { return Ok(()) }
-        render(fmt, md, false, MarkdownOutputStyle::Fancy)
+
+        let mut opts = Options::empty();
+        opts.insert(OPTION_ENABLE_TABLES);
+        opts.insert(OPTION_ENABLE_FOOTNOTES);
+
+        let p = Parser::new_ext(md, opts);
+
+        // Treat inline HTML as plain text.
+        let p = p.map(|event| match event {
+            Event::Html(text) | Event::InlineHtml(text) => Event::Text(text),
+            _ => event
+        });
+
+        let mut s = String::with_capacity(md.len() * 3 / 2);
+
+        html::push_html(&mut s,
+                        Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, None))));
+
+        fmt.write_str(&s)
+    }
+}
+
+impl<'a> fmt::Display for MarkdownSummaryLine<'a> {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        let MarkdownSummaryLine(md) = *self;
+        // This is actually common enough to special-case
+        if md.is_empty() { return Ok(()) }
+
+        let p = Parser::new(md);
+
+        let mut s = String::new();
+
+        html::push_html(&mut s, SummaryLine::new(p));
+
+        fmt.write_str(&s)
     }
 }
 
@@ -788,14 +604,10 @@ fn next(&mut self) -> Option<String> {
             let next_event = next_event.unwrap();
             let (ret, is_in) = match next_event {
                 Event::Start(Tag::Paragraph) => (None, 1),
-                Event::Start(Tag::Link(_, ref t)) if !self.is_first => {
-                    (Some(t.as_ref().to_owned()), 1)
-                }
                 Event::Start(Tag::Code) => (Some("`".to_owned()), 1),
                 Event::End(Tag::Code) => (Some("`".to_owned()), -1),
                 Event::Start(Tag::Header(_)) => (None, 1),
                 Event::Text(ref s) if self.is_in > 0 => (Some(s.as_ref().to_owned()), 0),
-                Event::End(Tag::Link(_, ref t)) => (Some(t.as_ref().to_owned()), -1),
                 Event::End(Tag::Paragraph) | Event::End(Tag::Header(_)) => (None, -1),
                 _ => (None, 0),
             };
@@ -826,7 +638,7 @@ fn next(&mut self) -> Option<String> {
 
 #[cfg(test)]
 mod tests {
-    use super::{LangString, Markdown, MarkdownHtml, MarkdownOutputStyle};
+    use super::{LangString, Markdown, MarkdownHtml};
     use super::plain_summary_line;
     use html::render::reset_ids;
 
@@ -866,14 +678,14 @@ fn t(s: &str,
     #[test]
     fn issue_17736() {
         let markdown = "# title";
-        format!("{}", Markdown(markdown, MarkdownOutputStyle::Fancy));
+        format!("{}", Markdown(markdown));
         reset_ids(true);
     }
 
     #[test]
     fn test_header() {
         fn t(input: &str, expect: &str) {
-            let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
+            let output = format!("{}", Markdown(input));
             assert_eq!(output, expect, "original: {}", input);
             reset_ids(true);
         }
@@ -895,7 +707,7 @@ fn t(input: &str, expect: &str) {
     #[test]
     fn test_header_ids_multiple_blocks() {
         fn t(input: &str, expect: &str) {
-            let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
+            let output = format!("{}", Markdown(input));
             assert_eq!(output, expect, "original: {}", input);
         }
 
@@ -926,6 +738,7 @@ fn t(input: &str, expect: &str) {
         }
 
         t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
+        t("hello [Rust](https://www.rust-lang.org \"Rust\") :)", "hello Rust :)");
         t("code `let x = i32;` ...", "code `let x = i32;` ...");
         t("type `Type<'static>` ...", "type `Type<'static>` ...");
         t("# top header", "top header");
@@ -939,7 +752,8 @@ fn t(input: &str, expect: &str) {
             assert_eq!(output, expect, "original: {}", input);
         }
 
-        t("`Struct<'a, T>`", "<p><code>Struct&lt;&#39;a, T&gt;</code></p>");
-        t("Struct<'a, T>", "<p>Struct&lt;&#39;a, T&gt;</p>");
+        t("`Struct<'a, T>`", "<p><code>Struct&lt;'a, T&gt;</code></p>\n");
+        t("Struct<'a, T>", "<p>Struct&lt;'a, T&gt;</p>\n");
+        t("Struct<br>", "<p>Struct&lt;br&gt;</p>\n");
     }
 }