Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 13 additions & 38 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
//! (bundled into the rust runtime). This module self-contains the C bindings
//! and necessary legwork to render markdown, and exposes all of the
//! functionality through a unit-struct, `Markdown`, which has an implementation
//! of `fmt::String`. Example usage:
//! of `fmt::Display`. Example usage:
//!
//! ```rust,ignore
//! use rustdoc::html::markdown::Markdown;
Expand All @@ -29,19 +29,19 @@
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;
use std::slice;
use std::str;

use html::render::with_unique_id;
use html::toc::TocBuilder;
use html::highlight;
use html::escape::Escape;
use test;

/// A unit struct which has the `fmt::String` trait implemented. When
/// 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);
Expand Down Expand Up @@ -210,10 +210,6 @@ fn collapse_whitespace(s: &str) -> String {
s.split_whitespace().collect::<Vec<_>>().join(" ")
}

thread_local!(static USED_HEADER_MAP: RefCell<HashMap<String, usize>> = {
RefCell::new(HashMap::new())
});

thread_local!(pub static PLAYGROUND_KRATE: RefCell<Option<Option<String>>> = {
RefCell::new(None)
});
Expand Down Expand Up @@ -311,32 +307,21 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state };
let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };

// Make sure our hyphenated ID is unique for this page
let id = USED_HEADER_MAP.with(|map| {
let id = match map.borrow_mut().get_mut(&id) {
None => id,
Some(a) => { *a += 1; format!("{}-{}", id, *a - 1) }
};
map.borrow_mut().insert(id.clone(), 1);
id
});

let text = with_unique_id(id, |id| {
let sec = opaque.toc_builder.as_mut().map_or("".to_owned(), |builder| {
format!("{} ", builder.push(level as u32, s.clone(), id.to_owned()))
});

let sec = opaque.toc_builder.as_mut().map_or("".to_owned(), |builder| {
format!("{} ", builder.push(level as u32, s.clone(), id.clone()))
// Render the HTML
format!("<h{lvl} id='{id}' class='section-header'>\
<a href='#{id}'>{sec}{}</a></h{lvl}>",
s, lvl = level, id = id, sec = sec)
});

// 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()) }
}

reset_headers();

extern fn codespan(
ob: *mut hoedown_buffer,
text: *const hoedown_buffer,
Expand Down Expand Up @@ -500,18 +485,6 @@ impl LangString {
}
}

/// By default this markdown renderer generates anchors for each header in the
/// rendered document. The anchor name is the contents of the header separated
/// by hyphens, and a thread-local map is used to disambiguate among duplicate
/// headers (numbers are appended).
///
/// This method will reset the local table for these headers. This is typically
/// used at the beginning of rendering an entire HTML page to reset from the
/// previous state (if any).
pub fn reset_headers() {
USED_HEADER_MAP.with(|s| s.borrow_mut().clear());
}

impl<'a> fmt::Display for Markdown<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let Markdown(md) = *self;
Expand Down Expand Up @@ -579,6 +552,7 @@ pub fn plain_summary_line(md: &str) -> String {
mod tests {
use super::{LangString, Markdown};
use super::plain_summary_line;
use html::render::reset_ids;

#[test]
fn test_lang_string_parse() {
Expand Down Expand Up @@ -618,6 +592,7 @@ mod tests {
fn t(input: &str, expect: &str) {
let output = format!("{}", Markdown(input));
assert_eq!(output, expect);
reset_ids();
}

t("# Foo bar", "\n<h1 id='foo-bar' class='section-header'>\
Expand Down
121 changes: 83 additions & 38 deletions src/librustdoc/html/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,55 @@ impl fmt::Display for IndexItemFunctionType {
thread_local!(static CACHE_KEY: RefCell<Arc<Cache>> = Default::default());
thread_local!(pub static CURRENT_LOCATION_KEY: RefCell<Vec<String>> =
RefCell::new(Vec::new()));
thread_local!(static USED_ID_MAP: RefCell<HashMap<String, usize>> =
RefCell::new(init_ids()));

fn init_ids() -> HashMap<String, usize> {
[
"main",
"search",
"help",
"TOC",
"render-detail",
"associated-types",
"associated-const",
"required-methods",
"provided-methods",
"implementors",
"implementors-list",
"methods",
"deref-methods",
"implementations",
"derived_implementations"
].into_iter().map(|id| (String::from(*id), 1)).collect::<HashMap<_, _>>()
}

/// This method resets the local table of used ID attributes. This is typically
/// used at the beginning of rendering an entire HTML page to reset from the
/// previous state (if any).
pub fn reset_ids() {
USED_ID_MAP.with(|s| *s.borrow_mut() = init_ids());
}

pub fn with_unique_id<T, F: FnOnce(&str) -> T>(candidate: String, f: F) -> T {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this return a unique id instead of taking a closure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I felt ambivalent about this anyway. (The intention was to avoid the extra heap allocation)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah don't worry too much about allocations, it happens a lot in rustdoc anyway :)

USED_ID_MAP.with(|map| {
let (id, ret) = match map.borrow_mut().get_mut(&candidate) {
None => {
let ret = f(&candidate);
(candidate, ret)
},
Some(a) => {
let id = format!("{}-{}", candidate, *a);
let ret = f(&id);
*a += 1;
(id, ret)
}
};

map.borrow_mut().insert(id, 1);
ret
})
}

/// Generates the documentation for `crate` into the directory `dst`
pub fn run(mut krate: clean::Crate,
Expand Down Expand Up @@ -1274,7 +1323,7 @@ impl Context {
keywords: &keywords,
};

markdown::reset_headers();
reset_ids();

// We have a huge number of calls to write, so try to alleviate some
// of the pain by using a buffered writer instead of invoking the
Expand Down Expand Up @@ -1696,10 +1745,10 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
ItemType::AssociatedType => ("associated-types", "Associated Types"),
ItemType::AssociatedConst => ("associated-consts", "Associated Constants"),
};
try!(write!(w,
"<h2 id='{id}' class='section-header'>\
<a href=\"#{id}\">{name}</a></h2>\n<table>",
id = short, name = name));
try!(with_unique_id(short.to_owned(), |id|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically multi-line closures are surrounded with braces rather than newlines

write!(w, "<h2 id='{id}' class='section-header'>\
<a href=\"#{id}\">{name}</a></h2>\n<table>",
id = id, name = name)));
}

match myitem.inner {
Expand Down Expand Up @@ -1920,10 +1969,11 @@ fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,

fn trait_item(w: &mut fmt::Formatter, cx: &Context, m: &clean::Item)
-> fmt::Result {
try!(write!(w, "<h3 id='{ty}.{name}' class='method stab {stab}'><code>",
ty = shortty(m),
name = *m.name.as_ref().unwrap(),
stab = m.stability_class()));
let name = m.name.as_ref().unwrap();
try!(with_unique_id(format!("{}.{}", shortty(m), name), |id|
write!(w, "<h3 id='{id}' class='method stab {stab}'><code>",
id = id,
stab = m.stability_class())));
try!(render_assoc_item(w, m, AssocItemLink::Anchor));
try!(write!(w, "</code></h3>"));
try!(document(w, cx, m));
Expand Down Expand Up @@ -2112,11 +2162,12 @@ fn item_struct(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
if fields.peek().is_some() {
try!(write!(w, "<h2 class='fields'>Fields</h2>\n<table>"));
for field in fields {
try!(write!(w, "<tr class='stab {stab}'>
<td id='structfield.{name}'>\
<code>{name}</code></td><td>",
stab = field.stability_class(),
name = field.name.as_ref().unwrap()));
let name = field.name.as_ref().unwrap();
try!(with_unique_id(format!("structfield.{}", name), |id|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that ids like this don't need to be altered because there shouldn't ever be more than one struct on a page?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same for some around this)

write!(w, "<tr class='stab {}'><td id='{}'><code>{}</code></td><td>",
field.stability_class(),
id,
name)));
try!(document(w, cx, field));
try!(write!(w, "</td></tr>"));
}
Expand Down Expand Up @@ -2183,8 +2234,9 @@ fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
if !e.variants.is_empty() {
try!(write!(w, "<h2 class='variants'>Variants</h2>\n<table>"));
for variant in &e.variants {
try!(write!(w, "<tr><td id='variant.{name}'><code>{name}</code></td><td>",
name = variant.name.as_ref().unwrap()));
let name = variant.name.as_ref().unwrap();
try!(with_unique_id(format!("variant.{}", name), |id|
write!(w, "<tr><td id='{}'><code>{}</code></td><td>", id, name)));
try!(document(w, cx, variant));
match variant.inner {
clean::VariantItem(ref var) => {
Expand All @@ -2202,11 +2254,10 @@ fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
try!(write!(w, "<h3 class='fields'>Fields</h3>\n
<table>"));
for field in fields {
try!(write!(w, "<tr><td \
id='variant.{v}.field.{f}'>\
<code>{f}</code></td><td>",
v = variant.name.as_ref().unwrap(),
f = field.name.as_ref().unwrap()));
let v = variant.name.as_ref().unwrap();
let f = field.name.as_ref().unwrap();
try!(with_unique_id(format!("variant.{}.field.{}", v, f), |id|
write!(w, "<tr><td id='{}'><code>{}</code></td><td>", id, f)));
try!(document(w, cx, field));
try!(write!(w, "</td></tr>"));
}
Expand Down Expand Up @@ -2418,44 +2469,38 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi

fn doctraititem(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item,
link: AssocItemLink, render_static: bool) -> fmt::Result {
let name = item.name.as_ref().unwrap();
match item.inner {
clean::MethodItem(..) | clean::TyMethodItem(..) => {
// Only render when the method is not static or we allow static methods
if !is_static_method(item) || render_static {
try!(write!(w, "<h4 id='method.{}' class='{}'><code>",
*item.name.as_ref().unwrap(),
shortty(item)));
try!(with_unique_id(format!("method.{}", name), |id|
write!(w, "<h4 id='{}' class='{}'><code>", id, shortty(item))));
try!(render_assoc_item(w, item, link));
try!(write!(w, "</code></h4>\n"));
}
}
clean::TypedefItem(ref tydef, _) => {
let name = item.name.as_ref().unwrap();
try!(write!(w, "<h4 id='assoc_type.{}' class='{}'><code>",
*name,
shortty(item)));
try!(with_unique_id(format!("assoc_type.{}", name), |id|
write!(w, "<h4 id='{}' class='{}'><code>", id, shortty(item))));
try!(write!(w, "type {} = {}", name, tydef.type_));
try!(write!(w, "</code></h4>\n"));
}
clean::AssociatedConstItem(ref ty, ref default) => {
let name = item.name.as_ref().unwrap();
try!(write!(w, "<h4 id='assoc_const.{}' class='{}'><code>",
*name, shortty(item)));
try!(with_unique_id(format!("assoc_const.{}", name), |id|
write!(w, "<h4 id='{}' class='{}'><code>", id, shortty(item))));
try!(assoc_const(w, item, ty, default.as_ref()));
try!(write!(w, "</code></h4>\n"));
}
clean::ConstantItem(ref c) => {
let name = item.name.as_ref().unwrap();
try!(write!(w, "<h4 id='assoc_const.{}' class='{}'><code>",
*name, shortty(item)));
try!(with_unique_id(format!("assoc_const.{}", name), |id|
write!(w, "<h4 id='{}' class='{}'><code>", id, shortty(item))));
try!(assoc_const(w, item, &c.type_, Some(&c.expr)));
try!(write!(w, "</code></h4>\n"));
}
clean::AssociatedTypeItem(ref bounds, ref default) => {
let name = item.name.as_ref().unwrap();
try!(write!(w, "<h4 id='assoc_type.{}' class='{}'><code>",
*name,
shortty(item)));
try!(with_unique_id(format!("assoc_type.{}", name), |id|
write!(w, "<h4 id='{}' class='{}'><code>", id, shortty(item))));
try!(assoc_type(w, item, bounds, default));
try!(write!(w, "</code></h4>\n"));
}
Expand Down
5 changes: 3 additions & 2 deletions src/librustdoc/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ use rustc::session::search_paths::SearchPaths;

use externalfiles::ExternalHtml;

use html::render::reset_ids;
use html::escape::Escape;
use html::markdown;
use html::markdown::{Markdown, MarkdownWithToc, find_testable_code, reset_headers};
use html::markdown::{Markdown, MarkdownWithToc, find_testable_code};
use test::{TestOptions, Collector};

/// Separate any lines at the start of the file that begin with `%`.
Expand Down Expand Up @@ -82,7 +83,7 @@ pub fn render(input: &str, mut output: PathBuf, matches: &getopts::Matches,
}
let title = metadata[0];

reset_headers();
reset_ids();

let rendered = if include_toc {
format!("{}", MarkdownWithToc(text))
Expand Down