From 1aebff96ade6518038e0f7522d3304bd0f9bf891 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 6 Feb 2024 10:22:24 -0700 Subject: [PATCH 1/8] 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). --- src/librustdoc/clean/types.rs | 6 - src/librustdoc/html/markdown.rs | 47 +++- src/librustdoc/html/render/context.rs | 3 +- src/librustdoc/html/render/sidebar.rs | 234 ++++++++++-------- src/librustdoc/html/static/css/rustdoc.css | 6 +- src/librustdoc/html/templates/sidebar.html | 17 +- src/librustdoc/html/toc.rs | 15 +- src/librustdoc/markdown.rs | 1 + tests/rustdoc-gui/sidebar.goml | 4 +- .../rustdoc/{ => sidebar}/sidebar-all-page.rs | 0 tests/rustdoc/{ => sidebar}/sidebar-items.rs | 0 .../{ => sidebar}/sidebar-link-generation.rs | 0 .../sidebar-links-to-foreign-impl.rs | 0 tests/rustdoc/sidebar/top-toc-html.rs | 19 ++ tests/rustdoc/sidebar/top-toc-idmap.rs | 44 ++++ 15 files changed, 267 insertions(+), 129 deletions(-) rename tests/rustdoc/{ => sidebar}/sidebar-all-page.rs (100%) rename tests/rustdoc/{ => sidebar}/sidebar-items.rs (100%) rename tests/rustdoc/{ => sidebar}/sidebar-link-generation.rs (100%) rename tests/rustdoc/{ => sidebar}/sidebar-links-to-foreign-impl.rs (100%) create mode 100644 tests/rustdoc/sidebar/top-toc-html.rs create mode 100644 tests/rustdoc/sidebar/top-toc-idmap.rs diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index ff5c16f2b3e9a..3ba4ae4bd0c8a 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -506,9 +506,6 @@ impl Item { 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 @@ impl Item { 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 } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 7bfe5d87d399f..58795508a582f 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -55,7 +55,7 @@ use crate::html::format::Buffer; 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 @@ impl<'a, 'b, 'ids, I: Iterator>> Iterator 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 @@ impl Markdown<'_> { } 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 @@ impl MarkdownWithToc<'_> { html::push_html(&mut s, p); } - format!("{s}", toc = toc.into_toc().print()) + (toc.into_toc(), s) + } + pub(crate) fn into_string(self) -> String { + let (toc, s) = self.into_parts(); + format!("{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>, + 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)] diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 0ed8921b1e8d7..5734b59134a62 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -616,7 +616,8 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { 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: "", diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 101cc839f0988..a5a22618f2234 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -7,12 +7,13 @@ use rustc_hir::def::CtorKind; 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>, } impl<'a> Link<'a> { pub fn new(href: impl Into>, name: impl Into>) -> 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) mod filters { } pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) { - let blocks: Vec> = 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> = 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() - { + // + // crate title is displayed as part of logo lockup + 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> { fields } +fn docblock_toc<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + ids: &mut IdMap, +) -> Option> { + 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> = 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> { + items: &mut Vec>, +) { 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> { + blocks: &mut Vec>, +) { fn filter_items<'a>( items: &'a [clean::Item], filt: impl Fn(&clean::Item) -> bool, @@ -222,19 +266,20 @@ fn sidebar_trait<'a>( foreign_impls.sort(); } - let mut blocks: Vec> = [ - ("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), - ("provided-associated-consts", "Provided Associated Constants", prov_assoc_const), - ("required-methods", "Required Methods", req_method), - ("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)) - .collect(); - sidebar_assoc_items(cx, it, &mut blocks); + 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), + ("provided-associated-consts", "Provided Associated Constants", prov_assoc_const), + ("required-methods", "Required Methods", req_method), + ("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)), + ); + sidebar_assoc_items(cx, it, blocks); if !t.is_object_safe(cx.tcx()) { blocks.push(LinkBlock::forced( @@ -250,20 +295,17 @@ fn sidebar_trait<'a>( "impl-auto", )); } - blocks } -fn sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec> { +fn sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item, items: &mut Vec>) { 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> { - let mut items = vec![]; + items: &mut Vec>, +) { 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> { + items: &mut Vec>, +) { 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,21 +386,22 @@ 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()) { + let mut blocks = vec![ + LinkBlock::new( + Link::new("implementations", "Associated Constants"), + "associatedconstant", + assoc_consts, + ), + LinkBlock::new(Link::new("implementations", "Methods"), "method", methods), + ]; + + 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, - ); + sidebar_deref_methods(cx, &mut blocks, impl_, v, &mut derefs, &mut used_links); } let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = @@ -367,21 +409,15 @@ fn sidebar_assoc_items<'a>( let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = concrete.into_iter().partition::, _>(|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"), - "associatedconstant", - assoc_consts, - ), - LinkBlock::new(Link::new("implementations", "Methods"), "method", methods), - ]; - blocks.append(&mut deref_methods); - blocks.extend([concrete, synthetic, 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> { + items: &mut Vec>, +) { let mut variants = e .variants() .filter_map(|v| v.name) @@ -479,24 +516,24 @@ fn sidebar_enum<'a>( .collect::>(); 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, + 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> { - 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>, +) { + 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>, +) { 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, url: String) -> String { diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index eb5a5d935e202..58e27735035ff 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -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; diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html index 0990c2716b8f2..ac6aa849f5c8f 100644 --- a/src/librustdoc/html/templates/sidebar.html +++ b/src/librustdoc/html/templates/sidebar.html @@ -15,14 +15,23 @@

{# #} {% for block in blocks %} {% if block.should_render() %} {% if !block.heading.name.is_empty() %} -

{# #} - {{block.heading.name|wrapped|safe}} {# #} -

{# #} + {# #} + {{block.heading.name|wrapped|safe}} {# #} + {# #} {% endif %} {% if !block.links.is_empty() %}
    {% for link in block.links %} -
  • {{link.name}}
  • +
  • {# #} + {{link.name}} {# #} + {% if !link.children.is_empty() %} +
      {# #} + {% for child in link.children %} +
    • {{child.name}}
    • + {% endfor %} +
    {# #} + {% endif %} +
  • {# #} {% endfor %}
{% endif %} diff --git a/src/librustdoc/html/toc.rs b/src/librustdoc/html/toc.rs index a12c2a6a16c32..a44c4a1b59ee3 100644 --- a/src/librustdoc/html/toc.rs +++ b/src/librustdoc/html/toc.rs @@ -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, + pub(crate) entries: Vec, } impl Toc { @@ -27,11 +28,11 @@ impl Toc { #[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 @@ impl Toc { "\n
  • {num} {name}", id = entry.id, num = entry.sec_number, - name = entry.name + name = EscapeBodyText(&entry.name) ); entry.children.print_inner(&mut *v); v.push_str("
  • "); diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index a98f81d011e84..581ebbbe58d7c 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -72,6 +72,7 @@ pub(crate) fn render>( let text = if !options.markdown_no_toc { MarkdownWithToc { content: text, + links: &[], ids: &mut ids, error_codes, edition, diff --git a/tests/rustdoc-gui/sidebar.goml b/tests/rustdoc-gui/sidebar.goml index e499c159c6c76..5ac36bd4cc27b 100644 --- a/tests/rustdoc-gui/sidebar.goml +++ b/tests/rustdoc-gui/sidebar.goml @@ -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", diff --git a/tests/rustdoc/sidebar-all-page.rs b/tests/rustdoc/sidebar/sidebar-all-page.rs similarity index 100% rename from tests/rustdoc/sidebar-all-page.rs rename to tests/rustdoc/sidebar/sidebar-all-page.rs diff --git a/tests/rustdoc/sidebar-items.rs b/tests/rustdoc/sidebar/sidebar-items.rs similarity index 100% rename from tests/rustdoc/sidebar-items.rs rename to tests/rustdoc/sidebar/sidebar-items.rs diff --git a/tests/rustdoc/sidebar-link-generation.rs b/tests/rustdoc/sidebar/sidebar-link-generation.rs similarity index 100% rename from tests/rustdoc/sidebar-link-generation.rs rename to tests/rustdoc/sidebar/sidebar-link-generation.rs diff --git a/tests/rustdoc/sidebar-links-to-foreign-impl.rs b/tests/rustdoc/sidebar/sidebar-links-to-foreign-impl.rs similarity index 100% rename from tests/rustdoc/sidebar-links-to-foreign-impl.rs rename to tests/rustdoc/sidebar/sidebar-links-to-foreign-impl.rs diff --git a/tests/rustdoc/sidebar/top-toc-html.rs b/tests/rustdoc/sidebar/top-toc-html.rs new file mode 100644 index 0000000000000..6fc84c1964c38 --- /dev/null +++ b/tests/rustdoc/sidebar/top-toc-html.rs @@ -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 diff --git a/tests/rustdoc/sidebar/top-toc-idmap.rs b/tests/rustdoc/sidebar/top-toc-idmap.rs new file mode 100644 index 0000000000000..ccfc7336e9f22 --- /dev/null +++ b/tests/rustdoc/sidebar/top-toc-idmap.rs @@ -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, +} From a7aea5d96bcafb7046ed7440122395e7f5e5d43d Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 6 Feb 2024 18:21:48 -0700 Subject: [PATCH 2/8] Add configuration options to hide TOC or module navigation --- src/librustdoc/html/render/context.rs | 1 + src/librustdoc/html/render/sidebar.rs | 15 +++++--- src/librustdoc/html/static/css/rustdoc.css | 12 +++++-- src/librustdoc/html/static/js/main.js | 4 +-- src/librustdoc/html/static/js/settings.js | 29 +++++++++++++++ src/librustdoc/html/static/js/storage.js | 13 ++++--- src/librustdoc/html/templates/sidebar.html | 20 ++++++----- tests/rustdoc-gui/sidebar.goml | 41 +++++++++++++++++++--- 8 files changed, 111 insertions(+), 24 deletions(-) diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 5734b59134a62..a167681b3163a 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -623,6 +623,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { title: "", is_crate: false, is_mod: false, + parent_is_crate: false, blocks: vec![blocks], path: String::new(), }; diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index a5a22618f2234..5e59754da79ce 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -21,6 +21,7 @@ pub(super) struct Sidebar<'a> { pub(super) title_prefix: &'static str, pub(super) title: &'a str, pub(super) is_crate: bool, + pub(super) parent_is_crate: bool, pub(super) is_mod: bool, pub(super) blocks: Vec>, pub(super) path: String, @@ -144,8 +145,15 @@ pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buf } else { "".into() }; - let sidebar = - Sidebar { title_prefix, title, is_mod: it.is_mod(), is_crate: it.is_crate(), blocks, path }; + let sidebar = Sidebar { + title_prefix, + title, + is_mod: it.is_mod(), + is_crate: it.is_crate(), + parent_is_crate: sidebar_path.len() == 1, + blocks, + path, + }; sidebar.render_into(buffer).unwrap(); } @@ -173,7 +181,6 @@ fn docblock_toc<'a>( 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> = toc @@ -202,7 +209,7 @@ fn docblock_toc<'a>( if links.is_empty() { None } else { - Some(LinkBlock::new(Link::new("#", "Sections"), "top-toc", links)) + Some(LinkBlock::new(Link::new("", "Sections"), "top-toc", links)) } } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 58e27735035ff..c2bb5fa8f2acc 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -574,8 +574,8 @@ ul.block, .block li, .block ul { list-style: none; } -.block ul { - margin-left: 12px; +.block ul a { + padding-left: 1rem; } .sidebar-elems a, @@ -589,6 +589,14 @@ ul.block, .block li, .block ul { background-clip: border-box; } +.hide-toc #TOC, .hide-toc .in-crate { + display: none; +} + +.hide-modnav #ModNav { + display: none; +} + .sidebar h2 { text-wrap: balance; overflow-wrap: anywhere; diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 75f2a1418cd8f..4135341b22e91 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -499,7 +499,7 @@ function preLoadCss(cssUrl) { if (!window.SIDEBAR_ITEMS) { return; } - const sidebar = document.getElementsByClassName("sidebar-elems")[0]; + const sidebar = document.getElementById("ModNav"); /** * Append to the sidebar a "block" of links - a heading along with a list (`
      `) of items. @@ -885,7 +885,7 @@ function preLoadCss(cssUrl) { if (!window.ALL_CRATES) { return; } - const sidebarElems = document.getElementsByClassName("sidebar-elems")[0]; + const sidebarElems = document.getElementById("ModNav"); if (!sidebarElems) { return; } diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index 2b42fbebb803d..c52a19ef98738 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -36,6 +36,20 @@ removeClass(document.documentElement, "hide-sidebar"); } break; + case "hide-toc": + if (value === true) { + addClass(document.documentElement, "hide-toc"); + } else { + removeClass(document.documentElement, "hide-toc"); + } + break; + case "hide-modnav": + if (value === true) { + addClass(document.documentElement, "hide-modnav"); + } else { + removeClass(document.documentElement, "hide-modnav"); + } + break; } } @@ -102,6 +116,11 @@ let output = ""; for (const setting of settings) { + if (setting === "hr") { + output += "
      "; + continue; + } + const js_data_name = setting["js_name"]; const setting_name = setting["name"]; @@ -198,6 +217,16 @@ "js_name": "hide-sidebar", "default": false, }, + { + "name": "Hide table of contents", + "js_name": "hide-toc", + "default": false, + }, + { + "name": "Hide module navigation", + "js_name": "hide-modnav", + "default": false, + }, { "name": "Disable keyboard shortcuts", "js_name": "disable-shortcuts", diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index 4a27ca92fff38..d75fb7a7fb5ab 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -196,16 +196,21 @@ updateTheme(); // This needs to be done here because this JS is render-blocking, // so that the sidebar doesn't "jump" after appearing on screen. // The user interaction to change this is set up in main.js. +// +// At this point in page load, `document.body` is not available yet. +// Set a class on the `` element instead. if (getSettingValue("source-sidebar-show") === "true") { - // At this point in page load, `document.body` is not available yet. - // Set a class on the `` element instead. addClass(document.documentElement, "src-sidebar-expanded"); } if (getSettingValue("hide-sidebar") === "true") { - // At this point in page load, `document.body` is not available yet. - // Set a class on the `` element instead. addClass(document.documentElement, "hide-sidebar"); } +if (getSettingValue("hide-toc") === "true") { + addClass(document.documentElement, "hide-toc"); +} +if (getSettingValue("hide-modnav") === "true") { + addClass(document.documentElement, "hide-modnav"); +} function updateSidebarWidth() { const desktopSidebarWidth = getSettingValue("desktop-sidebar-width"); if (desktopSidebarWidth && desktopSidebarWidth !== "null") { diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html index ac6aa849f5c8f..c0a9b2544259a 100644 --- a/src/librustdoc/html/templates/sidebar.html +++ b/src/librustdoc/html/templates/sidebar.html @@ -1,8 +1,3 @@ -{% if !title.is_empty() %} -

      {# #} - {{title_prefix}}{{title|wrapped|safe}} {# #} -

      -{% endif %} {# #} diff --git a/tests/rustdoc-gui/sidebar.goml b/tests/rustdoc-gui/sidebar.goml index 5ac36bd4cc27b..3253e6ea86a84 100644 --- a/tests/rustdoc-gui/sidebar.goml +++ b/tests/rustdoc-gui/sidebar.goml @@ -126,8 +126,8 @@ assert-text: (".sidebar-elems ul.block > li.current > a", "module") // - Module name, followed by TOC for module headings // - "In crate [name]" parent pointer, followed by sibling navigation assert-count: (".sidebar h2", 3) -assert-text: (".sidebar > .sidebar-elems > h2", "In crate lib2") -assert-property: (".sidebar > .sidebar-elems > h2 > a", { +assert-text: (".sidebar > .sidebar-elems > #ModNav > h2", "In crate lib2") +assert-property: (".sidebar > .sidebar-elems > #ModNav > h2 > a", { "href": "/lib2/index.html", }, ENDS_WITH) // We check that we don't have the crate list. @@ -137,8 +137,8 @@ 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 > .sidebar-elems > h2", "In lib2::module::sub_module") -assert-property: (".sidebar > .sidebar-elems > h2 > a", { +assert-text: (".sidebar > .sidebar-elems > #ModNav > h2", "In lib2::module::sub_module") +assert-property: (".sidebar > .sidebar-elems > #ModNav > h2 > a", { "href": "/module/sub_module/index.html", }, ENDS_WITH) assert-text: (".sidebar-elems ul.block > li.current > a", "sub_sub_module") @@ -198,3 +198,36 @@ assert-position-false: (".sidebar-crate > h2 > a", {"x": -3}) // when line-wrapped, see that it becomes flush-left again drag-and-drop: ((205, 100), (108, 100)) assert-position: (".sidebar-crate > h2 > a", {"x": -3}) + +// Configuration option to show TOC in sidebar. +set-local-storage: {"rustdoc-hide-toc": "true"} +go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html" +assert-css: ("#TOC", {"display": "none"}) +assert-css: (".sidebar .in-crate", {"display": "none"}) +set-local-storage: {"rustdoc-hide-toc": "false"} +go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html" +assert-css: ("#TOC", {"display": "block"}) +assert-css: (".sidebar .in-crate", {"display": "block"}) + +set-local-storage: {"rustdoc-hide-modnav": "true"} +go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html" +assert-css: ("#ModNav", {"display": "none"}) +set-local-storage: {"rustdoc-hide-modnav": "false"} +go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html" +assert-css: ("#ModNav", {"display": "block"}) + +set-local-storage: {"rustdoc-hide-toc": "true"} +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +assert-css: ("#TOC", {"display": "none"}) +assert-false: ".sidebar .in-crate" +set-local-storage: {"rustdoc-hide-toc": "false"} +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +assert-css: ("#TOC", {"display": "block"}) +assert-false: ".sidebar .in-crate" + +set-local-storage: {"rustdoc-hide-modnav": "true"} +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +assert-css: ("#ModNav", {"display": "none"}) +set-local-storage: {"rustdoc-hide-modnav": "false"} +go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" +assert-css: ("#ModNav", {"display": "block"}) From 5a6054b4a2850d195bf54d7176b4a32382a8df49 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 22 Jul 2024 10:31:24 -0700 Subject: [PATCH 3/8] rustdoc: add separate section for module items --- src/librustdoc/html/render/context.rs | 9 +++++--- src/librustdoc/html/render/sidebar.rs | 27 +++++++++++++++++++--- src/librustdoc/html/templates/sidebar.html | 10 ++++---- tests/rustdoc/sidebar/top-toc-nil.rs | 7 ++++++ 4 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 tests/rustdoc/sidebar/top-toc-nil.rs diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index a167681b3163a..510da99c9efff 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -14,9 +14,12 @@ use rustc_span::edition::Edition; use rustc_span::{sym, FileName, Symbol}; use super::print_item::{full_path, item_path, print_item}; -use super::sidebar::{print_sidebar, sidebar_module_like, Sidebar}; use super::write_shared::write_shared; -use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath}; +use super::{ + collect_spans_and_sources, scrape_examples_help, + sidebar::{print_sidebar, sidebar_module_like, ModuleLike, Sidebar}, + AllTypes, LinkFromSrc, StylePath, +}; use crate::clean::types::ExternalLocation; use crate::clean::utils::has_doc_flag; use crate::clean::{self, ExternalCrate}; @@ -617,7 +620,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { let mut sidebar = Buffer::html(); // all.html is not customizable, so a blank id map is fine - let blocks = sidebar_module_like(all.item_sections(), &mut IdMap::new()); + let blocks = sidebar_module_like(all.item_sections(), &mut IdMap::new(), ModuleLike::Crate); let bar = Sidebar { title_prefix: "", title: "", diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 5e59754da79ce..c76253c9fc3cd 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -15,6 +15,18 @@ use crate::{ use super::{item_ty_to_section, Context, ItemSection}; +#[derive(Clone, Copy)] +pub(crate) enum ModuleLike { + Module, + Crate, +} + +impl ModuleLike { + pub(crate) fn is_crate(self) -> bool { + matches!(self, ModuleLike::Crate) + } +} + #[derive(Template)] #[template(path = "sidebar.html")] pub(super) struct Sidebar<'a> { @@ -530,14 +542,23 @@ fn sidebar_enum<'a>( pub(crate) fn sidebar_module_like( item_sections_in_use: FxHashSet, ids: &mut IdMap, + module_like: ModuleLike, ) -> LinkBlock<'static> { - let item_sections = ItemSection::ALL + let item_sections: Vec> = ItemSection::ALL .iter() .copied() .filter(|sec| item_sections_in_use.contains(sec)) .map(|sec| Link::new(ids.derive(sec.id()), sec.name())) .collect(); - LinkBlock::new(Link::empty(), "", item_sections) + let header = if let Some(first_section) = item_sections.get(0) { + Link::new( + first_section.href.to_owned(), + if module_like.is_crate() { "Crate Items" } else { "Module Items" }, + ) + } else { + Link::empty() + }; + LinkBlock::new(header, "", item_sections) } fn sidebar_module(items: &[clean::Item], ids: &mut IdMap) -> LinkBlock<'static> { @@ -561,7 +582,7 @@ fn sidebar_module(items: &[clean::Item], ids: &mut IdMap) -> LinkBlock<'static> .map(|it| item_ty_to_section(it.type_())) .collect(); - sidebar_module_like(item_sections_in_use, ids) + sidebar_module_like(item_sections_in_use, ids, ModuleLike::Module) } fn sidebar_foreign_type<'a>( diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html index c0a9b2544259a..f49cdbabb8851 100644 --- a/src/librustdoc/html/templates/sidebar.html +++ b/src/librustdoc/html/templates/sidebar.html @@ -17,7 +17,7 @@

      {# #} {% if !block.heading.name.is_empty() %}

      {# #} {{block.heading.name|wrapped|safe}} {# #} -

      {# #} + {% endif %} {% if !block.links.is_empty() %}
        @@ -25,13 +25,13 @@

        {# #}
      • {# #} {{link.name}} {# #} {% if !link.children.is_empty() %} -
          {# #} + {# #} +
        {% endif %} -
      • {# #} + {% endfor %}

      {% endif %} @@ -43,7 +43,7 @@

      {# #} {% if !path.is_empty() %} {# #} In {{+ path|wrapped|safe}} {# #} -

      {# #} + {% endif %} {# #} diff --git a/tests/rustdoc/sidebar/top-toc-nil.rs b/tests/rustdoc/sidebar/top-toc-nil.rs new file mode 100644 index 0000000000000..91c7df50ab48f --- /dev/null +++ b/tests/rustdoc/sidebar/top-toc-nil.rs @@ -0,0 +1,7 @@ +#![crate_name = "foo"] + +//! This test case covers missing top TOC entries. + +// @has foo/index.html +// User header +// @!has - '//section[@id="TOC"]/ul[@class="block top-toc"]' 'Basic link and emphasis' From 68773c789a6a84a888b69f7287fd294b6dd3625c Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 22 Jul 2024 11:12:38 -0700 Subject: [PATCH 4/8] rustdoc: add separate section for module items --- src/librustdoc/html/render/sidebar.rs | 17 ++++++++++++++--- tests/rustdoc/sidebar/module.rs | 16 ++++++++++++++++ tests/rustdoc/sidebar/top-toc-html.rs | 1 + 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 tests/rustdoc/sidebar/module.rs diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index c76253c9fc3cd..a6b22a18de266 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -26,6 +26,11 @@ impl ModuleLike { matches!(self, ModuleLike::Crate) } } +impl<'a> From<&'a clean::Item> for ModuleLike { + fn from(it: &'a clean::Item) -> ModuleLike { + if it.is_crate() { ModuleLike::Crate } else { ModuleLike::Module } + } +} #[derive(Template)] #[template(path = "sidebar.html")] @@ -119,7 +124,9 @@ pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buf 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::ModuleItem(ref m) => { + blocks.push(sidebar_module(&m.items, &mut ids, ModuleLike::from(it))) + } clean::ForeignTypeItem => sidebar_foreign_type(cx, it, &mut blocks), _ => {} } @@ -561,7 +568,11 @@ pub(crate) fn sidebar_module_like( LinkBlock::new(header, "", item_sections) } -fn sidebar_module(items: &[clean::Item], ids: &mut IdMap) -> LinkBlock<'static> { +fn sidebar_module( + items: &[clean::Item], + ids: &mut IdMap, + module_like: ModuleLike, +) -> LinkBlock<'static> { let item_sections_in_use: FxHashSet<_> = items .iter() .filter(|it| { @@ -582,7 +593,7 @@ fn sidebar_module(items: &[clean::Item], ids: &mut IdMap) -> LinkBlock<'static> .map(|it| item_ty_to_section(it.type_())) .collect(); - sidebar_module_like(item_sections_in_use, ids, ModuleLike::Module) + sidebar_module_like(item_sections_in_use, ids, module_like) } fn sidebar_foreign_type<'a>( diff --git a/tests/rustdoc/sidebar/module.rs b/tests/rustdoc/sidebar/module.rs new file mode 100644 index 0000000000000..926af71ddfb8d --- /dev/null +++ b/tests/rustdoc/sidebar/module.rs @@ -0,0 +1,16 @@ +#![crate_name = "foo"] + +//@ has 'foo/index.html' +//@ has - '//section[@id="TOC"]/h3' 'Crate Items' + +//@ has 'foo/bar/index.html' +//@ has - '//section[@id="TOC"]/h3' 'Module Items' +pub mod bar { + //@ has 'foo/bar/struct.Baz.html' + //@ !has - '//section[@id="TOC"]/h3' 'Module Items' + pub struct Baz; +} + +//@ has 'foo/baz/index.html' +//@ !has - '//section[@id="TOC"]/h3' 'Module Items' +pub mod baz {} diff --git a/tests/rustdoc/sidebar/top-toc-html.rs b/tests/rustdoc/sidebar/top-toc-html.rs index 6fc84c1964c38..fa1325c8dca0a 100644 --- a/tests/rustdoc/sidebar/top-toc-html.rs +++ b/tests/rustdoc/sidebar/top-toc-html.rs @@ -14,6 +14,7 @@ // @has foo/index.html // User header +// @has - '//section[@id="TOC"]/h3' 'Sections' // @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 From 7091fa5880db84392783147171f5709b72ffa784 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 22 Jul 2024 12:59:34 -0700 Subject: [PATCH 5/8] rustdoc: show code spans as `` in TOC --- src/librustdoc/html/markdown.rs | 29 +++++++++++++++++-- src/librustdoc/html/render/sidebar.rs | 12 ++++++-- src/librustdoc/html/templates/sidebar.html | 18 ++++++++++-- src/librustdoc/html/toc.rs | 15 +++++++--- src/librustdoc/html/toc/tests.rs | 6 +++- tests/rustdoc/sidebar/top-toc-html.rs | 15 ++++++---- tests/rustdoc/sidebar/top-toc-idmap.rs | 22 +++++++------- tests/rustdoc/sidebar/top-toc-nil.rs | 4 +-- .../strip-enum-variant.no-not-shown.html | 2 +- 9 files changed, 92 insertions(+), 31 deletions(-) diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 58795508a582f..4b82f23276535 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -50,7 +50,7 @@ use rustc_span::{Span, Symbol}; use crate::clean::RenderedLink; use crate::doctest; use crate::doctest::GlobalTestOptions; -use crate::html::escape::Escape; +use crate::html::escape::{Escape, EscapeBodyText}; use crate::html::format::Buffer; use crate::html::highlight; use crate::html::length_limit::HtmlWithLimit; @@ -535,7 +535,9 @@ impl<'a, 'b, 'ids, I: Iterator>> Iterator if let Some(ref mut builder) = self.toc { 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()); + let mut html_header = String::new(); + html_text_from_events(self.buf.iter().map(|(ev, _)| ev.clone()), &mut html_header); + let sec = builder.push(level as u32, text_header, html_header, id.clone()); self.buf.push_front((Event::Html(format!("{sec} ").into()), 0..0)); } @@ -1655,6 +1657,29 @@ pub(crate) fn plain_text_from_events<'a>( } } +pub(crate) fn html_text_from_events<'a>( + events: impl Iterator>, + s: &mut String, +) { + for event in events { + match &event { + Event::Text(text) => { + write!(s, "{}", EscapeBodyText(text)).expect("string alloc infallible") + } + Event::Code(code) => { + s.push_str(""); + write!(s, "{}", EscapeBodyText(code)).expect("string alloc infallible"); + s.push_str(""); + } + Event::HardBreak | Event::SoftBreak => s.push(' '), + Event::Start(Tag::CodeBlock(..)) => break, + Event::End(TagEnd::Paragraph) => break, + Event::End(TagEnd::Heading(..)) => break, + _ => (), + } + } +} + #[derive(Debug)] pub(crate) struct MarkdownLink { pub kind: LinkType, diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index a6b22a18de266..351a23b880d40 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -81,8 +81,10 @@ impl<'a> LinkBlock<'a> { /// A link to an item. Content should not be escaped. #[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] pub(crate) struct Link<'a> { - /// The content for the anchor tag + /// The content for the anchor tag and title attr name: Cow<'a, str>, + /// The content for the anchor tag (if different from name) + name_html: Option>, /// The id of an anchor within the page (without a `#` prefix) href: Cow<'a, str>, /// Nested list of links (used only in top-toc) @@ -91,7 +93,7 @@ pub(crate) struct Link<'a> { impl<'a> Link<'a> { pub fn new(href: impl Into>, name: impl Into>) -> Self { - Self { href: href.into(), name: name.into(), children: vec![] } + Self { href: href.into(), name: name.into(), children: vec![], name_html: None } } pub fn empty() -> Link<'static> { Link::new("", "") @@ -207,6 +209,7 @@ fn docblock_toc<'a>( .into_iter() .map(|entry| { Link { + name_html: if entry.html == entry.name { None } else { Some(entry.html.into()) }, name: entry.name.into(), href: entry.id.into(), children: entry @@ -214,6 +217,11 @@ fn docblock_toc<'a>( .entries .into_iter() .map(|entry| Link { + name_html: if entry.html == entry.name { + None + } else { + Some(entry.html.into()) + }, name: entry.name.into(), href: entry.id.into(), // Only a single level of nesting is shown here. diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html index f49cdbabb8851..31823848ec385 100644 --- a/src/librustdoc/html/templates/sidebar.html +++ b/src/librustdoc/html/templates/sidebar.html @@ -23,11 +23,25 @@

      {# #}
        {% for link in block.links %}
      • {# #} - {{link.name}} {# #} + + {% match link.name_html %} + {% when Some with (html) %} + {{html|safe}} + {% else %} + {{link.name}} + {% endmatch %} + {# #} {% if !link.children.is_empty() %} {% endif %} diff --git a/src/librustdoc/html/toc.rs b/src/librustdoc/html/toc.rs index a44c4a1b59ee3..7fdce41a63422 100644 --- a/src/librustdoc/html/toc.rs +++ b/src/librustdoc/html/toc.rs @@ -1,5 +1,5 @@ //! Table-of-contents creation. -use crate::html::escape::EscapeBodyText; +use crate::html::escape::Escape; /// A (recursive) table of contents #[derive(Debug, PartialEq)] @@ -30,7 +30,12 @@ impl Toc { pub(crate) struct TocEntry { pub(crate) level: u32, pub(crate) sec_number: String, + // name is a plain text header that works in a `title` tag + // html includes `` tags + // the tooltip is used so that, when a toc is truncated, + // you can mouse over it to see the whole thing pub(crate) name: String, + pub(crate) html: String, pub(crate) id: String, pub(crate) children: Toc, } @@ -116,7 +121,7 @@ impl TocBuilder { /// Push a level `level` heading into the appropriate place in the /// hierarchy, returning a string containing the section number in /// `..` format. - pub(crate) fn push(&mut self, level: u32, name: String, id: String) -> &str { + pub(crate) fn push(&mut self, level: u32, name: String, html: String, id: String) -> &str { assert!(level >= 1); // collapse all previous sections into their parents until we @@ -150,6 +155,7 @@ impl TocBuilder { self.chain.push(TocEntry { level, name, + html, sec_number, id, children: Toc { entries: Vec::new() }, @@ -171,10 +177,11 @@ impl Toc { // recursively format this table of contents let _ = write!( v, - "\n
      • {num} {name}", + "\n
      • {num} {html}", id = entry.id, num = entry.sec_number, - name = EscapeBodyText(&entry.name) + name = Escape(&entry.name), + html = &entry.html, ); entry.children.print_inner(&mut *v); v.push_str("
      • "); diff --git a/src/librustdoc/html/toc/tests.rs b/src/librustdoc/html/toc/tests.rs index 014f346862b3f..81aca737baf18 100644 --- a/src/librustdoc/html/toc/tests.rs +++ b/src/librustdoc/html/toc/tests.rs @@ -9,7 +9,10 @@ fn builder_smoke() { // there's been no macro mistake. macro_rules! push { ($level: expr, $name: expr) => { - assert_eq!(builder.push($level, $name.to_string(), "".to_string()), $name); + assert_eq!( + builder.push($level, $name.to_string(), $name.to_string(), "".to_string()), + $name + ); }; } push!(2, "0.1"); @@ -48,6 +51,7 @@ fn builder_smoke() { TocEntry { level: $level, name: $name.to_string(), + html: $name.to_string(), sec_number: $name.to_string(), id: "".to_string(), children: toc!($($sub),*) diff --git a/tests/rustdoc/sidebar/top-toc-html.rs b/tests/rustdoc/sidebar/top-toc-html.rs index fa1325c8dca0a..db9987756443a 100644 --- a/tests/rustdoc/sidebar/top-toc-html.rs +++ b/tests/rustdoc/sidebar/top-toc-html.rs @@ -4,7 +4,7 @@ #![feature(lazy_type_alias)] #![allow(incomplete_features)] -//! # Basic [link](https://example.com) and *emphasis* +//! # Basic [link](https://example.com) and *emphasis* and `code` //! //! This test case covers TOC entries with rich text inside. //! Rustdoc normally supports headers with links, but for the @@ -12,9 +12,12 @@ //! //! For consistency, emphasis is also filtered out. -// @has foo/index.html +//@ has foo/index.html // User header -// @has - '//section[@id="TOC"]/h3' 'Sections' -// @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 +//@ has - '//section[@id="TOC"]/h3' 'Sections' +//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/@title' 'Basic link and emphasis and `code`' +//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]' 'Basic link and emphasis and code' +//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/em' 0 +//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/a' 0 +//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/code' 1 +//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/code' 'code' diff --git a/tests/rustdoc/sidebar/top-toc-idmap.rs b/tests/rustdoc/sidebar/top-toc-idmap.rs index ccfc7336e9f22..fdb99fd05a1bb 100644 --- a/tests/rustdoc/sidebar/top-toc-idmap.rs +++ b/tests/rustdoc/sidebar/top-toc-idmap.rs @@ -14,13 +14,13 @@ //! 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 +//@ 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' +//@ 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' +//@ 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 @@ -29,15 +29,15 @@ /// 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 +//@ 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' +//@ 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 +//@ 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' +//@ has - '//section[@id="TOC"]/h3/a[@href="#fields"]' 'Fields' +//@ has - '//section[@id="main-content"]/h2[@id="fields"]' 'Fields' pub struct MyStruct { pub fields: i32, diff --git a/tests/rustdoc/sidebar/top-toc-nil.rs b/tests/rustdoc/sidebar/top-toc-nil.rs index 91c7df50ab48f..c338bd67fcf3e 100644 --- a/tests/rustdoc/sidebar/top-toc-nil.rs +++ b/tests/rustdoc/sidebar/top-toc-nil.rs @@ -2,6 +2,6 @@ //! This test case covers missing top TOC entries. -// @has foo/index.html +//@ has foo/index.html // User header -// @!has - '//section[@id="TOC"]/ul[@class="block top-toc"]' 'Basic link and emphasis' +//@ !has - '//section[@id="TOC"]/ul[@class="block top-toc"]' 'Basic link and emphasis' diff --git a/tests/rustdoc/strip-enum-variant.no-not-shown.html b/tests/rustdoc/strip-enum-variant.no-not-shown.html index e072335297d6b..d7a36cc631ac6 100644 --- a/tests/rustdoc/strip-enum-variant.no-not-shown.html +++ b/tests/rustdoc/strip-enum-variant.no-not-shown.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 95fcddd29f7127bbca70375b8db3d455019843e0 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 23 Jul 2024 09:11:52 -0700 Subject: [PATCH 6/8] Add more test case --- tests/rustdoc/sidebar/top-toc-html.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/rustdoc/sidebar/top-toc-html.rs b/tests/rustdoc/sidebar/top-toc-html.rs index db9987756443a..8991ed0330e07 100644 --- a/tests/rustdoc/sidebar/top-toc-html.rs +++ b/tests/rustdoc/sidebar/top-toc-html.rs @@ -4,7 +4,7 @@ #![feature(lazy_type_alias)] #![allow(incomplete_features)] -//! # Basic [link](https://example.com) and *emphasis* and `code` +//! # Basic [link](https://example.com), *emphasis*, **_very emphasis_** and `code` //! //! This test case covers TOC entries with rich text inside. //! Rustdoc normally supports headers with links, but for the @@ -15,9 +15,9 @@ //@ has foo/index.html // User header //@ has - '//section[@id="TOC"]/h3' 'Sections' -//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/@title' 'Basic link and emphasis and `code`' -//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]' 'Basic link and emphasis and code' -//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/em' 0 -//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/a' 0 -//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/code' 1 -//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/code' 'code' +//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/@title' 'Basic link, emphasis, very emphasis and `code`' +//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]' 'Basic link, emphasis, very emphasis and code' +//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/em' 0 +//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/a' 0 +//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/code' 1 +//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/code' 'code' From 12a3c42cccd18d5fadcbad44c3c5442e4aca1a13 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 24 Jul 2024 13:16:08 -0700 Subject: [PATCH 7/8] rustdoc: consistentify `#TOC` and `#ModNav` to lowercase --- src/doc/not_found.md | 2 +- src/librustdoc/html/markdown.rs | 5 +++-- src/librustdoc/html/static/css/rustdoc.css | 4 ++-- src/librustdoc/html/static/js/main.js | 4 ++-- src/librustdoc/html/templates/sidebar.html | 4 ++-- tests/rustdoc-gui/sidebar.goml | 24 +++++++++++----------- tests/rustdoc/sidebar/module.rs | 8 ++++---- tests/rustdoc/sidebar/top-toc-html.rs | 14 ++++++------- tests/rustdoc/sidebar/top-toc-idmap.rs | 10 ++++----- tests/rustdoc/sidebar/top-toc-nil.rs | 2 +- 10 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/doc/not_found.md b/src/doc/not_found.md index f0794fc0be378..9552759d2b8b3 100644 --- a/src/doc/not_found.md +++ b/src/doc/not_found.md @@ -2,7 +2,7 @@