diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/doc/not_found.md | 2 | ||||
| -rw-r--r-- | src/librustdoc/clean/types.rs | 6 | ||||
| -rw-r--r-- | src/librustdoc/html/markdown.rs | 73 | ||||
| -rw-r--r-- | src/librustdoc/html/render/context.rs | 6 | ||||
| -rw-r--r-- | src/librustdoc/html/render/sidebar.rs | 282 | ||||
| -rw-r--r-- | src/librustdoc/html/static/css/rustdoc.css | 14 | ||||
| -rw-r--r-- | src/librustdoc/html/static/js/main.js | 4 | ||||
| -rw-r--r-- | src/librustdoc/html/static/js/settings.js | 29 | ||||
| -rw-r--r-- | src/librustdoc/html/static/js/storage.js | 13 | ||||
| -rw-r--r-- | src/librustdoc/html/templates/sidebar.html | 49 | ||||
| -rw-r--r-- | src/librustdoc/html/toc.rs | 26 | ||||
| -rw-r--r-- | src/librustdoc/html/toc/tests.rs | 6 | ||||
| -rw-r--r-- | src/librustdoc/markdown.rs | 1 |
13 files changed, 365 insertions, 146 deletions
diff --git a/src/doc/not_found.md b/src/doc/not_found.md index f0794fc0be3..9552759d2b8 100644 --- a/src/doc/not_found.md +++ b/src/doc/not_found.md @@ -2,7 +2,7 @@ <!-- Completely hide the TOC and the section numbers --> <style type="text/css"> -#TOC { display: none; } +#rustdoc-toc { display: none; } .header-section-number { display: none; } li {list-style-type: none; } #search-input { diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 58fcfc4ed8d..5666b229078 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -512,9 +512,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 } @@ -542,9 +539,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 e37c24c1438..6f4665db6f1 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -51,12 +51,12 @@ use tracing::{debug, trace}; 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; use crate::html::render::small_url_encode; -use crate::html::toc::TocBuilder; +use crate::html::toc::{Toc, TocBuilder}; #[cfg(test)] mod tests; @@ -102,6 +102,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, @@ -533,9 +534,11 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator let id = self.id_map.derive(id); 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 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()); + 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)); } @@ -1412,10 +1415,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); @@ -1429,7 +1445,11 @@ impl MarkdownWithToc<'_> { 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=\"rustdoc\">{toc}</nav>{s}", toc = toc.print()) } } @@ -1608,7 +1628,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) => { @@ -1623,8 +1652,29 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin _ => (), } } +} - s +pub(crate) fn html_text_from_events<'a>( + events: impl Iterator<Item = pulldown_cmark::Event<'a>>, + 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("<code>"); + write!(s, "{}", EscapeBodyText(code)).expect("string alloc infallible"); + s.push_str("</code>"); + } + Event::HardBreak | Event::SoftBreak => s.push(' '), + Event::Start(Tag::CodeBlock(..)) => break, + Event::End(TagEnd::Paragraph) => break, + Event::End(TagEnd::Heading(..)) => break, + _ => (), + } + } } #[derive(Debug)] @@ -1975,7 +2025,8 @@ fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> { map.insert("default-settings".into(), 1); map.insert("sidebar-vars".into(), 1); map.insert("copy-path".into(), 1); - map.insert("TOC".into(), 1); + map.insert("rustdoc-toc".into(), 1); + map.insert("rustdoc-modnav".into(), 1); // This is the list of IDs used by rustdoc sections (but still generated by // rustdoc). map.insert("fields".into(), 1); diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 19386fe5e69..9fe43e428f2 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -15,7 +15,7 @@ use rustc_span::{sym, FileName, Symbol}; use tracing::info; use super::print_item::{full_path, item_path, print_item}; -use super::sidebar::{print_sidebar, sidebar_module_like, Sidebar}; +use super::sidebar::{print_sidebar, sidebar_module_like, ModuleLike, Sidebar}; use super::write_shared::write_shared; use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath}; use crate::clean::types::ExternalLocation; @@ -617,12 +617,14 @@ 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(), ModuleLike::Crate); let bar = Sidebar { title_prefix: "", 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 5b9c93ef74a..c75c28ef0a3 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -13,7 +13,24 @@ use crate::clean; use crate::formats::item_type::ItemType; use crate::formats::Impl; use crate::html::format::Buffer; -use crate::html::markdown::IdMap; +use crate::html::markdown::{IdMap, MarkdownWithToc}; + +#[derive(Clone, Copy)] +pub(crate) enum ModuleLike { + Module, + Crate, +} + +impl ModuleLike { + pub(crate) fn is_crate(self) -> bool { + 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")] @@ -21,6 +38,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<LinkBlock<'a>>, pub(super) path: String, @@ -63,15 +81,19 @@ 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<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![], name_html: None } } pub fn empty() -> Link<'static> { Link::new("", "") @@ -95,17 +117,21 @@ pub(crate) mod filters { } 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, ModuleLike::from(it))) + } + 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), @@ -113,15 +139,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 ", @@ -146,8 +166,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(); } @@ -163,30 +190,80 @@ 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, + } + .into_parts(); + let links: Vec<Link<'_>> = toc + .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(), + children: entry + .children + .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. + // 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, @@ -223,19 +300,20 @@ fn sidebar_trait<'a>( foreign_impls.sort(); } - let mut blocks: Vec<LinkBlock<'_>> = [ - ("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( @@ -251,20 +329,17 @@ fn sidebar_trait<'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); } } @@ -272,8 +347,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 { @@ -295,19 +370,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 @@ -346,21 +420,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>) = @@ -368,21 +443,15 @@ fn sidebar_assoc_items<'a>( 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"), - "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); } } @@ -472,7 +541,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) @@ -480,24 +550,37 @@ 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, + module_like: ModuleLike, ) -> LinkBlock<'static> { - let item_sections = ItemSection::ALL + let item_sections: Vec<Link<'_>> = 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) + 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]) -> 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| { @@ -518,13 +601,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, module_like) } -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 @@ -534,7 +619,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(); @@ -559,7 +645,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", @@ -575,7 +661,7 @@ fn sidebar_render_assoc_items( "blanket-implementation", blanket, ), - ] + ]); } fn get_next_url(used_links: &mut FxHashSet<String>, url: String) -> String { diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 1e483062289..271de1b1ccf 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 a { + padding-left: 1rem; +} + .sidebar-elems a, .sidebar > h2 a { display: block; @@ -585,6 +589,14 @@ ul.block, .block li { background-clip: border-box; } +.hide-toc #rustdoc-toc, .hide-toc .in-crate { + display: none; +} + +.hide-modnav #rustdoc-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 c3e219f2c87..0291b6ed6f5 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("rustdoc-modnav"); /** * Append to the sidebar a "block" of links - a heading along with a list (`<ul>`) 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("rustdoc-modnav"); if (!sidebarElems) { return; } diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index 2b42fbebb80..c52a19ef987 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 += "<hr>"; + continue; + } + const js_data_name = setting["js_name"]; const setting_name = setting["name"]; @@ -199,6 +218,16 @@ "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", "default": false, diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index 4a27ca92fff..d75fb7a7fb5 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 `<html>` 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 `<html>` 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 `<html>` 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 0990c2716b8..fccf65cbefc 100644 --- a/src/librustdoc/html/templates/sidebar.html +++ b/src/librustdoc/html/templates/sidebar.html @@ -1,8 +1,3 @@ -{% if !title.is_empty() %} - <h2 class="location"> {# #} - <a href="#">{{title_prefix}}{{title|wrapped|safe}}</a> {# #} - </h2> -{% endif %} <div class="sidebar-elems"> {% if is_crate %} <ul class="block"> {# #} @@ -11,18 +6,46 @@ {% endif %} {% if self.should_render_blocks() %} - <section> + <section id="rustdoc-toc"> + {% if !title.is_empty() %} + <h2 class="location"> {# #} + <a href="#">{{title_prefix}}{{title|wrapped|safe}}</a> {# #} + </h2> + {% endif %} {% 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> {# #} + <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}}" title="{{link.name}}"> + {% match link.name_html %} + {% when Some with (html) %} + {{html|safe}} + {% else %} + {{link.name}} + {% endmatch %} + </a> {# #} + {% if !link.children.is_empty() %} + <ul> + {% for child in link.children %} + <li><a href="#{{child.href|safe}}" title="{{child.name}}"> + {% match child.name_html %} + {% when Some with (html) %} + {{html|safe}} + {% else %} + {{child.name}} + {% endmatch %} + </a></li> + {% endfor %} + </ul> + {% endif %} + </li> {% endfor %} </ul> {% endif %} @@ -30,7 +53,11 @@ {% endfor %} </section> {% endif %} + <div id="rustdoc-modnav"> {% if !path.is_empty() %} - <h2><a href="{% if is_mod %}../{% endif %}index.html">In {{+ path|wrapped|safe}}</a></h2> + <h2{% if parent_is_crate +%} class="in-crate"{% endif %}> {# #} + <a href="{% if is_mod %}../{% endif %}index.html">In {{+ path|wrapped|safe}}</a> {# #} + </h2> {% endif %} + </div> {# #} </div> diff --git a/src/librustdoc/html/toc.rs b/src/librustdoc/html/toc.rs index a12c2a6a16c..7fdce41a634 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::Escape; /// 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,16 @@ 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, + // name is a plain text header that works in a `title` tag + // html includes `<code>` 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, } /// Progressive construction of a table of contents. @@ -115,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 /// `<num>.<num>.<num>` 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 @@ -149,6 +155,7 @@ impl TocBuilder { self.chain.push(TocEntry { level, name, + html, sec_number, id, children: Toc { entries: Vec::new() }, @@ -170,10 +177,11 @@ impl Toc { // recursively format this table of contents let _ = write!( v, - "\n<li><a href=\"#{id}\">{num} {name}</a>", + "\n<li><a href=\"#{id}\" title=\"{name}\">{num} {html}</a>", id = entry.id, num = entry.sec_number, - name = entry.name + name = Escape(&entry.name), + html = &entry.html, ); entry.children.print_inner(&mut *v); v.push_str("</li>"); diff --git a/src/librustdoc/html/toc/tests.rs b/src/librustdoc/html/toc/tests.rs index 014f346862b..81aca737baf 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/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index a98f81d011e..581ebbbe58d 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -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, |
