From f9fb381b2a13260c3fcdae2f6d9546ab2c87e717 Mon Sep 17 00:00:00 2001 From: Oliver Middleton Date: Thu, 6 Apr 2017 13:09:20 +0100 Subject: [PATCH] rustdoc: Use pulldown-cmark for Markdown HTML rendering Instead of rendering all of the HTML in rustdoc this relies on pulldown-cmark's `push_html` to do most of the work. A few iterator adapters are used to make rustdoc specific modifications to the output. This also fixes MarkdownHtml and link titles in plain_summary_line. --- src/librustdoc/html/markdown.rs | 728 +++++++----------- src/librustdoc/html/render.rs | 12 +- src/librustdoc/markdown.rs | 4 +- src/test/rustdoc/check-hard-break.rs | 3 +- src/test/rustdoc/check-rule-image-footnote.rs | 14 +- src/test/rustdoc/test-lists.rs | 14 +- src/tools/error_index_generator/main.rs | 4 +- 7 files changed, 296 insertions(+), 483 deletions(-) diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 245a3946a3709..1e687d63f5875 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -16,10 +16,10 @@ //! 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; @@ -37,43 +37,23 @@ use syntax::codemap::Span; 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::>().join(" ") +/// "Hello, world!" -> "hello-world" +fn slugify(c: char) -> Option { + 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 @@ thread_local!(pub static PLAYGROUND: RefCell, 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, +/// Adds syntax highlighting and playground Run buttons to rust code blocks. +struct CodeBlocks<'a, I: Iterator>> { + 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>> CodeBlocks<'a, I> { + fn new(iter: I) -> Self { + CodeBlocks { + inner: iter, } } +} - pub fn next(&mut self) -> Option> { - self.parser.next() - } +impl<'a, I: Iterator>> 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 { + 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::>().join("\n"); - let block_info = if lang.is_empty() { - LangString::all_false() - } else { - LangString::parse(lang) - }; - if !block_info.rust { - buffer.push_str(&format!("
{}
", - 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 @@ pub fn render(w: &mut fmt::Formatter, }).collect::>().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(") { "&version=nightly" } else { @@ -239,376 +175,186 @@ pub fn render(w: &mut fmt::Formatter, 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, 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::(); - - 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!("\ - {sec}{}", - 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, 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!("{}", - Escape(&collapse_whitespace(content.trim_right())))); - } +/// Make headings links with anchor ids and build up TOC. +struct HeadingLinks<'a, 'b, I: Iterator>> { + inner: I, + toc: Option<&'b mut TocBuilder>, + buf: VecDeque>, +} - fn link(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option, - 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!("{}", url, content)); - } else { - buffer.push_str(&format!("{}", - url, Escape(title), content)); +impl<'a, 'b, I: Iterator>> 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, - 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!("\"{}\"", url, title)); - } - - fn paragraph(parser: &mut ParserWrapper, buffer: &mut String, - toc_builder: &mut Option, 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!("

{}

", content.trim_right())); - } - - fn table_cell(parser: &mut ParserWrapper, buffer: &mut String, - toc_builder: &mut Option, 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!("{}", content.trim())); - } +impl<'a, 'b, I: Iterator>> Iterator for HeadingLinks<'a, 'b, I> { + type Item = Event<'a>; - fn table_row(parser: &mut ParserWrapper, buffer: &mut String, - toc_builder: &mut Option, 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 { + if let Some(e) = self.buf.pop_front() { + return Some(e); } - buffer.push_str(&format!("{}", content)); - } - fn table_head(parser: &mut ParserWrapper, buffer: &mut String, - toc_builder: &mut Option, 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!("{}", content.replace("td>", "th>"))); - } - } + let id = derive_id(id); - fn table(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option, - 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!("{}{}
", - content, - if shorter.is_compact() || rows.is_empty() { - String::new() - } else { - format!("{}", rows) - })); - } - fn blockquote(parser: &mut ParserWrapper, buffer: &mut String, - toc_builder: &mut Option, 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!("
{}
", content.trim_right())); - } + self.buf.push_back(Event::InlineHtml(format!("", level).into())); - fn list_item(parser: &mut ParserWrapper, buffer: &mut String, - toc_builder: &mut Option, 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); - } - } - if shorter.is_compact() { - break - } + let start_tags = format!("\ + ", + id = id, + level = level); + return Some(Event::InlineHtml(start_tags.into())); } - buffer.push_str(&format!("
  • {}
  • ", content)); + event } +} - fn list(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option, - shorter: MarkdownOutputStyle, is_sorted_list: bool) { - 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); - } - } - if shorter.is_compact() { - break - } +/// Extracts just the first paragraph. +struct SummaryLine<'a, I: Iterator>> { + inner: I, + started: bool, + depth: u32, +} + +impl<'a, I: Iterator>> SummaryLine<'a, I> { + fn new(iter: I) -> Self { + SummaryLine { + inner: iter, + started: false, + depth: 0, } - buffer.push_str(&format!("<{0}>{1}", - if is_sorted_list { "ol" } else { "ul" }, - content)); } +} - fn emphasis(parser: &mut ParserWrapper, buffer: &mut String, - toc_builder: &mut Option, 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!("{}", content)); - } +impl<'a, I: Iterator>> Iterator for SummaryLine<'a, I> { + type Item = Event<'a>; - fn strong(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option, - 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!("{}", content)); + fn next(&mut self) -> Option { + 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, 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>> { + inner: I, + footnotes: HashMap>, u16)>, +} - fn rule(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option, - 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("
    "); +impl<'a, I: Iterator>> Footnotes<'a, I> { + fn new(iter: I) -> Self { + Footnotes { + inner: iter, + footnotes: HashMap::new(), + } + } + fn get_entry(&mut self, key: &str) -> &mut (Vec>, 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>, - toc_builder: &mut Option, 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(x)) => { - list(parser, buffer, toc_builder, shorter, x.is_some()); - } - 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!("
  • {} 

  • ", - cur_id, - if content.ends_with("

    ") { - &content[..content.len() - 4] - } else { - &content - })); - } - Event::FootnoteReference(ref reference) => { - debug!("FootnoteReference"); - let entry = parser.get_entry(reference.as_ref()); - buffer.push_str(&format!("{0}\ - ", - (*entry).1)); - } - Event::HardBreak => { - debug!("HardBreak"); - if shorter.is_fancy() { - buffer.push_str("
    "); - } else if !buffer.is_empty() { - buffer.push(' '); +impl<'a, I: Iterator>> Iterator for Footnotes<'a, I> { + type Item = Event<'a>; + + fn next(&mut self) -> Option { + loop { + match self.inner.next() { + Some(Event::FootnoteReference(ref reference)) => { + let entry = self.get_entry(&reference); + let reference = format!("{0}\ + ", + (*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("

      "); + for (mut content, id) in v { + write!(ret, "
    1. ", 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, + " ", + id).unwrap(); + if is_paragraph { + ret.push_str("

      "); + } + ret.push_str("
    2. "); + } + ret.push_str("
    "); + 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!("

      {}
    ", - v.iter() - .map(|s| s.0.as_str()) - .collect::>() - .join(""))); - } - let mut ret = toc_builder.map_or(Ok(()), |builder| { - write!(w, "", 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) { @@ -755,17 +501,45 @@ impl 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, "", toc.into_toc())?; + + fmt.write_str(&s) } } @@ -774,7 +548,41 @@ 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, 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) } } @@ -796,14 +604,10 @@ pub fn plain_summary_line(md: &str) -> 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), }; @@ -834,7 +638,7 @@ pub fn plain_summary_line(md: &str) -> 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; @@ -874,14 +678,14 @@ mod tests { #[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); } @@ -903,7 +707,7 @@ mod tests { #[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); } @@ -934,6 +738,7 @@ mod tests { } 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"); @@ -947,7 +752,8 @@ mod tests { assert_eq!(output, expect, "original: {}", input); } - t("`Struct<'a, T>`", "

    Struct<'a, T>

    "); - t("Struct<'a, T>", "

    Struct<'a, T>

    "); + t("`Struct<'a, T>`", "

    Struct<'a, T>

    \n"); + t("Struct<'a, T>", "

    Struct<'a, T>

    \n"); + t("Struct
    ", "

    Struct<br>

    \n"); } } diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index f0b624105e347..1e1202f04005c 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, MarkdownOutputStyle}; +use html::markdown::{self, Markdown, MarkdownHtml, MarkdownSummaryLine}; use html::{highlight, layout}; /// A pair of name and its optional document. @@ -1651,7 +1651,7 @@ fn document_short(w: &mut fmt::Formatter, item: &clean::Item, link: AssocItemLin format!("{}", &plain_summary_line(Some(s))) }; write!(w, "
    {}
    ", - Markdown(&markdown, MarkdownOutputStyle::Fancy))?; + Markdown(&markdown))?; } Ok(()) } @@ -1684,8 +1684,7 @@ 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, "
    {}
    ", - Markdown(&format!("{}{}", md_render_assoc_item(item), s), - MarkdownOutputStyle::Fancy))?; + Markdown(&format!("{}{}", md_render_assoc_item(item), s)))?; } Ok(()) } @@ -1873,8 +1872,7 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context, ", name = *myitem.name.as_ref().unwrap(), stab_docs = stab_docs, - docs = shorter(Some(&Markdown(doc_value, - MarkdownOutputStyle::Compact).to_string())), + docs = MarkdownSummaryLine(doc_value), class = myitem.type_(), stab = myitem.stability_class().unwrap_or("".to_string()), unsafety_flag = unsafety_flag, @@ -2904,7 +2902,7 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi write!(w, "")?; write!(w, "\n")?; if let Some(ref dox) = i.impl_item.doc_value() { - write!(w, "
    {}
    ", Markdown(dox, MarkdownOutputStyle::Fancy))?; + write!(w, "
    {}
    ", Markdown(dox))?; } } diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index 5cc0f03e1f629..5fadda030a4b4 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -25,7 +25,7 @@ use externalfiles::{ExternalHtml, LoadStringError, load_string}; use html::render::reset_ids; use html::escape::Escape; use html::markdown; -use html::markdown::{Markdown, MarkdownWithToc, MarkdownOutputStyle, find_testable_code}; +use html::markdown::{Markdown, MarkdownWithToc, find_testable_code}; use test::{TestOptions, Collector}; /// Separate any lines at the start of the file that begin with `# ` or `%`. @@ -96,7 +96,7 @@ pub fn render(input: &str, mut output: PathBuf, matches: &getopts::Matches, let rendered = if include_toc { format!("{}", MarkdownWithToc(text)) } else { - format!("{}", Markdown(text, MarkdownOutputStyle::Fancy)) + format!("{}", Markdown(text)) }; let err = write!( diff --git a/src/test/rustdoc/check-hard-break.rs b/src/test/rustdoc/check-hard-break.rs index 5c5e3f8136c73..f048b64d104ab 100644 --- a/src/test/rustdoc/check-hard-break.rs +++ b/src/test/rustdoc/check-hard-break.rs @@ -13,7 +13,8 @@ // ignore-tidy-end-whitespace // @has foo/fn.f.html -// @has - '

    hard break:
    after hard break

    ' +// @has - '

    hard break:
    ' +// @has - 'after hard break

    ' /// hard break: /// after hard break pub fn f() {} diff --git a/src/test/rustdoc/check-rule-image-footnote.rs b/src/test/rustdoc/check-rule-image-footnote.rs index 4d3bea20ba895..46542677857fc 100644 --- a/src/test/rustdoc/check-rule-image-footnote.rs +++ b/src/test/rustdoc/check-rule-image-footnote.rs @@ -13,16 +13,21 @@ // ignore-tidy-linelength // @has foo/fn.f.html -// @has - '

    markdown test

    this is a link.

    hard break: after hard break


    a footnote1.

    another footnote2.

    Rust


    1. Thing 

    2. Another Thing 

    ' +// @has - '

    markdown test

    ' +// @has - '

    this is a link.

    ' +// @has - '
    ' +// @has - '

    a footnote1.

    ' +// @has - '

    another footnote2.

    ' +// @has - '

    Rust

    ' +// @has - '

    1. ' +// @has - '

      Thing 

    2. ' +// @has - '

      Another Thing 

    ' /// markdown test /// /// this is a [link]. /// /// [link]: https://example.com "this is a title" /// -/// hard break: -/// after hard break -/// /// ----------- /// /// a footnote[^footnote]. @@ -36,5 +41,4 @@ /// /// /// ![Rust](https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png) -#[deprecated(note = "Struct")] pub fn f() {} diff --git a/src/test/rustdoc/test-lists.rs b/src/test/rustdoc/test-lists.rs index 71a826a2bed7f..29f157e0425c9 100644 --- a/src/test/rustdoc/test-lists.rs +++ b/src/test/rustdoc/test-lists.rs @@ -10,10 +10,11 @@ #![crate_name = "foo"] -// ignore-tidy-linelength - // @has foo/fn.f.html -// @has - "
    pub fn f()
    1. list
      1. fooooo
      2. x
    2. foo
    " +// @has - //ol/li "list" +// @has - //ol/li/ol/li "fooooo" +// @has - //ol/li/ol/li "x" +// @has - //ol/li "foo" /// 1. list /// 1. fooooo /// 2. x @@ -21,7 +22,10 @@ pub fn f() {} // @has foo/fn.foo2.html -// @has - "
    pub fn foo2()
    • normal list
      • sub list

      • new elem still same elem

        and again same elem!

    • new big elem
    " +// @has - //ul/li "normal list" +// @has - //ul/li/ul/li "sub list" +// @has - //ul/li/ul/li "new elem still same elem and again same elem!" +// @has - //ul/li "new big elem" /// * normal list /// * sub list /// * new elem @@ -29,4 +33,4 @@ pub fn f() {} /// /// and again same elem! /// * new big elem -pub fn foo2() {} \ No newline at end of file +pub fn foo2() {} diff --git a/src/tools/error_index_generator/main.rs b/src/tools/error_index_generator/main.rs index 5db2ad83a0a7a..efadde992277f 100644 --- a/src/tools/error_index_generator/main.rs +++ b/src/tools/error_index_generator/main.rs @@ -24,7 +24,7 @@ use std::path::PathBuf; use syntax::diagnostics::metadata::{get_metadata_dir, ErrorMetadataMap, ErrorMetadata}; -use rustdoc::html::markdown::{Markdown, MarkdownOutputStyle, PLAYGROUND}; +use rustdoc::html::markdown::{Markdown, PLAYGROUND}; use rustc_serialize::json; enum OutputFormat { @@ -100,7 +100,7 @@ impl Formatter for HTMLFormatter { // Description rendered as markdown. match info.description { - Some(ref desc) => write!(output, "{}", Markdown(desc, MarkdownOutputStyle::Fancy))?, + Some(ref desc) => write!(output, "{}", Markdown(desc))?, None => write!(output, "

    No description.

    \n")?, }