Sort examples by size

Improve styling

Start to clean up code, add comments
This commit is contained in:
Will Crichton 2021-08-25 20:15:46 -07:00
parent b6338e7792
commit eea8f0a39a
6 changed files with 158 additions and 61 deletions

View file

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

View file

@ -2,7 +2,6 @@ use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::ffi::OsStr;
use std::fmt;
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
@ -680,29 +679,7 @@ impl Options {
let scrape_examples = matches.opt_str("scrape-examples").map(PathBuf::from);
let with_examples = matches.opt_strs("with-examples");
let each_call_locations = with_examples
.into_iter()
.map(|path| {
let bytes = fs::read(&path).map_err(|e| format!("{} (for path {})", e, path))?;
let calls: AllCallLocations =
serde_json::from_slice(&bytes).map_err(|e| format!("{}", e))?;
Ok(calls)
})
.collect::<Result<Vec<_>, _>>()
.map_err(|e: String| {
diag.err(&format!("failed to load examples with error: {}", e));
1
})?;
let call_locations = (each_call_locations.len() > 0).then(move || {
each_call_locations.into_iter().fold(FxHashMap::default(), |mut acc, map| {
for (function, calls) in map.into_iter() {
acc.entry(function)
.or_insert_with(FxHashMap::default)
.extend(calls.into_iter());
}
acc
})
});
let call_locations = crate::scrape_examples::load_call_locations(with_examples, &diag)?;
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);

View file

@ -2453,32 +2453,31 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> {
const MAX_FULL_EXAMPLES: usize = 5;
/// Generates the HTML for example call locations generated via the --scrape-examples flag.
fn render_call_locations(
w: &mut Buffer,
cx: &Context<'_>,
call_locations: &Option<FnCallLocations>,
) {
let call_locations = match call_locations.as_ref() {
Some(call_locations) => call_locations,
None => {
Some(call_locations) if call_locations.len() > 0 => call_locations,
_ => {
return;
}
};
if call_locations.len() == 0 {
return;
}
// Generate a unique ID so users can link to this section for a given method
let id = cx.id_map.borrow_mut().derive("scraped-examples");
write!(
w,
r##"<div class="docblock scraped-example-list">
<h1 id="scraped-examples" class="small-section-header">
<a href="#{}">Uses found in <code>examples/</code></a>
<a href="#{}">Examples found in repository</a>
</h1>"##,
id
);
// Link to the source file containing a given example
let example_url = |call_data: &CallData| -> String {
format!(
r#"<a href="{root}{url}" target="_blank">{name}</a>"#,
@ -2488,18 +2487,27 @@ fn render_call_locations(
)
};
// Generate the HTML for a single example, being the title and code block
let write_example = |w: &mut Buffer, (path, call_data): (&PathBuf, &CallData)| {
let mut contents =
// FIXME(wcrichto): is there a better way to handle an I/O error than a panic?
// When would such an error arise?
let contents =
fs::read_to_string(&path).expect(&format!("Failed to read file: {}", path.display()));
// To reduce file sizes, we only want to embed the source code needed to understand the example, not
// the entire file. So we find the smallest byte range that covers all items enclosing examples.
let min_loc =
call_data.locations.iter().min_by_key(|loc| loc.enclosing_item_span.0).unwrap();
let min_byte = min_loc.enclosing_item_span.0;
let min_line = min_loc.enclosing_item_lines.0;
let max_byte =
call_data.locations.iter().map(|loc| loc.enclosing_item_span.1).max().unwrap();
contents = contents[min_byte..max_byte].to_string();
// The output code is limited to that byte range.
let contents_subset = &contents[min_byte..max_byte];
// The call locations need to be updated to reflect that the size of the program has changed.
// Specifically, the ranges are all subtracted by `min_byte` since that's the new zero point.
let locations = call_data
.locations
.iter()
@ -2510,23 +2518,44 @@ fn render_call_locations(
write!(
w,
r#"<div class="scraped-example" data-code="{code}" data-locs="{locations}">
<strong>{title}</strong>
<div class="scraped-example-title">{title}</div>
<div class="code-wrapper">"#,
code = contents.replace("\"", "&quot;"),
locations = serde_json::to_string(&locations).unwrap(),
title = example_url(call_data),
// The code and locations are encoded as data attributes, so they can be read
// later by the JS for interactions.
code = contents_subset.replace("\"", "&quot;"),
locations = serde_json::to_string(&locations).unwrap(),
);
write!(w, r#"<span class="prev">&pr;</span> <span class="next">&sc;</span>"#);
write!(w, r#"<span class="expand">&varr;</span>"#);
// FIXME(wcrichto): where should file_span and root_path come from?
let file_span = rustc_span::DUMMY_SP;
let root_path = "".to_string();
sources::print_src(w, &contents, edition, file_span, cx, &root_path, Some(min_line));
sources::print_src(w, contents_subset, edition, file_span, cx, &root_path, Some(min_line));
write!(w, "</div></div>");
};
let mut it = call_locations.into_iter().peekable();
// The call locations are output in sequence, so that sequence needs to be determined.
// Ideally the most "relevant" examples would be shown first, but there's no general algorithm
// for determining relevance. Instead, we prefer the smallest examples being likely the easiest to
// understand at a glance.
let ordered_locations = {
let sort_criterion = |(_, call_data): &(_, &CallData)| {
let (lo, hi) = call_data.locations[0].enclosing_item_span;
hi - lo
};
let mut locs = call_locations.into_iter().collect::<Vec<_>>();
locs.sort_by_key(|x| sort_criterion(x));
locs
};
// Write just one example that's visible by default in the method's description.
let mut it = ordered_locations.into_iter().peekable();
write_example(w, it.next().unwrap());
// Then add the remaining examples in a hidden section.
if it.peek().is_some() {
write!(
w,
@ -2534,19 +2563,29 @@ fn render_call_locations(
<summary class="hideme">
<span>More examples</span>
</summary>
<div class="more-scraped-examples">"#
<div class="more-scraped-examples">
<div class="toggle-line"><div class="toggle-line-inner"></div></div>
<div>
"#
);
// Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could
// make the page arbitrarily huge!
(&mut it).take(MAX_FULL_EXAMPLES).for_each(|ex| write_example(w, ex));
// For the remaining examples, generate a <ul /> containing links to the source files.
if it.peek().is_some() {
write!(w, "Additional examples can be found in:<br /><ul>");
write!(
w,
r#"<div class="example-links">Additional examples can be found in:<br /><ul>"#
);
it.for_each(|(_, call_data)| {
write!(w, "<li>{}</li>", example_url(call_data));
});
write!(w, "</ul>");
write!(w, "</ul></div>");
}
write!(w, "</div></details>");
write!(w, "</div></div></details>");
}
write!(w, "</div>");

View file

@ -137,7 +137,7 @@ h1.fqn {
margin-top: 0;
/* workaround to keep flex from breaking below 700 px width due to the float: right on the nav
above the h1 */
above the h1 */
padding-left: 1px;
}
h1.fqn > .in-band > a:hover {
@ -974,7 +974,7 @@ body.blur > :not(#help) {
text-shadow:
1px 0 0 black,
-1px 0 0 black,
0 1px 0 black,
0 1px 0 black,
0 -1px 0 black;
}
@ -1214,8 +1214,8 @@ a.test-arrow:hover{
.notable-traits-tooltip::after {
/* The margin on the tooltip does not capture hover events,
this extends the area of hover enough so that mouse hover is not
lost when moving the mouse to the tooltip */
this extends the area of hover enough so that mouse hover is not
lost when moving the mouse to the tooltip */
content: "\00a0\00a0\00a0";
}
@ -1715,7 +1715,7 @@ details.undocumented[open] > summary::before {
}
/* We do NOT hide this element so that alternative device readers still have this information
available. */
available. */
.sidebar-elems {
position: fixed;
z-index: 1;
@ -1973,10 +1973,15 @@ details.undocumented[open] > summary::before {
/* This part is for the new "examples" components */
.scraped-example-title {
font-family: 'Fira Sans';
font-weight: 500;
}
.scraped-example:not(.expanded) .code-wrapper pre.line-numbers,
.scraped-example:not(.expanded) .code-wrapper .example-wrap pre.rust {
overflow: hidden;
height: 240px;
max-height: 240px;
}
.scraped-example .code-wrapper .prev {
@ -2033,7 +2038,7 @@ details.undocumented[open] > summary::before {
.scraped-example:not(.expanded) .code-wrapper {
overflow: hidden;
height: 240px;
max-height: 240px;
}
.scraped-example .code-wrapper .line-numbers {
@ -2072,6 +2077,41 @@ details.undocumented[open] > summary::before {
.more-scraped-examples {
padding-left: 10px;
border-left: 1px solid #ccc;
margin-left: 24px;
margin-left: 15px;
display: flex;
flex-direction: row;
}
.toggle-line {
align-self: stretch;
margin-right: 10px;
margin-top: 5px;
padding: 0 4px;
cursor: pointer;
}
.toggle-line:hover .toggle-line-inner {
background: #aaa;
}
.toggle-line-inner {
min-width: 2px;
background: #ddd;
height: 100%;
}
h1 + .scraped-example {
margin-bottom: 10px;
}
.more-scraped-examples .scraped-example {
margin-bottom: 20px;
}
.example-links a {
font-family: 'Fira Sans';
}
.example-links ul {
margin-bottom: 0;
}

View file

@ -1121,15 +1121,22 @@ function hideThemeButtonState() {
example.querySelector('.next').remove();
}
// Show full code on expansion
example.querySelector('.expand').addEventListener('click', function () {
if (hasClass(example, "expanded")) {
removeClass(example, "expanded");
scrollToLoc(example, locs[0]);
} else {
addClass(example, "expanded");
}
});
let codeEl = example.querySelector('.rust');
let expandButton = example.querySelector('.expand');
if (codeEl.scrollHeight == codeEl.clientHeight) {
addClass(example, 'expanded');
expandButton.remove();
} else {
// Show full code on expansion
expandButton.addEventListener('click', function () {
if (hasClass(example, "expanded")) {
removeClass(example, "expanded");
scrollToLoc(example, locs[0]);
} else {
addClass(example, "expanded");
}
});
}
// Start with the first example in view
scrollToLoc(example, locs[0]);
@ -1139,6 +1146,10 @@ function hideThemeButtonState() {
var firstExamples = document.querySelectorAll('.scraped-example-list > .scraped-example');
onEach(firstExamples, updateScrapedExample);
onEach(document.querySelectorAll('.more-examples-toggle'), function(toggle) {
toggle.querySelector('.toggle-line').addEventListener('click', function() {
toggle.open = false;
});
var moreExamples = toggle.querySelectorAll('.scraped-example');
toggle.querySelector('summary').addEventListener('click', function() {
// Wrapping in setTimeout ensures the update happens after the elements are actually

View file

@ -1,4 +1,4 @@
//! This module analyzes provided crates to find examples of uses for items in the
//! This module analyzes crates to find examples of uses for items in the
//! current crate being documented.
use crate::clean;
@ -158,3 +158,31 @@ crate fn run(
rustc_errors::ErrorReported
})
}
crate fn load_call_locations(
with_examples: Vec<String>,
diag: &rustc_errors::Handler,
) -> Result<Option<AllCallLocations>, i32> {
let each_call_locations = with_examples
.into_iter()
.map(|path| {
let bytes = fs::read(&path).map_err(|e| format!("{} (for path {})", e, path))?;
let calls: AllCallLocations =
serde_json::from_slice(&bytes).map_err(|e| format!("{}", e))?;
Ok(calls)
})
.collect::<Result<Vec<_>, _>>()
.map_err(|e: String| {
diag.err(&format!("failed to load examples with error: {}", e));
1
})?;
Ok((each_call_locations.len() > 0).then(|| {
each_call_locations.into_iter().fold(FxHashMap::default(), |mut acc, map| {
for (function, calls) in map.into_iter() {
acc.entry(function).or_insert_with(FxHashMap::default).extend(calls.into_iter());
}
acc
})
}))
}