Sort examples by size
Improve styling Start to clean up code, add comments
This commit is contained in:
parent
b6338e7792
commit
eea8f0a39a
6 changed files with 158 additions and 61 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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("\"", """),
|
||||
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("\"", """),
|
||||
locations = serde_json::to_string(&locations).unwrap(),
|
||||
);
|
||||
write!(w, r#"<span class="prev">≺</span> <span class="next">≻</span>"#);
|
||||
write!(w, r#"<span class="expand">↕</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>");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue