Improve several aspects of the Rustdoc scrape-examples UI.
* Examples take up less screen height. * Snippets from binary crates are prioritized. * toggle-all-docs does not expand "More examples" sections.
This commit is contained in:
parent
01fbc5ae78
commit
6ccd14a782
@ -69,8 +69,8 @@ pub(crate) struct Options {
|
||||
pub(crate) input: PathBuf,
|
||||
/// The name of the crate being documented.
|
||||
pub(crate) crate_name: Option<String>,
|
||||
/// Whether or not this is a proc-macro crate
|
||||
pub(crate) proc_macro_crate: bool,
|
||||
/// The types of the crate being documented.
|
||||
pub(crate) crate_types: Vec<CrateType>,
|
||||
/// How to format errors and warnings.
|
||||
pub(crate) error_format: ErrorOutputType,
|
||||
/// Width of output buffer to truncate errors appropriately.
|
||||
@ -176,7 +176,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Options")
|
||||
.field("input", &self.input)
|
||||
.field("crate_name", &self.crate_name)
|
||||
.field("proc_macro_crate", &self.proc_macro_crate)
|
||||
.field("crate_types", &self.crate_types)
|
||||
.field("error_format", &self.error_format)
|
||||
.field("libs", &self.libs)
|
||||
.field("externs", &FmtExterns(&self.externs))
|
||||
@ -667,7 +667,6 @@ fn println_condition(condition: Condition) {
|
||||
None => OutputFormat::default(),
|
||||
};
|
||||
let crate_name = matches.opt_str("crate-name");
|
||||
let proc_macro_crate = crate_types.contains(&CrateType::ProcMacro);
|
||||
let playground_url = matches.opt_str("playground-url");
|
||||
let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from);
|
||||
let module_sorting = if matches.opt_present("sort-modules-by-appearance") {
|
||||
@ -718,7 +717,7 @@ fn println_condition(condition: Condition) {
|
||||
rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref());
|
||||
let options = Options {
|
||||
input,
|
||||
proc_macro_crate,
|
||||
crate_types,
|
||||
error_format,
|
||||
diagnostic_width,
|
||||
libs,
|
||||
|
@ -13,7 +13,7 @@
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
|
||||
use rustc_resolve as resolve;
|
||||
use rustc_session::config::{self, CrateType, ErrorOutputType};
|
||||
use rustc_session::config::{self, ErrorOutputType};
|
||||
use rustc_session::lint;
|
||||
use rustc_session::Session;
|
||||
use rustc_span::symbol::sym;
|
||||
@ -203,7 +203,7 @@ pub(crate) fn create_config(
|
||||
RustdocOptions {
|
||||
input,
|
||||
crate_name,
|
||||
proc_macro_crate,
|
||||
crate_types,
|
||||
error_format,
|
||||
diagnostic_width,
|
||||
libs,
|
||||
@ -247,8 +247,6 @@ pub(crate) fn create_config(
|
||||
Some((lint.name_lower(), lint::Allow))
|
||||
});
|
||||
|
||||
let crate_types =
|
||||
if proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] };
|
||||
let test = scrape_examples_options.map(|opts| opts.scrape_tests).unwrap_or(false);
|
||||
// plays with error output here!
|
||||
let sessopts = config::Options {
|
||||
|
@ -12,7 +12,7 @@
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_parse::maybe_new_parser_from_source_str;
|
||||
use rustc_parse::parser::attr::InnerAttrPolicy;
|
||||
use rustc_session::config::{self, CrateType, ErrorOutputType};
|
||||
use rustc_session::config::{self, ErrorOutputType};
|
||||
use rustc_session::parse::ParseSess;
|
||||
use rustc_session::{lint, Session};
|
||||
use rustc_span::edition::Edition;
|
||||
@ -68,8 +68,7 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
|
||||
|
||||
debug!(?lint_opts);
|
||||
|
||||
let crate_types =
|
||||
if options.proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] };
|
||||
let crate_types = options.crate_types.clone();
|
||||
|
||||
let sessopts = config::Options {
|
||||
maybe_sysroot: options.maybe_sysroot.clone(),
|
||||
|
@ -2957,14 +2957,22 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
|
||||
|
||||
// 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.
|
||||
// for determining relevance. We instead make a proxy for relevance with the following heuristics:
|
||||
// 1. Code written to be an example is better than code not written to be an example, e.g.
|
||||
// a snippet from examples/foo.rs is better than src/lib.rs. We don't know the Cargo directory
|
||||
// structure in Rustdoc, so we proxy this by prioriting code that comes from a --crate-type bin.
|
||||
// 2. Smaller examples are better than large examples. So we prioritize snippets that have the
|
||||
// smallest line span for their enclosing item.
|
||||
// 3. Finally we sort by the displayed file name, which is arbitrary but prevents the ordering
|
||||
// of examples from randomly changing between Rustdoc invocations.
|
||||
let ordered_locations = {
|
||||
let sort_criterion = |(_, call_data): &(_, &CallData)| {
|
||||
fn sort_criterion<'a>(
|
||||
(_, call_data): &(&PathBuf, &'a CallData),
|
||||
) -> (bool, u32, &'a String) {
|
||||
// Use the first location because that's what the user will see initially
|
||||
let (lo, hi) = call_data.locations[0].enclosing_item.byte_span;
|
||||
hi - lo
|
||||
};
|
||||
(!call_data.is_bin, hi - lo, &call_data.display_name)
|
||||
}
|
||||
|
||||
let mut locs = call_locations.iter().collect::<Vec<_>>();
|
||||
locs.sort_by_key(sort_criterion);
|
||||
|
@ -1901,6 +1901,10 @@ in storage.js
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.scraped-example {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.scraped-example .code-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@ -1909,16 +1913,31 @@ in storage.js
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scraped-example-title {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
background: white;
|
||||
bottom: 8px;
|
||||
right: 5px;
|
||||
padding: 2px 4px;
|
||||
box-shadow: 0 0 4px white;
|
||||
}
|
||||
|
||||
.scraped-example:not(.expanded) .code-wrapper {
|
||||
max-height: 240px;
|
||||
max-height: 120px;
|
||||
}
|
||||
|
||||
.scraped-example:not(.expanded) .code-wrapper pre {
|
||||
overflow-y: hidden;
|
||||
max-height: 240px;
|
||||
max-height: 120px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper,
|
||||
.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper pre {
|
||||
max-height: 240px;
|
||||
}
|
||||
|
||||
.scraped-example .code-wrapper .next,
|
||||
.scraped-example .code-wrapper .prev,
|
||||
.scraped-example .code-wrapper .expand {
|
||||
|
@ -622,7 +622,7 @@ function loadCss(cssUrl) {
|
||||
const innerToggle = document.getElementById(toggleAllDocsId);
|
||||
removeClass(innerToggle, "will-expand");
|
||||
onEachLazy(document.getElementsByClassName("rustdoc-toggle"), e => {
|
||||
if (!hasClass(e, "type-contents-toggle")) {
|
||||
if (!hasClass(e, "type-contents-toggle") && !hasClass(e, "more-examples-toggle")) {
|
||||
e.open = true;
|
||||
}
|
||||
});
|
||||
|
@ -4,17 +4,19 @@
|
||||
|
||||
(function() {
|
||||
// Number of lines shown when code viewer is not expanded
|
||||
const MAX_LINES = 10;
|
||||
const DEFAULT_MAX_LINES = 5;
|
||||
const HIDDEN_MAX_LINES = 10;
|
||||
|
||||
// Scroll code block to the given code location
|
||||
function scrollToLoc(elt, loc) {
|
||||
function scrollToLoc(elt, loc, isHidden) {
|
||||
const lines = elt.querySelector(".src-line-numbers");
|
||||
let scrollOffset;
|
||||
|
||||
// If the block is greater than the size of the viewer,
|
||||
// then scroll to the top of the block. Otherwise scroll
|
||||
// to the middle of the block.
|
||||
if (loc[1] - loc[0] > MAX_LINES) {
|
||||
let maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES;
|
||||
if (loc[1] - loc[0] > maxLines) {
|
||||
const line = Math.max(0, loc[0] - 1);
|
||||
scrollOffset = lines.children[line].offsetTop;
|
||||
} else {
|
||||
@ -29,7 +31,7 @@
|
||||
elt.querySelector(".rust").scrollTo(0, scrollOffset);
|
||||
}
|
||||
|
||||
function updateScrapedExample(example) {
|
||||
function updateScrapedExample(example, isHidden) {
|
||||
const locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent);
|
||||
let locIndex = 0;
|
||||
const highlights = Array.prototype.slice.call(example.querySelectorAll(".highlight"));
|
||||
@ -40,7 +42,7 @@
|
||||
const onChangeLoc = changeIndex => {
|
||||
removeClass(highlights[locIndex], "focus");
|
||||
changeIndex();
|
||||
scrollToLoc(example, locs[locIndex][0]);
|
||||
scrollToLoc(example, locs[locIndex][0], isHidden);
|
||||
addClass(highlights[locIndex], "focus");
|
||||
|
||||
const url = locs[locIndex][1];
|
||||
@ -70,7 +72,7 @@
|
||||
expandButton.addEventListener("click", () => {
|
||||
if (hasClass(example, "expanded")) {
|
||||
removeClass(example, "expanded");
|
||||
scrollToLoc(example, locs[0][0]);
|
||||
scrollToLoc(example, locs[0][0], isHidden);
|
||||
} else {
|
||||
addClass(example, "expanded");
|
||||
}
|
||||
@ -78,11 +80,11 @@
|
||||
}
|
||||
|
||||
// Start with the first example in view
|
||||
scrollToLoc(example, locs[0][0]);
|
||||
scrollToLoc(example, locs[0][0], isHidden);
|
||||
}
|
||||
|
||||
const firstExamples = document.querySelectorAll(".scraped-example-list > .scraped-example");
|
||||
onEachLazy(firstExamples, updateScrapedExample);
|
||||
onEachLazy(firstExamples, el => updateScrapedExample(el, false));
|
||||
onEachLazy(document.querySelectorAll(".more-examples-toggle"), toggle => {
|
||||
// Allow users to click the left border of the <details> section to close it,
|
||||
// since the section can be large and finding the [+] button is annoying.
|
||||
@ -99,7 +101,7 @@
|
||||
// depends on offsetHeight, a property that requires an element to be visible to
|
||||
// compute correctly.
|
||||
setTimeout(() => {
|
||||
onEachLazy(moreExamples, updateScrapedExample);
|
||||
onEachLazy(moreExamples, el => updateScrapedExample(el, true));
|
||||
});
|
||||
}, {once: true});
|
||||
});
|
||||
|
@ -774,6 +774,7 @@ fn main_args(at_args: &[String]) -> MainResult {
|
||||
let output_format = options.output_format;
|
||||
let externs = options.externs.clone();
|
||||
let scrape_examples_options = options.scrape_examples_options.clone();
|
||||
let crate_types = options.crate_types.clone();
|
||||
|
||||
let config = core::create_config(options);
|
||||
|
||||
@ -832,7 +833,14 @@ fn main_args(at_args: &[String]) -> MainResult {
|
||||
info!("finished with rustc");
|
||||
|
||||
if let Some(options) = scrape_examples_options {
|
||||
return scrape_examples::run(krate, render_opts, cache, tcx, options);
|
||||
return scrape_examples::run(
|
||||
krate,
|
||||
render_opts,
|
||||
cache,
|
||||
tcx,
|
||||
options,
|
||||
crate_types,
|
||||
);
|
||||
}
|
||||
|
||||
cache.crate_version = crate_version;
|
||||
|
@ -20,7 +20,7 @@
|
||||
opaque::{FileEncoder, MemDecoder},
|
||||
Decodable, Encodable,
|
||||
};
|
||||
use rustc_session::getopts;
|
||||
use rustc_session::{config::CrateType, getopts};
|
||||
use rustc_span::{
|
||||
def_id::{CrateNum, DefPathHash, LOCAL_CRATE},
|
||||
edition::Edition,
|
||||
@ -110,6 +110,7 @@ pub(crate) struct CallData {
|
||||
pub(crate) url: String,
|
||||
pub(crate) display_name: String,
|
||||
pub(crate) edition: Edition,
|
||||
pub(crate) is_bin: bool,
|
||||
}
|
||||
|
||||
pub(crate) type FnCallLocations = FxHashMap<PathBuf, CallData>;
|
||||
@ -122,6 +123,7 @@ struct FindCalls<'a, 'tcx> {
|
||||
cx: Context<'tcx>,
|
||||
target_crates: Vec<CrateNum>,
|
||||
calls: &'a mut AllCallLocations,
|
||||
crate_types: Vec<CrateType>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for FindCalls<'a, 'tcx>
|
||||
@ -245,7 +247,9 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
|
||||
let mk_call_data = || {
|
||||
let display_name = file_path.display().to_string();
|
||||
let edition = call_span.edition();
|
||||
CallData { locations: Vec::new(), url, display_name, edition }
|
||||
let is_bin = self.crate_types.contains(&CrateType::Executable);
|
||||
|
||||
CallData { locations: Vec::new(), url, display_name, edition, is_bin }
|
||||
};
|
||||
|
||||
let fn_key = tcx.def_path_hash(*def_id);
|
||||
@ -274,6 +278,7 @@ pub(crate) fn run(
|
||||
cache: formats::cache::Cache,
|
||||
tcx: TyCtxt<'_>,
|
||||
options: ScrapeExamplesOptions,
|
||||
crate_types: Vec<CrateType>,
|
||||
) -> interface::Result<()> {
|
||||
let inner = move || -> Result<(), String> {
|
||||
// Generates source files for examples
|
||||
@ -300,7 +305,8 @@ pub(crate) fn run(
|
||||
|
||||
// Run call-finder on all items
|
||||
let mut calls = FxHashMap::default();
|
||||
let mut finder = FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates };
|
||||
let mut finder =
|
||||
FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates, crate_types };
|
||||
tcx.hir().visit_all_item_likes_in_crate(&mut finder);
|
||||
|
||||
// The visitor might have found a type error, which we need to
|
||||
|
@ -25,3 +25,12 @@ store-property: (fullOffsetHeight, ".scraped-example-list > .scraped-example pre
|
||||
assert-property: (".scraped-example-list > .scraped-example pre", {
|
||||
"scrollHeight": |fullOffsetHeight|
|
||||
})
|
||||
|
||||
assert-attribute-false: (".more-examples-toggle", {"open": ""})
|
||||
click: ".more-examples-toggle"
|
||||
assert-attribute: (".more-examples-toggle", {"open": ""})
|
||||
click: "#toggle-all-docs"
|
||||
assert-attribute-false: (".more-examples-toggle", {"open": ""})
|
||||
// After re-opening the docs, the additional examples should stay closed
|
||||
click: "#toggle-all-docs"
|
||||
assert-attribute-false: (".more-examples-toggle", {"open": ""})
|
||||
|
25
src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs
Normal file
25
src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs
Normal file
@ -0,0 +1,25 @@
|
||||
fn main() {
|
||||
for i in 0..9 {
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
}
|
||||
scrape_examples::test();
|
||||
for i in 0..9 {
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
println!("hello world!");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user