Skip to content

Commit bfb923d

Browse files
Always display first line of impl blocks even when collapsed
1 parent c1cfab2 commit bfb923d

File tree

4 files changed

+142
-45
lines changed

4 files changed

+142
-45
lines changed

src/librustdoc/html/markdown.rs

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ use std::iter::Peekable;
3232
use std::ops::{ControlFlow, Range};
3333
use std::path::PathBuf;
3434
use std::str::{self, CharIndices};
35-
use std::sync::OnceLock;
35+
use std::sync::atomic::AtomicUsize;
36+
use std::sync::{Arc, OnceLock, Weak};
3637

3738
use pulldown_cmark::{
3839
BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, TagEnd, html,
@@ -1302,8 +1303,20 @@ impl LangString {
13021303
}
13031304
}
13041305

1305-
impl Markdown<'_> {
1306+
impl<'a> Markdown<'a> {
13061307
pub fn into_string(self) -> String {
1308+
// This is actually common enough to special-case
1309+
if self.content.is_empty() {
1310+
return String::new();
1311+
}
1312+
1313+
let mut s = String::with_capacity(self.content.len() * 3 / 2);
1314+
html::push_html(&mut s, self.into_iter());
1315+
1316+
s
1317+
}
1318+
1319+
fn into_iter(self) -> CodeBlocks<'a, 'a, impl Iterator<Item = Event<'a>>> {
13071320
let Markdown {
13081321
content: md,
13091322
links,
@@ -1314,32 +1327,23 @@ impl Markdown<'_> {
13141327
heading_offset,
13151328
} = self;
13161329

1317-
// This is actually common enough to special-case
1318-
if md.is_empty() {
1319-
return String::new();
1320-
}
1321-
let mut replacer = |broken_link: BrokenLink<'_>| {
1330+
let replacer = move |broken_link: BrokenLink<'_>| {
13221331
links
13231332
.iter()
13241333
.find(|link| *link.original_text == *broken_link.reference)
13251334
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
13261335
};
13271336

1328-
let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(&mut replacer));
1337+
let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(replacer));
13291338
let p = p.into_offset_iter();
13301339

1331-
let mut s = String::with_capacity(md.len() * 3 / 2);
1332-
13331340
ids.handle_footnotes(|ids, existing_footnotes| {
13341341
let p = HeadingLinks::new(p, None, ids, heading_offset);
13351342
let p = footnotes::Footnotes::new(p, existing_footnotes);
13361343
let p = LinkReplacer::new(p.map(|(ev, _)| ev), links);
13371344
let p = TableWrapper::new(p);
1338-
let p = CodeBlocks::new(p, codes, edition, playground);
1339-
html::push_html(&mut s, p);
1340-
});
1341-
1342-
s
1345+
CodeBlocks::new(p, codes, edition, playground)
1346+
})
13431347
}
13441348
}
13451349

@@ -1414,6 +1418,52 @@ impl MarkdownItemInfo<'_> {
14141418
}
14151419
}
14161420

1421+
pub(crate) fn markdown_split_summary_and_content(
1422+
md: Markdown<'_>,
1423+
) -> (Option<String>, Option<String>) {
1424+
if md.content.is_empty() {
1425+
return (None, None);
1426+
}
1427+
let mut p = md.into_iter();
1428+
1429+
let mut event_level = 0;
1430+
let mut summary_events = Vec::new();
1431+
let mut get_next_tag = false;
1432+
1433+
let mut end_of_summary = false;
1434+
while let Some(event) = p.next() {
1435+
match event {
1436+
Event::Start(_) => event_level += 1,
1437+
Event::End(kind) => {
1438+
event_level -= 1;
1439+
if event_level == 0 {
1440+
// We're back at the "top" so it means we're done with the summary.
1441+
end_of_summary = true;
1442+
// We surround tables with `<div>` HTML tags so this is a special case.
1443+
get_next_tag = kind == TagEnd::Table;
1444+
}
1445+
}
1446+
_ => {}
1447+
}
1448+
summary_events.push(event);
1449+
if end_of_summary {
1450+
if get_next_tag && let Some(event) = p.next() {
1451+
summary_events.push(event);
1452+
}
1453+
break;
1454+
}
1455+
}
1456+
let mut summary = String::new();
1457+
html::push_html(&mut summary, summary_events.into_iter());
1458+
if summary.is_empty() {
1459+
return (None, None);
1460+
}
1461+
let mut content = String::new();
1462+
html::push_html(&mut content, p);
1463+
1464+
if content.is_empty() { (Some(summary), None) } else { (Some(summary), Some(content)) }
1465+
}
1466+
14171467
impl MarkdownSummaryLine<'_> {
14181468
pub(crate) fn into_string_with_has_more_content(self) -> (String, bool) {
14191469
let MarkdownSummaryLine(md, links) = self;
@@ -1883,7 +1933,7 @@ pub(crate) fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec<Rust
18831933
#[derive(Clone, Default, Debug)]
18841934
pub struct IdMap {
18851935
map: FxHashMap<Cow<'static, str>, usize>,
1886-
existing_footnotes: usize,
1936+
existing_footnotes: Arc<AtomicUsize>,
18871937
}
18881938

18891939
// The map is pre-initialized and cloned each time to avoid reinitializing it repeatedly.
@@ -1945,7 +1995,10 @@ fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {
19451995

19461996
impl IdMap {
19471997
pub fn new() -> Self {
1948-
IdMap { map: DEFAULT_ID_MAP.get_or_init(init_id_map).clone(), existing_footnotes: 0 }
1998+
IdMap {
1999+
map: DEFAULT_ID_MAP.get_or_init(init_id_map).clone(),
2000+
existing_footnotes: Arc::new(AtomicUsize::new(0)),
2001+
}
19492002
}
19502003

19512004
pub(crate) fn derive<S: AsRef<str> + ToString>(&mut self, candidate: S) -> String {
@@ -1964,10 +2017,12 @@ impl IdMap {
19642017

19652018
/// Method to handle `existing_footnotes` increment automatically (to prevent forgetting
19662019
/// about it).
1967-
pub(crate) fn handle_footnotes<F: FnOnce(&mut Self, &mut usize)>(&mut self, closure: F) {
1968-
let mut existing_footnotes = self.existing_footnotes;
2020+
pub(crate) fn handle_footnotes<'a, T, F: FnOnce(&'a mut Self, Weak<AtomicUsize>) -> T>(
2021+
&'a mut self,
2022+
closure: F,
2023+
) -> T {
2024+
let existing_footnotes = Arc::downgrade(&self.existing_footnotes);
19692025

1970-
closure(self, &mut existing_footnotes);
1971-
self.existing_footnotes = existing_footnotes;
2026+
closure(self, existing_footnotes)
19722027
}
19732028
}

src/librustdoc/html/markdown/footnotes.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Markdown footnote handling.
2+
23
use std::fmt::Write as _;
4+
use std::sync::atomic::{AtomicUsize, Ordering};
5+
use std::sync::{Arc, Weak};
36

47
use pulldown_cmark::{CowStr, Event, Tag, TagEnd, html};
58
use rustc_data_structures::fx::FxIndexMap;
@@ -8,10 +11,11 @@ use super::SpannedEvent;
811

912
/// Moves all footnote definitions to the end and add back links to the
1013
/// references.
11-
pub(super) struct Footnotes<'a, 'b, I> {
14+
pub(super) struct Footnotes<'a, I> {
1215
inner: I,
1316
footnotes: FxIndexMap<String, FootnoteDef<'a>>,
14-
existing_footnotes: &'b mut usize,
17+
existing_footnotes: Arc<AtomicUsize>,
18+
start_id: usize,
1519
}
1620

1721
/// The definition of a single footnote.
@@ -21,13 +25,16 @@ struct FootnoteDef<'a> {
2125
id: usize,
2226
}
2327

24-
impl<'a, 'b, I: Iterator<Item = SpannedEvent<'a>>> Footnotes<'a, 'b, I> {
25-
pub(super) fn new(iter: I, existing_footnotes: &'b mut usize) -> Self {
26-
Footnotes { inner: iter, footnotes: FxIndexMap::default(), existing_footnotes }
28+
impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Footnotes<'a, I> {
29+
pub(super) fn new(iter: I, existing_footnotes: Weak<AtomicUsize>) -> Self {
30+
let existing_footnotes =
31+
existing_footnotes.upgrade().expect("`existing_footnotes` was dropped");
32+
let start_id = existing_footnotes.load(Ordering::Relaxed);
33+
Footnotes { inner: iter, footnotes: FxIndexMap::default(), existing_footnotes, start_id }
2734
}
2835

2936
fn get_entry(&mut self, key: &str) -> (&mut Vec<Event<'a>>, usize) {
30-
let new_id = self.footnotes.len() + 1 + *self.existing_footnotes;
37+
let new_id = self.footnotes.len() + 1 + self.start_id;
3138
let key = key.to_owned();
3239
let FootnoteDef { content, id } =
3340
self.footnotes.entry(key).or_insert(FootnoteDef { content: Vec::new(), id: new_id });
@@ -44,7 +51,7 @@ impl<'a, 'b, I: Iterator<Item = SpannedEvent<'a>>> Footnotes<'a, 'b, I> {
4451
id,
4552
// Although the ID count is for the whole page, the footnote reference
4653
// are local to the item so we make this ID "local" when displayed.
47-
id - *self.existing_footnotes
54+
id - self.start_id
4855
);
4956
Event::Html(reference.into())
5057
}
@@ -64,7 +71,7 @@ impl<'a, 'b, I: Iterator<Item = SpannedEvent<'a>>> Footnotes<'a, 'b, I> {
6471
}
6572
}
6673

67-
impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, '_, I> {
74+
impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, I> {
6875
type Item = SpannedEvent<'a>;
6976

7077
fn next(&mut self) -> Option<Self::Item> {
@@ -87,7 +94,7 @@ impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, '_, I>
8794
// After all the markdown is emmited, emit an <hr> then all the footnotes
8895
// in a list.
8996
let defs: Vec<_> = self.footnotes.drain(..).map(|(_, x)| x).collect();
90-
*self.existing_footnotes += defs.len();
97+
self.existing_footnotes.fetch_add(defs.len(), Ordering::Relaxed);
9198
let defs_html = render_footnotes_defs(defs);
9299
return Some((Event::Html(defs_html.into()), 0..0));
93100
} else {

src/librustdoc/html/render/mod.rs

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ use crate::html::format::{
7575
};
7676
use crate::html::markdown::{
7777
HeadingOffset, IdMap, Markdown, MarkdownItemInfo, MarkdownSummaryLine,
78+
markdown_split_summary_and_content,
7879
};
7980
use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD;
8081
use crate::html::{highlight, sources};
@@ -1938,6 +1939,22 @@ fn render_impl(
19381939
if rendering_params.toggle_open_by_default { " open" } else { "" }
19391940
);
19401941
}
1942+
1943+
let (before_dox, after_dox) = i
1944+
.impl_item
1945+
.opt_doc_value()
1946+
.map(|dox| {
1947+
markdown_split_summary_and_content(Markdown {
1948+
content: &*dox,
1949+
links: &i.impl_item.links(cx),
1950+
ids: &mut cx.id_map,
1951+
error_codes: cx.shared.codes,
1952+
edition: cx.shared.edition(),
1953+
playground: &cx.shared.playground,
1954+
heading_offset: HeadingOffset::H4,
1955+
})
1956+
})
1957+
.unwrap_or((None, None));
19411958
render_impl_summary(
19421959
w,
19431960
cx,
@@ -1946,33 +1963,23 @@ fn render_impl(
19461963
rendering_params.show_def_docs,
19471964
use_absolute,
19481965
aliases,
1966+
&before_dox,
19491967
);
19501968
if toggled {
19511969
w.write_str("</summary>");
19521970
}
19531971

1954-
if let Some(ref dox) = i.impl_item.opt_doc_value() {
1972+
if before_dox.is_some() {
19551973
if trait_.is_none() && impl_.items.is_empty() {
19561974
w.write_str(
19571975
"<div class=\"item-info\">\
19581976
<div class=\"stab empty-impl\">This impl block contains no items.</div>\
19591977
</div>",
19601978
);
19611979
}
1962-
write!(
1963-
w,
1964-
"<div class=\"docblock\">{}</div>",
1965-
Markdown {
1966-
content: dox,
1967-
links: &i.impl_item.links(cx),
1968-
ids: &mut cx.id_map,
1969-
error_codes: cx.shared.codes,
1970-
edition: cx.shared.edition(),
1971-
playground: &cx.shared.playground,
1972-
heading_offset: HeadingOffset::H4,
1973-
}
1974-
.into_string()
1975-
);
1980+
if let Some(after_dox) = after_dox {
1981+
write!(w, "<div class=\"docblock\">{after_dox}</div>");
1982+
}
19761983
}
19771984
if !default_impl_items.is_empty() || !impl_items.is_empty() {
19781985
w.write_str("<div class=\"impl-items\">");
@@ -2033,6 +2040,7 @@ pub(crate) fn render_impl_summary(
20332040
// This argument is used to reference same type with different paths to avoid duplication
20342041
// in documentation pages for trait with automatic implementations like "Send" and "Sync".
20352042
aliases: &[String],
2043+
doc: &Option<String>,
20362044
) {
20372045
let inner_impl = i.inner_impl();
20382046
let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id));
@@ -2084,6 +2092,10 @@ pub(crate) fn render_impl_summary(
20842092
);
20852093
}
20862094

2095+
if let Some(doc) = doc {
2096+
write!(w, "<div class=\"docblock\">{doc}</div>");
2097+
}
2098+
20872099
w.write_str("</section>");
20882100
}
20892101

src/librustdoc/html/static/css/rustdoc.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2195,6 +2195,29 @@ details.toggle[open] > summary::after {
21952195
content: "Collapse";
21962196
}
21972197

2198+
details.toggle:not([open]) > summary .docblock {
2199+
max-height: calc(1.5em + 0.75em);
2200+
overflow-y: hidden;
2201+
}
2202+
details.toggle:not([open]) > summary .docblock::after {
2203+
content: '';
2204+
position: absolute;
2205+
bottom: 0;
2206+
left: 0;
2207+
right: 0;
2208+
pointer-events: none;
2209+
background: linear-gradient(
2210+
to top,
2211+
var(--scrape-example-code-wrapper-background-start),
2212+
var(--scrape-example-code-wrapper-background-end)
2213+
);
2214+
height: 0.7em;
2215+
z-index: 1;
2216+
}
2217+
details.toggle > summary .docblock {
2218+
margin-top: 0.75em;
2219+
}
2220+
21982221
/* This is needed in docblocks to have the "▶" element to be on the same line. */
21992222
.docblock summary > * {
22002223
display: inline-block;

0 commit comments

Comments
 (0)