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 @@ pub(crate) fn is_crate(&self) -> bool {
|
||||
pub(crate) fn is_mod(&self) -> bool {
|
||||
self.type_() == ItemType::Module
|
||||
}
|
||||
pub(crate) fn is_trait(&self) -> bool {
|
||||
self.type_() == ItemType::Trait
|
||||
}
|
||||
pub(crate) fn is_struct(&self) -> bool {
|
||||
self.type_() == ItemType::Struct
|
||||
}
|
||||
@ -536,9 +533,6 @@ pub(crate) fn is_method(&self) -> bool {
|
||||
pub(crate) fn is_ty_method(&self) -> bool {
|
||||
self.type_() == ItemType::TyMethod
|
||||
}
|
||||
pub(crate) fn is_type_alias(&self) -> bool {
|
||||
self.type_() == ItemType::TypeAlias
|
||||
}
|
||||
pub(crate) fn is_primitive(&self) -> bool {
|
||||
self.type_() == ItemType::Primitive
|
||||
}
|
||||
|
@ -55,7 +55,7 @@
|
||||
use crate::html::highlight;
|
||||
use crate::html::length_limit::HtmlWithLimit;
|
||||
use crate::html::render::small_url_encode;
|
||||
use crate::html::toc::TocBuilder;
|
||||
use crate::html::toc::{Toc, TocBuilder};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@ -101,6 +101,7 @@ pub struct Markdown<'a> {
|
||||
/// A struct like `Markdown` that renders the markdown with a table of contents.
|
||||
pub(crate) struct MarkdownWithToc<'a> {
|
||||
pub(crate) content: &'a str,
|
||||
pub(crate) links: &'a [RenderedLink],
|
||||
pub(crate) ids: &'a mut IdMap,
|
||||
pub(crate) error_codes: ErrorCodes,
|
||||
pub(crate) edition: Edition,
|
||||
@ -532,9 +533,9 @@ fn next(&mut self) -> Option<Self::Item> {
|
||||
let id = self.id_map.derive(id);
|
||||
|
||||
if let Some(ref mut builder) = self.toc {
|
||||
let mut html_header = String::new();
|
||||
html::push_html(&mut html_header, self.buf.iter().map(|(ev, _)| ev.clone()));
|
||||
let sec = builder.push(level as u32, html_header, id.clone());
|
||||
let mut text_header = String::new();
|
||||
plain_text_from_events(self.buf.iter().map(|(ev, _)| ev.clone()), &mut text_header);
|
||||
let sec = builder.push(level as u32, text_header, id.clone());
|
||||
self.buf.push_front((Event::Html(format!("{sec} ").into()), 0..0));
|
||||
}
|
||||
|
||||
@ -1415,10 +1416,23 @@ pub fn into_string(self) -> String {
|
||||
}
|
||||
|
||||
impl MarkdownWithToc<'_> {
|
||||
pub(crate) fn into_string(self) -> String {
|
||||
let MarkdownWithToc { content: md, ids, error_codes: codes, edition, playground } = self;
|
||||
pub(crate) fn into_parts(self) -> (Toc, String) {
|
||||
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);
|
||||
|
||||
@ -1432,7 +1446,11 @@ pub(crate) fn into_string(self) -> String {
|
||||
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));
|
||||
|
||||
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 {
|
||||
Event::Text(text) => s.push_str(text),
|
||||
Event::Code(code) => {
|
||||
@ -1626,8 +1653,6 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -616,7 +616,8 @@ fn after_krate(&mut self) -> Result<(), Error> {
|
||||
let all = shared.all.replace(AllTypes::new());
|
||||
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 {
|
||||
title_prefix: "",
|
||||
title: "",
|
||||
|
@ -7,12 +7,13 @@
|
||||
use rustc_hir::def_id::DefIdSet;
|
||||
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 crate::clean;
|
||||
use crate::formats::item_type::ItemType;
|
||||
use crate::formats::Impl;
|
||||
use crate::html::format::Buffer;
|
||||
use crate::html::markdown::IdMap;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "sidebar.html")]
|
||||
@ -66,11 +67,13 @@ pub(crate) struct Link<'a> {
|
||||
name: Cow<'a, str>,
|
||||
/// The id of an anchor within the page (without a `#` prefix)
|
||||
href: Cow<'a, str>,
|
||||
/// Nested list of links (used only in top-toc)
|
||||
children: Vec<Link<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Link<'a> {
|
||||
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> {
|
||||
Link::new("", "")
|
||||
@ -94,17 +97,19 @@ pub(crate) fn wrapped<T>(v: T) -> rinja::Result<Safe<impl Display>>
|
||||
}
|
||||
|
||||
pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) {
|
||||
let blocks: Vec<LinkBlock<'_>> = match *it.kind {
|
||||
clean::StructItem(ref s) => sidebar_struct(cx, it, s),
|
||||
clean::TraitItem(ref t) => sidebar_trait(cx, it, t),
|
||||
clean::PrimitiveItem(_) => sidebar_primitive(cx, it),
|
||||
clean::UnionItem(ref u) => sidebar_union(cx, it, u),
|
||||
clean::EnumItem(ref e) => sidebar_enum(cx, it, e),
|
||||
clean::TypeAliasItem(ref t) => sidebar_type_alias(cx, it, t),
|
||||
clean::ModuleItem(ref m) => vec![sidebar_module(&m.items)],
|
||||
clean::ForeignTypeItem => sidebar_foreign_type(cx, it),
|
||||
_ => vec![],
|
||||
};
|
||||
let mut ids = IdMap::new();
|
||||
let mut blocks: Vec<LinkBlock<'_>> = docblock_toc(cx, it, &mut ids).into_iter().collect();
|
||||
match *it.kind {
|
||||
clean::StructItem(ref s) => sidebar_struct(cx, it, s, &mut blocks),
|
||||
clean::TraitItem(ref t) => sidebar_trait(cx, it, t, &mut blocks),
|
||||
clean::PrimitiveItem(_) => sidebar_primitive(cx, it, &mut blocks),
|
||||
clean::UnionItem(ref u) => sidebar_union(cx, it, u, &mut blocks),
|
||||
clean::EnumItem(ref e) => sidebar_enum(cx, it, e, &mut blocks),
|
||||
clean::TypeAliasItem(ref t) => sidebar_type_alias(cx, it, t, &mut blocks),
|
||||
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
|
||||
// other miscellaneous information. since there are lots of sibling
|
||||
// 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
|
||||
// as much HTML as possible in order to allow non-JS-enabled browsers
|
||||
// to navigate the documentation (though slightly inefficiently).
|
||||
let (title_prefix, title) = if it.is_struct()
|
||||
|| it.is_trait()
|
||||
|| it.is_primitive()
|
||||
|| 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()
|
||||
{
|
||||
let (title_prefix, title) = if !blocks.is_empty() && !it.is_crate() {
|
||||
(
|
||||
match *it.kind {
|
||||
clean::ModuleItem(..) => "Module ",
|
||||
@ -162,30 +161,75 @@ fn get_struct_fields_name<'a>(fields: &'a [clean::Item]) -> Vec<Link<'a>> {
|
||||
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>(
|
||||
cx: &'a Context<'_>,
|
||||
it: &'a clean::Item,
|
||||
s: &'a clean::Struct,
|
||||
) -> Vec<LinkBlock<'a>> {
|
||||
items: &mut Vec<LinkBlock<'a>>,
|
||||
) {
|
||||
let fields = get_struct_fields_name(&s.fields);
|
||||
let field_name = match s.ctor_kind {
|
||||
Some(CtorKind::Fn) => Some("Tuple Fields"),
|
||||
None => Some("Fields"),
|
||||
_ => None,
|
||||
};
|
||||
let mut items = vec![];
|
||||
if let Some(name) = field_name {
|
||||
items.push(LinkBlock::new(Link::new("fields", name), "structfield", fields));
|
||||
}
|
||||
sidebar_assoc_items(cx, it, &mut items);
|
||||
items
|
||||
sidebar_assoc_items(cx, it, items);
|
||||
}
|
||||
|
||||
fn sidebar_trait<'a>(
|
||||
cx: &'a Context<'_>,
|
||||
it: &'a clean::Item,
|
||||
t: &'a clean::Trait,
|
||||
) -> Vec<LinkBlock<'a>> {
|
||||
blocks: &mut Vec<LinkBlock<'a>>,
|
||||
) {
|
||||
fn filter_items<'a>(
|
||||
items: &'a [clean::Item],
|
||||
filt: impl Fn(&clean::Item) -> bool,
|
||||
@ -222,7 +266,8 @@ fn filter_items<'a>(
|
||||
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-consts", "Required Associated Constants", req_assoc_const),
|
||||
@ -232,9 +277,9 @@ fn filter_items<'a>(
|
||||
("foreign-impls", "Implementations on Foreign Types", foreign_impls),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(id, title, items)| LinkBlock::new(Link::new(id, title), "", items))
|
||||
.collect();
|
||||
sidebar_assoc_items(cx, it, &mut blocks);
|
||||
.map(|(id, title, items)| LinkBlock::new(Link::new(id, title), "", items)),
|
||||
);
|
||||
sidebar_assoc_items(cx, it, blocks);
|
||||
|
||||
if !t.is_object_safe(cx.tcx()) {
|
||||
blocks.push(LinkBlock::forced(
|
||||
@ -250,20 +295,17 @@ fn filter_items<'a>(
|
||||
"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) {
|
||||
let mut items = vec![];
|
||||
sidebar_assoc_items(cx, it, &mut items);
|
||||
items
|
||||
sidebar_assoc_items(cx, it, items);
|
||||
} else {
|
||||
let shared = Rc::clone(&cx.shared);
|
||||
let (concrete, synthetic, blanket_impl) =
|
||||
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<'_>,
|
||||
it: &'a clean::Item,
|
||||
t: &'a clean::TypeAlias,
|
||||
) -> Vec<LinkBlock<'a>> {
|
||||
let mut items = vec![];
|
||||
items: &mut Vec<LinkBlock<'a>>,
|
||||
) {
|
||||
if let Some(inner_type) = &t.inner_type {
|
||||
items.push(LinkBlock::forced(Link::new("aliased-type", "Aliased type"), "type"));
|
||||
match inner_type {
|
||||
@ -294,19 +336,18 @@ fn sidebar_type_alias<'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
sidebar_assoc_items(cx, it, &mut items);
|
||||
items
|
||||
sidebar_assoc_items(cx, it, items);
|
||||
}
|
||||
|
||||
fn sidebar_union<'a>(
|
||||
cx: &'a Context<'_>,
|
||||
it: &'a clean::Item,
|
||||
u: &'a clean::Union,
|
||||
) -> Vec<LinkBlock<'a>> {
|
||||
items: &mut Vec<LinkBlock<'a>>,
|
||||
) {
|
||||
let fields = get_struct_fields_name(&u.fields);
|
||||
let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), "structfield", fields)];
|
||||
sidebar_assoc_items(cx, it, &mut items);
|
||||
items
|
||||
items.push(LinkBlock::new(Link::new("fields", "Fields"), "structfield", fields));
|
||||
sidebar_assoc_items(cx, it, items);
|
||||
}
|
||||
|
||||
/// Adds trait implementations into the blocks of links
|
||||
@ -345,33 +386,6 @@ fn sidebar_assoc_items<'a>(
|
||||
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![
|
||||
LinkBlock::new(
|
||||
Link::new("implementations", "Associated Constants"),
|
||||
@ -380,8 +394,30 @@ fn sidebar_assoc_items<'a>(
|
||||
),
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -471,7 +507,8 @@ fn sidebar_enum<'a>(
|
||||
cx: &'a Context<'_>,
|
||||
it: &'a clean::Item,
|
||||
e: &'a clean::Enum,
|
||||
) -> Vec<LinkBlock<'a>> {
|
||||
items: &mut Vec<LinkBlock<'a>>,
|
||||
) {
|
||||
let mut variants = e
|
||||
.variants()
|
||||
.filter_map(|v| v.name)
|
||||
@ -479,24 +516,24 @@ fn sidebar_enum<'a>(
|
||||
.collect::<Vec<_>>();
|
||||
variants.sort_unstable();
|
||||
|
||||
let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), "variant", variants)];
|
||||
sidebar_assoc_items(cx, it, &mut items);
|
||||
items
|
||||
items.push(LinkBlock::new(Link::new("variants", "Variants"), "variant", variants));
|
||||
sidebar_assoc_items(cx, it, items);
|
||||
}
|
||||
|
||||
pub(crate) fn sidebar_module_like(
|
||||
item_sections_in_use: FxHashSet<ItemSection>,
|
||||
ids: &mut IdMap,
|
||||
) -> LinkBlock<'static> {
|
||||
let item_sections = ItemSection::ALL
|
||||
.iter()
|
||||
.copied()
|
||||
.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();
|
||||
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
|
||||
.iter()
|
||||
.filter(|it| {
|
||||
@ -517,13 +554,15 @@ fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> {
|
||||
.map(|it| item_ty_to_section(it.type_()))
|
||||
.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>> {
|
||||
let mut items = vec![];
|
||||
sidebar_assoc_items(cx, it, &mut items);
|
||||
items
|
||||
fn sidebar_foreign_type<'a>(
|
||||
cx: &'a Context<'_>,
|
||||
it: &'a clean::Item,
|
||||
items: &mut Vec<LinkBlock<'a>>,
|
||||
) {
|
||||
sidebar_assoc_items(cx, it, items);
|
||||
}
|
||||
|
||||
/// Renders the trait implementations for this type
|
||||
@ -533,7 +572,8 @@ fn sidebar_render_assoc_items(
|
||||
concrete: Vec<&Impl>,
|
||||
synthetic: Vec<&Impl>,
|
||||
blanket_impl: Vec<&Impl>,
|
||||
) -> [LinkBlock<'static>; 3] {
|
||||
items: &mut Vec<LinkBlock<'_>>,
|
||||
) {
|
||||
let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| {
|
||||
let mut links = FxHashSet::default();
|
||||
|
||||
@ -558,7 +598,7 @@ fn sidebar_render_assoc_items(
|
||||
let concrete = format_impls(concrete, id_map);
|
||||
let synthetic = format_impls(synthetic, id_map);
|
||||
let blanket = format_impls(blanket_impl, id_map);
|
||||
[
|
||||
items.extend([
|
||||
LinkBlock::new(
|
||||
Link::new("trait-implementations", "Trait Implementations"),
|
||||
"trait-implementation",
|
||||
@ -574,7 +614,7 @@ fn sidebar_render_assoc_items(
|
||||
"blanket-implementation",
|
||||
blanket,
|
||||
),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String {
|
||||
|
@ -568,12 +568,16 @@ img {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
ul.block, .block li {
|
||||
ul.block, .block li, .block ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.block ul {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.sidebar-elems a,
|
||||
.sidebar > h2 a {
|
||||
display: block;
|
||||
|
@ -15,14 +15,23 @@
|
||||
{% for block in blocks %}
|
||||
{% if block.should_render() %}
|
||||
{% if !block.heading.name.is_empty() %}
|
||||
<h3><a href="#{{block.heading.href|safe}}"> {# #}
|
||||
{{block.heading.name|wrapped|safe}} {# #}
|
||||
</a></h3> {# #}
|
||||
<h3{% if !block.class.is_empty() +%} class="{{block.class}}"{% endif %}> {# #}
|
||||
<a href="#{{block.heading.href|safe}}">{{block.heading.name|wrapped|safe}}</a> {# #}
|
||||
</h3> {# #}
|
||||
{% endif %}
|
||||
{% if !block.links.is_empty() %}
|
||||
<ul class="block{% if !block.class.is_empty() +%} {{+block.class}}{% endif %}">
|
||||
{% 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 %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! Table-of-contents creation.
|
||||
use crate::html::escape::EscapeBodyText;
|
||||
|
||||
/// A (recursive) table of contents
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -16,7 +17,7 @@ pub(crate) struct Toc {
|
||||
/// ### A
|
||||
/// ## B
|
||||
/// ```
|
||||
entries: Vec<TocEntry>,
|
||||
pub(crate) entries: Vec<TocEntry>,
|
||||
}
|
||||
|
||||
impl Toc {
|
||||
@ -27,11 +28,11 @@ fn count_entries_with_level(&self, level: u32) -> usize {
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct TocEntry {
|
||||
level: u32,
|
||||
sec_number: String,
|
||||
name: String,
|
||||
id: String,
|
||||
children: Toc,
|
||||
pub(crate) level: u32,
|
||||
pub(crate) sec_number: String,
|
||||
pub(crate) name: String,
|
||||
pub(crate) id: String,
|
||||
pub(crate) children: Toc,
|
||||
}
|
||||
|
||||
/// Progressive construction of a table of contents.
|
||||
@ -173,7 +174,7 @@ fn print_inner(&self, v: &mut String) {
|
||||
"\n<li><a href=\"#{id}\">{num} {name}</a>",
|
||||
id = entry.id,
|
||||
num = entry.sec_number,
|
||||
name = entry.name
|
||||
name = EscapeBodyText(&entry.name)
|
||||
);
|
||||
entry.children.print_inner(&mut *v);
|
||||
v.push_str("</li>");
|
||||
|
@ -72,6 +72,7 @@ pub(crate) fn render<P: AsRef<Path>>(
|
||||
let text = if !options.markdown_no_toc {
|
||||
MarkdownWithToc {
|
||||
content: text,
|
||||
links: &[],
|
||||
ids: &mut ids,
|
||||
error_codes,
|
||||
edition,
|
||||
|
@ -118,7 +118,7 @@ assert-false: ".sidebar-elems > .crate"
|
||||
go-to: "./module/index.html"
|
||||
assert-property: (".sidebar", {"clientWidth": "200"})
|
||||
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-text: (".sidebar-elems ul.block > li.current > a", "module")
|
||||
// Module page requires three headings:
|
||||
@ -136,7 +136,7 @@ assert-false: ".sidebar-elems > .crate"
|
||||
go-to: "./sub_module/sub_sub_module/index.html"
|
||||
assert-property: (".sidebar", {"clientWidth": "200"})
|
||||
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-property: (".sidebar > .sidebar-elems > h2 > a", {
|
||||
"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…
Reference in New Issue
Block a user