diff options
| author | Esteban Küber <esteban@kuber.com.ar> | 2017-04-04 08:10:22 -0700 |
|---|---|---|
| committer | Esteban Küber <esteban@kuber.com.ar> | 2017-04-04 08:13:27 -0700 |
| commit | dedb7bbbbf272226f327b8cda8aaa12cf6325eca (patch) | |
| tree | 9c7c7b656750a53b5ca4f7646ee1dfe60829b271 /src/librustdoc/html | |
| parent | b477682dca3343eb89a467f0d3c73986a53d49d9 (diff) | |
| parent | 5309a3e31d88def1f3ea966162ed4f81f161d500 (diff) | |
Merge branch 'master' into issue-32540
Diffstat (limited to 'src/librustdoc/html')
| -rw-r--r-- | src/librustdoc/html/layout.rs | 4 | ||||
| -rw-r--r-- | src/librustdoc/html/markdown.rs | 1078 | ||||
| -rw-r--r-- | src/librustdoc/html/render.rs | 67 | ||||
| -rw-r--r-- | src/librustdoc/html/static/rustdoc.css | 6 | ||||
| -rw-r--r-- | src/librustdoc/html/static/styles/main.css | 4 |
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(") { - "&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(") { + "&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>", - "<", ">", "&", "'", """]; - 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{}\">{} <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!?!& -_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> & *bar?!* \ + "<h3 id=\"foo-bar-baz--qux-\" class=\"section-header\">\ + <a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \ + <em>bar</em> baz!?!& -<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> & *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<'a, T></code></p>\n"); - t("Struct<'a, T>", "<p>Struct<'a, T></p>\n"); + t("`Struct<'a, T>`", "<p><code>Struct<'a, T></code></p>"); + t("Struct<'a, T>", "<p>Struct<'a, T></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; |
