about summary refs log tree commit diff
diff options
context:
space:
mode:
authorCorey Farwell <coreyf@rwell.org>2017-04-17 13:26:20 -0400
committerGitHub <noreply@github.com>2017-04-17 13:26:20 -0400
commit4ae25a60ba04d9b15f1d1eeda3b02cbd224b069d (patch)
treebc7527958d8e27c9fc4c72aa2959e6777ba50450
parent914b6f1f14e62550c9b7ca7f59490aa0f10ef288 (diff)
parent27bfbd56f08ab64122a79cb84446a27b99099590 (diff)
downloadrust-4ae25a60ba04d9b15f1d1eeda3b02cbd224b069d.tar.gz
rust-4ae25a60ba04d9b15f1d1eeda3b02cbd224b069d.zip
Rollup merge of #41280 - QuietMisdreavus:rustdoc-toc, r=GuillaumeGomez
rustdoc: add a list of headings to the sidebar

It's another misdreavus rustdoc PR, which means it's time for Bikeshed City once again! `:3`

In an effort to aid navigation in long documentation pages, this PR adds a listing of headings to the sidebars of pages where such headings exist. For example, for structs, links to their fields, inherent methods, and trait implementations are available where applicable.

Examples:

* Modules/Crate roots
  ![image](https://cloud.githubusercontent.com/assets/5217170/25019930/1000fa3a-2052-11e7-98ff-ddf5af18b3e6.png)
* Enums
  ![image](https://cloud.githubusercontent.com/assets/5217170/25019954/33497f9e-2052-11e7-88cf-df46f1b3b8a3.png)
* Primitives
  ![image](https://cloud.githubusercontent.com/assets/5217170/25019978/4820bbc6-2052-11e7-8b5a-96a864eb2a5b.png)
* Traits
  ![image](https://cloud.githubusercontent.com/assets/5217170/25020093/bd1bc9f2-2052-11e7-9cd1-00a0ad8007bc.png)
* Structs
  ![image](https://cloud.githubusercontent.com/assets/5217170/25020145/d75206b0-2052-11e7-88cc-9e9525084775.png)

Open questions:

* Right now, these kinds of pages (and also unions) are the only pages that will receive the name header - pages for functions, constants, macros, etc, won't have the corresponding name in their sidebar. Should I print the name regardless and only add table-of-contents links for pages that have them? This would make them match, for example, a struct with no public fields, no methods, and no trait implementations. The latter would still have a "Struct MyStruct" line above the module contents, with no header links to speak of, whereas a function wouldn't even have "Function my\_function".
* This is only a header listing, but there has been requests to include a more-complete listing of fields/methods/traits/etc, for example in #41123.
-rw-r--r--src/librustdoc/clean/mod.rs5
-rw-r--r--src/librustdoc/html/render.rs224
2 files changed, 225 insertions, 4 deletions
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 3d233463bba..99838635356 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -292,7 +292,7 @@ impl Item {
         self.type_() == ItemType::Struct
     }
     pub fn is_enum(&self) -> bool {
-        self.type_() == ItemType::Module
+        self.type_() == ItemType::Enum
     }
     pub fn is_fn(&self) -> bool {
         self.type_() == ItemType::Function
@@ -312,6 +312,9 @@ impl Item {
     pub fn is_primitive(&self) -> bool {
         self.type_() == ItemType::Primitive
     }
+    pub fn is_union(&self) -> bool {
+        self.type_() == ItemType::Union
+    }
     pub fn is_stripped(&self) -> bool {
         match self.inner { StrippedItem(..) => true, _ => false }
     }
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index 42a18345681..d55a0640562 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -2430,7 +2430,7 @@ fn item_struct(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
     }).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'>Fields</h2>")?;
             for (field, ty) in fields {
                 let id = derive_id(format!("{}.{}",
                                            ItemType::StructField,
@@ -2478,7 +2478,7 @@ fn item_union(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
         }
     }).peekable();
     if fields.peek().is_some() {
-        write!(w, "<h2 class='fields'>Fields</h2>")?;
+        write!(w, "<h2 id='fields' class='fields'>Fields</h2>")?;
         for (field, ty) in fields {
             write!(w, "<span id='{shortty}.{name}' class=\"{shortty}\"><code>{name}: {ty}</code>
                        </span>",
@@ -2550,7 +2550,7 @@ fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
 
     document(w, cx, it)?;
     if !e.variants.is_empty() {
-        write!(w, "<h2 class='variants'>Variants</h2>\n")?;
+        write!(w, "<h2 id='variants' class='variants'>Variants</h2>\n")?;
         for variant in &e.variants {
             let id = derive_id(format!("{}.{}",
                                        ItemType::Variant,
@@ -3074,6 +3074,37 @@ 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()
+        {
+            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::ModuleItem(..) => if it.is_crate() {
+                    write!(fmt, "Crate ")?;
+                } else {
+                    write!(fmt, "Module ")?;
+                },
+                _ => (),
+            }
+            write!(fmt, "{}", it.name.as_ref().unwrap())?;
+            write!(fmt, "</p>")?;
+
+            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::ModuleItem(ref m) => sidebar_module(fmt, it, &m.items)?,
+                _ => (),
+            }
+        }
+
         // 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),
@@ -3116,6 +3147,193 @@ impl<'a> fmt::Display for Sidebar<'a> {
     }
 }
 
+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) {
+        if v.iter().any(|i| i.inner_impl().trait_.is_none()) {
+            out.push_str("<li><a href=\"#methods\">Methods</a></li>");
+        }
+
+        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 inner_impl.is_some() {
+                        out.push_str("<li><a href=\"#deref-methods\">");
+                        out.push_str(&format!("Methods from {:#}&lt;Target={:#}&gt;",
+                                                  impl_.inner_impl().trait_.as_ref().unwrap(),
+                                                  target));
+                        out.push_str("</a></li>");
+                    }
+                }
+            }
+            out.push_str("<li><a href=\"#implementations\">Trait Implementations</a></li>");
+        }
+    }
+
+    out
+}
+
+fn sidebar_struct(fmt: &mut fmt::Formatter, it: &clean::Item,
+                  s: &clean::Struct) -> fmt::Result {
+    let mut sidebar = String::new();
+
+    if s.fields.iter()
+               .any(|f| if let clean::StructFieldItem(..) = f.inner { true } else { false }) {
+        if let doctree::Plain = s.struct_type {
+            sidebar.push_str("<li><a href=\"#fields\">Fields</a></li>");
+        }
+    }
+
+    sidebar.push_str(&sidebar_assoc_items(it));
+
+    if !sidebar.is_empty() {
+        write!(fmt, "<div class=\"block items\"><ul>{}</ul></div>", sidebar)?;
+    }
+    Ok(())
+}
+
+fn sidebar_trait(fmt: &mut fmt::Formatter, it: &clean::Item,
+                 t: &clean::Trait) -> fmt::Result {
+    let mut sidebar = String::new();
+
+    let has_types = t.items.iter().any(|m| m.is_associated_type());
+    let has_consts = t.items.iter().any(|m| m.is_associated_const());
+    let has_required = t.items.iter().any(|m| m.is_ty_method());
+    let has_provided = t.items.iter().any(|m| m.is_method());
+
+    if has_types {
+        sidebar.push_str("<li><a href=\"#associated-types\">Associated Types</a></li>");
+    }
+    if has_consts {
+        sidebar.push_str("<li><a href=\"#associated-const\">Associated Constants</a></li>");
+    }
+    if has_required {
+        sidebar.push_str("<li><a href=\"#required-methods\">Required Methods</a></li>");
+    }
+    if has_provided {
+        sidebar.push_str("<li><a href=\"#provided-methods\">Provided Methods</a></li>");
+    }
+
+    sidebar.push_str(&sidebar_assoc_items(it));
+
+    sidebar.push_str("<li><a href=\"#implementors\">Implementors</a></li>");
+
+    write!(fmt, "<div class=\"block items\"><ul>{}</ul></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\"><ul>{}</ul></div>", sidebar)?;
+    }
+    Ok(())
+}
+
+fn sidebar_union(fmt: &mut fmt::Formatter, it: &clean::Item,
+                 u: &clean::Union) -> fmt::Result {
+    let mut sidebar = String::new();
+
+    if u.fields.iter()
+               .any(|f| if let clean::StructFieldItem(..) = f.inner { true } else { false }) {
+        sidebar.push_str("<li><a href=\"#fields\">Fields</a></li>");
+    }
+
+    sidebar.push_str(&sidebar_assoc_items(it));
+
+    if !sidebar.is_empty() {
+        write!(fmt, "<div class=\"block items\"><ul>{}</ul></div>", sidebar)?;
+    }
+    Ok(())
+}
+
+fn sidebar_enum(fmt: &mut fmt::Formatter, it: &clean::Item,
+                e: &clean::Enum) -> fmt::Result {
+    let mut sidebar = String::new();
+
+    if !e.variants.is_empty() {
+        sidebar.push_str("<li><a href=\"#variants\">Variants</a></li>");
+    }
+
+    sidebar.push_str(&sidebar_assoc_items(it));
+
+    if !sidebar.is_empty() {
+        write!(fmt, "<div class=\"block items\"><ul>{}</ul></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] {
+        if items.iter().any(|it| {
+            if let clean::DefaultImplItem(..) = it.inner {
+                false
+            } else {
+                !maybe_ignore_item(it) && !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"),
+            };
+            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(())
+}
+
 impl<'a> fmt::Display for Source<'a> {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
         let Source(s) = *self;