//! both occur before the crate is rendered.
pub use self::ExternalLocation::*;
+#[cfg(stage0)]
use std::ascii::AsciiExt;
use std::cell::RefCell;
use std::cmp::Ordering;
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, HashSet};
use std::default::Default;
use std::error;
use std::fmt::{self, Display, Formatter, Write as FmtWrite};
use serialize::json::{ToJson, Json, as_json};
use syntax::{abi, ast};
-use syntax::feature_gate::UnstableFeatures;
use rustc::hir::def_id::{CrateNum, CRATE_DEF_INDEX, DefId};
use rustc::middle::privacy::AccessLevels;
use rustc::middle::stability;
use rustc::util::nodemap::{FxHashMap, FxHashSet};
use rustc_data_structures::flock;
-use clean::{self, AttributesExt, GetDefId, SelfTy, Mutability};
+use clean::{self, AttributesExt, GetDefId, SelfTy, Mutability, Span};
use doctree;
use fold::DocFolder;
use html::escape::Escape;
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};
+use html::markdown::{self, Markdown, MarkdownHtml, MarkdownSummaryLine, RenderType};
use html::{highlight, layout};
+use html_diff;
+
/// A pair of name and its optional document.
pub type NameDoc = (String, Option<String>);
/// publicly reused items to redirect to the right location.
pub render_redirect_pages: bool,
pub shared: Arc<SharedContext>,
+ pub render_type: RenderType,
}
pub struct SharedContext {
/// The given user css file which allow to customize the generated
/// documentation theme.
pub css_file_extension: Option<PathBuf>,
+ /// Warnings for the user if rendering would differ using different markdown
+ /// parsers.
+ pub markdown_warnings: RefCell<Vec<(Span, String, Vec<html_diff::Difference>)>>,
+ /// The directories that have already been created in this doc run. Used to reduce the number
+ /// of spurious `create_dir_all` calls.
+ pub created_dirs: RefCell<FxHashSet<PathBuf>>,
+}
+
+impl SharedContext {
+ fn ensure_dir(&self, dst: &Path) -> io::Result<()> {
+ let mut dirs = self.created_dirs.borrow_mut();
+ if !dirs.contains(dst) {
+ fs::create_dir_all(dst)?;
+ dirs.insert(dst.to_path_buf());
+ }
+
+ Ok(())
+ }
}
/// Indicates where an external crate can be found.
// the access levels from crateanalysis.
pub access_levels: Arc<AccessLevels<DefId>>,
+ /// The version of the crate being documented, if given fron the `--crate-version` flag.
+ pub crate_version: Option<String>,
+
// Private fields only used when initially crawling a crate to build a cache
stack: Vec<String>,
stripped_mod: bool,
deref_trait_did: Option<DefId>,
deref_mut_trait_did: Option<DefId>,
+ owned_box_did: Option<DefId>,
+ masked_crates: FxHashSet<CrateNum>,
// In rare case where a structure is defined in one module but implemented
// in another, if the implementing module is parsed before defining module,
pub external_typarams: FxHashMap<DefId, String>,
pub deref_trait_did: Option<DefId>,
pub deref_mut_trait_did: Option<DefId>,
+ pub owned_box_did: Option<DefId>,
}
/// Helper struct to render all source code to HTML pages
/// A type used for the search index.
struct Type {
name: Option<String>,
+ generics: Option<Vec<String>>,
}
impl ToJson for Type {
Some(ref name) => {
let mut data = BTreeMap::new();
data.insert("name".to_owned(), name.to_json());
+ if let Some(ref generics) = self.generics {
+ data.insert("generics".to_owned(), generics.to_json());
+ }
Json::Object(data)
},
None => Json::Null
"methods",
"deref-methods",
"implementations",
- ].into_iter().map(|id| (String::from(*id), 1)).collect()
+ ].into_iter().map(|id| (String::from(*id), 1)).collect()
}
/// This method resets the local table of used ID attributes. This is typically
dst: PathBuf,
passes: FxHashSet<String>,
css_file_extension: Option<PathBuf>,
- renderinfo: RenderInfo) -> Result<(), Error> {
+ renderinfo: RenderInfo,
+ render_type: RenderType) -> Result<(), Error> {
let src_root = match krate.src.parent() {
Some(p) => p.to_path_buf(),
None => PathBuf::new(),
};
let mut scx = SharedContext {
- src_root: src_root,
- passes: passes,
+ src_root,
+ passes,
include_sources: true,
local_sources: FxHashMap(),
issue_tracker_base_url: None,
krate: krate.name.clone(),
},
css_file_extension: css_file_extension.clone(),
+ markdown_warnings: RefCell::new(vec![]),
+ created_dirs: RefCell::new(FxHashSet()),
};
// If user passed in `--playground-url` arg, we fill in crate name here
}
}
}
- try_err!(mkdir(&dst), &dst);
+ try_err!(fs::create_dir_all(&dst), &dst);
krate = render_sources(&dst, &mut scx, krate)?;
let cx = Context {
current: Vec::new(),
- dst: dst,
+ dst,
render_redirect_pages: false,
shared: Arc::new(scx),
+ render_type,
};
// Crawl the crate to build various caches used for the output
external_typarams,
deref_trait_did,
deref_mut_trait_did,
+ owned_box_did,
} = renderinfo;
let external_paths = external_paths.into_iter()
let mut cache = Cache {
impls: FxHashMap(),
- external_paths: external_paths,
+ external_paths,
paths: FxHashMap(),
implementors: FxHashMap(),
stack: Vec::new(),
primitive_locations: FxHashMap(),
stripped_mod: false,
access_levels: krate.access_levels.clone(),
+ crate_version: krate.version.take(),
orphan_impl_items: Vec::new(),
traits: mem::replace(&mut krate.external_traits, FxHashMap()),
- deref_trait_did: deref_trait_did,
- deref_mut_trait_did: deref_mut_trait_did,
+ deref_trait_did,
+ deref_mut_trait_did,
+ owned_box_did,
+ masked_crates: mem::replace(&mut krate.masked_crates, FxHashSet()),
typarams: external_typarams,
};
write_shared(&cx, &krate, &*cache, index)?;
+ let scx = cx.shared.clone();
+
// And finally render the whole crate's documentation
- cx.krate(krate)
+ let result = cx.krate(krate);
+
+ let markdown_warnings = scx.markdown_warnings.borrow();
+ if !markdown_warnings.is_empty() {
+ let mut intro_msg = false;
+ for &(ref span, ref text, ref diffs) in &*markdown_warnings {
+ for d in diffs {
+ render_difference(d, &mut intro_msg, span, text);
+ }
+ }
+ }
+
+ result
+}
+
+// A short, single-line view of `s`.
+fn concise_str(mut s: &str) -> String {
+ if s.contains('\n') {
+ s = s.lines().next().expect("Impossible! We just found a newline");
+ }
+ if s.len() > 70 {
+ let mut lo = 50;
+ let mut hi = s.len() - 20;
+ while !s.is_char_boundary(lo) {
+ lo -= 1;
+ }
+ while !s.is_char_boundary(hi) {
+ hi += 1;
+ }
+ return format!("{} ... {}", &s[..lo], &s[hi..]);
+ }
+ s.to_owned()
+}
+
+// Returns short versions of s1 and s2, starting from where the strings differ.
+fn concise_compared_strs(s1: &str, s2: &str) -> (String, String) {
+ let s1 = s1.trim();
+ let s2 = s2.trim();
+ if !s1.contains('\n') && !s2.contains('\n') && s1.len() <= 70 && s2.len() <= 70 {
+ return (s1.to_owned(), s2.to_owned());
+ }
+
+ let mut start_byte = 0;
+ for (c1, c2) in s1.chars().zip(s2.chars()) {
+ if c1 != c2 {
+ break;
+ }
+
+ start_byte += c1.len_utf8();
+ }
+
+ if start_byte == 0 {
+ return (concise_str(s1), concise_str(s2));
+ }
+
+ let s1 = &s1[start_byte..];
+ let s2 = &s2[start_byte..];
+ (format!("...{}", concise_str(s1)), format!("...{}", concise_str(s2)))
+}
+
+fn print_message(msg: &str, intro_msg: &mut bool, span: &Span, text: &str) {
+ if !*intro_msg {
+ println!("WARNING: documentation for this crate may be rendered \
+ differently using the new Pulldown renderer.");
+ println!(" See https://github.com/rust-lang/rust/issues/44229 for details.");
+ *intro_msg = true;
+ }
+ println!("WARNING: rendering difference in `{}`", concise_str(text));
+ println!(" --> {}:{}:{}", span.filename, span.loline, span.locol);
+ println!("{}", msg);
+}
+
+fn render_difference(diff: &html_diff::Difference, intro_msg: &mut bool, span: &Span, text: &str) {
+ match *diff {
+ html_diff::Difference::NodeType { ref elem, ref opposite_elem } => {
+ print_message(&format!(" {} Types differ: expected: `{}`, found: `{}`",
+ elem.path, elem.element_name, opposite_elem.element_name),
+ intro_msg, span, text);
+ }
+ html_diff::Difference::NodeName { ref elem, ref opposite_elem } => {
+ print_message(&format!(" {} Tags differ: expected: `{}`, found: `{}`",
+ elem.path, elem.element_name, opposite_elem.element_name),
+ intro_msg, span, text);
+ }
+ html_diff::Difference::NodeAttributes { ref elem,
+ ref elem_attributes,
+ ref opposite_elem_attributes,
+ .. } => {
+ print_message(&format!(" {} Attributes differ in `{}`: expected: `{:?}`, \
+ found: `{:?}`",
+ elem.path, elem.element_name, elem_attributes,
+ opposite_elem_attributes),
+ intro_msg, span, text);
+ }
+ html_diff::Difference::NodeText { ref elem, ref elem_text, ref opposite_elem_text, .. } => {
+ if elem_text.split("\n")
+ .zip(opposite_elem_text.split("\n"))
+ .any(|(a, b)| a.trim() != b.trim()) {
+ let (s1, s2) = concise_compared_strs(elem_text, opposite_elem_text);
+ print_message(&format!(" {} Text differs:\n expected: `{}`\n \
+ found: `{}`",
+ elem.path, s1, s2),
+ intro_msg, span, text);
+ }
+ }
+ html_diff::Difference::NotPresent { ref elem, ref opposite_elem } => {
+ if let Some(ref elem) = *elem {
+ print_message(&format!(" {} One element is missing: expected: `{}`",
+ elem.path, elem.element_name),
+ intro_msg, span, text);
+ } else if let Some(ref elem) = *opposite_elem {
+ if elem.element_name.is_empty() {
+ print_message(&format!(" {} One element is missing: expected: `{}`",
+ elem.path, concise_str(&elem.element_content)),
+ intro_msg, span, text);
+ } else {
+ print_message(&format!(" {} Unexpected element `{}`: found: `{}`",
+ elem.path, elem.element_name,
+ concise_str(&elem.element_content)),
+ intro_msg, span, text);
+ }
+ }
+ }
+ }
}
/// Build the search index from the collected metadata
ty: item.type_(),
name: item.name.clone().unwrap(),
path: fqp[..fqp.len() - 1].join("::"),
- desc: Escape(&shorter(item.doc_value())).to_string(),
+ desc: plain_summary_line(item.doc_value()),
parent: Some(did),
parent_idx: None,
search_type: get_index_search_type(&item),
}
let crate_doc = krate.module.as_ref().map(|module| {
- Escape(&shorter(module.doc_value())).to_string()
+ plain_summary_line(module.doc_value())
}).unwrap_or(String::new());
let mut crate_data = BTreeMap::new();
// Write out the shared files. Note that these are shared among all rustdoc
// docs placed in the output directory, so this needs to be a synchronized
// operation with respect to all other rustdocs running around.
- try_err!(mkdir(&cx.dst), &cx.dst);
let _lock = flock::Lock::panicking_new(&cx.dst.join(".lock"), true, true, true);
// Add all the static files. These may already exist, but we just
// overwrite them anyway to make sure that they're fresh and up-to-date.
- write(cx.dst.join("jquery.js"),
- include_bytes!("static/jquery-2.1.4.min.js"))?;
write(cx.dst.join("main.js"),
include_bytes!("static/main.js"))?;
write(cx.dst.join("rustdoc.css"),
}
};
+ let mut have_impls = false;
let mut implementors = format!(r#"implementors["{}"] = ["#, krate.name);
for imp in imps {
// If the trait and implementation are in the same crate, then
// going on). If they're in different crates then the crate defining
// the trait will be interested in our implementation.
if imp.def_id.krate == did.krate { continue }
- write!(implementors, r#""{}","#, imp.impl_).unwrap();
+ // If the implementation is from another crate then that crate
+ // should add it.
+ if !imp.def_id.is_local() { continue }
+ have_impls = true;
+ write!(implementors, "{},", as_json(&imp.impl_.to_string())).unwrap();
}
implementors.push_str("];");
+ // Only create a js file if we have impls to add to it. If the trait is
+ // documented locally though we always create the file to avoid dead
+ // links.
+ if !have_impls && !cache.paths.contains_key(&did) {
+ continue;
+ }
+
let mut mydst = dst.clone();
for part in &remote_path[..remote_path.len() - 1] {
mydst.push(part);
fn render_sources(dst: &Path, scx: &mut SharedContext,
krate: clean::Crate) -> Result<clean::Crate, Error> {
info!("emitting source files");
- let dst = dst.join("src");
- try_err!(mkdir(&dst), &dst);
- let dst = dst.join(&krate.name);
- try_err!(mkdir(&dst), &dst);
+ let dst = dst.join("src").join(&krate.name);
+ try_err!(fs::create_dir_all(&dst), &dst);
let mut folder = SourceCollector {
- dst: dst,
- scx: scx,
+ dst,
+ scx,
};
Ok(folder.fold_crate(krate))
}
Ok(try_err!(try_err!(File::create(&dst), &dst).write_all(contents), &dst))
}
-/// Makes a directory on the filesystem, failing the thread if an error occurs
-/// and skipping if the directory already exists.
-///
-/// Note that this also handles races as rustdoc is likely to be run
-/// concurrently against another invocation.
-fn mkdir(path: &Path) -> io::Result<()> {
- match fs::create_dir(path) {
- Ok(()) => Ok(()),
- Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()),
- Err(e) => Err(e)
- }
-}
-
/// Takes a path to a source file and cleans the path to it. This canonicalizes
/// things like ".." to components which preserve the "top down" hierarchy of a
/// static HTML tree. Each component in the cleaned path will be passed as an
let mut href = String::new();
clean_srcpath(&self.scx.src_root, &p, false, |component| {
cur.push(component);
- mkdir(&cur).unwrap();
+ fs::create_dir_all(&cur).unwrap();
root_path.push_str("../");
href.push_str(component);
href.push('/');
// Collect all the implementors of traits.
if let clean::ImplItem(ref i) = item.inner {
- if let Some(did) = i.trait_.def_id() {
- self.implementors.entry(did).or_insert(vec![]).push(Implementor {
- def_id: item.def_id,
- stability: item.stability.clone(),
- impl_: i.clone(),
- });
+ if !self.masked_crates.contains(&item.def_id.krate) {
+ if let Some(did) = i.trait_.def_id() {
+ if i.for_.def_id().map_or(true, |d| !self.masked_crates.contains(&d.krate)) {
+ self.implementors.entry(did).or_insert(vec![]).push(Implementor {
+ def_id: item.def_id,
+ stability: item.stability.clone(),
+ impl_: i.clone(),
+ });
+ }
+ }
}
}
ty: item.type_(),
name: s.to_string(),
path: path.join("::").to_string(),
- desc: Escape(&shorter(item.doc_value())).to_string(),
- parent: parent,
+ desc: plain_summary_line(item.doc_value()),
+ parent,
parent_idx: None,
search_type: get_index_search_type(&item),
});
clean::FunctionItem(..) | clean::ModuleItem(..) |
clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) |
clean::ConstantItem(..) | clean::StaticItem(..) |
- clean::UnionItem(..)
+ clean::UnionItem(..) | clean::ForeignTypeItem
if !self.stripped_mod => {
// Reexported items mean that the same id can show up twice
// in the rustdoc ast that we're looking at. We know,
// Maintain the parent stack
let orig_parent_is_trait_impl = self.parent_is_trait_impl;
let parent_pushed = match item.inner {
- clean::TraitItem(..) | clean::EnumItem(..) |
+ clean::TraitItem(..) | clean::EnumItem(..) | clean::ForeignTypeItem |
clean::StructItem(..) | clean::UnionItem(..) => {
self.parent_stack.push(item.def_id);
self.parent_is_trait_impl = false;
// Figure out the id of this impl. This may map to a
// primitive rather than always to a struct/enum.
// Note: matching twice to restrict the lifetime of the `i` borrow.
- let did = if let clean::Item { inner: clean::ImplItem(ref i), .. } = item {
- match i.for_ {
- clean::ResolvedPath { did, .. } |
- clean::BorrowedRef {
- type_: box clean::ResolvedPath { did, .. }, ..
- } => {
- Some(did)
+ let mut dids = FxHashSet();
+ if let clean::Item { inner: clean::ImplItem(ref i), .. } = item {
+ let masked_trait = i.trait_.def_id().map_or(false,
+ |d| self.masked_crates.contains(&d.krate));
+ if !masked_trait {
+ match i.for_ {
+ clean::ResolvedPath { did, .. } |
+ clean::BorrowedRef {
+ type_: box clean::ResolvedPath { did, .. }, ..
+ } => {
+ dids.insert(did);
+ }
+ ref t => {
+ let did = t.primitive_type().and_then(|t| {
+ self.primitive_locations.get(&t).cloned()
+ });
+
+ if let Some(did) = did {
+ dids.insert(did);
+ }
+ }
}
- ref t => {
- t.primitive_type().and_then(|t| {
- self.primitive_locations.get(&t).cloned()
- })
+ }
+
+ if let Some(generics) = i.trait_.as_ref().and_then(|t| t.generics()) {
+ for bound in generics {
+ if let Some(did) = bound.def_id() {
+ dids.insert(did);
+ }
}
}
} else {
unreachable!()
};
- if let Some(did) = did {
+ for did in dids {
self.impls.entry(did).or_insert(vec![]).push(Impl {
- impl_item: item,
+ impl_item: item.clone(),
});
}
None
// these modules are recursed into, but not rendered normally
// (a flag on the context).
if !self.render_redirect_pages {
- self.render_redirect_pages = maybe_ignore_item(&item);
+ self.render_redirect_pages = item.is_stripped();
}
if item.is_mod() {
this.render_item(&mut buf, &item, false).unwrap();
// buf will be empty if the module is stripped and there is no redirect for it
if !buf.is_empty() {
+ try_err!(this.shared.ensure_dir(&this.dst), &this.dst);
let joint_dst = this.dst.join("index.html");
- try_err!(fs::create_dir_all(&this.dst), &this.dst);
let mut dst = try_err!(File::create(&joint_dst), &joint_dst);
try_err!(dst.write_all(&buf), &joint_dst);
}
let name = item.name.as_ref().unwrap();
let item_type = item.type_();
let file_name = &item_path(item_type, name);
+ try_err!(self.shared.ensure_dir(&self.dst), &self.dst);
let joint_dst = self.dst.join(file_name);
- try_err!(fs::create_dir_all(&self.dst), &self.dst);
let mut dst = try_err!(File::create(&joint_dst), &joint_dst);
try_err!(dst.write_all(&buf), &joint_dst);
// URL for the page.
let redir_name = format!("{}.{}.html", name, item_type.name_space());
let redir_dst = self.dst.join(redir_name);
- if let Ok(mut redirect_out) = OpenOptions::new().create_new(true)
+ if let Ok(redirect_out) = OpenOptions::new().create_new(true)
.write(true)
.open(&redir_dst) {
+ let mut redirect_out = BufWriter::new(redirect_out);
try_err!(layout::redirect(&mut redirect_out, file_name), &redir_dst);
}
if item_type == ItemType::Macro {
let redir_name = format!("{}.{}!.html", item_type, name);
let redir_dst = self.dst.join(redir_name);
- let mut redirect_out = try_err!(File::create(&redir_dst), &redir_dst);
+ let redirect_out = try_err!(File::create(&redir_dst), &redir_dst);
+ let mut redirect_out = BufWriter::new(redirect_out);
try_err!(layout::redirect(&mut redirect_out, file_name), &redir_dst);
}
}
// BTreeMap instead of HashMap to get a sorted output
let mut map = BTreeMap::new();
for item in &m.items {
- if maybe_ignore_item(item) { continue }
+ if item.is_stripped() { continue }
let short = item.type_().css_class();
let myname = match item.name {
format!("{}-{}", self.item.source.loline, self.item.source.hiline)
};
Some(format!("{root}src/{krate}/{path}#{lines}",
- root = root,
+ root = Escape(&root),
krate = krate,
path = path,
lines = lines))
} else {
write!(fmt, "Module ")?;
},
- clean::FunctionItem(..) | clean::ForeignFunctionItem(..) =>
- write!(fmt, "Function ")?,
+ clean::FunctionItem(..) | clean::ForeignFunctionItem(..) => write!(fmt, "Function ")?,
clean::TraitItem(..) => write!(fmt, "Trait ")?,
clean::StructItem(..) => write!(fmt, "Struct ")?,
clean::UnionItem(..) => write!(fmt, "Union ")?,
clean::TypedefItem(..) => write!(fmt, "Type Definition ")?,
clean::MacroItem(..) => write!(fmt, "Macro ")?,
clean::PrimitiveItem(..) => write!(fmt, "Primitive Type ")?,
- clean::StaticItem(..) | clean::ForeignStaticItem(..) =>
- write!(fmt, "Static ")?,
+ clean::StaticItem(..) | clean::ForeignStaticItem(..) => write!(fmt, "Static ")?,
clean::ConstantItem(..) => write!(fmt, "Constant ")?,
+ clean::ForeignTypeItem => write!(fmt, "Foreign Type ")?,
_ => {
// We don't generate pages for any other type.
unreachable!();
component)?;
}
}
- write!(fmt, "<a class='{}' href=''>{}</a>",
+ write!(fmt, "<a class=\"{}\" href=''>{}</a>",
self.item.type_(), self.item.name.as_ref().unwrap())?;
write!(fmt, "</span>")?; // in-band
clean::StaticItem(ref i) | clean::ForeignStaticItem(ref i) =>
item_static(fmt, self.cx, self.item, i),
clean::ConstantItem(ref c) => item_constant(fmt, self.cx, self.item, c),
+ clean::ForeignTypeItem => item_foreign_type(fmt, self.cx, self.item),
_ => {
// We don't generate pages for any other type.
unreachable!();
fn document(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item) -> fmt::Result {
document_stability(w, cx, item)?;
- document_full(w, item)?;
+ let prefix = render_assoc_const_value(item);
+ document_full(w, item, cx, &prefix)?;
Ok(())
}
-fn document_short(w: &mut fmt::Formatter, item: &clean::Item, link: AssocItemLink) -> fmt::Result {
+/// Render md_text as markdown. Warns the user if there are difference in
+/// rendering between Pulldown and Hoedown.
+fn render_markdown(w: &mut fmt::Formatter,
+ md_text: &str,
+ span: Span,
+ render_type: RenderType,
+ prefix: &str,
+ scx: &SharedContext)
+ -> fmt::Result {
+ // Save the state of USED_ID_MAP so it only gets updated once even
+ // though we're rendering twice.
+ let orig_used_id_map = USED_ID_MAP.with(|map| map.borrow().clone());
+ let hoedown_output = format!("{}", Markdown(md_text, RenderType::Hoedown));
+ USED_ID_MAP.with(|map| *map.borrow_mut() = orig_used_id_map);
+ let pulldown_output = format!("{}", Markdown(md_text, RenderType::Pulldown));
+ let mut differences = html_diff::get_differences(&pulldown_output, &hoedown_output);
+ differences.retain(|s| {
+ match *s {
+ html_diff::Difference::NodeText { ref elem_text,
+ ref opposite_elem_text,
+ .. }
+ if elem_text.split_whitespace().eq(opposite_elem_text.split_whitespace()) => {
+ false
+ }
+ _ => true,
+ }
+ });
+
+ if !differences.is_empty() {
+ scx.markdown_warnings.borrow_mut().push((span, md_text.to_owned(), differences));
+ }
+
+ write!(w, "<div class='docblock'>{}{}</div>",
+ prefix,
+ if render_type == RenderType::Pulldown { pulldown_output } else { hoedown_output })
+}
+
+fn document_short(w: &mut fmt::Formatter, item: &clean::Item, link: AssocItemLink,
+ cx: &Context, prefix: &str) -> fmt::Result {
if let Some(s) = item.doc_value() {
let markdown = if s.contains('\n') {
format!("{} [Read more]({})",
} else {
format!("{}", &plain_summary_line(Some(s)))
};
- write!(w, "<div class='docblock'>{}</div>", Markdown(&markdown))?;
+ render_markdown(w, &markdown, item.source.clone(), cx.render_type, prefix, &cx.shared)?;
+ } else if !prefix.is_empty() {
+ write!(w, "<div class='docblock'>{}</div>", prefix)?;
}
Ok(())
}
-fn document_full(w: &mut fmt::Formatter, item: &clean::Item) -> fmt::Result {
+fn render_assoc_const_value(item: &clean::Item) -> String {
+ match item.inner {
+ clean::AssociatedConstItem(ref ty, Some(ref default)) => {
+ highlight::render_with_highlighting(
+ &format!("{}: {:#} = {}", item.name.as_ref().unwrap(), ty, default),
+ None,
+ None,
+ None,
+ None,
+ )
+ }
+ _ => String::new(),
+ }
+}
+
+fn document_full(w: &mut fmt::Formatter, item: &clean::Item,
+ cx: &Context, prefix: &str) -> fmt::Result {
if let Some(s) = item.doc_value() {
- write!(w, "<div class='docblock'>{}</div>", Markdown(s))?;
+ render_markdown(w, s, item.source.clone(), cx.render_type, prefix, &cx.shared)?;
+ } else if !prefix.is_empty() {
+ write!(w, "<div class='docblock'>{}</div>", prefix)?;
}
Ok(())
}
fn document_stability(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item) -> fmt::Result {
- for stability in short_stability(item, cx, true) {
- write!(w, "<div class='stability'>{}</div>", stability)?;
+ let stabilities = short_stability(item, cx, true);
+ if !stabilities.is_empty() {
+ write!(w, "<div class='stability'>")?;
+ for stability in stabilities {
+ write!(w, "{}", stability)?;
+ }
+ write!(w, "</div>")?;
}
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)?;
let mut indices = (0..items.len()).filter(|i| {
- if let clean::DefaultImplItem(..) = items[*i].inner {
+ if let clean::AutoImplItem(..) = items[*i].inner {
return false;
}
- !maybe_ignore_item(&items[*i])
+ !items[*i].is_stripped()
}).collect::<Vec<usize>>();
// the order of item types in the listing
(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));
+ // This call is to remove reexport duplicates in cases such as:
+ //
+ // ```
+ // pub mod foo {
+ // pub mod bar {
+ // pub trait Double { fn foo(); }
+ // }
+ // }
+ //
+ // pub use foo::bar::*;
+ // pub use foo::*;
+ // ```
+ //
+ // `Double` will appear twice in the generated docs.
+ //
+ // FIXME: This code is quite ugly and could be improved. Small issue: DefId
+ // can be identical even if the elements are different (mostly in imports).
+ // So in case this is an import, we keep everything by adding a "unique id"
+ // (which is the position in the vector).
+ indices.dedup_by_key(|i| (items[*i].def_id,
+ if items[*i].name.as_ref().is_some() {
+ Some(full_path(cx, &items[*i]).clone())
+ } else {
+ None
+ },
+ items[*i].type_(),
+ if items[*i].is_import() {
+ *i
+ } else {
+ 0
+ }));
debug!("{:?}", indices);
let mut curty = None;
ItemType::Primitive => ("primitives", "Primitive Types"),
ItemType::AssociatedType => ("associated-types", "Associated Types"),
ItemType::AssociatedConst => ("associated-consts", "Associated Constants"),
+ ItemType::ForeignType => ("foreign-types", "Foreign Types"),
};
write!(w, "<h2 id='{id}' class='section-header'>\
<a href=\"#{id}\">{name}</a></h2>\n<table>",
String::new()
};
- let mut unsafety_flag = "";
- if let clean::FunctionItem(ref func) = myitem.inner {
- if func.unsafety == hir::Unsafety::Unsafe {
- unsafety_flag = "<a title='unsafe function' href='#'><sup>âš </sup></a>";
+ let unsafety_flag = match myitem.inner {
+ clean::FunctionItem(ref func) | clean::ForeignFunctionItem(ref func)
+ if func.unsafety == hir::Unsafety::Unsafe => {
+ "<a title='unsafe function' href='#'><sup>âš </sup></a>"
}
- }
+ _ => "",
+ };
let doc_value = myitem.doc_value().unwrap_or("");
write!(w, "
<tr class='{stab} module-item'>
- <td><a class='{class}' href='{href}'
- title='{title}'>{name}</a>{unsafety_flag}</td>
+ <td><a class=\"{class}\" href=\"{href}\"
+ title='{title_type} {title}'>{name}</a>{unsafety_flag}</td>
<td class='docblock-short'>
{stab_docs} {docs}
</td>
</tr>",
name = *myitem.name.as_ref().unwrap(),
stab_docs = stab_docs,
- docs = shorter(Some(&Markdown(doc_value).to_string())),
+ docs = if cx.render_type == RenderType::Hoedown {
+ format!("{}",
+ shorter(Some(&Markdown(doc_value,
+ RenderType::Hoedown).to_string())))
+ } else {
+ format!("{}", MarkdownSummaryLine(doc_value))
+ },
class = myitem.type_(),
- stab = myitem.stability_class(),
+ stab = myitem.stability_class().unwrap_or("".to_string()),
unsafety_flag = unsafety_flag,
href = item_path(myitem.type_(), myitem.name.as_ref().unwrap()),
+ title_type = myitem.type_(),
title = full_path(cx, myitem))?;
}
}
Ok(())
}
-fn maybe_ignore_item(it: &clean::Item) -> bool {
- match it.inner {
- clean::StrippedItem(..) => true,
- clean::ModuleItem(ref m) => {
- it.doc_value().is_none() && m.items.is_empty()
- && it.visibility != Some(clean::Public)
- },
- _ => false,
- }
-}
-
fn short_stability(item: &clean::Item, cx: &Context, show_reason: bool) -> Vec<String> {
let mut stability = vec![];
} else {
String::new()
};
- let text = format!("Deprecated{}{}", since, Markdown(&deprecated_reason));
- stability.push(format!("<em class='stab deprecated'>{}</em>", text))
+ let text = format!("Deprecated{}{}",
+ since,
+ MarkdownHtml(&deprecated_reason, cx.render_type));
+ stability.push(format!("<div class='stab deprecated'>{}</div>", text))
};
if stab.level == stability::Unstable {
- let unstable_extra = if show_reason {
- match (!stab.feature.is_empty(), &cx.shared.issue_tracker_base_url, stab.issue) {
+ if show_reason {
+ let unstable_extra = match (!stab.feature.is_empty(),
+ &cx.shared.issue_tracker_base_url,
+ stab.issue) {
(true, &Some(ref tracker_url), Some(issue_no)) if issue_no > 0 =>
- format!(" (<code>{}</code> <a href=\"{}{}\">#{}</a>)",
+ format!(" (<code>{} </code><a href=\"{}{}\">#{}</a>)",
Escape(&stab.feature), tracker_url, issue_no, issue_no),
(false, &Some(ref tracker_url), Some(issue_no)) if issue_no > 0 =>
format!(" (<a href=\"{}{}\">#{}</a>)", Escape(&tracker_url), issue_no,
(true, ..) =>
format!(" (<code>{}</code>)", Escape(&stab.feature)),
_ => String::new(),
+ };
+ if stab.unstable_reason.is_empty() {
+ stability.push(format!("<div class='stab unstable'>\
+ <span class=microscope>🔬</span> \
+ This is a nightly-only experimental API. {}\
+ </div>",
+ unstable_extra));
+ } else {
+ let text = format!("<summary><span class=microscope>🔬</span> \
+ This is a nightly-only experimental API. {}\
+ </summary>{}",
+ unstable_extra,
+ MarkdownHtml(&stab.unstable_reason, cx.render_type));
+ stability.push(format!("<div class='stab unstable'><details>{}</details></div>",
+ text));
}
} else {
- String::new()
- };
- let unstable_reason = if show_reason && !stab.unstable_reason.is_empty() {
- format!(": {}", stab.unstable_reason)
- } else {
- String::new()
- };
- let text = format!("Unstable{}{}", unstable_extra, Markdown(&unstable_reason));
- stability.push(format!("<em class='stab unstable'>{}</em>", text))
+ stability.push(format!("<div class='stab unstable'>Experimental</div>"))
+ }
};
} else if let Some(depr) = item.deprecation.as_ref() {
let note = if show_reason && !depr.note.is_empty() {
String::new()
};
- let text = format!("Deprecated{}{}", since, Markdown(¬e));
- stability.push(format!("<em class='stab deprecated'>{}</em>", text))
+ let text = format!("Deprecated{}{}", since, MarkdownHtml(¬e, cx.render_type));
+ stability.push(format!("<div class='stab deprecated'>{}</div>", text))
+ }
+
+ if let Some(ref cfg) = item.attrs.cfg {
+ stability.push(format!("<div class='stab portability'>{}</div>", if show_reason {
+ cfg.render_long_html()
+ } else {
+ cfg.render_short_html()
+ }));
}
stability
fn item_constant(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
c: &clean::Constant) -> fmt::Result {
- write!(w, "<pre class='rust const'>{vis}const \
+ write!(w, "<pre class='rust const'>")?;
+ render_attributes(w, it)?;
+ write!(w, "{vis}const \
{name}: {typ}{init}</pre>",
vis = VisSpace(&it.visibility),
name = it.name.as_ref().unwrap(),
fn item_static(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
s: &clean::Static) -> fmt::Result {
- write!(w, "<pre class='rust static'>{vis}static {mutability}\
+ write!(w, "<pre class='rust static'>")?;
+ render_attributes(w, it)?;
+ write!(w, "{vis}static {mutability}\
{name}: {typ}{init}</pre>",
vis = VisSpace(&it.visibility),
mutability = MutableSpace(s.mutability),
fn item_function(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
f: &clean::Function) -> fmt::Result {
- // FIXME(#24111): remove when `const_fn` is stabilized
- let vis_constness = match UnstableFeatures::from_environment() {
- UnstableFeatures::Allow => f.constness,
- _ => hir::Constness::NotConst
- };
- let indent = format!("{}{}{}{:#}fn {}{:#}",
- VisSpace(&it.visibility),
- ConstnessSpace(vis_constness),
- UnsafetySpace(f.unsafety),
- AbiSpace(f.abi),
- it.name.as_ref().unwrap(),
- f.generics).len();
- write!(w, "<pre class='rust fn'>{vis}{constness}{unsafety}{abi}fn \
+ let name_len = format!("{}{}{}{:#}fn {}{:#}",
+ VisSpace(&it.visibility),
+ ConstnessSpace(f.constness),
+ UnsafetySpace(f.unsafety),
+ AbiSpace(f.abi),
+ it.name.as_ref().unwrap(),
+ f.generics).len();
+ write!(w, "<pre class='rust fn'>")?;
+ render_attributes(w, it)?;
+ write!(w, "{vis}{constness}{unsafety}{abi}fn \
{name}{generics}{decl}{where_clause}</pre>",
vis = VisSpace(&it.visibility),
- constness = ConstnessSpace(vis_constness),
+ constness = ConstnessSpace(f.constness),
unsafety = UnsafetySpace(f.unsafety),
abi = AbiSpace(f.abi),
name = it.name.as_ref().unwrap(),
generics = f.generics,
- where_clause = WhereClause(&f.generics, 2),
- decl = Method(&f.decl, indent))?;
+ where_clause = WhereClause { gens: &f.generics, indent: 0, end_newline: true },
+ decl = Method {
+ decl: &f.decl,
+ name_len,
+ indent: 0,
+ })?;
document(w, cx, it)
}
+fn implementor2item<'a>(cache: &'a Cache, imp : &Implementor) -> Option<&'a clean::Item> {
+ if let Some(t_did) = imp.impl_.for_.def_id() {
+ if let Some(impl_item) = cache.impls.get(&t_did).and_then(|i| i.iter()
+ .find(|i| i.impl_item.def_id == imp.def_id))
+ {
+ let i = &impl_item.impl_item;
+ return Some(i);
+ }
+ }
+ None
+}
+
fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
t: &clean::Trait) -> fmt::Result {
let mut bounds = String::new();
}
// Output the trait definition
- write!(w, "<pre class='rust trait'>{}{}trait {}{}{}{} ",
+ write!(w, "<pre class='rust trait'>")?;
+ render_attributes(w, it)?;
+ write!(w, "{}{}trait {}{}{}",
VisSpace(&it.visibility),
UnsafetySpace(t.unsafety),
it.name.as_ref().unwrap(),
t.generics,
- bounds,
- // Where clauses in traits are indented nine spaces, per rustdoc.css
- WhereClause(&t.generics, 9))?;
+ bounds)?;
+
+ if !t.generics.where_predicates.is_empty() {
+ write!(w, "{}", WhereClause { gens: &t.generics, indent: 0, end_newline: true })?;
+ } else {
+ write!(w, " ")?;
+ }
let types = t.items.iter().filter(|m| m.is_associated_type()).collect::<Vec<_>>();
let consts = t.items.iter().filter(|m| m.is_associated_const()).collect::<Vec<_>>();
if !consts.is_empty() && !required.is_empty() {
w.write_str("\n")?;
}
- for m in &required {
+ for (pos, m) in required.iter().enumerate() {
write!(w, " ")?;
render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait)?;
write!(w, ";\n")?;
+
+ if pos < required.len() - 1 {
+ write!(w, "<div class='item-spacer'></div>")?;
+ }
}
if !required.is_empty() && !provided.is_empty() {
w.write_str("\n")?;
}
- for m in &provided {
+ for (pos, m) in provided.iter().enumerate() {
write!(w, " ")?;
render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait)?;
- write!(w, " {{ ... }}\n")?;
+ match m.inner {
+ clean::MethodItem(ref inner) if !inner.generics.where_predicates.is_empty() => {
+ write!(w, ",\n {{ ... }}\n")?;
+ },
+ _ => {
+ write!(w, " {{ ... }}\n")?;
+ },
+ }
+ if pos < provided.len() - 1 {
+ write!(w, "<div class='item-spacer'></div>")?;
+ }
}
write!(w, "}}")?;
}
let item_type = m.type_();
let id = derive_id(format!("{}.{}", item_type, name));
let ns_id = derive_id(format!("{}.{}", name, item_type.name_space()));
- write!(w, "<h3 id='{id}' class='method stab {stab}'>\
+ write!(w, "<h3 id='{id}' class='method'>\
<span id='{ns_id}' class='invisible'><code>",
id = id,
- stab = m.stability_class(),
ns_id = ns_id)?;
render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl)?;
write!(w, "</code>")?;
if !types.is_empty() {
write!(w, "
- <h2 id='associated-types'>Associated Types</h2>
+ <h2 id='associated-types' class='small-section-header'>
+ Associated Types<a href='#associated-types' class='anchor'></a>
+ </h2>
<div class='methods'>
")?;
for t in &types {
if !consts.is_empty() {
write!(w, "
- <h2 id='associated-const'>Associated Constants</h2>
+ <h2 id='associated-const' class='small-section-header'>
+ Associated Constants<a href='#associated-const' class='anchor'></a>
+ </h2>
<div class='methods'>
")?;
for t in &consts {
// Output the documentation for each function individually
if !required.is_empty() {
write!(w, "
- <h2 id='required-methods'>Required Methods</h2>
+ <h2 id='required-methods' class='small-section-header'>
+ Required Methods<a href='#required-methods' class='anchor'></a>
+ </h2>
<div class='methods'>
")?;
for m in &required {
}
if !provided.is_empty() {
write!(w, "
- <h2 id='provided-methods'>Provided Methods</h2>
+ <h2 id='provided-methods' class='small-section-header'>
+ Provided Methods<a href='#provided-methods' class='anchor'></a>
+ </h2>
<div class='methods'>
")?;
for m in &provided {
render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)?;
let cache = cache();
- write!(w, "
- <h2 id='implementors'>Implementors</h2>
+ let impl_header = "
+ <h2 id='implementors' class='small-section-header'>
+ Implementors<a href='#implementors' class='anchor'></a>
+ </h2>
<ul class='item-list' id='implementors-list'>
- ")?;
+ ";
if let Some(implementors) = cache.implementors.get(&it.def_id) {
- for i in implementors {
- write!(w, "<li><code>")?;
- fmt_impl_for_trait_page(&i.impl_, w)?;
+ // The DefId is for the first Type found with that name. The bool is
+ // if any Types with the same name but different DefId have been found.
+ let mut implementor_dups: FxHashMap<&str, (DefId, bool)> = FxHashMap();
+ for implementor in implementors {
+ match implementor.impl_.for_ {
+ clean::ResolvedPath { ref path, did, is_generic: false, .. } |
+ clean::BorrowedRef {
+ type_: box clean::ResolvedPath { ref path, did, is_generic: false, .. },
+ ..
+ } => {
+ let &mut (prev_did, ref mut has_duplicates) =
+ implementor_dups.entry(path.last_name()).or_insert((did, false));
+ if prev_did != did {
+ *has_duplicates = true;
+ }
+ }
+ _ => {}
+ }
+ }
+
+ let (local, foreign) = implementors.iter()
+ .partition::<Vec<_>, _>(|i| i.impl_.for_.def_id()
+ .map_or(true, |d| cache.paths.contains_key(&d)));
+
+ if !foreign.is_empty() {
+ write!(w, "
+ <h2 id='foreign-impls' class='small-section-header'>
+ Implementations on Foreign Types<a href='#foreign-impls' class='anchor'></a>
+ </h2>
+ ")?;
+
+ for implementor in foreign {
+ if let Some(i) = implementor2item(&cache, implementor) {
+ let impl_ = Impl { impl_item: i.clone() };
+ let assoc_link = AssocItemLink::GotoSource(
+ i.def_id, &implementor.impl_.provided_trait_methods
+ );
+ render_impl(w, cx, &impl_, assoc_link,
+ RenderMode::Normal, i.stable_since(), false)?;
+ }
+ }
+ }
+
+ write!(w, "{}", impl_header)?;
+
+ for implementor in local {
+ write!(w, "<li>")?;
+ if let Some(item) = implementor2item(&cache, implementor) {
+ if let Some(l) = (Item { cx, item }).src_href() {
+ write!(w, "<div class='out-of-band'>")?;
+ write!(w, "<a class='srclink' href='{}' title='{}'>[src]</a>",
+ l, "goto source code")?;
+ write!(w, "</div>")?;
+ }
+ }
+ write!(w, "<code>")?;
+ // If there's already another implementor that has the same abbridged name, use the
+ // full path, for example in `std::iter::ExactSizeIterator`
+ let use_absolute = match implementor.impl_.for_ {
+ clean::ResolvedPath { ref path, is_generic: false, .. } |
+ clean::BorrowedRef {
+ type_: box clean::ResolvedPath { ref path, is_generic: false, .. },
+ ..
+ } => implementor_dups[path.last_name()].1,
+ _ => false,
+ };
+ fmt_impl_for_trait_page(&implementor.impl_, w, use_absolute)?;
+ for it in &implementor.impl_.items {
+ if let clean::TypedefItem(ref tydef, _) = it.inner {
+ write!(w, "<span class=\"where fmt-newline\"> ")?;
+ assoc_type(w, it, &vec![], Some(&tydef.type_), AssocItemLink::Anchor(None))?;
+ write!(w, ";</span>")?;
+ }
+ }
writeln!(w, "</code></li>")?;
}
+ } else {
+ // even without any implementations to write in, we still want the heading and list, so the
+ // implementors javascript file pulled in below has somewhere to write the impls into
+ write!(w, "{}", impl_header)?;
}
write!(w, "</ul>")?;
write!(w, r#"<script type="text/javascript" async
fn assoc_const(w: &mut fmt::Formatter,
it: &clean::Item,
ty: &clean::Type,
- default: Option<&String>,
+ _default: Option<&String>,
link: AssocItemLink) -> fmt::Result {
- write!(w, "const <a href='{}' class='constant'>{}</a>",
+ write!(w, "const <a href='{}' class=\"constant\"><b>{}</b></a>: {}",
naive_assoc_href(it, link),
- it.name.as_ref().unwrap())?;
-
- write!(w, ": {}", ty)?;
- if let Some(default) = default {
- write!(w, " = {}", Escape(default))?;
- }
+ it.name.as_ref().unwrap(),
+ ty)?;
Ok(())
}
bounds: &Vec<clean::TyParamBound>,
default: Option<&clean::Type>,
link: AssocItemLink) -> fmt::Result {
- write!(w, "type <a href='{}' class='type'>{}</a>",
+ write!(w, "type <a href='{}' class=\"type\">{}</a>",
naive_assoc_href(it, link),
it.name.as_ref().unwrap())?;
if !bounds.is_empty() {
href(did).map(|p| format!("{}#{}.{}", p.0, ty, name)).unwrap_or(anchor)
}
};
- // FIXME(#24111): remove when `const_fn` is stabilized
- let vis_constness = match UnstableFeatures::from_environment() {
- UnstableFeatures::Allow => constness,
- _ => hir::Constness::NotConst
- };
- let prefix = format!("{}{}{:#}fn {}{:#}",
- ConstnessSpace(vis_constness),
- UnsafetySpace(unsafety),
- AbiSpace(abi),
- name,
- *g);
- let mut indent = prefix.len();
- let where_indent = if parent == ItemType::Trait {
- indent += 4;
- 8
- } else if parent == ItemType::Impl {
- 2
+ let mut head_len = format!("{}{}{}{:#}fn {}{:#}",
+ VisSpace(&meth.visibility),
+ ConstnessSpace(constness),
+ UnsafetySpace(unsafety),
+ AbiSpace(abi),
+ name,
+ *g).len();
+ let (indent, end_newline) = if parent == ItemType::Trait {
+ head_len += 4;
+ (4, false)
} else {
- let prefix = prefix + &format!("{:#}", Method(d, indent));
- prefix.lines().last().unwrap().len() + 1
+ (0, true)
};
- write!(w, "{}{}{}fn <a href='{href}' class='fnname'>{name}</a>\
+ write!(w, "{}{}{}{}fn <a href='{href}' class='fnname'>{name}</a>\
{generics}{decl}{where_clause}",
- ConstnessSpace(vis_constness),
+ VisSpace(&meth.visibility),
+ ConstnessSpace(constness),
UnsafetySpace(unsafety),
AbiSpace(abi),
href = href,
name = name,
generics = *g,
- decl = Method(d, indent),
- where_clause = WhereClause(g, where_indent))
+ decl = Method {
+ decl: d,
+ name_len: head_len,
+ indent,
+ },
+ where_clause = WhereClause {
+ gens: g,
+ indent,
+ end_newline,
+ })
}
match item.inner {
clean::StrippedItem(..) => Ok(()),
}).peekable();
if let doctree::Plain = s.struct_type {
if fields.peek().is_some() {
- write!(w, "<h2 class='fields'>Fields</h2>")?;
+ write!(w, "<h2 id='fields' class='fields small-section-header'>
+ Fields<a href='#fields' class='anchor'></a></h2>")?;
for (field, ty) in fields {
let id = derive_id(format!("{}.{}",
ItemType::StructField,
let ns_id = derive_id(format!("{}.{}",
field.name.as_ref().unwrap(),
ItemType::StructField.name_space()));
- write!(w, "<span id='{id}' class='{item_type}'>
- <span id='{ns_id}' class='invisible'>
+ write!(w, "<span id=\"{id}\" class=\"{item_type} small-section-header\">
+ <a href=\"#{id}\" class=\"anchor field\"></a>
+ <span id=\"{ns_id}\" class='invisible'>
<code>{name}: {ty}</code>
- </span></span><span class='stab {stab}'></span>",
+ </span></span>",
item_type = ItemType::StructField,
id = id,
ns_id = ns_id,
- stab = field.stability_class(),
name = field.name.as_ref().unwrap(),
ty = ty)?;
+ if let Some(stability_class) = field.stability_class() {
+ write!(w, "<span class='stab {stab}'></span>",
+ stab = stability_class)?;
+ }
document(w, cx, field)?;
}
}
}
}).peekable();
if fields.peek().is_some() {
- write!(w, "<h2 class='fields'>Fields</h2>")?;
+ write!(w, "<h2 id='fields' class='fields small-section-header'>
+ Fields<a href='#fields' class='anchor'></a></h2>")?;
for (field, ty) in fields {
- write!(w, "<span id='{shortty}.{name}' class='{shortty}'><code>{name}: {ty}</code>
- </span><span class='stab {stab}'></span>",
+ write!(w, "<span id='{shortty}.{name}' class=\"{shortty}\"><code>{name}: {ty}</code>
+ </span>",
shortty = ItemType::StructField,
- stab = field.stability_class(),
name = field.name.as_ref().unwrap(),
ty = ty)?;
+ if let Some(stability_class) = field.stability_class() {
+ write!(w, "<span class='stab {stab}'></span>",
+ stab = stability_class)?;
+ }
document(w, cx, field)?;
}
}
e: &clean::Enum) -> fmt::Result {
write!(w, "<pre class='rust enum'>")?;
render_attributes(w, it)?;
- let padding = format!("{}enum {}{:#} ",
- VisSpace(&it.visibility),
- it.name.as_ref().unwrap(),
- e.generics).len();
write!(w, "{}enum {}{}{}",
VisSpace(&it.visibility),
it.name.as_ref().unwrap(),
e.generics,
- WhereClause(&e.generics, padding))?;
+ WhereClause { gens: &e.generics, indent: 0, end_newline: true })?;
if e.variants.is_empty() && !e.variants_stripped {
write!(w, " {{}}")?;
} else {
document(w, cx, it)?;
if !e.variants.is_empty() {
- write!(w, "<h2 class='variants'>Variants</h2>\n")?;
+ write!(w, "<h2 id='variants' class='variants small-section-header'>
+ Variants<a href='#variants' class='anchor'></a></h2>\n")?;
for variant in &e.variants {
let id = derive_id(format!("{}.{}",
ItemType::Variant,
let ns_id = derive_id(format!("{}.{}",
variant.name.as_ref().unwrap(),
ItemType::Variant.name_space()));
- write!(w, "<span id='{id}' class='variant'>\
+ write!(w, "<span id=\"{id}\" class=\"variant small-section-header\">\
+ <a href=\"#{id}\" class=\"anchor field\"></a>\
<span id='{ns_id}' class='invisible'><code>{name}",
id = id,
ns_id = ns_id,
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))
let mut attrs = String::new();
for attr in &it.attrs.other_attrs {
- let name = attr.name();
- if !ATTRIBUTE_WHITELIST.contains(&&name.as_str()[..]) {
+ let name = attr.name().unwrap();
+ if !ATTRIBUTE_WHITELIST.contains(&&*name.as_str()) {
continue;
}
- if let Some(s) = render_attribute(attr.meta()) {
+ if let Some(s) = render_attribute(&attr.meta().unwrap()) {
attrs.push_str(&format!("#[{}]\n", s));
}
}
fields: &[clean::Item],
tab: &str,
structhead: bool) -> fmt::Result {
- let mut plain = String::new();
write!(w, "{}{}{}",
VisSpace(&it.visibility),
if structhead {"struct "} else {""},
it.name.as_ref().unwrap())?;
- plain.push_str(&format!("{}{}{}",
- VisSpace(&it.visibility),
- if structhead {"struct "} else {""},
- it.name.as_ref().unwrap()));
if let Some(g) = g {
- plain.push_str(&format!("{:#}", g));
write!(w, "{}", g)?
}
match ty {
doctree::Plain => {
if let Some(g) = g {
- write!(w, "{}", WhereClause(g, plain.len() + 1))?
+ write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true })?
}
let mut has_visible_fields = false;
write!(w, " {{")?;
}
doctree::Tuple => {
write!(w, "(")?;
- plain.push_str("(");
for (i, field) in fields.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
- plain.push_str(", ");
}
match field.inner {
clean::StrippedItem(box clean::StructFieldItem(..)) => {
- plain.push_str("_");
write!(w, "_")?
}
clean::StructFieldItem(ref ty) => {
- plain.push_str(&format!("{}{:#}", VisSpace(&field.visibility), *ty));
write!(w, "{}{}", VisSpace(&field.visibility), *ty)?
}
_ => unreachable!()
}
}
write!(w, ")")?;
- plain.push_str(")");
if let Some(g) = g {
- write!(w, "{}", WhereClause(g, plain.len() + 1))?
+ write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: false })?
}
write!(w, ";")?;
}
doctree::Unit => {
// Needed for PhantomData.
if let Some(g) = g {
- write!(w, "{}", WhereClause(g, plain.len() + 1))?
+ write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: false })?
}
write!(w, ";")?;
}
fields: &[clean::Item],
tab: &str,
structhead: bool) -> fmt::Result {
- let mut plain = String::new();
write!(w, "{}{}{}",
VisSpace(&it.visibility),
if structhead {"union "} else {""},
it.name.as_ref().unwrap())?;
- plain.push_str(&format!("{}{}{}",
- VisSpace(&it.visibility),
- if structhead {"union "} else {""},
- it.name.as_ref().unwrap()));
if let Some(g) = g {
write!(w, "{}", g)?;
- plain.push_str(&format!("{:#}", g));
- write!(w, "{}", WhereClause(g, plain.len() + 1))?;
+ write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true })?;
}
write!(w, " {{\n{}", tab)?;
if !non_trait.is_empty() {
let render_mode = match what {
AssocItemRender::All => {
- write!(w, "<h2 id='methods'>Methods</h2>")?;
+ write!(w, "
+ <h2 id='methods' class='small-section-header'>
+ Methods<a href='#methods' class='anchor'></a>
+ </h2>
+ ")?;
RenderMode::Normal
}
AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => {
- write!(w, "<h2 id='deref-methods'>Methods from \
- {}<Target={}></h2>", trait_, type_)?;
+ write!(w, "
+ <h2 id='deref-methods' class='small-section-header'>
+ Methods from {}<Target = {}><a href='#deref-methods' class='anchor'></a>
+ </h2>
+ ", trait_, type_)?;
RenderMode::ForDeref { mut_: deref_mut_ }
}
};
for i in &non_trait {
render_impl(w, cx, i, AssocItemLink::Anchor(None), render_mode,
- containing_item.stable_since())?;
+ containing_item.stable_since(), true)?;
}
}
if let AssocItemRender::DerefFor { .. } = what {
}).is_some();
render_deref_methods(w, cx, impl_, containing_item, has_deref_mut)?;
}
- write!(w, "<h2 id='implementations'>Trait \
- Implementations</h2>")?;
+ write!(w, "
+ <h2 id='implementations' class='small-section-header'>
+ Trait Implementations<a href='#implementations' class='anchor'></a>
+ </h2>
+ ")?;
for i in &traits {
let did = i.trait_did().unwrap();
let assoc_link = AssocItemLink::GotoSource(did, &i.inner_impl().provided_trait_methods);
render_impl(w, cx, i, assoc_link,
- RenderMode::Normal, containing_item.stable_since())?;
+ RenderMode::Normal, containing_item.stable_since(), true)?;
}
}
Ok(())
}
}
+fn should_render_item(item: &clean::Item, deref_mut_: bool) -> bool {
+ let self_type_opt = match item.inner {
+ clean::MethodItem(ref method) => method.decl.self_type(),
+ clean::TyMethodItem(ref method) => method.decl.self_type(),
+ _ => None
+ };
+
+ if let Some(self_ty) = self_type_opt {
+ let (by_mut_ref, by_box, by_value) = match self_ty {
+ SelfTy::SelfBorrowed(_, mutability) |
+ SelfTy::SelfExplicit(clean::BorrowedRef { mutability, .. }) => {
+ (mutability == Mutability::Mutable, false, false)
+ },
+ SelfTy::SelfExplicit(clean::ResolvedPath { did, .. }) => {
+ (false, Some(did) == cache().owned_box_did, false)
+ },
+ SelfTy::SelfValue => (false, false, true),
+ _ => (false, false, false),
+ };
+
+ (deref_mut_ || !by_mut_ref) && !by_box && !by_value
+ } else {
+ false
+ }
+}
+
fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLink,
- render_mode: RenderMode, outer_version: Option<&str>) -> fmt::Result {
+ render_mode: RenderMode, outer_version: Option<&str>,
+ show_def_docs: bool) -> fmt::Result {
if render_mode == RenderMode::Normal {
- write!(w, "<h3 class='impl'><span class='in-band'><code>{}</code>", i.inner_impl())?;
+ let id = derive_id(match i.inner_impl().trait_ {
+ Some(ref t) => format!("impl-{}", small_url_encode(&format!("{:#}", t))),
+ None => "impl".to_string(),
+ });
+ write!(w, "<h3 id='{}' class='impl'><span class='in-band'><code>{}</code>",
+ id, i.inner_impl())?;
+ write!(w, "<a href='#{}' class='anchor'></a>", id)?;
write!(w, "</span><span class='out-of-band'>")?;
let since = i.impl_item.stability.as_ref().map(|s| &s.since[..]);
if let Some(l) = (Item { item: &i.impl_item, cx: cx }).src_href() {
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, cx.render_type))?;
}
}
fn doc_impl_item(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item,
link: AssocItemLink, render_mode: RenderMode,
is_default_item: bool, outer_version: Option<&str>,
- trait_: Option<&clean::Trait>) -> fmt::Result {
+ trait_: Option<&clean::Trait>, show_def_docs: bool) -> fmt::Result {
let item_type = item.type_();
let name = item.name.as_ref().unwrap();
let render_method_item: bool = match render_mode {
RenderMode::Normal => true,
- RenderMode::ForDeref { mut_: deref_mut_ } => {
- let self_type_opt = match item.inner {
- clean::MethodItem(ref method) => method.decl.self_type(),
- clean::TyMethodItem(ref method) => method.decl.self_type(),
- _ => None
- };
-
- if let Some(self_ty) = self_type_opt {
- let by_mut_ref = match self_ty {
- SelfTy::SelfBorrowed(_lifetime, mutability) => {
- mutability == Mutability::Mutable
- },
- SelfTy::SelfExplicit(clean::BorrowedRef { mutability, .. }) => {
- mutability == Mutability::Mutable
- },
- _ => false,
- };
-
- deref_mut_ || !by_mut_ref
- } else {
- false
- }
- },
+ RenderMode::ForDeref { mut_: deref_mut_ } => should_render_item(&item, deref_mut_),
};
match item.inner {
if render_method_item {
let id = derive_id(format!("{}.{}", item_type, name));
let ns_id = derive_id(format!("{}.{}", name, item_type.name_space()));
- write!(w, "<h4 id='{}' class='{}'>", id, item_type)?;
+ write!(w, "<h4 id='{}' class=\"{}\">", id, item_type)?;
write!(w, "<span id='{}' class='invisible'>", ns_id)?;
write!(w, "<code>")?;
render_assoc_item(w, item, link.anchor(&id), ItemType::Impl)?;
write!(w, "</code>")?;
- render_stability_since_raw(w, item.stable_since(), outer_version)?;
+ if let Some(l) = (Item { cx, item }).src_href() {
+ write!(w, "</span><span class='out-of-band'>")?;
+ write!(w, "<div class='ghost'></div>")?;
+ render_stability_since_raw(w, item.stable_since(), outer_version)?;
+ write!(w, "<a class='srclink' href='{}' title='{}'>[src]</a>",
+ l, "goto source code")?;
+ } else {
+ render_stability_since_raw(w, item.stable_since(), outer_version)?;
+ }
write!(w, "</span></h4>\n")?;
}
}
clean::TypedefItem(ref tydef, _) => {
let id = derive_id(format!("{}.{}", ItemType::AssociatedType, name));
let ns_id = derive_id(format!("{}.{}", name, item_type.name_space()));
- write!(w, "<h4 id='{}' class='{}'>", id, item_type)?;
+ write!(w, "<h4 id='{}' class=\"{}\">", id, item_type)?;
write!(w, "<span id='{}' class='invisible'><code>", ns_id)?;
assoc_type(w, item, &Vec::new(), Some(&tydef.type_), link.anchor(&id))?;
write!(w, "</code></span></h4>\n")?;
clean::AssociatedConstItem(ref ty, ref default) => {
let id = derive_id(format!("{}.{}", item_type, name));
let ns_id = derive_id(format!("{}.{}", name, item_type.name_space()));
- write!(w, "<h4 id='{}' class='{}'>", id, item_type)?;
+ write!(w, "<h4 id='{}' class=\"{}\">", id, item_type)?;
write!(w, "<span id='{}' class='invisible'><code>", ns_id)?;
assoc_const(w, item, ty, default.as_ref(), link.anchor(&id))?;
write!(w, "</code></span></h4>\n")?;
}
- clean::ConstantItem(ref c) => {
- let id = derive_id(format!("{}.{}", item_type, name));
- let ns_id = derive_id(format!("{}.{}", name, item_type.name_space()));
- write!(w, "<h4 id='{}' class='{}'>", id, item_type)?;
- write!(w, "<span id='{}' class='invisible'><code>", ns_id)?;
- assoc_const(w, item, &c.type_, Some(&c.expr), link.anchor(&id))?;
- write!(w, "</code></span></h4>\n")?;
- }
clean::AssociatedTypeItem(ref bounds, ref default) => {
let id = derive_id(format!("{}.{}", item_type, name));
let ns_id = derive_id(format!("{}.{}", name, item_type.name_space()));
- write!(w, "<h4 id='{}' class='{}'>", id, item_type)?;
+ write!(w, "<h4 id='{}' class=\"{}\">", id, item_type)?;
write!(w, "<span id='{}' class='invisible'><code>", ns_id)?;
assoc_type(w, item, bounds, default.as_ref(), link.anchor(&id))?;
write!(w, "</code></span></h4>\n")?;
}
if render_method_item || render_mode == RenderMode::Normal {
+ let prefix = render_assoc_const_value(item);
if !is_default_item {
if let Some(t) = trait_ {
// The trait item may have been stripped so we might not
// because impls can't have a stability.
document_stability(w, cx, it)?;
if item.doc_value().is_some() {
- document_full(w, item)?;
- } else {
+ document_full(w, item, cx, &prefix)?;
+ } else if show_def_docs {
// In case the item isn't documented,
// provide short documentation from the trait.
- document_short(w, it, link)?;
+ document_short(w, it, link, cx, &prefix)?;
}
}
} else {
- document(w, cx, item)?;
+ document_stability(w, cx, item)?;
+ if show_def_docs {
+ document_full(w, item, cx, &prefix)?;
+ }
}
} else {
document_stability(w, cx, item)?;
- document_short(w, item, link)?;
+ if show_def_docs {
+ document_short(w, item, link, cx, &prefix)?;
+ }
}
}
Ok(())
let traits = &cache().traits;
let trait_ = i.trait_did().and_then(|did| traits.get(&did));
+ if !show_def_docs {
+ write!(w, "<span class='docblock autohide'>")?;
+ }
+
write!(w, "<div class='impl-items'>")?;
for trait_item in &i.inner_impl().items {
doc_impl_item(w, cx, trait_item, link, render_mode,
- false, outer_version, trait_)?;
+ false, outer_version, trait_, show_def_docs)?;
}
fn render_default_items(w: &mut fmt::Formatter,
t: &clean::Trait,
i: &clean::Impl,
render_mode: RenderMode,
- outer_version: Option<&str>) -> fmt::Result {
+ outer_version: Option<&str>,
+ show_def_docs: bool) -> fmt::Result {
for trait_item in &t.items {
let n = trait_item.name.clone();
if i.items.iter().find(|m| m.name == n).is_some() {
let assoc_link = AssocItemLink::GotoSource(did, &i.provided_trait_methods);
doc_impl_item(w, cx, trait_item, assoc_link, render_mode, true,
- outer_version, None)?;
+ outer_version, None, show_def_docs)?;
}
Ok(())
}
// If we've implemented a trait, then also emit documentation for all
// default items which weren't overridden in the implementation block.
if let Some(t) = trait_ {
- render_default_items(w, cx, t, &i.inner_impl(), render_mode, outer_version)?;
+ render_default_items(w, cx, t, &i.inner_impl(),
+ render_mode, outer_version, show_def_docs)?;
}
write!(w, "</div>")?;
+
+ if !show_def_docs {
+ write!(w, "</span>")?;
+ }
+
Ok(())
}
fn item_typedef(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
t: &clean::Typedef) -> fmt::Result {
- let indent = format!("type {}{:#} ", it.name.as_ref().unwrap(), t.generics).len();
- write!(w, "<pre class='rust typedef'>type {}{}{where_clause} = {type_};</pre>",
+ write!(w, "<pre class='rust typedef'>")?;
+ render_attributes(w, it)?;
+ write!(w, "type {}{}{where_clause} = {type_};</pre>",
it.name.as_ref().unwrap(),
t.generics,
- where_clause = WhereClause(&t.generics, indent),
+ where_clause = WhereClause { gens: &t.generics, indent: 0, end_newline: true },
type_ = t.type_)?;
- document(w, cx, it)
+ document(w, cx, it)?;
+
+ // Render any items associated directly to this alias, as otherwise they
+ // won't be visible anywhere in the docs. It would be nice to also show
+ // associated items from the aliased type (see discussion in #32077), but
+ // we need #14072 to make sense of the generics.
+ render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
+}
+
+fn item_foreign_type(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item) -> fmt::Result {
+ writeln!(w, "<pre class='rust foreigntype'>extern {{")?;
+ render_attributes(w, it)?;
+ write!(
+ w,
+ " {}type {};\n}}</pre>",
+ VisSpace(&it.visibility),
+ it.name.as_ref().unwrap(),
+ )?;
+
+ document(w, cx, it)?;
+
+ render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
}
impl<'a> fmt::Display for Sidebar<'a> {
let it = self.item;
let parentlen = cx.current.len() - if it.is_mod() {1} else {0};
+ if it.is_struct() || it.is_trait() || it.is_primitive() || it.is_union()
+ || it.is_enum() || it.is_mod() || it.is_typedef()
+ {
+ write!(fmt, "<p class='location'>")?;
+ match it.inner {
+ clean::StructItem(..) => write!(fmt, "Struct ")?,
+ clean::TraitItem(..) => write!(fmt, "Trait ")?,
+ clean::PrimitiveItem(..) => write!(fmt, "Primitive Type ")?,
+ clean::UnionItem(..) => write!(fmt, "Union ")?,
+ clean::EnumItem(..) => write!(fmt, "Enum ")?,
+ clean::TypedefItem(..) => write!(fmt, "Type Definition ")?,
+ clean::ForeignTypeItem => write!(fmt, "Foreign Type ")?,
+ clean::ModuleItem(..) => if it.is_crate() {
+ write!(fmt, "Crate ")?;
+ } else {
+ write!(fmt, "Module ")?;
+ },
+ _ => (),
+ }
+ write!(fmt, "{}", it.name.as_ref().unwrap())?;
+ write!(fmt, "</p>")?;
+
+ if it.is_crate() {
+ if let Some(ref version) = cache().crate_version {
+ write!(fmt,
+ "<div class='block version'>\
+ <p>Version {}</p>\
+ </div>",
+ version)?;
+ }
+ }
+
+ match it.inner {
+ clean::StructItem(ref s) => sidebar_struct(fmt, it, s)?,
+ clean::TraitItem(ref t) => sidebar_trait(fmt, it, t)?,
+ clean::PrimitiveItem(ref p) => sidebar_primitive(fmt, it, p)?,
+ clean::UnionItem(ref u) => sidebar_union(fmt, it, u)?,
+ clean::EnumItem(ref e) => sidebar_enum(fmt, it, e)?,
+ clean::TypedefItem(ref t, _) => sidebar_typedef(fmt, it, t)?,
+ clean::ModuleItem(ref m) => sidebar_module(fmt, it, &m.items)?,
+ clean::ForeignTypeItem => sidebar_foreign_type(fmt, it)?,
+ _ => (),
+ }
+ }
+
// The sidebar is designed to display sibling functions, modules and
// other miscellaneous information. since there are lots of sibling
// items (and that causes quadratic growth in large modules),
}
}
+fn get_methods(i: &clean::Impl, for_deref: bool) -> Vec<String> {
+ i.items.iter().filter_map(|item| {
+ match item.name {
+ // Maybe check with clean::Visibility::Public as well?
+ Some(ref name) if !name.is_empty() && item.visibility.is_some() && item.is_method() => {
+ if !for_deref || should_render_item(item, false) {
+ Some(format!("<a href=\"#method.{name}\">{name}</a>", name = name))
+ } else {
+ None
+ }
+ }
+ _ => None,
+ }
+ }).collect::<Vec<_>>()
+}
+
+// The point is to url encode any potential character from a type with genericity.
+fn small_url_encode(s: &str) -> String {
+ s.replace("<", "%3C")
+ .replace(">", "%3E")
+ .replace(" ", "%20")
+ .replace("?", "%3F")
+ .replace("'", "%27")
+ .replace("&", "%26")
+ .replace(",", "%2C")
+ .replace(":", "%3A")
+ .replace(";", "%3B")
+ .replace("[", "%5B")
+ .replace("]", "%5D")
+}
+
+fn sidebar_assoc_items(it: &clean::Item) -> String {
+ let mut out = String::new();
+ let c = cache();
+ if let Some(v) = c.impls.get(&it.def_id) {
+ let ret = v.iter()
+ .filter(|i| i.inner_impl().trait_.is_none())
+ .flat_map(|i| get_methods(i.inner_impl(), false))
+ .collect::<String>();
+ if !ret.is_empty() {
+ out.push_str(&format!("<a class=\"sidebar-title\" href=\"#methods\">Methods\
+ </a><div class=\"sidebar-links\">{}</div>", ret));
+ }
+
+ if v.iter().any(|i| i.inner_impl().trait_.is_some()) {
+ if let Some(impl_) = v.iter()
+ .filter(|i| i.inner_impl().trait_.is_some())
+ .find(|i| i.inner_impl().trait_.def_id() == c.deref_trait_did) {
+ if let Some(target) = impl_.inner_impl().items.iter().filter_map(|item| {
+ match item.inner {
+ clean::TypedefItem(ref t, true) => Some(&t.type_),
+ _ => None,
+ }
+ }).next() {
+ let inner_impl = target.def_id().or(target.primitive_type().and_then(|prim| {
+ c.primitive_locations.get(&prim).cloned()
+ })).and_then(|did| c.impls.get(&did));
+ if let Some(impls) = inner_impl {
+ out.push_str("<a class=\"sidebar-title\" href=\"#deref-methods\">");
+ out.push_str(&format!("Methods from {:#}<Target={:#}>",
+ impl_.inner_impl().trait_.as_ref().unwrap(),
+ target));
+ out.push_str("</a>");
+ let ret = impls.iter()
+ .filter(|i| i.inner_impl().trait_.is_none())
+ .flat_map(|i| get_methods(i.inner_impl(), true))
+ .collect::<String>();
+ out.push_str(&format!("<div class=\"sidebar-links\">{}</div>", ret));
+ }
+ }
+ }
+ let mut links = HashSet::new();
+ let ret = v.iter()
+ .filter_map(|i| if let Some(ref i) = i.inner_impl().trait_ {
+ let i_display = format!("{:#}", i);
+ let out = Escape(&i_display);
+ let encoded = small_url_encode(&format!("{:#}", i));
+ let generated = format!("<a href=\"#impl-{}\">{}</a>", encoded, out);
+ if !links.contains(&generated) && links.insert(generated.clone()) {
+ Some(generated)
+ } else {
+ None
+ }
+ } else {
+ None
+ })
+ .collect::<String>();
+ if !ret.is_empty() {
+ out.push_str("<a class=\"sidebar-title\" href=\"#implementations\">\
+ Trait Implementations</a>");
+ out.push_str(&format!("<div class=\"sidebar-links\">{}</div>", ret));
+ }
+ }
+ }
+
+ out
+}
+
+fn sidebar_struct(fmt: &mut fmt::Formatter, it: &clean::Item,
+ s: &clean::Struct) -> fmt::Result {
+ let mut sidebar = String::new();
+ let fields = get_struct_fields_name(&s.fields);
+
+ if !fields.is_empty() {
+ if let doctree::Plain = s.struct_type {
+ sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#fields\">Fields</a>\
+ <div class=\"sidebar-links\">{}</div>", fields));
+ }
+ }
+
+ sidebar.push_str(&sidebar_assoc_items(it));
+
+ if !sidebar.is_empty() {
+ write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?;
+ }
+ Ok(())
+}
+
+fn extract_for_impl_name(item: &clean::Item) -> Option<(String, String)> {
+ match item.inner {
+ clean::ItemEnum::ImplItem(ref i) => {
+ if let Some(ref trait_) = i.trait_ {
+ Some((format!("{:#}", i.for_), format!("{:#}", trait_)))
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+fn sidebar_trait(fmt: &mut fmt::Formatter, it: &clean::Item,
+ t: &clean::Trait) -> fmt::Result {
+ let mut sidebar = String::new();
+
+ let types = t.items
+ .iter()
+ .filter_map(|m| {
+ match m.name {
+ Some(ref name) if m.is_associated_type() => {
+ Some(format!("<a href=\"#associatedtype.{name}\">{name}</a>",
+ name=name))
+ }
+ _ => None,
+ }
+ })
+ .collect::<String>();
+ let consts = t.items
+ .iter()
+ .filter_map(|m| {
+ match m.name {
+ Some(ref name) if m.is_associated_const() => {
+ Some(format!("<a href=\"#associatedconstant.{name}\">{name}</a>",
+ name=name))
+ }
+ _ => None,
+ }
+ })
+ .collect::<String>();
+ let required = t.items
+ .iter()
+ .filter_map(|m| {
+ match m.name {
+ Some(ref name) if m.is_ty_method() => {
+ Some(format!("<a href=\"#tymethod.{name}\">{name}</a>",
+ name=name))
+ }
+ _ => None,
+ }
+ })
+ .collect::<String>();
+ let provided = t.items
+ .iter()
+ .filter_map(|m| {
+ match m.name {
+ Some(ref name) if m.is_method() => {
+ Some(format!("<a href=\"#method.{name}\">{name}</a>", name=name))
+ }
+ _ => None,
+ }
+ })
+ .collect::<String>();
+
+ if !types.is_empty() {
+ sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#associated-types\">\
+ Associated Types</a><div class=\"sidebar-links\">{}</div>",
+ types));
+ }
+ if !consts.is_empty() {
+ sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#associated-const\">\
+ Associated Constants</a><div class=\"sidebar-links\">{}</div>",
+ consts));
+ }
+ if !required.is_empty() {
+ sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#required-methods\">\
+ Required Methods</a><div class=\"sidebar-links\">{}</div>",
+ required));
+ }
+ if !provided.is_empty() {
+ sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#provided-methods\">\
+ Provided Methods</a><div class=\"sidebar-links\">{}</div>",
+ provided));
+ }
+
+ let c = cache();
+
+ if let Some(implementors) = c.implementors.get(&it.def_id) {
+ let res = implementors.iter()
+ .filter(|i| i.impl_.for_.def_id()
+ .map_or(false, |d| !c.paths.contains_key(&d)))
+ .filter_map(|i| {
+ if let Some(item) = implementor2item(&c, i) {
+ match extract_for_impl_name(&item) {
+ Some((ref name, ref url)) => {
+ Some(format!("<a href=\"#impl-{}\">{}</a>",
+ small_url_encode(url),
+ Escape(name)))
+ }
+ _ => None,
+ }
+ } else {
+ None
+ }
+ })
+ .collect::<String>();
+ if !res.is_empty() {
+ sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#foreign-impls\">\
+ Implementations on Foreign Types</a><div \
+ class=\"sidebar-links\">{}</div>",
+ res));
+ }
+ }
+
+ sidebar.push_str("<a class=\"sidebar-title\" href=\"#implementors\">Implementors</a>");
+
+ sidebar.push_str(&sidebar_assoc_items(it));
+
+ write!(fmt, "<div class=\"block items\">{}</div>", sidebar)
+}
+
+fn sidebar_primitive(fmt: &mut fmt::Formatter, it: &clean::Item,
+ _p: &clean::PrimitiveType) -> fmt::Result {
+ let sidebar = sidebar_assoc_items(it);
+
+ if !sidebar.is_empty() {
+ write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?;
+ }
+ Ok(())
+}
+
+fn sidebar_typedef(fmt: &mut fmt::Formatter, it: &clean::Item,
+ _t: &clean::Typedef) -> fmt::Result {
+ let sidebar = sidebar_assoc_items(it);
+
+ if !sidebar.is_empty() {
+ write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?;
+ }
+ Ok(())
+}
+
+fn get_struct_fields_name(fields: &[clean::Item]) -> String {
+ fields.iter()
+ .filter(|f| if let clean::StructFieldItem(..) = f.inner {
+ true
+ } else {
+ false
+ })
+ .filter_map(|f| match f.name {
+ Some(ref name) => Some(format!("<a href=\"#structfield.{name}\">\
+ {name}</a>", name=name)),
+ _ => None,
+ })
+ .collect()
+}
+
+fn sidebar_union(fmt: &mut fmt::Formatter, it: &clean::Item,
+ u: &clean::Union) -> fmt::Result {
+ let mut sidebar = String::new();
+ let fields = get_struct_fields_name(&u.fields);
+
+ if !fields.is_empty() {
+ sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#fields\">Fields</a>\
+ <div class=\"sidebar-links\">{}</div>", fields));
+ }
+
+ sidebar.push_str(&sidebar_assoc_items(it));
+
+ if !sidebar.is_empty() {
+ write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?;
+ }
+ Ok(())
+}
+
+fn sidebar_enum(fmt: &mut fmt::Formatter, it: &clean::Item,
+ e: &clean::Enum) -> fmt::Result {
+ let mut sidebar = String::new();
+
+ let variants = e.variants.iter()
+ .filter_map(|v| match v.name {
+ Some(ref name) => Some(format!("<a href=\"#variant.{name}\">{name}\
+ </a>", name = name)),
+ _ => None,
+ })
+ .collect::<String>();
+ if !variants.is_empty() {
+ sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#variants\">Variants</a>\
+ <div class=\"sidebar-links\">{}</div>", variants));
+ }
+
+ sidebar.push_str(&sidebar_assoc_items(it));
+
+ if !sidebar.is_empty() {
+ write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?;
+ }
+ Ok(())
+}
+
+fn sidebar_module(fmt: &mut fmt::Formatter, _it: &clean::Item,
+ items: &[clean::Item]) -> fmt::Result {
+ let mut sidebar = String::new();
+
+ if items.iter().any(|it| it.type_() == ItemType::ExternCrate ||
+ it.type_() == ItemType::Import) {
+ sidebar.push_str(&format!("<li><a href=\"#{id}\">{name}</a></li>",
+ id = "reexports",
+ name = "Reexports"));
+ }
+
+ // ordering taken from item_module, reorder, where it prioritized elements in a certain order
+ // to print its headings
+ for &myty in &[ItemType::Primitive, ItemType::Module, ItemType::Macro, ItemType::Struct,
+ ItemType::Enum, ItemType::Constant, ItemType::Static, ItemType::Trait,
+ ItemType::Function, ItemType::Typedef, ItemType::Union, ItemType::Impl,
+ ItemType::TyMethod, ItemType::Method, ItemType::StructField, ItemType::Variant,
+ ItemType::AssociatedType, ItemType::AssociatedConst, ItemType::ForeignType] {
+ if items.iter().any(|it| {
+ if let clean::AutoImplItem(..) = it.inner {
+ false
+ } else {
+ !it.is_stripped() && it.type_() == myty
+ }
+ }) {
+ let (short, name) = match myty {
+ ItemType::ExternCrate |
+ ItemType::Import => ("reexports", "Reexports"),
+ ItemType::Module => ("modules", "Modules"),
+ ItemType::Struct => ("structs", "Structs"),
+ ItemType::Union => ("unions", "Unions"),
+ ItemType::Enum => ("enums", "Enums"),
+ ItemType::Function => ("functions", "Functions"),
+ ItemType::Typedef => ("types", "Type Definitions"),
+ ItemType::Static => ("statics", "Statics"),
+ ItemType::Constant => ("constants", "Constants"),
+ ItemType::Trait => ("traits", "Traits"),
+ ItemType::Impl => ("impls", "Implementations"),
+ ItemType::TyMethod => ("tymethods", "Type Methods"),
+ ItemType::Method => ("methods", "Methods"),
+ ItemType::StructField => ("fields", "Struct Fields"),
+ ItemType::Variant => ("variants", "Variants"),
+ ItemType::Macro => ("macros", "Macros"),
+ ItemType::Primitive => ("primitives", "Primitive Types"),
+ ItemType::AssociatedType => ("associated-types", "Associated Types"),
+ ItemType::AssociatedConst => ("associated-consts", "Associated Constants"),
+ ItemType::ForeignType => ("foreign-types", "Foreign Types"),
+ };
+ sidebar.push_str(&format!("<li><a href=\"#{id}\">{name}</a></li>",
+ id = short,
+ name = name));
+ }
+ }
+
+ if !sidebar.is_empty() {
+ write!(fmt, "<div class=\"block items\"><ul>{}</ul></div>", sidebar)?;
+ }
+ Ok(())
+}
+
+fn sidebar_foreign_type(fmt: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result {
+ let sidebar = sidebar_assoc_items(it);
+ if !sidebar.is_empty() {
+ write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?;
+ }
+ Ok(())
+}
+
impl<'a> fmt::Display for Source<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let Source(s) = *self;
write!(fmt, "<span id=\"{0}\">{0:1$}</span>\n", i, cols)?;
}
write!(fmt, "</pre>")?;
- write!(fmt, "{}", highlight::render_with_highlighting(s, None, None, None))?;
+ write!(fmt, "{}",
+ highlight::render_with_highlighting(s, None, None, None, None))?;
Ok(())
}
}
w.write_str(&highlight::render_with_highlighting(&t.source,
Some("macro"),
None,
+ None,
None))?;
document(w, cx, it)
}
}
fn get_index_type(clean_type: &clean::Type) -> Type {
- Type { name: get_index_type_name(clean_type).map(|s| s.to_ascii_lowercase()) }
+ let t = Type {
+ name: get_index_type_name(clean_type, true).map(|s| s.to_ascii_lowercase()),
+ generics: get_generics(clean_type),
+ };
+ t
}
-fn get_index_type_name(clean_type: &clean::Type) -> Option<String> {
+fn get_index_type_name(clean_type: &clean::Type, accept_generic: bool) -> Option<String> {
match *clean_type {
clean::ResolvedPath { ref path, .. } => {
let segments = &path.segments;
Some(segments[segments.len() - 1].name.clone())
- },
- clean::Generic(ref s) => Some(s.clone()),
+ }
+ clean::Generic(ref s) if accept_generic => Some(s.clone()),
clean::Primitive(ref p) => Some(format!("{:?}", p)),
- clean::BorrowedRef { ref type_, .. } => get_index_type_name(type_),
+ clean::BorrowedRef { ref type_, .. } => get_index_type_name(type_, accept_generic),
// FIXME: add all from clean::Type.
_ => None
}
}
+fn get_generics(clean_type: &clean::Type) -> Option<Vec<String>> {
+ clean_type.generics()
+ .and_then(|types| {
+ let r = types.iter()
+ .filter_map(|t| get_index_type_name(t, false))
+ .map(|s| s.to_ascii_lowercase())
+ .collect::<Vec<_>>();
+ if r.is_empty() {
+ None
+ } else {
+ Some(r)
+ }
+ })
+}
+
pub fn cache() -> Arc<Cache> {
CACHE_KEY.with(|c| c.borrow().clone())
}
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);
+}