Skip to content

Commit 7091fa5

Browse files
committed
rustdoc: show code spans as <code> in TOC
1 parent 68773c7 commit 7091fa5

File tree

9 files changed

+92
-31
lines changed

9 files changed

+92
-31
lines changed

src/librustdoc/html/markdown.rs

+27-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ use rustc_span::{Span, Symbol};
5050
use crate::clean::RenderedLink;
5151
use crate::doctest;
5252
use crate::doctest::GlobalTestOptions;
53-
use crate::html::escape::Escape;
53+
use crate::html::escape::{Escape, EscapeBodyText};
5454
use crate::html::format::Buffer;
5555
use crate::html::highlight;
5656
use crate::html::length_limit::HtmlWithLimit;
@@ -535,7 +535,9 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
535535
if let Some(ref mut builder) = self.toc {
536536
let mut text_header = String::new();
537537
plain_text_from_events(self.buf.iter().map(|(ev, _)| ev.clone()), &mut text_header);
538-
let sec = builder.push(level as u32, text_header, id.clone());
538+
let mut html_header = String::new();
539+
html_text_from_events(self.buf.iter().map(|(ev, _)| ev.clone()), &mut html_header);
540+
let sec = builder.push(level as u32, text_header, html_header, id.clone());
539541
self.buf.push_front((Event::Html(format!("{sec} ").into()), 0..0));
540542
}
541543

@@ -1655,6 +1657,29 @@ pub(crate) fn plain_text_from_events<'a>(
16551657
}
16561658
}
16571659

1660+
pub(crate) fn html_text_from_events<'a>(
1661+
events: impl Iterator<Item = pulldown_cmark::Event<'a>>,
1662+
s: &mut String,
1663+
) {
1664+
for event in events {
1665+
match &event {
1666+
Event::Text(text) => {
1667+
write!(s, "{}", EscapeBodyText(text)).expect("string alloc infallible")
1668+
}
1669+
Event::Code(code) => {
1670+
s.push_str("<code>");
1671+
write!(s, "{}", EscapeBodyText(code)).expect("string alloc infallible");
1672+
s.push_str("</code>");
1673+
}
1674+
Event::HardBreak | Event::SoftBreak => s.push(' '),
1675+
Event::Start(Tag::CodeBlock(..)) => break,
1676+
Event::End(TagEnd::Paragraph) => break,
1677+
Event::End(TagEnd::Heading(..)) => break,
1678+
_ => (),
1679+
}
1680+
}
1681+
}
1682+
16581683
#[derive(Debug)]
16591684
pub(crate) struct MarkdownLink {
16601685
pub kind: LinkType,

src/librustdoc/html/render/sidebar.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,10 @@ impl<'a> LinkBlock<'a> {
8181
/// A link to an item. Content should not be escaped.
8282
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone)]
8383
pub(crate) struct Link<'a> {
84-
/// The content for the anchor tag
84+
/// The content for the anchor tag and title attr
8585
name: Cow<'a, str>,
86+
/// The content for the anchor tag (if different from name)
87+
name_html: Option<Cow<'a, str>>,
8688
/// The id of an anchor within the page (without a `#` prefix)
8789
href: Cow<'a, str>,
8890
/// Nested list of links (used only in top-toc)
@@ -91,7 +93,7 @@ pub(crate) struct Link<'a> {
9193

9294
impl<'a> Link<'a> {
9395
pub fn new(href: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>) -> Self {
94-
Self { href: href.into(), name: name.into(), children: vec![] }
96+
Self { href: href.into(), name: name.into(), children: vec![], name_html: None }
9597
}
9698
pub fn empty() -> Link<'static> {
9799
Link::new("", "")
@@ -207,13 +209,19 @@ fn docblock_toc<'a>(
207209
.into_iter()
208210
.map(|entry| {
209211
Link {
212+
name_html: if entry.html == entry.name { None } else { Some(entry.html.into()) },
210213
name: entry.name.into(),
211214
href: entry.id.into(),
212215
children: entry
213216
.children
214217
.entries
215218
.into_iter()
216219
.map(|entry| Link {
220+
name_html: if entry.html == entry.name {
221+
None
222+
} else {
223+
Some(entry.html.into())
224+
},
217225
name: entry.name.into(),
218226
href: entry.id.into(),
219227
// Only a single level of nesting is shown here.

src/librustdoc/html/templates/sidebar.html

+16-2
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,25 @@ <h3> {# #}
2323
<ul class="block{% if !block.class.is_empty() +%} {{+block.class}}{% endif %}">
2424
{% for link in block.links %}
2525
<li> {# #}
26-
<a href="#{{link.href|safe}}">{{link.name}}</a> {# #}
26+
<a href="#{{link.href|safe}}" title="{{link.name}}">
27+
{% match link.name_html %}
28+
{% when Some with (html) %}
29+
{{html|safe}}
30+
{% else %}
31+
{{link.name}}
32+
{% endmatch %}
33+
</a> {# #}
2734
{% if !link.children.is_empty() %}
2835
<ul>
2936
{% for child in link.children %}
30-
<li><a href="#{{child.href|safe}}">{{child.name}}</a></li>
37+
<li><a href="#{{child.href|safe}}" title="{{child.name}}">
38+
{% match child.name_html %}
39+
{% when Some with (html) %}
40+
{{html|safe}}
41+
{% else %}
42+
{{child.name}}
43+
{% endmatch %}
44+
</a></li>
3145
{% endfor %}
3246
</ul>
3347
{% endif %}

src/librustdoc/html/toc.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Table-of-contents creation.
2-
use crate::html::escape::EscapeBodyText;
2+
use crate::html::escape::Escape;
33

44
/// A (recursive) table of contents
55
#[derive(Debug, PartialEq)]
@@ -30,7 +30,12 @@ impl Toc {
3030
pub(crate) struct TocEntry {
3131
pub(crate) level: u32,
3232
pub(crate) sec_number: String,
33+
// name is a plain text header that works in a `title` tag
34+
// html includes `<code>` tags
35+
// the tooltip is used so that, when a toc is truncated,
36+
// you can mouse over it to see the whole thing
3337
pub(crate) name: String,
38+
pub(crate) html: String,
3439
pub(crate) id: String,
3540
pub(crate) children: Toc,
3641
}
@@ -116,7 +121,7 @@ impl TocBuilder {
116121
/// Push a level `level` heading into the appropriate place in the
117122
/// hierarchy, returning a string containing the section number in
118123
/// `<num>.<num>.<num>` format.
119-
pub(crate) fn push(&mut self, level: u32, name: String, id: String) -> &str {
124+
pub(crate) fn push(&mut self, level: u32, name: String, html: String, id: String) -> &str {
120125
assert!(level >= 1);
121126

122127
// collapse all previous sections into their parents until we
@@ -150,6 +155,7 @@ impl TocBuilder {
150155
self.chain.push(TocEntry {
151156
level,
152157
name,
158+
html,
153159
sec_number,
154160
id,
155161
children: Toc { entries: Vec::new() },
@@ -171,10 +177,11 @@ impl Toc {
171177
// recursively format this table of contents
172178
let _ = write!(
173179
v,
174-
"\n<li><a href=\"#{id}\">{num} {name}</a>",
180+
"\n<li><a href=\"#{id}\" title=\"{name}\">{num} {html}</a>",
175181
id = entry.id,
176182
num = entry.sec_number,
177-
name = EscapeBodyText(&entry.name)
183+
name = Escape(&entry.name),
184+
html = &entry.html,
178185
);
179186
entry.children.print_inner(&mut *v);
180187
v.push_str("</li>");

src/librustdoc/html/toc/tests.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ fn builder_smoke() {
99
// there's been no macro mistake.
1010
macro_rules! push {
1111
($level: expr, $name: expr) => {
12-
assert_eq!(builder.push($level, $name.to_string(), "".to_string()), $name);
12+
assert_eq!(
13+
builder.push($level, $name.to_string(), $name.to_string(), "".to_string()),
14+
$name
15+
);
1316
};
1417
}
1518
push!(2, "0.1");
@@ -48,6 +51,7 @@ fn builder_smoke() {
4851
TocEntry {
4952
level: $level,
5053
name: $name.to_string(),
54+
html: $name.to_string(),
5155
sec_number: $name.to_string(),
5256
id: "".to_string(),
5357
children: toc!($($sub),*)

tests/rustdoc/sidebar/top-toc-html.rs

+9-6
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@
44
#![feature(lazy_type_alias)]
55
#![allow(incomplete_features)]
66

7-
//! # Basic [link](https://example.com) and *emphasis*
7+
//! # Basic [link](https://example.com) and *emphasis* and `code`
88
//!
99
//! This test case covers TOC entries with rich text inside.
1010
//! Rustdoc normally supports headers with links, but for the
1111
//! TOC, that would break the layout.
1212
//!
1313
//! For consistency, emphasis is also filtered out.
1414
15-
// @has foo/index.html
15+
//@ has foo/index.html
1616
// User header
17-
// @has - '//section[@id="TOC"]/h3' 'Sections'
18-
// @has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis"]' 'Basic link and emphasis'
19-
// @count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis"]/em' 0
20-
// @count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis"]/a' 0
17+
//@ has - '//section[@id="TOC"]/h3' 'Sections'
18+
//@ 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`'
19+
//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]' 'Basic link and emphasis and code'
20+
//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/em' 0
21+
//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/a' 0
22+
//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/code' 1
23+
//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-and-emphasis-and-code"]/code' 'code'

tests/rustdoc/sidebar/top-toc-idmap.rs

+11-11
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
//! in the `top-doc`, and the one that's not in the `top-doc`
1515
//! needs to match the one that isn't in the `top-toc`.
1616
17-
// @has foo/index.html
17+
//@ has foo/index.html
1818
// User header
19-
// @has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#structs"]' 'Structs'
20-
// @has - '//details[@class="toggle top-doc"]/div[@class="docblock"]/h2[@id="structs"]' 'Structs'
19+
//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#structs"]' 'Structs'
20+
//@ has - '//details[@class="toggle top-doc"]/div[@class="docblock"]/h2[@id="structs"]' 'Structs'
2121
// Built-in header
22-
// @has - '//section[@id="TOC"]/ul[@class="block"]/li/a[@href="#structs-1"]' 'Structs'
23-
// @has - '//section[@id="main-content"]/h2[@id="structs-1"]' 'Structs'
22+
//@ has - '//section[@id="TOC"]/ul[@class="block"]/li/a[@href="#structs-1"]' 'Structs'
23+
//@ has - '//section[@id="main-content"]/h2[@id="structs-1"]' 'Structs'
2424

2525
/// # Fields
2626
/// ## Fields
@@ -29,15 +29,15 @@
2929
/// The difference between struct-like headers and module-like headers
3030
/// is strange, but not actually a problem as long as we're consistent.
3131
32-
// @has foo/struct.MyStruct.html
32+
//@ has foo/struct.MyStruct.html
3333
// User header
34-
// @has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#fields-1"]' 'Fields'
35-
// @has - '//details[@class="toggle top-doc"]/div[@class="docblock"]/h2[@id="fields-1"]' 'Fields'
34+
//@ has - '//section[@id="TOC"]/ul[@class="block top-toc"]/li/a[@href="#fields-1"]' 'Fields'
35+
//@ has - '//details[@class="toggle top-doc"]/div[@class="docblock"]/h2[@id="fields-1"]' 'Fields'
3636
// Only one level of nesting
37-
// @count - '//section[@id="TOC"]/ul[@class="block top-toc"]//a' 2
37+
//@ count - '//section[@id="TOC"]/ul[@class="block top-toc"]//a' 2
3838
// Built-in header
39-
// @has - '//section[@id="TOC"]/h3/a[@href="#fields"]' 'Fields'
40-
// @has - '//section[@id="main-content"]/h2[@id="fields"]' 'Fields'
39+
//@ has - '//section[@id="TOC"]/h3/a[@href="#fields"]' 'Fields'
40+
//@ has - '//section[@id="main-content"]/h2[@id="fields"]' 'Fields'
4141

4242
pub struct MyStruct {
4343
pub fields: i32,

tests/rustdoc/sidebar/top-toc-nil.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
//! This test case covers missing top TOC entries.
44
5-
// @has foo/index.html
5+
//@ has foo/index.html
66
// User header
7-
// @!has - '//section[@id="TOC"]/ul[@class="block top-toc"]' 'Basic link and emphasis'
7+
//@ !has - '//section[@id="TOC"]/ul[@class="block top-toc"]' 'Basic link and emphasis'
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<ul class="block variant"><li><a href="#variant.Shown">Shown</a></li></ul>
1+
<ul class="block variant"><li><a href="#variant.Shown" title="Shown">Shown</a></li></ul>

0 commit comments

Comments
 (0)