Add Top TOC support to rustdoc
This commit adds the headers for the top level documentation to rustdoc's existing table of contents, along with associated items. It only show two levels of headers. Going further would require the sidebar to be wider, and that seems unnecessary (the crates that have manually-built TOCs usually don't need deeply nested headers).
This commit is contained in:
parent
5aea14073e
commit
1aebff96ad
@ -506,9 +506,6 @@ impl Item {
|
|||||||
pub(crate) fn is_mod(&self) -> bool {
|
pub(crate) fn is_mod(&self) -> bool {
|
||||||
self.type_() == ItemType::Module
|
self.type_() == ItemType::Module
|
||||||
}
|
}
|
||||||
pub(crate) fn is_trait(&self) -> bool {
|
|
||||||
self.type_() == ItemType::Trait
|
|
||||||
}
|
|
||||||
pub(crate) fn is_struct(&self) -> bool {
|
pub(crate) fn is_struct(&self) -> bool {
|
||||||
self.type_() == ItemType::Struct
|
self.type_() == ItemType::Struct
|
||||||
}
|
}
|
||||||
@ -536,9 +533,6 @@ impl Item {
|
|||||||
pub(crate) fn is_ty_method(&self) -> bool {
|
pub(crate) fn is_ty_method(&self) -> bool {
|
||||||
self.type_() == ItemType::TyMethod
|
self.type_() == ItemType::TyMethod
|
||||||
}
|
}
|
||||||
pub(crate) fn is_type_alias(&self) -> bool {
|
|
||||||
self.type_() == ItemType::TypeAlias
|
|
||||||
}
|
|
||||||
pub(crate) fn is_primitive(&self) -> bool {
|
pub(crate) fn is_primitive(&self) -> bool {
|
||||||
self.type_() == ItemType::Primitive
|
self.type_() == ItemType::Primitive
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ use crate::html::format::Buffer;
|
|||||||
use crate::html::highlight;
|
use crate::html::highlight;
|
||||||
use crate::html::length_limit::HtmlWithLimit;
|
use crate::html::length_limit::HtmlWithLimit;
|
||||||
use crate::html::render::small_url_encode;
|
use crate::html::render::small_url_encode;
|
||||||
use crate::html::toc::TocBuilder;
|
use crate::html::toc::{Toc, TocBuilder};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
@ -101,6 +101,7 @@ pub struct Markdown<'a> {
|
|||||||
/// A struct like `Markdown` that renders the markdown with a table of contents.
|
/// A struct like `Markdown` that renders the markdown with a table of contents.
|
||||||
pub(crate) struct MarkdownWithToc<'a> {
|
pub(crate) struct MarkdownWithToc<'a> {
|
||||||
pub(crate) content: &'a str,
|
pub(crate) content: &'a str,
|
||||||
|
pub(crate) links: &'a [RenderedLink],
|
||||||
pub(crate) ids: &'a mut IdMap,
|
pub(crate) ids: &'a mut IdMap,
|
||||||
pub(crate) error_codes: ErrorCodes,
|
pub(crate) error_codes: ErrorCodes,
|
||||||
pub(crate) edition: Edition,
|
pub(crate) edition: Edition,
|
||||||
@ -532,9 +533,9 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
|
|||||||
let id = self.id_map.derive(id);
|
let id = self.id_map.derive(id);
|
||||||
|
|
||||||
if let Some(ref mut builder) = self.toc {
|
if let Some(ref mut builder) = self.toc {
|
||||||
let mut html_header = String::new();
|
let mut text_header = String::new();
|
||||||
html::push_html(&mut html_header, self.buf.iter().map(|(ev, _)| ev.clone()));
|
plain_text_from_events(self.buf.iter().map(|(ev, _)| ev.clone()), &mut text_header);
|
||||||
let sec = builder.push(level as u32, html_header, id.clone());
|
let sec = builder.push(level as u32, text_header, id.clone());
|
||||||
self.buf.push_front((Event::Html(format!("{sec} ").into()), 0..0));
|
self.buf.push_front((Event::Html(format!("{sec} ").into()), 0..0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1415,10 +1416,23 @@ impl Markdown<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MarkdownWithToc<'_> {
|
impl MarkdownWithToc<'_> {
|
||||||
pub(crate) fn into_string(self) -> String {
|
pub(crate) fn into_parts(self) -> (Toc, String) {
|
||||||
let MarkdownWithToc { content: md, ids, error_codes: codes, edition, playground } = self;
|
let MarkdownWithToc { content: md, links, ids, error_codes: codes, edition, playground } =
|
||||||
|
self;
|
||||||
|
|
||||||
let p = Parser::new_ext(md, main_body_opts()).into_offset_iter();
|
// This is actually common enough to special-case
|
||||||
|
if md.is_empty() {
|
||||||
|
return (Toc { entries: Vec::new() }, String::new());
|
||||||
|
}
|
||||||
|
let mut replacer = |broken_link: BrokenLink<'_>| {
|
||||||
|
links
|
||||||
|
.iter()
|
||||||
|
.find(|link| &*link.original_text == &*broken_link.reference)
|
||||||
|
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
|
||||||
|
};
|
||||||
|
|
||||||
|
let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(&mut replacer));
|
||||||
|
let p = p.into_offset_iter();
|
||||||
|
|
||||||
let mut s = String::with_capacity(md.len() * 3 / 2);
|
let mut s = String::with_capacity(md.len() * 3 / 2);
|
||||||
|
|
||||||
@ -1432,7 +1446,11 @@ impl MarkdownWithToc<'_> {
|
|||||||
html::push_html(&mut s, p);
|
html::push_html(&mut s, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
format!("<nav id=\"TOC\">{toc}</nav>{s}", toc = toc.into_toc().print())
|
(toc.into_toc(), s)
|
||||||
|
}
|
||||||
|
pub(crate) fn into_string(self) -> String {
|
||||||
|
let (toc, s) = self.into_parts();
|
||||||
|
format!("<nav id=\"TOC\">{toc}</nav>{s}", toc = toc.print())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1611,7 +1629,16 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin
|
|||||||
|
|
||||||
let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer));
|
let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer));
|
||||||
|
|
||||||
for event in p {
|
plain_text_from_events(p, &mut s);
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn plain_text_from_events<'a>(
|
||||||
|
events: impl Iterator<Item = pulldown_cmark::Event<'a>>,
|
||||||
|
s: &mut String,
|
||||||
|
) {
|
||||||
|
for event in events {
|
||||||
match &event {
|
match &event {
|
||||||
Event::Text(text) => s.push_str(text),
|
Event::Text(text) => s.push_str(text),
|
||||||
Event::Code(code) => {
|
Event::Code(code) => {
|
||||||
@ -1626,8 +1653,6 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -616,7 +616,8 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
|
|||||||
let all = shared.all.replace(AllTypes::new());
|
let all = shared.all.replace(AllTypes::new());
|
||||||
let mut sidebar = Buffer::html();
|
let mut sidebar = Buffer::html();
|
||||||
|
|
||||||
let blocks = sidebar_module_like(all.item_sections());
|
// all.html is not customizable, so a blank id map is fine
|
||||||
|
let blocks = sidebar_module_like(all.item_sections(), &mut IdMap::new());
|
||||||
let bar = Sidebar {
|
let bar = Sidebar {
|
||||||
title_prefix: "",
|
title_prefix: "",
|
||||||
title: "",
|
title: "",
|
||||||
|
@ -7,12 +7,13 @@ use rustc_hir::def::CtorKind;
|
|||||||
use rustc_hir::def_id::DefIdSet;
|
use rustc_hir::def_id::DefIdSet;
|
||||||
use rustc_middle::ty::{self, TyCtxt};
|
use rustc_middle::ty::{self, TyCtxt};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
clean,
|
||||||
|
formats::{item_type::ItemType, Impl},
|
||||||
|
html::{format::Buffer, markdown::IdMap, markdown::MarkdownWithToc},
|
||||||
|
};
|
||||||
|
|
||||||
use super::{item_ty_to_section, Context, ItemSection};
|
use super::{item_ty_to_section, Context, ItemSection};
|
||||||
use crate::clean;
|
|
||||||
use crate::formats::item_type::ItemType;
|
|
||||||
use crate::formats::Impl;
|
|
||||||
use crate::html::format::Buffer;
|
|
||||||
use crate::html::markdown::IdMap;
|
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "sidebar.html")]
|
#[template(path = "sidebar.html")]
|
||||||
@ -66,11 +67,13 @@ pub(crate) struct Link<'a> {
|
|||||||
name: Cow<'a, str>,
|
name: Cow<'a, str>,
|
||||||
/// The id of an anchor within the page (without a `#` prefix)
|
/// The id of an anchor within the page (without a `#` prefix)
|
||||||
href: Cow<'a, str>,
|
href: Cow<'a, str>,
|
||||||
|
/// Nested list of links (used only in top-toc)
|
||||||
|
children: Vec<Link<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Link<'a> {
|
impl<'a> Link<'a> {
|
||||||
pub fn new(href: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>) -> Self {
|
pub fn new(href: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>) -> Self {
|
||||||
Self { href: href.into(), name: name.into() }
|
Self { href: href.into(), name: name.into(), children: vec![] }
|
||||||
}
|
}
|
||||||
pub fn empty() -> Link<'static> {
|
pub fn empty() -> Link<'static> {
|
||||||
Link::new("", "")
|
Link::new("", "")
|
||||||
@ -94,17 +97,19 @@ pub(crate) mod filters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) {
|
pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) {
|
||||||
let blocks: Vec<LinkBlock<'_>> = match *it.kind {
|
let mut ids = IdMap::new();
|
||||||
clean::StructItem(ref s) => sidebar_struct(cx, it, s),
|
let mut blocks: Vec<LinkBlock<'_>> = docblock_toc(cx, it, &mut ids).into_iter().collect();
|
||||||
clean::TraitItem(ref t) => sidebar_trait(cx, it, t),
|
match *it.kind {
|
||||||
clean::PrimitiveItem(_) => sidebar_primitive(cx, it),
|
clean::StructItem(ref s) => sidebar_struct(cx, it, s, &mut blocks),
|
||||||
clean::UnionItem(ref u) => sidebar_union(cx, it, u),
|
clean::TraitItem(ref t) => sidebar_trait(cx, it, t, &mut blocks),
|
||||||
clean::EnumItem(ref e) => sidebar_enum(cx, it, e),
|
clean::PrimitiveItem(_) => sidebar_primitive(cx, it, &mut blocks),
|
||||||
clean::TypeAliasItem(ref t) => sidebar_type_alias(cx, it, t),
|
clean::UnionItem(ref u) => sidebar_union(cx, it, u, &mut blocks),
|
||||||
clean::ModuleItem(ref m) => vec![sidebar_module(&m.items)],
|
clean::EnumItem(ref e) => sidebar_enum(cx, it, e, &mut blocks),
|
||||||
clean::ForeignTypeItem => sidebar_foreign_type(cx, it),
|
clean::TypeAliasItem(ref t) => sidebar_type_alias(cx, it, t, &mut blocks),
|
||||||
_ => vec![],
|
clean::ModuleItem(ref m) => blocks.push(sidebar_module(&m.items, &mut ids)),
|
||||||
};
|
clean::ForeignTypeItem => sidebar_foreign_type(cx, it, &mut blocks),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
// The sidebar is designed to display sibling functions, modules and
|
// The sidebar is designed to display sibling functions, modules and
|
||||||
// other miscellaneous information. since there are lots of sibling
|
// other miscellaneous information. since there are lots of sibling
|
||||||
// items (and that causes quadratic growth in large modules),
|
// items (and that causes quadratic growth in large modules),
|
||||||
@ -112,15 +117,9 @@ pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buf
|
|||||||
// still, we don't move everything into JS because we want to preserve
|
// still, we don't move everything into JS because we want to preserve
|
||||||
// as much HTML as possible in order to allow non-JS-enabled browsers
|
// as much HTML as possible in order to allow non-JS-enabled browsers
|
||||||
// to navigate the documentation (though slightly inefficiently).
|
// to navigate the documentation (though slightly inefficiently).
|
||||||
let (title_prefix, title) = if it.is_struct()
|
//
|
||||||
|| it.is_trait()
|
// crate title is displayed as part of logo lockup
|
||||||
|| it.is_primitive()
|
let (title_prefix, title) = if !blocks.is_empty() && !it.is_crate() {
|
||||||
|| it.is_union()
|
|
||||||
|| it.is_enum()
|
|
||||||
// crate title is displayed as part of logo lockup
|
|
||||||
|| (it.is_mod() && !it.is_crate())
|
|
||||||
|| it.is_type_alias()
|
|
||||||
{
|
|
||||||
(
|
(
|
||||||
match *it.kind {
|
match *it.kind {
|
||||||
clean::ModuleItem(..) => "Module ",
|
clean::ModuleItem(..) => "Module ",
|
||||||
@ -162,30 +161,75 @@ fn get_struct_fields_name<'a>(fields: &'a [clean::Item]) -> Vec<Link<'a>> {
|
|||||||
fields
|
fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn docblock_toc<'a>(
|
||||||
|
cx: &'a Context<'_>,
|
||||||
|
it: &'a clean::Item,
|
||||||
|
ids: &mut IdMap,
|
||||||
|
) -> Option<LinkBlock<'a>> {
|
||||||
|
let (toc, _) = MarkdownWithToc {
|
||||||
|
content: &it.doc_value(),
|
||||||
|
links: &it.links(cx),
|
||||||
|
ids,
|
||||||
|
error_codes: cx.shared.codes,
|
||||||
|
edition: cx.shared.edition(),
|
||||||
|
playground: &cx.shared.playground,
|
||||||
|
custom_code_classes_in_docs: cx.tcx().features().custom_code_classes_in_docs,
|
||||||
|
}
|
||||||
|
.into_parts();
|
||||||
|
let links: Vec<Link<'_>> = toc
|
||||||
|
.entries
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| {
|
||||||
|
Link {
|
||||||
|
name: entry.name.into(),
|
||||||
|
href: entry.id.into(),
|
||||||
|
children: entry
|
||||||
|
.children
|
||||||
|
.entries
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| Link {
|
||||||
|
name: entry.name.into(),
|
||||||
|
href: entry.id.into(),
|
||||||
|
// Only a single level of nesting is shown here.
|
||||||
|
// Going the full six could break the layout,
|
||||||
|
// so we have to cut it off somewhere.
|
||||||
|
children: vec![],
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if links.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(LinkBlock::new(Link::new("#", "Sections"), "top-toc", links))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn sidebar_struct<'a>(
|
fn sidebar_struct<'a>(
|
||||||
cx: &'a Context<'_>,
|
cx: &'a Context<'_>,
|
||||||
it: &'a clean::Item,
|
it: &'a clean::Item,
|
||||||
s: &'a clean::Struct,
|
s: &'a clean::Struct,
|
||||||
) -> Vec<LinkBlock<'a>> {
|
items: &mut Vec<LinkBlock<'a>>,
|
||||||
|
) {
|
||||||
let fields = get_struct_fields_name(&s.fields);
|
let fields = get_struct_fields_name(&s.fields);
|
||||||
let field_name = match s.ctor_kind {
|
let field_name = match s.ctor_kind {
|
||||||
Some(CtorKind::Fn) => Some("Tuple Fields"),
|
Some(CtorKind::Fn) => Some("Tuple Fields"),
|
||||||
None => Some("Fields"),
|
None => Some("Fields"),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let mut items = vec![];
|
|
||||||
if let Some(name) = field_name {
|
if let Some(name) = field_name {
|
||||||
items.push(LinkBlock::new(Link::new("fields", name), "structfield", fields));
|
items.push(LinkBlock::new(Link::new("fields", name), "structfield", fields));
|
||||||
}
|
}
|
||||||
sidebar_assoc_items(cx, it, &mut items);
|
sidebar_assoc_items(cx, it, items);
|
||||||
items
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sidebar_trait<'a>(
|
fn sidebar_trait<'a>(
|
||||||
cx: &'a Context<'_>,
|
cx: &'a Context<'_>,
|
||||||
it: &'a clean::Item,
|
it: &'a clean::Item,
|
||||||
t: &'a clean::Trait,
|
t: &'a clean::Trait,
|
||||||
) -> Vec<LinkBlock<'a>> {
|
blocks: &mut Vec<LinkBlock<'a>>,
|
||||||
|
) {
|
||||||
fn filter_items<'a>(
|
fn filter_items<'a>(
|
||||||
items: &'a [clean::Item],
|
items: &'a [clean::Item],
|
||||||
filt: impl Fn(&clean::Item) -> bool,
|
filt: impl Fn(&clean::Item) -> bool,
|
||||||
@ -222,19 +266,20 @@ fn sidebar_trait<'a>(
|
|||||||
foreign_impls.sort();
|
foreign_impls.sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut blocks: Vec<LinkBlock<'_>> = [
|
blocks.extend(
|
||||||
("required-associated-types", "Required Associated Types", req_assoc),
|
[
|
||||||
("provided-associated-types", "Provided Associated Types", prov_assoc),
|
("required-associated-types", "Required Associated Types", req_assoc),
|
||||||
("required-associated-consts", "Required Associated Constants", req_assoc_const),
|
("provided-associated-types", "Provided Associated Types", prov_assoc),
|
||||||
("provided-associated-consts", "Provided Associated Constants", prov_assoc_const),
|
("required-associated-consts", "Required Associated Constants", req_assoc_const),
|
||||||
("required-methods", "Required Methods", req_method),
|
("provided-associated-consts", "Provided Associated Constants", prov_assoc_const),
|
||||||
("provided-methods", "Provided Methods", prov_method),
|
("required-methods", "Required Methods", req_method),
|
||||||
("foreign-impls", "Implementations on Foreign Types", foreign_impls),
|
("provided-methods", "Provided Methods", prov_method),
|
||||||
]
|
("foreign-impls", "Implementations on Foreign Types", foreign_impls),
|
||||||
.into_iter()
|
]
|
||||||
.map(|(id, title, items)| LinkBlock::new(Link::new(id, title), "", items))
|
.into_iter()
|
||||||
.collect();
|
.map(|(id, title, items)| LinkBlock::new(Link::new(id, title), "", items)),
|
||||||
sidebar_assoc_items(cx, it, &mut blocks);
|
);
|
||||||
|
sidebar_assoc_items(cx, it, blocks);
|
||||||
|
|
||||||
if !t.is_object_safe(cx.tcx()) {
|
if !t.is_object_safe(cx.tcx()) {
|
||||||
blocks.push(LinkBlock::forced(
|
blocks.push(LinkBlock::forced(
|
||||||
@ -250,20 +295,17 @@ fn sidebar_trait<'a>(
|
|||||||
"impl-auto",
|
"impl-auto",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
blocks
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> {
|
fn sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item, items: &mut Vec<LinkBlock<'a>>) {
|
||||||
if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) {
|
if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) {
|
||||||
let mut items = vec![];
|
sidebar_assoc_items(cx, it, items);
|
||||||
sidebar_assoc_items(cx, it, &mut items);
|
|
||||||
items
|
|
||||||
} else {
|
} else {
|
||||||
let shared = Rc::clone(&cx.shared);
|
let shared = Rc::clone(&cx.shared);
|
||||||
let (concrete, synthetic, blanket_impl) =
|
let (concrete, synthetic, blanket_impl) =
|
||||||
super::get_filtered_impls_for_reference(&shared, it);
|
super::get_filtered_impls_for_reference(&shared, it);
|
||||||
|
|
||||||
sidebar_render_assoc_items(cx, &mut IdMap::new(), concrete, synthetic, blanket_impl).into()
|
sidebar_render_assoc_items(cx, &mut IdMap::new(), concrete, synthetic, blanket_impl, items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,8 +313,8 @@ fn sidebar_type_alias<'a>(
|
|||||||
cx: &'a Context<'_>,
|
cx: &'a Context<'_>,
|
||||||
it: &'a clean::Item,
|
it: &'a clean::Item,
|
||||||
t: &'a clean::TypeAlias,
|
t: &'a clean::TypeAlias,
|
||||||
) -> Vec<LinkBlock<'a>> {
|
items: &mut Vec<LinkBlock<'a>>,
|
||||||
let mut items = vec![];
|
) {
|
||||||
if let Some(inner_type) = &t.inner_type {
|
if let Some(inner_type) = &t.inner_type {
|
||||||
items.push(LinkBlock::forced(Link::new("aliased-type", "Aliased type"), "type"));
|
items.push(LinkBlock::forced(Link::new("aliased-type", "Aliased type"), "type"));
|
||||||
match inner_type {
|
match inner_type {
|
||||||
@ -294,19 +336,18 @@ fn sidebar_type_alias<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sidebar_assoc_items(cx, it, &mut items);
|
sidebar_assoc_items(cx, it, items);
|
||||||
items
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sidebar_union<'a>(
|
fn sidebar_union<'a>(
|
||||||
cx: &'a Context<'_>,
|
cx: &'a Context<'_>,
|
||||||
it: &'a clean::Item,
|
it: &'a clean::Item,
|
||||||
u: &'a clean::Union,
|
u: &'a clean::Union,
|
||||||
) -> Vec<LinkBlock<'a>> {
|
items: &mut Vec<LinkBlock<'a>>,
|
||||||
|
) {
|
||||||
let fields = get_struct_fields_name(&u.fields);
|
let fields = get_struct_fields_name(&u.fields);
|
||||||
let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), "structfield", fields)];
|
items.push(LinkBlock::new(Link::new("fields", "Fields"), "structfield", fields));
|
||||||
sidebar_assoc_items(cx, it, &mut items);
|
sidebar_assoc_items(cx, it, items);
|
||||||
items
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds trait implementations into the blocks of links
|
/// Adds trait implementations into the blocks of links
|
||||||
@ -345,33 +386,6 @@ fn sidebar_assoc_items<'a>(
|
|||||||
methods.sort();
|
methods.sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut deref_methods = Vec::new();
|
|
||||||
let [concrete, synthetic, blanket] = if v.iter().any(|i| i.inner_impl().trait_.is_some()) {
|
|
||||||
if let Some(impl_) =
|
|
||||||
v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait())
|
|
||||||
{
|
|
||||||
let mut derefs = DefIdSet::default();
|
|
||||||
derefs.insert(did);
|
|
||||||
sidebar_deref_methods(
|
|
||||||
cx,
|
|
||||||
&mut deref_methods,
|
|
||||||
impl_,
|
|
||||||
v,
|
|
||||||
&mut derefs,
|
|
||||||
&mut used_links,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
|
|
||||||
v.iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_auto());
|
|
||||||
let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) =
|
|
||||||
concrete.into_iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_blanket());
|
|
||||||
|
|
||||||
sidebar_render_assoc_items(cx, &mut id_map, concrete, synthetic, blanket_impl)
|
|
||||||
} else {
|
|
||||||
std::array::from_fn(|_| LinkBlock::new(Link::empty(), "", vec![]))
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut blocks = vec![
|
let mut blocks = vec![
|
||||||
LinkBlock::new(
|
LinkBlock::new(
|
||||||
Link::new("implementations", "Associated Constants"),
|
Link::new("implementations", "Associated Constants"),
|
||||||
@ -380,8 +394,30 @@ fn sidebar_assoc_items<'a>(
|
|||||||
),
|
),
|
||||||
LinkBlock::new(Link::new("implementations", "Methods"), "method", methods),
|
LinkBlock::new(Link::new("implementations", "Methods"), "method", methods),
|
||||||
];
|
];
|
||||||
blocks.append(&mut deref_methods);
|
|
||||||
blocks.extend([concrete, synthetic, blanket]);
|
if v.iter().any(|i| i.inner_impl().trait_.is_some()) {
|
||||||
|
if let Some(impl_) =
|
||||||
|
v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait())
|
||||||
|
{
|
||||||
|
let mut derefs = DefIdSet::default();
|
||||||
|
derefs.insert(did);
|
||||||
|
sidebar_deref_methods(cx, &mut blocks, impl_, v, &mut derefs, &mut used_links);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
|
||||||
|
v.iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_auto());
|
||||||
|
let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) =
|
||||||
|
concrete.into_iter().partition::<Vec<_>, _>(|i| i.inner_impl().kind.is_blanket());
|
||||||
|
|
||||||
|
sidebar_render_assoc_items(
|
||||||
|
cx,
|
||||||
|
&mut id_map,
|
||||||
|
concrete,
|
||||||
|
synthetic,
|
||||||
|
blanket_impl,
|
||||||
|
&mut blocks,
|
||||||
|
);
|
||||||
|
}
|
||||||
links.append(&mut blocks);
|
links.append(&mut blocks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -471,7 +507,8 @@ fn sidebar_enum<'a>(
|
|||||||
cx: &'a Context<'_>,
|
cx: &'a Context<'_>,
|
||||||
it: &'a clean::Item,
|
it: &'a clean::Item,
|
||||||
e: &'a clean::Enum,
|
e: &'a clean::Enum,
|
||||||
) -> Vec<LinkBlock<'a>> {
|
items: &mut Vec<LinkBlock<'a>>,
|
||||||
|
) {
|
||||||
let mut variants = e
|
let mut variants = e
|
||||||
.variants()
|
.variants()
|
||||||
.filter_map(|v| v.name)
|
.filter_map(|v| v.name)
|
||||||
@ -479,24 +516,24 @@ fn sidebar_enum<'a>(
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
variants.sort_unstable();
|
variants.sort_unstable();
|
||||||
|
|
||||||
let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), "variant", variants)];
|
items.push(LinkBlock::new(Link::new("variants", "Variants"), "variant", variants));
|
||||||
sidebar_assoc_items(cx, it, &mut items);
|
sidebar_assoc_items(cx, it, items);
|
||||||
items
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn sidebar_module_like(
|
pub(crate) fn sidebar_module_like(
|
||||||
item_sections_in_use: FxHashSet<ItemSection>,
|
item_sections_in_use: FxHashSet<ItemSection>,
|
||||||
|
ids: &mut IdMap,
|
||||||
) -> LinkBlock<'static> {
|
) -> LinkBlock<'static> {
|
||||||
let item_sections = ItemSection::ALL
|
let item_sections = ItemSection::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.filter(|sec| item_sections_in_use.contains(sec))
|
.filter(|sec| item_sections_in_use.contains(sec))
|
||||||
.map(|sec| Link::new(sec.id(), sec.name()))
|
.map(|sec| Link::new(ids.derive(sec.id()), sec.name()))
|
||||||
.collect();
|
.collect();
|
||||||
LinkBlock::new(Link::empty(), "", item_sections)
|
LinkBlock::new(Link::empty(), "", item_sections)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> {
|
fn sidebar_module(items: &[clean::Item], ids: &mut IdMap) -> LinkBlock<'static> {
|
||||||
let item_sections_in_use: FxHashSet<_> = items
|
let item_sections_in_use: FxHashSet<_> = items
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|it| {
|
.filter(|it| {
|
||||||
@ -517,13 +554,15 @@ fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> {
|
|||||||
.map(|it| item_ty_to_section(it.type_()))
|
.map(|it| item_ty_to_section(it.type_()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
sidebar_module_like(item_sections_in_use)
|
sidebar_module_like(item_sections_in_use, ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sidebar_foreign_type<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec<LinkBlock<'a>> {
|
fn sidebar_foreign_type<'a>(
|
||||||
let mut items = vec![];
|
cx: &'a Context<'_>,
|
||||||
sidebar_assoc_items(cx, it, &mut items);
|
it: &'a clean::Item,
|
||||||
items
|
items: &mut Vec<LinkBlock<'a>>,
|
||||||
|
) {
|
||||||
|
sidebar_assoc_items(cx, it, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders the trait implementations for this type
|
/// Renders the trait implementations for this type
|
||||||
@ -533,7 +572,8 @@ fn sidebar_render_assoc_items(
|
|||||||
concrete: Vec<&Impl>,
|
concrete: Vec<&Impl>,
|
||||||
synthetic: Vec<&Impl>,
|
synthetic: Vec<&Impl>,
|
||||||
blanket_impl: Vec<&Impl>,
|
blanket_impl: Vec<&Impl>,
|
||||||
) -> [LinkBlock<'static>; 3] {
|
items: &mut Vec<LinkBlock<'_>>,
|
||||||
|
) {
|
||||||
let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| {
|
let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| {
|
||||||
let mut links = FxHashSet::default();
|
let mut links = FxHashSet::default();
|
||||||
|
|
||||||
@ -558,7 +598,7 @@ fn sidebar_render_assoc_items(
|
|||||||
let concrete = format_impls(concrete, id_map);
|
let concrete = format_impls(concrete, id_map);
|
||||||
let synthetic = format_impls(synthetic, id_map);
|
let synthetic = format_impls(synthetic, id_map);
|
||||||
let blanket = format_impls(blanket_impl, id_map);
|
let blanket = format_impls(blanket_impl, id_map);
|
||||||
[
|
items.extend([
|
||||||
LinkBlock::new(
|
LinkBlock::new(
|
||||||
Link::new("trait-implementations", "Trait Implementations"),
|
Link::new("trait-implementations", "Trait Implementations"),
|
||||||
"trait-implementation",
|
"trait-implementation",
|
||||||
@ -574,7 +614,7 @@ fn sidebar_render_assoc_items(
|
|||||||
"blanket-implementation",
|
"blanket-implementation",
|
||||||
blanket,
|
blanket,
|
||||||
),
|
),
|
||||||
]
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String {
|
fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String {
|
||||||
|
@ -568,12 +568,16 @@ img {
|
|||||||
width: 48px;
|
width: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.block, .block li {
|
ul.block, .block li, .block ul {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block ul {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-elems a,
|
.sidebar-elems a,
|
||||||
.sidebar > h2 a {
|
.sidebar > h2 a {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -15,14 +15,23 @@
|
|||||||
{% for block in blocks %}
|
{% for block in blocks %}
|
||||||
{% if block.should_render() %}
|
{% if block.should_render() %}
|
||||||
{% if !block.heading.name.is_empty() %}
|
{% if !block.heading.name.is_empty() %}
|
||||||
<h3><a href="#{{block.heading.href|safe}}"> {# #}
|
<h3{% if !block.class.is_empty() +%} class="{{block.class}}"{% endif %}> {# #}
|
||||||
{{block.heading.name|wrapped|safe}} {# #}
|
<a href="#{{block.heading.href|safe}}">{{block.heading.name|wrapped|safe}}</a> {# #}
|
||||||
</a></h3> {# #}
|
</h3> {# #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if !block.links.is_empty() %}
|
{% if !block.links.is_empty() %}
|
||||||
<ul class="block{% if !block.class.is_empty() +%} {{+block.class}}{% endif %}">
|
<ul class="block{% if !block.class.is_empty() +%} {{+block.class}}{% endif %}">
|
||||||
{% for link in block.links %}
|
{% for link in block.links %}
|
||||||
<li><a href="#{{link.href|safe}}">{{link.name}}</a></li>
|
<li> {# #}
|
||||||
|
<a href="#{{link.href|safe}}">{{link.name}}</a> {# #}
|
||||||
|
{% if !link.children.is_empty() %}
|
||||||
|
<ul> {# #}
|
||||||
|
{% for child in link.children %}
|
||||||
|
<li><a href="#{{child.href|safe}}">{{child.name}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul> {# #}
|
||||||
|
{% endif %}
|
||||||
|
</li> {# #}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
//! Table-of-contents creation.
|
//! Table-of-contents creation.
|
||||||
|
use crate::html::escape::EscapeBodyText;
|
||||||
|
|
||||||
/// A (recursive) table of contents
|
/// A (recursive) table of contents
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -16,7 +17,7 @@ pub(crate) struct Toc {
|
|||||||
/// ### A
|
/// ### A
|
||||||
/// ## B
|
/// ## B
|
||||||
/// ```
|
/// ```
|
||||||
entries: Vec<TocEntry>,
|
pub(crate) entries: Vec<TocEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Toc {
|
impl Toc {
|
||||||
@ -27,11 +28,11 @@ impl Toc {
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub(crate) struct TocEntry {
|
pub(crate) struct TocEntry {
|
||||||
level: u32,
|
pub(crate) level: u32,
|
||||||
sec_number: String,
|
pub(crate) sec_number: String,
|
||||||
name: String,
|
pub(crate) name: String,
|
||||||
id: String,
|
pub(crate) id: String,
|
||||||
children: Toc,
|
pub(crate) children: Toc,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Progressive construction of a table of contents.
|
/// Progressive construction of a table of contents.
|
||||||
@ -173,7 +174,7 @@ impl Toc {
|
|||||||
"\n<li><a href=\"#{id}\">{num} {name}</a>",
|
"\n<li><a href=\"#{id}\">{num} {name}</a>",
|
||||||
id = entry.id,
|
id = entry.id,
|
||||||
num = entry.sec_number,
|
num = entry.sec_number,
|
||||||
name = entry.name
|
name = EscapeBodyText(&entry.name)
|
||||||
);
|
);
|
||||||
entry.children.print_inner(&mut *v);
|
entry.children.print_inner(&mut *v);
|
||||||
v.push_str("</li>");
|
v.push_str("</li>");
|
||||||
|
@ -72,6 +72,7 @@ pub(crate) fn render<P: AsRef<Path>>(
|
|||||||
let text = if !options.markdown_no_toc {
|
let text = if !options.markdown_no_toc {
|
||||||
MarkdownWithToc {
|
MarkdownWithToc {
|
||||||
content: text,
|
content: text,
|
||||||
|
links: &[],
|
||||||
ids: &mut ids,
|
ids: &mut ids,
|
||||||
error_codes,
|
error_codes,
|
||||||
edition,
|
edition,
|
||||||
|
@ -118,7 +118,7 @@ assert-false: ".sidebar-elems > .crate"
|
|||||||
go-to: "./module/index.html"
|
go-to: "./module/index.html"
|
||||||
assert-property: (".sidebar", {"clientWidth": "200"})
|
assert-property: (".sidebar", {"clientWidth": "200"})
|
||||||
assert-text: (".sidebar > .sidebar-crate > h2 > a", "lib2")
|
assert-text: (".sidebar > .sidebar-crate > h2 > a", "lib2")
|
||||||
assert-text: (".sidebar > .location", "Module module")
|
assert-text: (".sidebar .location", "Module module")
|
||||||
assert-count: (".sidebar .location", 1)
|
assert-count: (".sidebar .location", 1)
|
||||||
assert-text: (".sidebar-elems ul.block > li.current > a", "module")
|
assert-text: (".sidebar-elems ul.block > li.current > a", "module")
|
||||||
// Module page requires three headings:
|
// Module page requires three headings:
|
||||||
@ -136,7 +136,7 @@ assert-false: ".sidebar-elems > .crate"
|
|||||||
go-to: "./sub_module/sub_sub_module/index.html"
|
go-to: "./sub_module/sub_sub_module/index.html"
|
||||||
assert-property: (".sidebar", {"clientWidth": "200"})
|
assert-property: (".sidebar", {"clientWidth": "200"})
|
||||||
assert-text: (".sidebar > .sidebar-crate > h2 > a", "lib2")
|
assert-text: (".sidebar > .sidebar-crate > h2 > a", "lib2")
|
||||||
assert-text: (".sidebar > .location", "Module sub_sub_module")
|
assert-text: (".sidebar .location", "Module sub_sub_module")
|
||||||
assert-text: (".sidebar > .sidebar-elems > h2", "In lib2::module::sub_module")
|
assert-text: (".sidebar > .sidebar-elems > h2", "In lib2::module::sub_module")
|
||||||
assert-property: (".sidebar > .sidebar-elems > h2 > a", {
|
assert-property: (".sidebar > .sidebar-elems > h2 > a", {
|
||||||
"href": "/module/sub_module/index.html",
|
"href": "/module/sub_module/index.html",
|
||||||
|
19
tests/rustdoc/sidebar/top-toc-html.rs
Normal file
19
tests/rustdoc/sidebar/top-toc-html.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// ignore-tidy-linelength
|
||||||
|
|
||||||
|
#![crate_name = "foo"]
|
||||||
|
#![feature(lazy_type_alias)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
|
//! # Basic [link](https://example.com) and *emphasis*
|
||||||
|
//!
|
||||||
|
//! This test case covers TOC entries with rich text inside.
|
||||||
|
//! Rustdoc normally supports headers with links, but for the
|
||||||
|
//! TOC, that would break the layout.
|
||||||
|
//!
|
||||||
|
//! For consistency, emphasis is also filtered out.
|
||||||
|
|
||||||
|
// @has foo/index.html
|
||||||
|
// User header
|
||||||
|
// @has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis"]' 'Basic link and emphasis'
|
||||||
|
// @count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis"]/em' 0
|
||||||
|
// @count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis"]/a' 0
|
44
tests/rustdoc/sidebar/top-toc-idmap.rs
Normal file
44
tests/rustdoc/sidebar/top-toc-idmap.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#![crate_name = "foo"]
|
||||||
|
#![feature(lazy_type_alias)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
|
//! # Structs
|
||||||
|
//!
|
||||||
|
//! This header has the same name as a built-in header,
|
||||||
|
//! and we need to make sure they're disambiguated with
|
||||||
|
//! suffixes.
|
||||||
|
//!
|
||||||
|
//! Module-like headers get derived from the internal ID map,
|
||||||
|
//! so the *internal* one gets a suffix here. To make sure it
|
||||||
|
//! works right, the one in the `top-toc` needs to match the one
|
||||||
|
//! in the `top-doc`, and the one that's not in the `top-doc`
|
||||||
|
//! needs to match the one that isn't in the `top-toc`.
|
||||||
|
|
||||||
|
// @has foo/index.html
|
||||||
|
// User header
|
||||||
|
// @has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#structs"]' 'Structs'
|
||||||
|
// @has - '//details[@class="toggle top-doc"]/div[@class="docblock"]/h2[@id="structs"]' 'Structs'
|
||||||
|
// Built-in header
|
||||||
|
// @has - '//section[@id="TOC"]/ul[@class="block"]/li/a[@href="#structs-1"]' 'Structs'
|
||||||
|
// @has - '//section[@id="main-content"]/h2[@id="structs-1"]' 'Structs'
|
||||||
|
|
||||||
|
/// # Fields
|
||||||
|
/// ## Fields
|
||||||
|
/// ### Fields
|
||||||
|
///
|
||||||
|
/// The difference between struct-like headers and module-like headers
|
||||||
|
/// is strange, but not actually a problem as long as we're consistent.
|
||||||
|
|
||||||
|
// @has foo/struct.MyStruct.html
|
||||||
|
// User header
|
||||||
|
// @has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#fields-1"]' 'Fields'
|
||||||
|
// @has - '//details[@class="toggle top-doc"]/div[@class="docblock"]/h2[@id="fields-1"]' 'Fields'
|
||||||
|
// Only one level of nesting
|
||||||
|
// @count - '//section[@id="TOC"]/ul[@class="block top-toc"]//a' 2
|
||||||
|
// Built-in header
|
||||||
|
// @has - '//section[@id="TOC"]/h3/a[@href="#fields"]' 'Fields'
|
||||||
|
// @has - '//section[@id="main-content"]/h2[@id="fields"]' 'Fields'
|
||||||
|
|
||||||
|
pub struct MyStruct {
|
||||||
|
pub fields: i32,
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user