about summary refs log tree commit diff
path: root/src/librustdoc/html
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2017-04-04 08:10:22 -0700
committerEsteban Küber <esteban@kuber.com.ar>2017-04-04 08:13:27 -0700
commitdedb7bbbbf272226f327b8cda8aaa12cf6325eca (patch)
tree9c7c7b656750a53b5ca4f7646ee1dfe60829b271 /src/librustdoc/html
parentb477682dca3343eb89a467f0d3c73986a53d49d9 (diff)
parent5309a3e31d88def1f3ea966162ed4f81f161d500 (diff)
Merge branch 'master' into issue-32540
Diffstat (limited to 'src/librustdoc/html')
-rw-r--r--src/librustdoc/html/layout.rs4
-rw-r--r--src/librustdoc/html/markdown.rs1078
-rw-r--r--src/librustdoc/html/render.rs67
-rw-r--r--src/librustdoc/html/static/rustdoc.css6
-rw-r--r--src/librustdoc/html/static/styles/main.css4
5 files changed, 698 insertions, 461 deletions
diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs
index 06a81641296..d6033a69da7 100644
--- a/src/librustdoc/html/layout.rs
+++ b/src/librustdoc/html/layout.rs
@@ -54,7 +54,7 @@ r##"<!DOCTYPE html>
     {favicon}
     {in_header}
 </head>
-<body class="rustdoc">
+<body class="rustdoc {css_class}">
     <!--[if lte IE 8]>
     <div class="warning">
         This old browser is unsupported and will most likely display funky
@@ -80,7 +80,7 @@ r##"<!DOCTYPE html>
         </form>
     </nav>
 
-    <section id='main' class="content {css_class}">{content}</section>
+    <section id='main' class="content">{content}</section>
     <section id='search' class="content hidden"></section>
 
     <section class="footer"></section>
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index c7c5aabab97..0b098fb14f1 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -10,29 +10,26 @@
 
 //! Markdown formatting for rustdoc
 //!
-//! This module implements markdown formatting through the hoedown C-library
-//! (bundled into the rust runtime). This module self-contains the C bindings
-//! and necessary legwork to render markdown, and exposes all of the
+//! This module implements markdown formatting through the pulldown-cmark
+//! rust-library. This module exposes all of the
 //! functionality through a unit-struct, `Markdown`, which has an implementation
 //! of `fmt::Display`. Example usage:
 //!
 //! ```rust,ignore
-//! use rustdoc::html::markdown::Markdown;
+//! use rustdoc::html::markdown::{Markdown, MarkdownOutputStyle};
 //!
 //! let s = "My *markdown* _text_";
-//! let html = format!("{}", Markdown(s));
+//! let html = format!("{}", Markdown(s, MarkdownOutputStyle::Fancy));
 //! // ... something using html
 //! ```
 
 #![allow(non_camel_case_types)]
 
-use libc;
 use std::ascii::AsciiExt;
 use std::cell::RefCell;
+use std::collections::HashMap;
 use std::default::Default;
-use std::ffi::CString;
 use std::fmt::{self, Write};
-use std::slice;
 use std::str;
 use syntax::feature_gate::UnstableFeatures;
 use syntax::codemap::Span;
@@ -43,156 +40,41 @@ 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,
+        }
+    }
+}
+
 /// 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.
-pub struct Markdown<'a>(pub &'a str);
+// The second parameter is whether we need a shorter version or not.
+pub struct Markdown<'a>(pub &'a str, pub MarkdownOutputStyle);
 /// 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);
 
-const DEF_OUNIT: libc::size_t = 64;
-const HOEDOWN_EXT_NO_INTRA_EMPHASIS: libc::c_uint = 1 << 11;
-const HOEDOWN_EXT_TABLES: libc::c_uint = 1 << 0;
-const HOEDOWN_EXT_FENCED_CODE: libc::c_uint = 1 << 1;
-const HOEDOWN_EXT_AUTOLINK: libc::c_uint = 1 << 3;
-const HOEDOWN_EXT_STRIKETHROUGH: libc::c_uint = 1 << 4;
-const HOEDOWN_EXT_SUPERSCRIPT: libc::c_uint = 1 << 8;
-const HOEDOWN_EXT_FOOTNOTES: libc::c_uint = 1 << 2;
-const HOEDOWN_HTML_ESCAPE: libc::c_uint = 1 << 1;
-
-const HOEDOWN_EXTENSIONS: libc::c_uint =
-    HOEDOWN_EXT_NO_INTRA_EMPHASIS | HOEDOWN_EXT_TABLES |
-    HOEDOWN_EXT_FENCED_CODE | HOEDOWN_EXT_AUTOLINK |
-    HOEDOWN_EXT_STRIKETHROUGH | HOEDOWN_EXT_SUPERSCRIPT |
-    HOEDOWN_EXT_FOOTNOTES;
-
-enum hoedown_document {}
-
-type blockcodefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                                 *const hoedown_buffer, *const hoedown_renderer_data,
-                                 libc::size_t);
-
-type blockquotefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                                  *const hoedown_renderer_data, libc::size_t);
-
-type headerfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                              libc::c_int, *const hoedown_renderer_data,
-                              libc::size_t);
-
-type blockhtmlfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                                 *const hoedown_renderer_data, libc::size_t);
-
-type codespanfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                                *const hoedown_renderer_data, libc::size_t) -> libc::c_int;
-
-type linkfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer,
-                             *const hoedown_buffer, *const hoedown_buffer,
-                             *const hoedown_renderer_data, libc::size_t) -> libc::c_int;
-
-type entityfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer,
-                               *const hoedown_renderer_data, libc::size_t);
-
-type normaltextfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                                  *const hoedown_renderer_data, libc::size_t);
-
-#[repr(C)]
-struct hoedown_renderer_data {
-    opaque: *mut libc::c_void,
-}
-
-#[repr(C)]
-struct hoedown_renderer {
-    opaque: *mut libc::c_void,
-
-    blockcode: Option<blockcodefn>,
-    blockquote: Option<blockquotefn>,
-    header: Option<headerfn>,
-
-    other_block_level_callbacks: [libc::size_t; 11],
-
-    blockhtml: Option<blockhtmlfn>,
-
-    /* span level callbacks - NULL or return 0 prints the span verbatim */
-    autolink: libc::size_t, // unused
-    codespan: Option<codespanfn>,
-    other_span_level_callbacks_1: [libc::size_t; 7],
-    link: Option<linkfn>,
-    other_span_level_callbacks_2: [libc::size_t; 6],
-
-    /* low level callbacks - NULL copies input directly into the output */
-    entity: Option<entityfn>,
-    normal_text: Option<normaltextfn>,
-
-    /* header and footer */
-    other_callbacks: [libc::size_t; 2],
-}
-
-#[repr(C)]
-struct hoedown_html_renderer_state {
-    opaque: *mut libc::c_void,
-    toc_data: html_toc_data,
-    flags: libc::c_uint,
-    link_attributes: Option<extern "C" fn(*mut hoedown_buffer,
-                                          *const hoedown_buffer,
-                                          *const hoedown_renderer_data)>,
-}
-
-#[repr(C)]
-struct html_toc_data {
-    header_count: libc::c_int,
-    current_level: libc::c_int,
-    level_offset: libc::c_int,
-    nesting_level: libc::c_int,
-}
-
-struct MyOpaque {
-    dfltblk: extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                           *const hoedown_buffer, *const hoedown_renderer_data,
-                           libc::size_t),
-    toc_builder: Option<TocBuilder>,
-}
-
-#[repr(C)]
-struct hoedown_buffer {
-    data: *const u8,
-    size: libc::size_t,
-    asize: libc::size_t,
-    unit: libc::size_t,
-}
-
-extern {
-    fn hoedown_html_renderer_new(render_flags: libc::c_uint,
-                                 nesting_level: libc::c_int)
-        -> *mut hoedown_renderer;
-    fn hoedown_html_renderer_free(renderer: *mut hoedown_renderer);
-
-    fn hoedown_document_new(rndr: *const hoedown_renderer,
-                            extensions: libc::c_uint,
-                            max_nesting: libc::size_t) -> *mut hoedown_document;
-    fn hoedown_document_render(doc: *mut hoedown_document,
-                               ob: *mut hoedown_buffer,
-                               document: *const u8,
-                               doc_size: libc::size_t);
-    fn hoedown_document_free(md: *mut hoedown_document);
-
-    fn hoedown_buffer_new(unit: libc::size_t) -> *mut hoedown_buffer;
-    fn hoedown_buffer_put(b: *mut hoedown_buffer, c: *const libc::c_char,
-                          n: libc::size_t);
-    fn hoedown_buffer_puts(b: *mut hoedown_buffer, c: *const libc::c_char);
-    fn hoedown_buffer_free(b: *mut hoedown_buffer);
-
-}
-
-// hoedown_buffer helpers
-impl hoedown_buffer {
-    fn as_bytes(&self) -> &[u8] {
-        unsafe { slice::from_raw_parts(self.data, self.size as usize) }
-    }
-}
-
 /// Returns Some(code) if `s` is a line that should be stripped from
 /// documentation but used in example code. `code` is the portion of
 /// `s` that should be used in tests. (None for lines that should be
@@ -222,123 +104,158 @@ thread_local!(pub static PLAYGROUND: RefCell<Option<(Option<String>, 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)>,
+}
+
+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(),
+        }
+    }
+
+    pub fn next(&mut self) -> Option<Event<'a>> {
+        self.parser.next()
+    }
+
+    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))
+    }
+}
 
 pub fn render(w: &mut fmt::Formatter,
               s: &str,
               print_toc: bool,
-              html_flags: libc::c_uint) -> fmt::Result {
-    extern fn block(ob: *mut hoedown_buffer, orig_text: *const hoedown_buffer,
-                    lang: *const hoedown_buffer, data: *const hoedown_renderer_data,
-                    line: libc::size_t) {
-        unsafe {
-            if orig_text.is_null() { return }
-
-            let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
-            let my_opaque: &MyOpaque = &*((*opaque).opaque as *const MyOpaque);
-            let text = (*orig_text).as_bytes();
-            let origtext = str::from_utf8(text).unwrap();
-            let origtext = origtext.trim_left();
-            debug!("docblock: ==============\n{:?}\n=======", text);
-            let rendered = if lang.is_null() || origtext.is_empty() {
-                false
-            } else {
-                let rlang = (*lang).as_bytes();
-                let rlang = str::from_utf8(rlang).unwrap();
-                if !LangString::parse(rlang).rust {
-                    (my_opaque.dfltblk)(ob, orig_text, lang,
-                                        opaque as *const hoedown_renderer_data,
-                                        line);
-                    true
-                } else {
-                    false
+              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() {
+            match event {
+                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");
-            if rendered { return }
-            PLAYGROUND.with(|play| {
-                // insert newline to clearly separate it from the
-                // previous block so we can shorten the html output
-                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;
-                    }
-                    let test = origtext.lines().map(|l| {
-                        stripped_filtered_line(l).unwrap_or(l)
-                    }).collect::<Vec<&str>>().join("\n");
-                    let krate = krate.as_ref().map(|s| &**s);
-                    let test = test::maketest(&test, krate, false,
-                                              &Default::default());
-                    let channel = if test.contains("#![feature(") {
-                        "&amp;version=nightly"
+        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 playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
+                if url.is_empty() {
+                    return None;
+                }
+                let test = origtext.lines().map(|l| {
+                    stripped_filtered_line(l).unwrap_or(l)
+                }).collect::<Vec<&str>>().join("\n");
+                let krate = krate.as_ref().map(|s| &**s);
+                let test = test::maketest(&test, krate, false,
+                                          &Default::default());
+                let channel = if test.contains("#![feature(") {
+                    "&amp;version=nightly"
+                } else {
+                    ""
+                };
+                // These characters don't need to be escaped in a URI.
+                // FIXME: use a library function for percent encoding.
+                fn dont_escape(c: u8) -> bool {
+                    (b'a' <= c && c <= b'z') ||
+                    (b'A' <= c && c <= b'Z') ||
+                    (b'0' <= c && c <= b'9') ||
+                    c == b'-' || c == b'_' || c == b'.' ||
+                    c == b'~' || c == b'!' || c == b'\'' ||
+                    c == b'(' || c == b')' || c == b'*'
+                }
+                let mut test_escaped = String::new();
+                for b in test.bytes() {
+                    if dont_escape(b) {
+                        test_escaped.push(char::from(b));
                     } else {
-                        ""
-                    };
-                    // These characters don't need to be escaped in a URI.
-                    // FIXME: use a library function for percent encoding.
-                    fn dont_escape(c: u8) -> bool {
-                        (b'a' <= c && c <= b'z') ||
-                        (b'A' <= c && c <= b'Z') ||
-                        (b'0' <= c && c <= b'9') ||
-                        c == b'-' || c == b'_' || c == b'.' ||
-                        c == b'~' || c == b'!' || c == b'\'' ||
-                        c == b'(' || c == b')' || c == b'*'
-                    }
-                    let mut test_escaped = String::new();
-                    for b in test.bytes() {
-                        if dont_escape(b) {
-                            test_escaped.push(char::from(b));
-                        } else {
-                            write!(test_escaped, "%{:02X}", b).unwrap();
-                        }
+                        write!(test_escaped, "%{:02X}", b).unwrap();
                     }
-                    Some(format!(
-                        r#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
-                        url, test_escaped, channel
-                    ))
-                });
-                s.push_str(&highlight::render_with_highlighting(
-                               &text,
-                               Some("rust-example-rendered"),
-                               None,
-                               playground_button.as_ref().map(String::as_str)));
-                let output = CString::new(s).unwrap();
-                hoedown_buffer_puts(ob, output.as_ptr());
-            })
-        }
+                }
+                Some(format!(
+                    r#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
+                    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)));
+        });
     }
 
-    extern fn header(ob: *mut hoedown_buffer, text: *const hoedown_buffer,
-                     level: libc::c_int, data: *const hoedown_renderer_data,
-                     _: libc::size_t) {
-        // hoedown does this, we may as well too
-        unsafe { hoedown_buffer_puts(ob, "\n\0".as_ptr() as *const _); }
-
-        // Extract the text provided
-        let s = if text.is_null() {
-            "".to_owned()
-        } else {
-            let s = unsafe { (*text).as_bytes() };
-            str::from_utf8(&s).unwrap().to_owned()
-        };
+    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();
 
-        // Discard '<em>', '<code>' tags and some escaped characters,
-        // transform the contents of the header into a hyphenated string
-        // without non-alphanumeric characters other than '-' and '_'.
-        //
-        // This is a terrible hack working around how hoedown gives us rendered
-        // html for text rather than the raw text.
-        let mut id = s.clone();
-        let repl_sub = vec!["<em>", "</em>", "<code>", "</code>",
-                            "<strong>", "</strong>",
-                            "&lt;", "&gt;", "&amp;", "&#39;", "&quot;"];
-        for sub in repl_sub {
-            id = id.replace(sub, "");
-        }
         let id = id.chars().filter_map(|c| {
             if c.is_alphanumeric() || c == '-' || c == '_' {
                 if c.is_ascii() {
@@ -353,145 +270,405 @@ pub fn render(w: &mut fmt::Formatter,
             }
         }).collect::<String>();
 
-        let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state };
-        let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
-
         let id = derive_id(id);
 
-        let sec = opaque.toc_builder.as_mut().map_or("".to_owned(), |builder| {
-            format!("{} ", builder.push(level as u32, s.clone(), id.clone()))
+        let sec = toc_builder.as_mut().map_or("".to_owned(), |builder| {
+            format!("{} ", builder.push(level as u32, ret.clone(), id.clone()))
         });
 
         // Render the HTML
-        let text = format!("<h{lvl} id='{id}' class='section-header'>\
-                           <a href='#{id}'>{sec}{}</a></h{lvl}>",
-                           s, lvl = level, id = id, sec = sec);
-
-        let text = CString::new(text).unwrap();
-        unsafe { hoedown_buffer_puts(ob, text.as_ptr()) }
-    }
-
-    extern fn codespan(
-        ob: *mut hoedown_buffer,
-        text: *const hoedown_buffer,
-        _: *const hoedown_renderer_data,
-        _: libc::size_t
-    ) -> libc::c_int {
-        let content = if text.is_null() {
-            "".to_owned()
+        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));
+    }
+
+    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()))));
+    }
+
+    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 {
-            let bytes = unsafe { (*text).as_bytes() };
-            let s = str::from_utf8(bytes).unwrap();
-            collapse_whitespace(s)
-        };
+            buffer.push_str(&format!("<a href=\"{}\" title=\"{}\">{}</a>",
+                                     url, Escape(title), content));
+        }
+    }
 
-        let content = format!("<code>{}</code>", Escape(&content));
-        let element = CString::new(content).unwrap();
-        unsafe { hoedown_buffer_puts(ob, element.as_ptr()); }
-        // Return anything except 0, which would mean "also print the code span verbatim".
-        1
+    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));
     }
 
-    unsafe {
-        let ob = hoedown_buffer_new(DEF_OUNIT);
-        let renderer = hoedown_html_renderer_new(html_flags, 0);
-        let mut opaque = MyOpaque {
-            dfltblk: (*renderer).blockcode.unwrap(),
-            toc_builder: if print_toc {Some(TocBuilder::new())} else {None}
-        };
-        (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque
-                = &mut opaque as *mut _ as *mut libc::c_void;
-        (*renderer).blockcode = Some(block);
-        (*renderer).header = Some(header);
-        (*renderer).codespan = Some(codespan);
+    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()));
+    }
 
-        let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
-        hoedown_document_render(document, ob, s.as_ptr(),
-                                s.len() as libc::size_t);
-        hoedown_document_free(document);
+    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()));
+    }
 
-        hoedown_html_renderer_free(renderer);
+    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);
+                }
+            }
+        }
+        buffer.push_str(&format!("<tr>{}</tr>", content));
+    }
 
-        let mut ret = opaque.toc_builder.map_or(Ok(()), |builder| {
-            write!(w, "<nav id=\"TOC\">{}</nav>", builder.into_toc())
-        });
+    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);
+                }
+            }
+        }
+        if !content.is_empty() {
+            buffer.push_str(&format!("<thead><tr>{}</tr></thead>", content.replace("td>", "th>")));
+        }
+    }
 
-        if ret.is_ok() {
-            let buf = (*ob).as_bytes();
-            ret = w.write_str(str::from_utf8(buf).unwrap());
+    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);
+                }
+                _ => {}
+            }
         }
-        hoedown_buffer_free(ob);
-        ret
+        buffer.push_str(&format!("<table>{}{}</table>",
+                                 content,
+                                 if shorter.is_compact() || rows.is_empty() {
+                                     String::new()
+                                 } else {
+                                     format!("<tbody>{}</tbody>", rows)
+                                 }));
     }
-}
 
-pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
-    extern fn block(_ob: *mut hoedown_buffer,
-                    text: *const hoedown_buffer,
-                    lang: *const hoedown_buffer,
-                    data: *const hoedown_renderer_data,
-                    line: libc::size_t) {
-        unsafe {
-            if text.is_null() { return }
-            let block_info = if lang.is_null() {
-                LangString::all_false()
-            } else {
-                let lang = (*lang).as_bytes();
-                let s = str::from_utf8(lang).unwrap();
-                LangString::parse(s)
-            };
-            if !block_info.rust { return }
-            let text = (*text).as_bytes();
-            let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
-            let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
-            let text = str::from_utf8(text).unwrap();
-            let lines = text.lines().map(|l| {
-                stripped_filtered_line(l).unwrap_or(l)
-            });
-            let text = lines.collect::<Vec<&str>>().join("\n");
-            let line = tests.get_line() + line;
-            let filename = tests.get_filename();
-            tests.add_test(text.to_owned(),
-                           block_info.should_panic, block_info.no_run,
-                           block_info.ignore, block_info.test_harness,
-                           block_info.compile_fail, block_info.error_codes,
-                           line, filename);
+    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()));
+    }
+
+    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);
+                }
+            }
         }
+        buffer.push_str(&format!("<li>{}</li>", content));
     }
 
-    extern fn header(_ob: *mut hoedown_buffer,
-                     text: *const hoedown_buffer,
-                     level: libc::c_int, data: *const hoedown_renderer_data,
-                     _: libc::size_t) {
-        unsafe {
-            let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
-            let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
-            if text.is_null() {
-                tests.register_header("", level as u32);
-            } else {
-                let text = (*text).as_bytes();
-                let text = str::from_utf8(text).unwrap();
-                tests.register_header(text, level as u32);
+    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);
+                }
+            }
+        }
+        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));
+    }
+
+    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 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);
+    }
+
+    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>");
+    }
+
+    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(' ');
+                    }
+                }
+                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) {
     tests.set_position(position);
-    unsafe {
-        let ob = hoedown_buffer_new(DEF_OUNIT);
-        let renderer = hoedown_html_renderer_new(0, 0);
-        (*renderer).blockcode = Some(block);
-        (*renderer).header = Some(header);
-        (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque
-                = tests as *mut _ as *mut libc::c_void;
-
-        let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
-        hoedown_document_render(document, ob, doc.as_ptr(),
-                                doc.len() as libc::size_t);
-        hoedown_document_free(document);
-
-        hoedown_html_renderer_free(renderer);
-        hoedown_buffer_free(ob);
+
+    let mut parser = Parser::new(doc);
+    let mut prev_offset = 0;
+    let mut nb_lines = 0;
+    let mut register_header = None;
+    'main: while let Some(event) = parser.next() {
+        match event {
+            Event::Start(Tag::CodeBlock(s)) => {
+                let block_info = if s.is_empty() {
+                    LangString::all_false()
+                } else {
+                    LangString::parse(&*s)
+                };
+                if !block_info.rust {
+                    continue
+                }
+                let mut test_s = String::new();
+                let mut offset = None;
+                loop {
+                    let event = parser.next();
+                    if let Some(event) = event {
+                        match event {
+                            Event::End(Tag::CodeBlock(_)) => break,
+                            Event::Text(ref s) => {
+                                test_s.push_str(s);
+                                if offset.is_none() {
+                                    offset = Some(parser.get_offset());
+                                }
+                            }
+                            _ => {}
+                        }
+                    } else {
+                        break 'main;
+                    }
+                }
+                let offset = offset.unwrap_or(0);
+                let lines = test_s.lines().map(|l| {
+                    stripped_filtered_line(l).unwrap_or(l)
+                });
+                let text = lines.collect::<Vec<&str>>().join("\n");
+                nb_lines += doc[prev_offset..offset].lines().count();
+                let line = tests.get_line() + (nb_lines - 1);
+                let filename = tests.get_filename();
+                tests.add_test(text.to_owned(),
+                               block_info.should_panic, block_info.no_run,
+                               block_info.ignore, block_info.test_harness,
+                               block_info.compile_fail, block_info.error_codes,
+                               line, filename);
+                prev_offset = offset;
+            }
+            Event::Start(Tag::Header(level)) => {
+                register_header = Some(level as u32);
+            }
+            Event::Text(ref s) if register_header.is_some() => {
+                let level = register_header.unwrap();
+                if s.is_empty() {
+                    tests.register_header("", level);
+                } else {
+                    tests.register_header(s, level);
+                }
+                register_header = None;
+            }
+            _ => {}
+        }
     }
 }
 
@@ -570,17 +747,17 @@ impl LangString {
 
 impl<'a> fmt::Display for Markdown<'a> {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
-        let Markdown(md) = *self;
+        let Markdown(md, shorter) = *self;
         // This is actually common enough to special-case
         if md.is_empty() { return Ok(()) }
-        render(fmt, md, false, 0)
+        render(fmt, md, false, shorter)
     }
 }
 
 impl<'a> fmt::Display for MarkdownWithToc<'a> {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
         let MarkdownWithToc(md) = *self;
-        render(fmt, md, true, 0)
+        render(fmt, md, true, MarkdownOutputStyle::Fancy)
     }
 }
 
@@ -589,62 +766,67 @@ impl<'a> fmt::Display for MarkdownHtml<'a> {
         let MarkdownHtml(md) = *self;
         // This is actually common enough to special-case
         if md.is_empty() { return Ok(()) }
-        render(fmt, md, false, HOEDOWN_HTML_ESCAPE)
+        render(fmt, md, false, MarkdownOutputStyle::Fancy)
     }
 }
 
 pub fn plain_summary_line(md: &str) -> String {
-    extern fn link(_ob: *mut hoedown_buffer,
-                       _link: *const hoedown_buffer,
-                       _title: *const hoedown_buffer,
-                       content: *const hoedown_buffer,
-                       data: *const hoedown_renderer_data,
-                       _: libc::size_t) -> libc::c_int
-    {
-        unsafe {
-            if !content.is_null() && (*content).size > 0 {
-                let ob = (*data).opaque as *mut hoedown_buffer;
-                hoedown_buffer_put(ob, (*content).data as *const libc::c_char,
-                                   (*content).size);
-            }
-        }
-        1
-    }
-
-    extern fn normal_text(_ob: *mut hoedown_buffer,
-                          text: *const hoedown_buffer,
-                          data: *const hoedown_renderer_data,
-                          _: libc::size_t)
-    {
-        unsafe {
-            let ob = (*data).opaque as *mut hoedown_buffer;
-            hoedown_buffer_put(ob, (*text).data as *const libc::c_char,
-                               (*text).size);
-        }
+    struct ParserWrapper<'a> {
+        inner: Parser<'a>,
+        is_in: isize,
+        is_first: bool,
     }
 
-    unsafe {
-        let ob = hoedown_buffer_new(DEF_OUNIT);
-        let mut plain_renderer: hoedown_renderer = ::std::mem::zeroed();
-        let renderer: *mut hoedown_renderer = &mut plain_renderer;
-        (*renderer).opaque = ob as *mut libc::c_void;
-        (*renderer).link = Some(link);
-        (*renderer).normal_text = Some(normal_text);
+    impl<'a> Iterator for ParserWrapper<'a> {
+        type Item = String;
 
-        let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
-        hoedown_document_render(document, ob, md.as_ptr(),
-                                md.len() as libc::size_t);
-        hoedown_document_free(document);
-        let plain_slice = (*ob).as_bytes();
-        let plain = str::from_utf8(plain_slice).unwrap_or("").to_owned();
-        hoedown_buffer_free(ob);
-        plain
+        fn next(&mut self) -> Option<String> {
+            let next_event = self.inner.next();
+            if next_event.is_none() {
+                return None
+            }
+            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),
+            };
+            if is_in > 0 || (is_in < 0 && self.is_in > 0) {
+                self.is_in += is_in;
+            }
+            if ret.is_some() {
+                self.is_first = false;
+                ret
+            } else {
+                Some(String::new())
+            }
+        }
+    }
+    let mut s = String::with_capacity(md.len() * 3 / 2);
+    let mut p = ParserWrapper {
+        inner: Parser::new(md),
+        is_in: 0,
+        is_first: true,
+    };
+    while let Some(t) = p.next() {
+        if !t.is_empty() {
+            s.push_str(&t);
+        }
     }
+    s
 }
 
 #[cfg(test)]
 mod tests {
-    use super::{LangString, Markdown, MarkdownHtml};
+    use super::{LangString, Markdown, MarkdownHtml, MarkdownOutputStyle};
     use super::plain_summary_line;
     use html::render::reset_ids;
 
@@ -684,52 +866,52 @@ mod tests {
     #[test]
     fn issue_17736() {
         let markdown = "# title";
-        format!("{}", Markdown(markdown));
+        format!("{}", Markdown(markdown, MarkdownOutputStyle::Fancy));
         reset_ids(true);
     }
 
     #[test]
     fn test_header() {
         fn t(input: &str, expect: &str) {
-            let output = format!("{}", Markdown(input));
-            assert_eq!(output, expect);
+            let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
+            assert_eq!(output, expect, "original: {}", input);
             reset_ids(true);
         }
 
-        t("# Foo bar", "\n<h1 id='foo-bar' class='section-header'>\
-          <a href='#foo-bar'>Foo bar</a></h1>");
-        t("## Foo-bar_baz qux", "\n<h2 id='foo-bar_baz-qux' class=\'section-\
-          header'><a href='#foo-bar_baz-qux'>Foo-bar_baz qux</a></h2>");
+        t("# Foo bar", "<h1 id=\"foo-bar\" class=\"section-header\">\
+          <a href=\"#foo-bar\">Foo bar</a></h1>");
+        t("## Foo-bar_baz qux", "<h2 id=\"foo-bar_baz-qux\" class=\"section-\
+          header\"><a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h2>");
         t("### **Foo** *bar* baz!?!& -_qux_-%",
-          "\n<h3 id='foo-bar-baz--_qux_-' class='section-header'>\
-          <a href='#foo-bar-baz--_qux_-'><strong>Foo</strong> \
-          <em>bar</em> baz!?!&amp; -_qux_-%</a></h3>");
-        t("####**Foo?** & \\*bar?!*  _`baz`_ ❤ #qux",
-          "\n<h4 id='foo--bar--baz--qux' class='section-header'>\
-          <a href='#foo--bar--baz--qux'><strong>Foo?</strong> &amp; *bar?!*  \
+          "<h3 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
+          <a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
+          <em>bar</em> baz!?!&amp; -<em>qux</em>-%</a></h3>");
+        t("#### **Foo?** & \\*bar?!*  _`baz`_ ❤ #qux",
+          "<h4 id=\"foo--bar--baz--qux\" class=\"section-header\">\
+          <a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> &amp; *bar?!*  \
           <em><code>baz</code></em> ❤ #qux</a></h4>");
     }
 
     #[test]
     fn test_header_ids_multiple_blocks() {
         fn t(input: &str, expect: &str) {
-            let output = format!("{}", Markdown(input));
-            assert_eq!(output, expect);
+            let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
+            assert_eq!(output, expect, "original: {}", input);
         }
 
         let test = || {
-            t("# Example", "\n<h1 id='example' class='section-header'>\
-              <a href='#example'>Example</a></h1>");
-            t("# Panics", "\n<h1 id='panics' class='section-header'>\
-              <a href='#panics'>Panics</a></h1>");
-            t("# Example", "\n<h1 id='example-1' class='section-header'>\
-              <a href='#example-1'>Example</a></h1>");
-            t("# Main", "\n<h1 id='main-1' class='section-header'>\
-              <a href='#main-1'>Main</a></h1>");
-            t("# Example", "\n<h1 id='example-2' class='section-header'>\
-              <a href='#example-2'>Example</a></h1>");
-            t("# Panics", "\n<h1 id='panics-1' class='section-header'>\
-              <a href='#panics-1'>Panics</a></h1>");
+            t("# Example", "<h1 id=\"example\" class=\"section-header\">\
+              <a href=\"#example\">Example</a></h1>");
+            t("# Panics", "<h1 id=\"panics\" class=\"section-header\">\
+              <a href=\"#panics\">Panics</a></h1>");
+            t("# Example", "<h1 id=\"example-1\" class=\"section-header\">\
+              <a href=\"#example-1\">Example</a></h1>");
+            t("# Main", "<h1 id=\"main-1\" class=\"section-header\">\
+              <a href=\"#main-1\">Main</a></h1>");
+            t("# Example", "<h1 id=\"example-2\" class=\"section-header\">\
+              <a href=\"#example-2\">Example</a></h1>");
+            t("# Panics", "<h1 id=\"panics-1\" class=\"section-header\">\
+              <a href=\"#panics-1\">Panics</a></h1>");
         };
         test();
         reset_ids(true);
@@ -740,7 +922,7 @@ mod tests {
     fn test_plain_summary_line() {
         fn t(input: &str, expect: &str) {
             let output = plain_summary_line(input);
-            assert_eq!(output, expect);
+            assert_eq!(output, expect, "original: {}", input);
         }
 
         t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
@@ -754,10 +936,10 @@ mod tests {
     fn test_markdown_html_escape() {
         fn t(input: &str, expect: &str) {
             let output = format!("{}", MarkdownHtml(input));
-            assert_eq!(output, expect);
+            assert_eq!(output, expect, "original: {}", input);
         }
 
-        t("`Struct<'a, T>`", "<p><code>Struct&lt;&#39;a, T&gt;</code></p>\n");
-        t("Struct<'a, T>", "<p>Struct&lt;&#39;a, T&gt;</p>\n");
+        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>");
     }
 }
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index 10fde67a456..f0b624105e3 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -72,7 +72,7 @@ use html::format::{TyParamBounds, WhereClause, href, AbiSpace};
 use html::format::{VisSpace, Method, UnsafetySpace, MutableSpace};
 use html::format::fmt_impl_for_trait_page;
 use html::item_type::ItemType;
-use html::markdown::{self, Markdown, MarkdownHtml};
+use html::markdown::{self, Markdown, MarkdownHtml, MarkdownOutputStyle};
 use html::{highlight, layout};
 
 /// A pair of name and its optional document.
@@ -1650,7 +1650,8 @@ fn document_short(w: &mut fmt::Formatter, item: &clean::Item, link: AssocItemLin
         } else {
             format!("{}", &plain_summary_line(Some(s)))
         };
-        write!(w, "<div class='docblock'>{}</div>", Markdown(&markdown))?;
+        write!(w, "<div class='docblock'>{}</div>",
+               Markdown(&markdown, MarkdownOutputStyle::Fancy))?;
     }
     Ok(())
 }
@@ -1683,7 +1684,8 @@ fn get_doc_value(item: &clean::Item) -> Option<&str> {
 fn document_full(w: &mut fmt::Formatter, item: &clean::Item) -> fmt::Result {
     if let Some(s) = get_doc_value(item) {
         write!(w, "<div class='docblock'>{}</div>",
-               Markdown(&format!("{}{}", md_render_assoc_item(item), s)))?;
+               Markdown(&format!("{}{}", md_render_assoc_item(item), s),
+                                 MarkdownOutputStyle::Fancy))?;
     }
     Ok(())
 }
@@ -1700,6 +1702,23 @@ fn document_stability(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item)
     Ok(())
 }
 
+fn name_key(name: &str) -> (&str, u64, usize) {
+    // find number at end
+    let split = name.bytes().rposition(|b| b < b'0' || b'9' < b).map_or(0, |s| s + 1);
+
+    // count leading zeroes
+    let after_zeroes =
+        name[split..].bytes().position(|b| b != b'0').map_or(name.len(), |extra| split + extra);
+
+    // sort leading zeroes last
+    let num_zeroes = after_zeroes - split;
+
+    match name[split..].parse() {
+        Ok(n) => (&name[..split], n, num_zeroes),
+        Err(_) => (name, 0, num_zeroes),
+    }
+}
+
 fn item_module(w: &mut fmt::Formatter, cx: &Context,
                item: &clean::Item, items: &[clean::Item]) -> fmt::Result {
     document(w, cx, item)?;
@@ -1744,7 +1763,9 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
             (Some(stability::Stable), Some(stability::Unstable)) => return Ordering::Less,
             _ => {}
         }
-        i1.name.cmp(&i2.name)
+        let lhs = i1.name.as_ref().map_or("", |s| &**s);
+        let rhs = i2.name.as_ref().map_or("", |s| &**s);
+        name_key(lhs).cmp(&name_key(rhs))
     }
 
     indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2));
@@ -1852,7 +1873,8 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
                        </tr>",
                        name = *myitem.name.as_ref().unwrap(),
                        stab_docs = stab_docs,
-                       docs = shorter(Some(&Markdown(doc_value).to_string())),
+                       docs = shorter(Some(&Markdown(doc_value,
+                                                     MarkdownOutputStyle::Compact).to_string())),
                        class = myitem.type_(),
                        stab = myitem.stability_class().unwrap_or("".to_string()),
                        unsafety_flag = unsafety_flag,
@@ -2592,7 +2614,7 @@ fn render_attribute(attr: &ast::MetaItem) -> Option<String> {
     if attr.is_word() {
         Some(format!("{}", name))
     } else if let Some(v) = attr.value_str() {
-        Some(format!("{} = {:?}", name, &v.as_str()[..]))
+        Some(format!("{} = {:?}", name, v.as_str()))
     } else if let Some(values) = attr.meta_item_list() {
         let display: Vec<_> = values.iter().filter_map(|attr| {
             attr.meta_item().and_then(|mi| render_attribute(mi))
@@ -2623,7 +2645,7 @@ fn render_attributes(w: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result {
 
     for attr in &it.attrs.other_attrs {
         let name = attr.name().unwrap();
-        if !ATTRIBUTE_WHITELIST.contains(&&name.as_str()[..]) {
+        if !ATTRIBUTE_WHITELIST.contains(&&*name.as_str()) {
             continue;
         }
         if let Some(s) = render_attribute(&attr.meta().unwrap()) {
@@ -2882,7 +2904,7 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi
         write!(w, "</span>")?;
         write!(w, "</h3>\n")?;
         if let Some(ref dox) = i.impl_item.doc_value() {
-            write!(w, "<div class='docblock'>{}</div>", Markdown(dox))?;
+            write!(w, "<div class='docblock'>{}</div>", Markdown(dox, MarkdownOutputStyle::Fancy))?;
         }
     }
 
@@ -3198,3 +3220,32 @@ fn test_unique_id() {
     reset_ids(true);
     test();
 }
+
+#[cfg(test)]
+#[test]
+fn test_name_key() {
+    assert_eq!(name_key("0"), ("", 0, 1));
+    assert_eq!(name_key("123"), ("", 123, 0));
+    assert_eq!(name_key("Fruit"), ("Fruit", 0, 0));
+    assert_eq!(name_key("Fruit0"), ("Fruit", 0, 1));
+    assert_eq!(name_key("Fruit0000"), ("Fruit", 0, 4));
+    assert_eq!(name_key("Fruit01"), ("Fruit", 1, 1));
+    assert_eq!(name_key("Fruit10"), ("Fruit", 10, 0));
+    assert_eq!(name_key("Fruit123"), ("Fruit", 123, 0));
+}
+
+#[cfg(test)]
+#[test]
+fn test_name_sorting() {
+    let names = ["Apple",
+                 "Banana",
+                 "Fruit", "Fruit0", "Fruit00",
+                 "Fruit1", "Fruit01",
+                 "Fruit2", "Fruit02",
+                 "Fruit20",
+                 "Fruit100",
+                 "Pear"];
+    let mut sorted = names.to_owned();
+    sorted.sort_by_key(|&s| name_key(s));
+    assert_eq!(names, sorted);
+}
diff --git a/src/librustdoc/html/static/rustdoc.css b/src/librustdoc/html/static/rustdoc.css
index 3df120eece9..4edf6309346 100644
--- a/src/librustdoc/html/static/rustdoc.css
+++ b/src/librustdoc/html/static/rustdoc.css
@@ -141,7 +141,7 @@ pre {
 	padding: 14px;
 }
 
-.source pre {
+.source .content pre {
 	padding: 20px;
 }
 
@@ -149,7 +149,7 @@ img {
 	max-width: 100%;
 }
 
-.content.source {
+.source .content {
 	margin-top: 50px;
 	max-width: none;
 	overflow: visible;
@@ -231,7 +231,7 @@ nav.sub {
 	padding: 15px 0;
 }
 
-.content.source pre.rust {
+.source .content pre.rust {
 	white-space: pre;
 	overflow: auto;
 	padding-left: 0;
diff --git a/src/librustdoc/html/static/styles/main.css b/src/librustdoc/html/static/styles/main.css
index 74ec3691b38..c0310199088 100644
--- a/src/librustdoc/html/static/styles/main.css
+++ b/src/librustdoc/html/static/styles/main.css
@@ -45,6 +45,10 @@ pre {
 	background-color: #fff;
 }
 
+.source .sidebar {
+	background-color: #fff;
+}
+
 .sidebar .location {
 	border-color: #000;
 	background-color: #fff;