Skip to content

Commit eea8f0a

Browse files
committed
Sort examples by size
Improve styling Start to clean up code, add comments
1 parent b6338e7 commit eea8f0a

File tree

6 files changed

+158
-61
lines changed

6 files changed

+158
-61
lines changed

src/librustdoc/clean/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,8 @@ crate struct Function {
12591259
}
12601260

12611261
impl Function {
1262+
/// If --scrape-examples is used, then this function attempts to find call locations
1263+
/// for `self` within `RenderOptions::call_locations` and store them in `Function::call_locations`.
12621264
crate fn load_call_locations(&mut self, def_id: hir::def_id::DefId, cx: &DocContext<'_>) {
12631265
if let Some(call_locations) = cx.render_options.call_locations.as_ref() {
12641266
let key = scrape_examples::def_id_call_key(cx.tcx, def_id);

src/librustdoc/config.rs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
22
use std::convert::TryFrom;
33
use std::ffi::OsStr;
44
use std::fmt;
5-
use std::fs;
65
use std::path::PathBuf;
76
use std::str::FromStr;
87

@@ -680,29 +679,7 @@ impl Options {
680679

681680
let scrape_examples = matches.opt_str("scrape-examples").map(PathBuf::from);
682681
let with_examples = matches.opt_strs("with-examples");
683-
let each_call_locations = with_examples
684-
.into_iter()
685-
.map(|path| {
686-
let bytes = fs::read(&path).map_err(|e| format!("{} (for path {})", e, path))?;
687-
let calls: AllCallLocations =
688-
serde_json::from_slice(&bytes).map_err(|e| format!("{}", e))?;
689-
Ok(calls)
690-
})
691-
.collect::<Result<Vec<_>, _>>()
692-
.map_err(|e: String| {
693-
diag.err(&format!("failed to load examples with error: {}", e));
694-
1
695-
})?;
696-
let call_locations = (each_call_locations.len() > 0).then(move || {
697-
each_call_locations.into_iter().fold(FxHashMap::default(), |mut acc, map| {
698-
for (function, calls) in map.into_iter() {
699-
acc.entry(function)
700-
.or_insert_with(FxHashMap::default)
701-
.extend(calls.into_iter());
702-
}
703-
acc
704-
})
705-
});
682+
let call_locations = crate::scrape_examples::load_call_locations(with_examples, &diag)?;
706683

707684
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
708685

src/librustdoc/html/render/mod.rs

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2453,32 +2453,31 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> {
24532453

24542454
const MAX_FULL_EXAMPLES: usize = 5;
24552455

2456+
/// Generates the HTML for example call locations generated via the --scrape-examples flag.
24562457
fn render_call_locations(
24572458
w: &mut Buffer,
24582459
cx: &Context<'_>,
24592460
call_locations: &Option<FnCallLocations>,
24602461
) {
24612462
let call_locations = match call_locations.as_ref() {
2462-
Some(call_locations) => call_locations,
2463-
None => {
2463+
Some(call_locations) if call_locations.len() > 0 => call_locations,
2464+
_ => {
24642465
return;
24652466
}
24662467
};
24672468

2468-
if call_locations.len() == 0 {
2469-
return;
2470-
}
2471-
2469+
// Generate a unique ID so users can link to this section for a given method
24722470
let id = cx.id_map.borrow_mut().derive("scraped-examples");
24732471
write!(
24742472
w,
24752473
r##"<div class="docblock scraped-example-list">
24762474
<h1 id="scraped-examples" class="small-section-header">
2477-
<a href="#{}">Uses found in <code>examples/</code></a>
2475+
<a href="#{}">Examples found in repository</a>
24782476
</h1>"##,
24792477
id
24802478
);
24812479

2480+
// Link to the source file containing a given example
24822481
let example_url = |call_data: &CallData| -> String {
24832482
format!(
24842483
r#"<a href="{root}{url}" target="_blank">{name}</a>"#,
@@ -2488,18 +2487,27 @@ fn render_call_locations(
24882487
)
24892488
};
24902489

2490+
// Generate the HTML for a single example, being the title and code block
24912491
let write_example = |w: &mut Buffer, (path, call_data): (&PathBuf, &CallData)| {
2492-
let mut contents =
2492+
// FIXME(wcrichto): is there a better way to handle an I/O error than a panic?
2493+
// When would such an error arise?
2494+
let contents =
24932495
fs::read_to_string(&path).expect(&format!("Failed to read file: {}", path.display()));
24942496

2497+
// To reduce file sizes, we only want to embed the source code needed to understand the example, not
2498+
// the entire file. So we find the smallest byte range that covers all items enclosing examples.
24952499
let min_loc =
24962500
call_data.locations.iter().min_by_key(|loc| loc.enclosing_item_span.0).unwrap();
24972501
let min_byte = min_loc.enclosing_item_span.0;
24982502
let min_line = min_loc.enclosing_item_lines.0;
24992503
let max_byte =
25002504
call_data.locations.iter().map(|loc| loc.enclosing_item_span.1).max().unwrap();
2501-
contents = contents[min_byte..max_byte].to_string();
25022505

2506+
// The output code is limited to that byte range.
2507+
let contents_subset = &contents[min_byte..max_byte];
2508+
2509+
// The call locations need to be updated to reflect that the size of the program has changed.
2510+
// Specifically, the ranges are all subtracted by `min_byte` since that's the new zero point.
25032511
let locations = call_data
25042512
.locations
25052513
.iter()
@@ -2510,43 +2518,74 @@ fn render_call_locations(
25102518
write!(
25112519
w,
25122520
r#"<div class="scraped-example" data-code="{code}" data-locs="{locations}">
2513-
<strong>{title}</strong>
2521+
<div class="scraped-example-title">{title}</div>
25142522
<div class="code-wrapper">"#,
2515-
code = contents.replace("\"", "&quot;"),
2516-
locations = serde_json::to_string(&locations).unwrap(),
25172523
title = example_url(call_data),
2524+
// The code and locations are encoded as data attributes, so they can be read
2525+
// later by the JS for interactions.
2526+
code = contents_subset.replace("\"", "&quot;"),
2527+
locations = serde_json::to_string(&locations).unwrap(),
25182528
);
25192529
write!(w, r#"<span class="prev">&pr;</span> <span class="next">&sc;</span>"#);
25202530
write!(w, r#"<span class="expand">&varr;</span>"#);
2531+
2532+
// FIXME(wcrichto): where should file_span and root_path come from?
25212533
let file_span = rustc_span::DUMMY_SP;
25222534
let root_path = "".to_string();
2523-
sources::print_src(w, &contents, edition, file_span, cx, &root_path, Some(min_line));
2535+
sources::print_src(w, contents_subset, edition, file_span, cx, &root_path, Some(min_line));
25242536
write!(w, "</div></div>");
25252537
};
25262538

2527-
let mut it = call_locations.into_iter().peekable();
2539+
// The call locations are output in sequence, so that sequence needs to be determined.
2540+
// Ideally the most "relevant" examples would be shown first, but there's no general algorithm
2541+
// for determining relevance. Instead, we prefer the smallest examples being likely the easiest to
2542+
// understand at a glance.
2543+
let ordered_locations = {
2544+
let sort_criterion = |(_, call_data): &(_, &CallData)| {
2545+
let (lo, hi) = call_data.locations[0].enclosing_item_span;
2546+
hi - lo
2547+
};
2548+
2549+
let mut locs = call_locations.into_iter().collect::<Vec<_>>();
2550+
locs.sort_by_key(|x| sort_criterion(x));
2551+
locs
2552+
};
2553+
2554+
// Write just one example that's visible by default in the method's description.
2555+
let mut it = ordered_locations.into_iter().peekable();
25282556
write_example(w, it.next().unwrap());
25292557

2558+
// Then add the remaining examples in a hidden section.
25302559
if it.peek().is_some() {
25312560
write!(
25322561
w,
25332562
r#"<details class="rustdoc-toggle more-examples-toggle">
25342563
<summary class="hideme">
25352564
<span>More examples</span>
25362565
</summary>
2537-
<div class="more-scraped-examples">"#
2566+
<div class="more-scraped-examples">
2567+
<div class="toggle-line"><div class="toggle-line-inner"></div></div>
2568+
<div>
2569+
"#
25382570
);
2571+
2572+
// Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could
2573+
// make the page arbitrarily huge!
25392574
(&mut it).take(MAX_FULL_EXAMPLES).for_each(|ex| write_example(w, ex));
25402575

2576+
// For the remaining examples, generate a <ul /> containing links to the source files.
25412577
if it.peek().is_some() {
2542-
write!(w, "Additional examples can be found in:<br /><ul>");
2578+
write!(
2579+
w,
2580+
r#"<div class="example-links">Additional examples can be found in:<br /><ul>"#
2581+
);
25432582
it.for_each(|(_, call_data)| {
25442583
write!(w, "<li>{}</li>", example_url(call_data));
25452584
});
2546-
write!(w, "</ul>");
2585+
write!(w, "</ul></div>");
25472586
}
25482587

2549-
write!(w, "</div></details>");
2588+
write!(w, "</div></div></details>");
25502589
}
25512590

25522591
write!(w, "</div>");

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

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ h1.fqn {
137137
margin-top: 0;
138138

139139
/* workaround to keep flex from breaking below 700 px width due to the float: right on the nav
140-
above the h1 */
140+
above the h1 */
141141
padding-left: 1px;
142142
}
143143
h1.fqn > .in-band > a:hover {
@@ -974,7 +974,7 @@ body.blur > :not(#help) {
974974
text-shadow:
975975
1px 0 0 black,
976976
-1px 0 0 black,
977-
0 1px 0 black,
977+
0 1px 0 black,
978978
0 -1px 0 black;
979979
}
980980

@@ -1214,8 +1214,8 @@ a.test-arrow:hover{
12141214

12151215
.notable-traits-tooltip::after {
12161216
/* The margin on the tooltip does not capture hover events,
1217-
this extends the area of hover enough so that mouse hover is not
1218-
lost when moving the mouse to the tooltip */
1217+
this extends the area of hover enough so that mouse hover is not
1218+
lost when moving the mouse to the tooltip */
12191219
content: "\00a0\00a0\00a0";
12201220
}
12211221

@@ -1715,7 +1715,7 @@ details.undocumented[open] > summary::before {
17151715
}
17161716

17171717
/* We do NOT hide this element so that alternative device readers still have this information
1718-
available. */
1718+
available. */
17191719
.sidebar-elems {
17201720
position: fixed;
17211721
z-index: 1;
@@ -1973,10 +1973,15 @@ details.undocumented[open] > summary::before {
19731973

19741974
/* This part is for the new "examples" components */
19751975

1976+
.scraped-example-title {
1977+
font-family: 'Fira Sans';
1978+
font-weight: 500;
1979+
}
1980+
19761981
.scraped-example:not(.expanded) .code-wrapper pre.line-numbers,
19771982
.scraped-example:not(.expanded) .code-wrapper .example-wrap pre.rust {
19781983
overflow: hidden;
1979-
height: 240px;
1984+
max-height: 240px;
19801985
}
19811986

19821987
.scraped-example .code-wrapper .prev {
@@ -2033,7 +2038,7 @@ details.undocumented[open] > summary::before {
20332038

20342039
.scraped-example:not(.expanded) .code-wrapper {
20352040
overflow: hidden;
2036-
height: 240px;
2041+
max-height: 240px;
20372042
}
20382043

20392044
.scraped-example .code-wrapper .line-numbers {
@@ -2072,6 +2077,41 @@ details.undocumented[open] > summary::before {
20722077

20732078
.more-scraped-examples {
20742079
padding-left: 10px;
2075-
border-left: 1px solid #ccc;
2076-
margin-left: 24px;
2080+
margin-left: 15px;
2081+
display: flex;
2082+
flex-direction: row;
2083+
}
2084+
2085+
.toggle-line {
2086+
align-self: stretch;
2087+
margin-right: 10px;
2088+
margin-top: 5px;
2089+
padding: 0 4px;
2090+
cursor: pointer;
2091+
}
2092+
2093+
.toggle-line:hover .toggle-line-inner {
2094+
background: #aaa;
2095+
}
2096+
2097+
.toggle-line-inner {
2098+
min-width: 2px;
2099+
background: #ddd;
2100+
height: 100%;
2101+
}
2102+
2103+
h1 + .scraped-example {
2104+
margin-bottom: 10px;
2105+
}
2106+
2107+
.more-scraped-examples .scraped-example {
2108+
margin-bottom: 20px;
2109+
}
2110+
2111+
.example-links a {
2112+
font-family: 'Fira Sans';
2113+
}
2114+
2115+
.example-links ul {
2116+
margin-bottom: 0;
20772117
}

src/librustdoc/html/static/js/main.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,15 +1121,22 @@ function hideThemeButtonState() {
11211121
example.querySelector('.next').remove();
11221122
}
11231123

1124-
// Show full code on expansion
1125-
example.querySelector('.expand').addEventListener('click', function () {
1126-
if (hasClass(example, "expanded")) {
1127-
removeClass(example, "expanded");
1128-
scrollToLoc(example, locs[0]);
1129-
} else {
1130-
addClass(example, "expanded");
1131-
}
1132-
});
1124+
let codeEl = example.querySelector('.rust');
1125+
let expandButton = example.querySelector('.expand');
1126+
if (codeEl.scrollHeight == codeEl.clientHeight) {
1127+
addClass(example, 'expanded');
1128+
expandButton.remove();
1129+
} else {
1130+
// Show full code on expansion
1131+
expandButton.addEventListener('click', function () {
1132+
if (hasClass(example, "expanded")) {
1133+
removeClass(example, "expanded");
1134+
scrollToLoc(example, locs[0]);
1135+
} else {
1136+
addClass(example, "expanded");
1137+
}
1138+
});
1139+
}
11331140

11341141
// Start with the first example in view
11351142
scrollToLoc(example, locs[0]);
@@ -1139,6 +1146,10 @@ function hideThemeButtonState() {
11391146
var firstExamples = document.querySelectorAll('.scraped-example-list > .scraped-example');
11401147
onEach(firstExamples, updateScrapedExample);
11411148
onEach(document.querySelectorAll('.more-examples-toggle'), function(toggle) {
1149+
toggle.querySelector('.toggle-line').addEventListener('click', function() {
1150+
toggle.open = false;
1151+
});
1152+
11421153
var moreExamples = toggle.querySelectorAll('.scraped-example');
11431154
toggle.querySelector('summary').addEventListener('click', function() {
11441155
// Wrapping in setTimeout ensures the update happens after the elements are actually

src/librustdoc/scrape_examples.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! This module analyzes provided crates to find examples of uses for items in the
1+
//! This module analyzes crates to find examples of uses for items in the
22
//! current crate being documented.
33
44
use crate::clean;
@@ -158,3 +158,31 @@ crate fn run(
158158
rustc_errors::ErrorReported
159159
})
160160
}
161+
162+
crate fn load_call_locations(
163+
with_examples: Vec<String>,
164+
diag: &rustc_errors::Handler,
165+
) -> Result<Option<AllCallLocations>, i32> {
166+
let each_call_locations = with_examples
167+
.into_iter()
168+
.map(|path| {
169+
let bytes = fs::read(&path).map_err(|e| format!("{} (for path {})", e, path))?;
170+
let calls: AllCallLocations =
171+
serde_json::from_slice(&bytes).map_err(|e| format!("{}", e))?;
172+
Ok(calls)
173+
})
174+
.collect::<Result<Vec<_>, _>>()
175+
.map_err(|e: String| {
176+
diag.err(&format!("failed to load examples with error: {}", e));
177+
1
178+
})?;
179+
180+
Ok((each_call_locations.len() > 0).then(|| {
181+
each_call_locations.into_iter().fold(FxHashMap::default(), |mut acc, map| {
182+
for (function, calls) in map.into_iter() {
183+
acc.entry(function).or_insert_with(FxHashMap::default).extend(calls.into_iter());
184+
}
185+
acc
186+
})
187+
}))
188+
}

0 commit comments

Comments
 (0)