From 60384e70a119c75020681b2826b88b096b79425a Mon Sep 17 00:00:00 2001 From: Kang Seonghoon Date: Fri, 25 Apr 2014 13:04:28 +0900 Subject: [PATCH] rustdoc: Inline reexported items if possible. some examples to look at: `std::io`, `std::prelude`, `libc`. there are some known exceptions to inlining (e.g. statics), so they are preserved to the reexport section as much as possible. it would be desirable to remove such exceptions in the future. --- src/librustdoc/html/format.rs | 2 +- src/librustdoc/html/render.rs | 443 +++++++++++++++++++--------- src/librustdoc/html/static/main.css | 5 + 3 files changed, 313 insertions(+), 137 deletions(-) diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 1e2f89659cd7d..27adc9fdbfda0 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -154,7 +154,7 @@ fn resolved_path(w: &mut io::Writer, id: ast::NodeId, p: &clean::Path, |cache| { match cache.paths.find(&id) { None => None, - Some(&(ref fqp, shortty)) => Some((fqp.clone(), shortty)) + Some(info) => Some((info.fqp.clone(), info.ty)) } }) } diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index d65876a1c834d..6a4ea83a86af7 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -44,6 +44,7 @@ use std::strbuf::StrBuf; use sync::Arc; use serialize::json::ToJson; use syntax::ast; +use syntax::ast_util; use syntax::attr; use syntax::parse::token::InternedString; use rustc::util::nodemap::NodeSet; @@ -111,6 +112,18 @@ pub enum Implementor { OtherType(clean::Generics, /* trait */ clean::Type, /* for */ clean::Type), } +pub struct PathInfo { + /// The fully qualified path. + pub fqp: Vec<~str>, + + /// A short type description. Combined with the fully qualified name, + /// this is used to create an URL to the documented path. + pub ty: ItemType, + + /// A shortened description for the information. + pub desc: ~str, +} + /// This cache is used to store information about the `clean::Crate` being /// rendered in order to provide more useful documentation. This contains /// information like all implementors of a trait, all traits a type implements, @@ -139,7 +152,7 @@ pub struct Cache { /// URLs when a type is being linked to. External paths are not located in /// this map because the `External` type itself has all the information /// necessary. - pub paths: HashMap , ItemType)>, + pub paths: HashMap, /// This map contains information about all known traits of this crate. /// Implementations of a crate should inherit the documentation of the @@ -274,11 +287,11 @@ pub fn run(mut krate: clean::Crate, dst: Path) -> io::IoResult<()> { // has since been learned. for &(ref pid, ref item) in meths.iter() { match paths.find(pid) { - Some(&(ref fqp, _)) => { + Some(info) => { index.push(IndexItem { ty: shortty(item), name: item.name.clone().unwrap(), - path: fqp.slice_to(fqp.len() - 1).connect("::"), + path: info.fqp.slice_to(info.fqp.len() - 1).connect("::"), desc: shorter(item.doc_value()).to_owned(), parent: Some(*pid), }); @@ -339,12 +352,12 @@ pub fn run(mut krate: clean::Crate, dst: Path) -> io::IoResult<()> { try!(write!(&mut w, r#"],"paths":["#)); for (i, &nodeid) in pathid_to_nodeid.iter().enumerate() { - let &(ref fqp, short) = cache.paths.find(&nodeid).unwrap(); + let info = cache.paths.find(&nodeid).unwrap(); if i > 0 { try!(write!(&mut w, ",")); } try!(write!(&mut w, r#"[{:u},"{}"]"#, - short, *fqp.last().unwrap())); + info.ty, *info.fqp.last().unwrap())); } try!(write!(&mut w, r"]\};")); @@ -636,13 +649,13 @@ impl DocFolder for Cache { } else { let last = self.parent_stack.last().unwrap(); let path = match self.paths.find(last) { - Some(&(_, item_type::Trait)) => + Some(&PathInfo { ty: item_type::Trait, .. }) => Some(self.stack.slice_to(self.stack.len() - 1)), // The current stack not necessarily has correlation for // where the type was defined. On the other hand, // `paths` always has the right information if present. - Some(&(ref fqp, item_type::Struct)) | - Some(&(ref fqp, item_type::Enum)) => + Some(&PathInfo { ty: item_type::Struct, fqp: ref fqp, .. }) | + Some(&PathInfo { ty: item_type::Enum, fqp: ref fqp, .. }) => Some(fqp.slice_to(fqp.len() - 1)), Some(..) => Some(self.stack.as_slice()), None => None @@ -694,7 +707,8 @@ impl DocFolder for Cache { if !self.paths.contains_key(&item.id) || self.public_items.contains(&item.id) { self.paths.insert(item.id, - (self.stack.clone(), shortty(&item))); + PathInfo { fqp: self.stack.clone(), ty: shortty(&item), + desc: shorter(item.doc_value()).to_owned() }); } } // link variants to their parent enum because pages aren't emitted @@ -702,7 +716,9 @@ impl DocFolder for Cache { clean::VariantItem(..) => { let mut stack = self.stack.clone(); stack.pop(); - self.paths.insert(item.id, (stack, item_type::Enum)); + self.paths.insert(item.id, + PathInfo { fqp: stack, ty: item_type::Enum, + desc: shorter(item.doc_value()).to_owned() }); } _ => {} } @@ -904,7 +920,7 @@ impl Context { // Things which don't have names (like impls) don't get special // pages dedicated to them. _ if item.name.is_some() => { - let dst = self.dst.join(item_path(&item)); + let dst = self.dst.join(item_path(shortty(&item), *item.name.get_ref())); let dst = try!(File::create(&dst)); render(dst, self, &item, true) } @@ -1001,17 +1017,40 @@ impl<'a> fmt::Show for Item<'a> { } } -fn item_path(item: &clean::Item) -> ~str { - match item.inner { - clean::ModuleItem(..) => *item.name.get_ref() + "/index.html", - _ => shortty(item).to_static_str() + "." + *item.name.get_ref() + ".html" +fn item_path(ty: ItemType, name: &str) -> ~str { + match ty { + item_type::Module => name + "/index.html", + _ => ty.to_static_str() + "." + name + ".html" } } -fn full_path(cx: &Context, item: &clean::Item) -> ~str { +fn nonlocal_item_path(cx: &Context, ty: ItemType, fqp: &[~str]) -> ~str { + let mut url = StrBuf::new(); + url.push_str("../".repeat(cx.current.len())); + let to_link = fqp.slice_to(fqp.len() - 1); + for component in to_link.iter() { + url.push_str(*component); + url.push_str("/"); + } + match ty { + item_type::Module => { + url.push_str(*fqp.last().unwrap()); + url.push_str("/index.html"); + } + _ => { + url.push_str(ty.to_static_str()); + url.push_str("."); + url.push_str(*fqp.last().unwrap()); + url.push_str(".html"); + } + } + url.into_owned() +} + +fn full_path(cx: &Context, name: &str) -> ~str { let mut s = StrBuf::from_str(cx.current.connect("::")); s.push_str("::"); - s.push_str(item.name.get_ref().as_slice()); + s.push_str(name.as_slice()); return s.into_owned(); } @@ -1046,154 +1085,286 @@ fn item_module(w: &mut Writer, cx: &Context, item: &clean::Item, items: &[clean::Item]) -> fmt::Result { try!(document(w, item)); debug!("{:?}", items); - let mut indices = Vec::from_fn(items.len(), |i| i); - fn cmp(i1: &clean::Item, i2: &clean::Item, idx1: uint, idx2: uint) -> Ordering { - if shortty(i1) == shortty(i2) { - return i1.name.cmp(&i2.name); + local_data::get(cache_key, |cache| { + let cache = cache.unwrap(); + + enum Indexed<'a> { + Borrowed(&'a clean::Item), + Owned(clean::Item), // used for reconstructed unresolved imports + Reexported(~str /* reexported name */, ast::NodeId /* resolved id */), } - match (&i1.inner, &i2.inner) { - (&clean::ViewItemItem(ref a), &clean::ViewItemItem(ref b)) => { - match (&a.inner, &b.inner) { - (&clean::ExternCrate(..), _) => Less, - (_, &clean::ExternCrate(..)) => Greater, - _ => idx1.cmp(&idx2), + + let defid_to_type = |did: Option| { + // FIXME: shouldn't be limited to just local imports + if did.is_some() && ast_util::is_local(did.unwrap()) { + match cache.paths.find(&did.unwrap().node) { + None => None, + Some(info) => Some(info.ty) } + } else { + None } - (&clean::ViewItemItem(..), _) => Less, - (_, &clean::ViewItemItem(..)) => Greater, - (&clean::ModuleItem(..), _) => Less, - (_, &clean::ModuleItem(..)) => Greater, - (&clean::MacroItem(..), _) => Less, - (_, &clean::MacroItem(..)) => Greater, - (&clean::StructItem(..), _) => Less, - (_, &clean::StructItem(..)) => Greater, - (&clean::EnumItem(..), _) => Less, - (_, &clean::EnumItem(..)) => Greater, - (&clean::StaticItem(..), _) => Less, - (_, &clean::StaticItem(..)) => Greater, - (&clean::ForeignFunctionItem(..), _) => Less, - (_, &clean::ForeignFunctionItem(..)) => Greater, - (&clean::ForeignStaticItem(..), _) => Less, - (_, &clean::ForeignStaticItem(..)) => Greater, - (&clean::TraitItem(..), _) => Less, - (_, &clean::TraitItem(..)) => Greater, - (&clean::FunctionItem(..), _) => Less, - (_, &clean::FunctionItem(..)) => Greater, - (&clean::TypedefItem(..), _) => Less, - (_, &clean::TypedefItem(..)) => Greater, - _ => idx1.cmp(&idx2), - } - } + }; - debug!("{:?}", indices); - indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2)); + // try to resolve the use view paths, and returns a copy of + // any unresolved paths. resolved paths go to the indices. + let resolve_view_paths = |indices: &mut Vec<(ItemType, Indexed, uint)>, + vps: &Vec, + pos: uint| -> Vec { + let mut unresolved = Vec::new(); + for vp in vps.iter() { + match vp { + &clean::SimpleImport(ref name, ref src) => { + match defid_to_type(src.did) { + Some(ty) => { + let id = src.did.unwrap().node; + let name = name.to_owned(); + indices.push((ty, Reexported(name, id), pos)); + } + None => { + unresolved.push(vp.clone()); + } + } + } + &clean::GlobImport(..) => { + unresolved.push(vp.clone()); + } + &clean::ImportList(ref src, ref idents) => { + let mut unresolved_idents = Vec::new(); + for ident in idents.iter() { + match defid_to_type(ident.source) { + Some(ty) => { + let id = ident.source.unwrap().node; + let name = ident.name.to_owned(); + indices.push((ty, Reexported(name, id), pos)); + } + None => { + unresolved_idents.push(ident.clone()); + } + } + } + if !unresolved_idents.is_empty() { + unresolved.push(clean::ImportList(src.clone(), + unresolved_idents)); + } + } + } + } + unresolved + }; - debug!("{:?}", indices); - let mut curty = None; - for &idx in indices.iter() { - let myitem = &items[idx]; + let mut indices = Vec::new(); + for (i, item) in items.iter().enumerate() { + match item.inner { + clean::ViewItemItem(ref v) => { + match v.inner { + clean::Import(ref vps) => { + let unresolved = resolve_view_paths(&mut indices, vps, i); + if !unresolved.is_empty() { + // reconstructs the item out of remaining paths + let item = clean::Item { + source: item.source.clone(), + name: item.name.clone(), + attrs: item.attrs.clone(), + inner: clean::ViewItemItem(clean::ViewItem { + inner: clean::Import(unresolved) + }), + visibility: item.visibility, + id: item.id, + }; + indices.push((shortty(&item), Owned(item), i)); + } + } + _ => { + indices.push((shortty(item), Borrowed(item), i)); + } + } + } + _ => { + indices.push((shortty(item), Borrowed(item), i)); + } + } + } - let myty = Some(shortty(myitem)); - if myty != curty { - if curty.is_some() { - try!(write!(w, "")); + fn indexed_order(ty: ItemType) -> uint { + match ty { + // for unresolved "reexports" section + item_type::ViewItem => 0, + + item_type::Module => 1, + item_type::Macro => 2, + item_type::Struct => 3, + item_type::Enum => 4, + item_type::Static => 5, + item_type::ForeignFunction => 6, + item_type::ForeignStatic => 7, + item_type::Trait => 8, + item_type::Function => 9, + item_type::Typedef => 10, + + // these items should not appear in the module anyway + item_type::Impl => 11, + item_type::TyMethod => 12, + item_type::Method => 13, + item_type::StructField => 14, + item_type::Variant => 15, } - curty = myty; - let (short, name) = match myitem.inner { - clean::ModuleItem(..) => ("modules", "Modules"), - clean::StructItem(..) => ("structs", "Structs"), - clean::EnumItem(..) => ("enums", "Enums"), - clean::FunctionItem(..) => ("functions", "Functions"), - clean::TypedefItem(..) => ("types", "Type Definitions"), - clean::StaticItem(..) => ("statics", "Statics"), - clean::TraitItem(..) => ("traits", "Traits"), - clean::ImplItem(..) => ("impls", "Implementations"), - clean::ViewItemItem(..) => ("reexports", "Reexports"), - clean::TyMethodItem(..) => ("tymethods", "Type Methods"), - clean::MethodItem(..) => ("methods", "Methods"), - clean::StructFieldItem(..) => ("fields", "Struct Fields"), - clean::VariantItem(..) => ("variants", "Variants"), - clean::ForeignFunctionItem(..) => ("ffi-fns", "Foreign Functions"), - clean::ForeignStaticItem(..) => ("ffi-statics", "Foreign Statics"), - clean::MacroItem(..) => ("macros", "Macros"), - }; - try!(write!(w, - "\n", - id = short, name = name)); } - match myitem.inner { - clean::StaticItem(ref s) | clean::ForeignStaticItem(ref s) => { - struct Initializer<'a>(&'a str); - impl<'a> fmt::Show for Initializer<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let Initializer(s) = *self; - if s.len() == 0 { return Ok(()); } - try!(write!(f.buf, " = ")); - let tag = if s.contains("\n") { "pre" } else { "code" }; - try!(write!(f.buf, "<{tag}>{}", - s.as_slice(), tag=tag)); - Ok(()) - } - } + fn indexed_name<'a>(indexed: &'a Indexed<'a>) -> Option<&'a str> { + match indexed { + &Borrowed(item) => item.name.as_ref().map(|name| name.as_slice()), + &Owned(ref item) => item.name.as_ref().map(|name| name.as_slice()), + &Reexported(ref name, _id) => Some(name.as_slice()), + } + } - try!(write!(w, " - - - - - ", - VisSpace(myitem.visibility), - *myitem.name.get_ref(), - s.type_, - Initializer(s.expr), - Markdown(blank(myitem.doc_value())))); + debug!("{:?}", indices); + indices.sort_by(|&(ty1, ref index1, pos1), &(ty2, ref index2, pos2)| { + let lhs = (indexed_order(ty1), indexed_name(index1), pos1); + let rhs = (indexed_order(ty2), indexed_name(index2), pos2); + lhs.cmp(&rhs) + }); + + debug!("{:?}", indices); + let mut curty = None; + for (ty, indexed, _pos) in indices.move_iter() { + // render a heading if needed + if Some(ty) != curty { + if curty.is_some() { + try!(write!(w, "
{}static {}: {}{}{} 
")); + } + curty = Some(ty); + let (short, name) = match ty { + item_type::Module => ("modules", "Modules"), + item_type::Struct => ("structs", "Structs"), + item_type::Enum => ("enums", "Enums"), + item_type::Function => ("functions", "Functions"), + item_type::Typedef => ("types", "Type Definitions"), + item_type::Static => ("statics", "Statics"), + item_type::Trait => ("traits", "Traits"), + item_type::Impl => ("impls", "Implementations"), + item_type::ViewItem => ("reexports", "Reexports"), + item_type::TyMethod => ("tymethods", "Type Methods"), + item_type::Method => ("methods", "Methods"), + item_type::StructField => ("fields", "Struct Fields"), + item_type::Variant => ("variants", "Variants"), + item_type::ForeignFunction => ("ffi-fns", "Foreign Functions"), + item_type::ForeignStatic => ("ffi-statics", "Foreign Statics"), + item_type::Macro => ("macros", "Macros"), + }; + try!(write!(w, + "\n", + id = short, name = name)); } - clean::ViewItemItem(ref item) => { - match item.inner { - clean::ExternCrate(ref name, ref src, _) => { - try!(write!(w, "")); + + try!(write!(w, " + + + + + ", + VisSpace(myitem.visibility), + *myitem.name.get_ref(), + s.type_, + Initializer(s.expr), + Markdown(blank(myitem.doc_value())))); } - clean::Import(ref imports) => { - for import in imports.iter() { - try!(write!(w, "", - VisSpace(myitem.visibility), - *import)); + clean::ViewItemItem(ref item) => { + match item.inner { + clean::ExternCrate(ref name, ref src, _) => { + try!(write!(w, "")); + } + + clean::Import(ref imports) => { + for import in imports.iter() { + try!(write!(w, "", + VisSpace(myitem.visibility), + *import)); + } + } } + + } + + _ => { + if myitem.name.is_none() { return Ok(()) } + let name = myitem.name.get_ref().as_slice(); + + try!(write!(w, " + + + + + ", + name, + Markdown(shorter(myitem.doc_value())), + class = ty, + href = item_path(ty, name), + title = full_path(cx, name))); } } - } + Ok(()) + }; + + let render_reexported = |name: ~str, id: ast::NodeId| { + // this should always return `Some` + let info = cache.paths.find(&id).unwrap(); - _ => { - if myitem.name.is_none() { continue } try!(write!(w, " - + ", - *myitem.name.get_ref(), - Markdown(shorter(myitem.doc_value())), - class = shortty(myitem), - href = item_path(myitem), - title = full_path(cx, myitem))); + name, + Markdown(info.desc), + class = ty, + href = nonlocal_item_path(cx, ty, info.fqp.as_slice()), + title = info.fqp.connect("::"))); + + Ok(()) + }; + + match indexed { + Borrowed(myitem) => try!(render_item(myitem)), + Owned(ref myitem) => try!(render_item(myitem)), + Reexported(name, id) => try!(render_reexported(name, id)), } } - } - write!(w, "
extern crate {}", - name.as_slice())); - match *src { - Some(ref src) => try!(write!(w, " = \"{}\"", - src.as_slice())), - None => {} + let render_item = |myitem: &clean::Item| { + match myitem.inner { + clean::StaticItem(ref s) | clean::ForeignStaticItem(ref s) => { + struct Initializer<'a>(&'a str); + impl<'a> fmt::Show for Initializer<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Initializer(s) = *self; + if s.len() == 0 { return Ok(()); } + try!(write!(f.buf, " = ")); + let tag = if s.contains("\n") { "pre" } else { "code" }; + try!(write!(f.buf, "<{tag}>{}", + s.as_slice(), tag=tag)); + Ok(()) + } } - try!(write!(w, ";
{}static {}: {}{}{} 
{}{}
extern crate {}", + name.as_slice())); + match *src { + Some(ref src) => try!(write!(w, " = \"{}\"", + src.as_slice())), + None => {} + } + try!(write!(w, ";
{}{}
{}{}
{}{} + (Reexported) + {}
") + write!(w, "") + }) } fn item_function(w: &mut Writer, it: &clean::Item, diff --git a/src/librustdoc/html/static/main.css b/src/librustdoc/html/static/main.css index 9a0b991237397..0fcf411d66a6d 100644 --- a/src/librustdoc/html/static/main.css +++ b/src/librustdoc/html/static/main.css @@ -195,10 +195,15 @@ nav.sub { text-overflow: ellipsis; } .docblock.short p { + display: inline; overflow: hidden; text-overflow: ellipsis; margin: 0; } +.docblock.short .reexported { + color: #888; + font-size: 90%; +} .docblock h1, .docblock h2, .docblock h3, .docblock h4, .docblock h5 { margin: 40px 0 10px 0;