From 7091fa5880db84392783147171f5709b72ffa784 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 22 Jul 2024 12:59:34 -0700 Subject: [PATCH] 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 58795508a58..4b82f232765 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -50,7 +50,7 @@ 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 @@ fn next(&mut self) -> Option { 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 a6b22a18de2..351a23b880d 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -81,8 +81,10 @@ pub fn should_render(&self) -> bool { /// 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 f49cdbabb88..31823848ec3 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 a44c4a1b59e..7fdce41a634 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 @@ fn count_entries_with_level(&self, level: u32) -> usize { 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 @@ fn fold_until(&mut self, level: u32) { /// 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 @@ pub(crate) fn push(&mut self, level: u32, name: String, id: String) -> &str { self.chain.push(TocEntry { level, name, + html, sec_number, id, children: Toc { entries: Vec::new() }, @@ -171,10 +177,11 @@ fn print_inner(&self, v: &mut String) { // 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 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 @@ macro_rules! toc { 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 fa1325c8dca..db998775644 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 ccfc7336e9f..fdb99fd05a1 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 91c7df50ab4..c338bd67fcf 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 e072335297d..d7a36cc631a 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