From 6ccd14a782420d27aef9d9fcd9196949a3276427 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Sun, 27 Nov 2022 13:11:21 -0600 Subject: [PATCH 01/10] 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. --- src/librustdoc/config.rs | 9 +++---- src/librustdoc/core.rs | 6 ++--- src/librustdoc/doctest.rs | 5 ++-- src/librustdoc/html/render/mod.rs | 18 +++++++++---- src/librustdoc/html/static/css/rustdoc.css | 23 +++++++++++++++-- src/librustdoc/html/static/js/main.js | 2 +- .../html/static/js/scrape-examples.js | 20 ++++++++------- src/librustdoc/lib.rs | 10 +++++++- src/librustdoc/scrape_examples.rs | 12 ++++++--- .../scrape-examples-button-focus.goml | 9 +++++++ .../src/scrape_examples/examples/check2.rs | 25 +++++++++++++++++++ 11 files changed, 106 insertions(+), 33 deletions(-) create mode 100644 src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index e0cdb86d9d1dc..41af4f9561b53 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -69,8 +69,8 @@ pub(crate) struct Options { pub(crate) input: PathBuf, /// The name of the crate being documented. pub(crate) crate_name: Option, - /// 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, /// How to format errors and warnings. pub(crate) error_format: ErrorOutputType, /// Width of output buffer to truncate errors appropriately. @@ -176,7 +176,7 @@ impl fmt::Debug for Options { 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 @@ impl Options { 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 @@ impl Options { rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref()); let options = Options { input, - proc_macro_crate, + crate_types, error_format, diagnostic_width, libs, diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index da0df596c41e3..c6358874c6162 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -13,7 +13,7 @@ use rustc_interface::interface; 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 { diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 81d9c46447a37..b70444ec67395 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -12,7 +12,7 @@ use rustc_middle::hir::nested_filter; 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(), diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 08f8096b07bd6..ea466c639c39e 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -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::>(); locs.sort_by_key(sort_criterion); diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 5ebc545d10c2b..4a8babaef7554 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -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 { diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 623f46b109666..152116089c7fc 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -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; } }); diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index e328e656edda4..82b71ad0fca5d 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -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
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}); }); diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 6d34f484754c7..e27af61051c4f 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -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; diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index dfa6ba38b883b..9adccda0e7200 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -20,7 +20,7 @@ use rustc_serialize::{ 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; @@ -122,6 +123,7 @@ struct FindCalls<'a, 'tcx> { cx: Context<'tcx>, target_crates: Vec, calls: &'a mut AllCallLocations, + crate_types: Vec, } impl<'a, 'tcx> Visitor<'tcx> for FindCalls<'a, 'tcx> @@ -245,7 +247,9 @@ where 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, ) -> 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 diff --git a/src/test/rustdoc-gui/scrape-examples-button-focus.goml b/src/test/rustdoc-gui/scrape-examples-button-focus.goml index a222139f1dc44..a353504df6ad0 100644 --- a/src/test/rustdoc-gui/scrape-examples-button-focus.goml +++ b/src/test/rustdoc-gui/scrape-examples-button-focus.goml @@ -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": ""}) diff --git a/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs b/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs new file mode 100644 index 0000000000000..3e69c6086ae2e --- /dev/null +++ b/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs @@ -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!"); + } +} From acd70e674d1dd28ced1917997df6dd7cd560e095 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Mon, 5 Dec 2022 23:34:02 -0800 Subject: [PATCH 02/10] Add explanations to scrape-examples integration test --- ...-button-focus.goml => scrape-examples-interactions.goml} | 6 ++++++ 1 file changed, 6 insertions(+) rename src/test/rustdoc-gui/{scrape-examples-button-focus.goml => scrape-examples-interactions.goml} (85%) diff --git a/src/test/rustdoc-gui/scrape-examples-button-focus.goml b/src/test/rustdoc-gui/scrape-examples-interactions.goml similarity index 85% rename from src/test/rustdoc-gui/scrape-examples-button-focus.goml rename to src/test/rustdoc-gui/scrape-examples-interactions.goml index a353504df6ad0..f7b43c4c85bbf 100644 --- a/src/test/rustdoc-gui/scrape-examples-button-focus.goml +++ b/src/test/rustdoc-gui/scrape-examples-interactions.goml @@ -1,5 +1,6 @@ goto: "file://" + |DOC_PATH| + "/scrape_examples/fn.test.html" +// The next/prev buttons vertically scroll the code viewport between examples store-property: (initialScrollTop, ".scraped-example-list > .scraped-example pre", "scrollTop") focus: ".scraped-example-list > .scraped-example .next" press-key: "Enter" @@ -12,6 +13,7 @@ assert-property: (".scraped-example-list > .scraped-example pre", { "scrollTop": |initialScrollTop| }) +// The expand button increases the scrollHeight of the minimized code viewport store-property: (smallOffsetHeight, ".scraped-example-list > .scraped-example pre", "offsetHeight") assert-property-false: (".scraped-example-list > .scraped-example pre", { "scrollHeight": |smallOffsetHeight| @@ -26,11 +28,15 @@ assert-property: (".scraped-example-list > .scraped-example pre", { "scrollHeight": |fullOffsetHeight| }) +// Clicking "More examples..." will open additional examples assert-attribute-false: (".more-examples-toggle", {"open": ""}) click: ".more-examples-toggle" assert-attribute: (".more-examples-toggle", {"open": ""}) + +// Toggling all docs will close additional examples 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": ""}) From 45742170741d6c6b9abda5fc51012a01ce1c704a Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Mon, 5 Dec 2022 23:48:07 -0800 Subject: [PATCH 03/10] Only put title over example on large screens --- src/librustdoc/html/static/css/rustdoc.css | 23 ++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 4a8babaef7554..ec941c3a93813 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1815,6 +1815,19 @@ in storage.js } } +/* Should have min-width: (N + 1)px where N is the mobile breakpoint above. */ +@media (min-width: 701px) { + .scraped-example-title { + position: absolute; + z-index: 1000; + background: var(--main-background-color); + bottom: 8px; + right: 5px; + padding: 2px 4px; + box-shadow: 0 0 4px var(--main-background-color); + } +} + @media print { nav.sidebar, nav.sub, .out-of-band, a.srclink, #copy-path, details.rustdoc-toggle[open] > summary::before, details.rustdoc-toggle > summary::before, @@ -1913,16 +1926,6 @@ 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: 120px; } From 679d7ea064491dfc8d067fcef07827e7569a093e Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 09:34:37 -0800 Subject: [PATCH 04/10] Include additional documentation for scrape-examples changes --- src/librustdoc/html/static/css/rustdoc.css | 6 +++++- src/librustdoc/html/static/js/scrape-examples.js | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index ec941c3a93813..0432d445d53fb 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1817,9 +1817,12 @@ in storage.js /* Should have min-width: (N + 1)px where N is the mobile breakpoint above. */ @media (min-width: 701px) { + /* Places file-link for a scraped example on top of the example to save space. + We only do this on large screens so the file-link doesn't overlap too much + with the example's content. */ .scraped-example-title { position: absolute; - z-index: 1000; + z-index: 10; background: var(--main-background-color); bottom: 8px; right: 5px; @@ -1915,6 +1918,7 @@ in storage.js } .scraped-example { + /* So .scraped-example-title can be positioned absolutely */ position: relative; } diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index 82b71ad0fca5d..d179909ac7fab 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -3,7 +3,9 @@ "use strict"; (function() { - // Number of lines shown when code viewer is not expanded + // Number of lines shown when code viewer is not expanded. + // DEFAULT is the first example shown by default, while HIDDEN is + // the examples hidden beneath the "More examples" toggle. const DEFAULT_MAX_LINES = 5; const HIDDEN_MAX_LINES = 10; From 212d03dadca627e55273294404bcc6312aa65969 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 09:59:49 -0800 Subject: [PATCH 05/10] Factor scrape-examples toggle test into a new file --- ...goml => scrape-examples-button-focus.goml} | 13 ---------- .../rustdoc-gui/scrape-examples-toggle.goml | 14 +++++++++++ .../src/scrape_examples/examples/check2.rs | 25 ------------------- 3 files changed, 14 insertions(+), 38 deletions(-) rename src/test/rustdoc-gui/{scrape-examples-interactions.goml => scrape-examples-button-focus.goml} (71%) create mode 100644 src/test/rustdoc-gui/scrape-examples-toggle.goml delete mode 100644 src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs diff --git a/src/test/rustdoc-gui/scrape-examples-interactions.goml b/src/test/rustdoc-gui/scrape-examples-button-focus.goml similarity index 71% rename from src/test/rustdoc-gui/scrape-examples-interactions.goml rename to src/test/rustdoc-gui/scrape-examples-button-focus.goml index f7b43c4c85bbf..bba518db099a4 100644 --- a/src/test/rustdoc-gui/scrape-examples-interactions.goml +++ b/src/test/rustdoc-gui/scrape-examples-button-focus.goml @@ -27,16 +27,3 @@ store-property: (fullOffsetHeight, ".scraped-example-list > .scraped-example pre assert-property: (".scraped-example-list > .scraped-example pre", { "scrollHeight": |fullOffsetHeight| }) - -// Clicking "More examples..." will open additional examples -assert-attribute-false: (".more-examples-toggle", {"open": ""}) -click: ".more-examples-toggle" -assert-attribute: (".more-examples-toggle", {"open": ""}) - -// Toggling all docs will close additional examples -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": ""}) diff --git a/src/test/rustdoc-gui/scrape-examples-toggle.goml b/src/test/rustdoc-gui/scrape-examples-toggle.goml new file mode 100644 index 0000000000000..ee720afb788fe --- /dev/null +++ b/src/test/rustdoc-gui/scrape-examples-toggle.goml @@ -0,0 +1,14 @@ +goto: "file://" + |DOC_PATH| + "/scrape_examples/fn.test_many.html" + +// Clicking "More examples..." will open additional examples +assert-attribute-false: (".more-examples-toggle", {"open": ""}) +click: ".more-examples-toggle" +assert-attribute: (".more-examples-toggle", {"open": ""}) + +// Toggling all docs will close additional examples +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": ""}) diff --git a/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs b/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs deleted file mode 100644 index 3e69c6086ae2e..0000000000000 --- a/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs +++ /dev/null @@ -1,25 +0,0 @@ -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!"); - } -} From ae270f1b991ced071d93eab76219a0e3788b89ad Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 10:11:55 -0800 Subject: [PATCH 06/10] Update scrape-examples help, fix documentation typos --- src/librustdoc/html/render/mod.rs | 15 ++++++++------- .../html/static/scrape-examples-help.md | 5 +++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index ea466c639c39e..36d15ec3b8640 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2957,14 +2957,15 @@ 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. We instead make a proxy for relevance with the following heuristics: + // for determining relevance. We instead proxy 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. + // 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 prioritizing code that comes from + // a --crate-type bin. + // 2. Smaller examples are better than large examples. So we prioritize snippets that have + // the smallest number of lines in 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 = { fn sort_criterion<'a>( (_, call_data): &(&PathBuf, &'a CallData), diff --git a/src/librustdoc/html/static/scrape-examples-help.md b/src/librustdoc/html/static/scrape-examples-help.md index 035b2e18b00eb..002d19ec9b67d 100644 --- a/src/librustdoc/html/static/scrape-examples-help.md +++ b/src/librustdoc/html/static/scrape-examples-help.md @@ -1,4 +1,4 @@ -Rustdoc will automatically scrape examples of documented items from the `examples/` directory of a project. These examples will be included within the generated documentation for that item. For example, if your library contains a public function: +Rustdoc will automatically scrape examples of documented items from a project's source code. These examples will be included within the generated documentation for that item. For example, if your library contains a public function: ```rust // src/lib.rs @@ -16,6 +16,7 @@ fn main() { Then this code snippet will be included in the documentation for `a_func`. + ## How to read scraped examples Scraped examples are shown as blocks of code from a given file. The relevant item will be highlighted. If the file is larger than a couple lines, only a small window will be shown which you can expand by clicking ↕ in the top-right. If a file contains multiple instances of an item, you can use the ≺ and ≻ buttons to toggle through each instance. @@ -25,7 +26,7 @@ If there is more than one file that contains examples, then you should click "Mo ## How Rustdoc scrapes examples -When you run `cargo doc`, Rustdoc will analyze all the crates that match Cargo's `--examples` filter for instances of items that occur in the crates being documented. Then Rustdoc will include the source code of these instances in the generated documentation. +When you run `cargo doc -Zunstable-options -Zrustdoc-scrape-examples`, Rustdoc will analyze all the documented crates for uses of documented items. Then Rustdoc will include the source code of these instances in the generated documentation. Rustdoc has a few techniques to ensure this doesn't overwhelm documentation readers, and that it doesn't blow up the page size: From bcdab876c823ef4e66f88e2716bd13d7a42634dd Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 10:22:23 -0800 Subject: [PATCH 07/10] Fix es-check --- src/librustdoc/html/static/js/scrape-examples.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index d179909ac7fab..830b44d6bc0fd 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -17,7 +17,7 @@ // 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. - let maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES; + const 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; From 0709e534df2a85486f981bfbebd153bb25e3703d Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 11:24:26 -0800 Subject: [PATCH 08/10] Fix rustdoc error with no providec crate-type, fix scrape examples button colors w/ themes --- src/librustdoc/core.rs | 3 ++- src/librustdoc/doctest.rs | 8 ++++++-- src/librustdoc/html/static/css/rustdoc.css | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index c6358874c6162..58954ecc73f1a 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -13,7 +13,7 @@ use rustc_interface::interface; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{ParamEnv, Ty, TyCtxt}; use rustc_resolve as resolve; -use rustc_session::config::{self, ErrorOutputType}; +use rustc_session::config::{self, CrateType, ErrorOutputType}; use rustc_session::lint; use rustc_session::Session; use rustc_span::symbol::sym; @@ -247,6 +247,7 @@ pub(crate) fn create_config( Some((lint.name_lower(), lint::Allow)) }); + let crate_types = if crate_types.is_empty() { vec![CrateType::Rlib] } else { crate_types }; let test = scrape_examples_options.map(|opts| opts.scrape_tests).unwrap_or(false); // plays with error output here! let sessopts = config::Options { diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index b70444ec67395..30bc2f90d2c52 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -12,7 +12,7 @@ use rustc_middle::hir::nested_filter; 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, ErrorOutputType}; +use rustc_session::config::{self, CrateType, ErrorOutputType}; use rustc_session::parse::ParseSess; use rustc_session::{lint, Session}; use rustc_span::edition::Edition; @@ -68,7 +68,11 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> { debug!(?lint_opts); - let crate_types = options.crate_types.clone(); + let crate_types = if options.crate_types.is_empty() { + vec![CrateType::Rlib] + } else { + options.crate_types.clone() + }; let sessopts = config::Options { maybe_sysroot: options.maybe_sysroot.clone(), diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 0432d445d53fb..6e5e293780d3d 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1948,6 +1948,7 @@ in storage.js .scraped-example .code-wrapper .next, .scraped-example .code-wrapper .prev, .scraped-example .code-wrapper .expand { + color: var(--main-color); position: absolute; top: 0.25em; z-index: 1; From 8a459384ad02d120f1d1cc81166f95262c1d4fac Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 12:56:02 -0800 Subject: [PATCH 09/10] Revert crate_types change, add new bin_crate field --- src/librustdoc/config.rs | 14 ++++++++++---- src/librustdoc/core.rs | 5 +++-- src/librustdoc/doctest.rs | 7 ++----- src/librustdoc/lib.rs | 4 ++-- src/librustdoc/scrape_examples.rs | 10 +++++----- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 41af4f9561b53..56b40d8c66baf 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -69,8 +69,10 @@ pub(crate) struct Options { pub(crate) input: PathBuf, /// The name of the crate being documented. pub(crate) crate_name: Option, - /// The types of the crate being documented. - pub(crate) crate_types: Vec, + /// Whether or not this is a bin crate + pub(crate) bin_crate: bool, + /// Whether or not this is a proc-macro crate + pub(crate) proc_macro_crate: bool, /// How to format errors and warnings. pub(crate) error_format: ErrorOutputType, /// Width of output buffer to truncate errors appropriately. @@ -176,7 +178,8 @@ impl fmt::Debug for Options { f.debug_struct("Options") .field("input", &self.input) .field("crate_name", &self.crate_name) - .field("crate_types", &self.crate_types) + .field("bin_crate", &self.bin_crate) + .field("proc_macro_crate", &self.proc_macro_crate) .field("error_format", &self.error_format) .field("libs", &self.libs) .field("externs", &FmtExterns(&self.externs)) @@ -667,6 +670,8 @@ impl Options { None => OutputFormat::default(), }; let crate_name = matches.opt_str("crate-name"); + let bin_crate = crate_types.contains(&CrateType::Executable); + 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") { @@ -717,7 +722,8 @@ impl Options { rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref()); let options = Options { input, - crate_types, + bin_crate, + proc_macro_crate, error_format, diagnostic_width, libs, diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 58954ecc73f1a..da0df596c41e3 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -203,7 +203,7 @@ pub(crate) fn create_config( RustdocOptions { input, crate_name, - crate_types, + proc_macro_crate, error_format, diagnostic_width, libs, @@ -247,7 +247,8 @@ pub(crate) fn create_config( Some((lint.name_lower(), lint::Allow)) }); - let crate_types = if crate_types.is_empty() { vec![CrateType::Rlib] } else { crate_types }; + 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 { diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 30bc2f90d2c52..81d9c46447a37 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -68,11 +68,8 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> { debug!(?lint_opts); - let crate_types = if options.crate_types.is_empty() { - vec![CrateType::Rlib] - } else { - options.crate_types.clone() - }; + let crate_types = + if options.proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] }; let sessopts = config::Options { maybe_sysroot: options.maybe_sysroot.clone(), diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index e27af61051c4f..3f84eb0b4c655 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -774,7 +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 bin_crate = options.bin_crate; let config = core::create_config(options); @@ -839,7 +839,7 @@ fn main_args(at_args: &[String]) -> MainResult { cache, tcx, options, - crate_types, + bin_crate, ); } diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index 9adccda0e7200..f2ee99cd9d494 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -20,7 +20,7 @@ use rustc_serialize::{ opaque::{FileEncoder, MemDecoder}, Decodable, Encodable, }; -use rustc_session::{config::CrateType, getopts}; +use rustc_session::getopts; use rustc_span::{ def_id::{CrateNum, DefPathHash, LOCAL_CRATE}, edition::Edition, @@ -123,7 +123,7 @@ struct FindCalls<'a, 'tcx> { cx: Context<'tcx>, target_crates: Vec, calls: &'a mut AllCallLocations, - crate_types: Vec, + bin_crate: bool, } impl<'a, 'tcx> Visitor<'tcx> for FindCalls<'a, 'tcx> @@ -247,7 +247,7 @@ where let mk_call_data = || { let display_name = file_path.display().to_string(); let edition = call_span.edition(); - let is_bin = self.crate_types.contains(&CrateType::Executable); + let is_bin = self.bin_crate; CallData { locations: Vec::new(), url, display_name, edition, is_bin } }; @@ -278,7 +278,7 @@ pub(crate) fn run( cache: formats::cache::Cache, tcx: TyCtxt<'_>, options: ScrapeExamplesOptions, - crate_types: Vec, + bin_crate: bool, ) -> interface::Result<()> { let inner = move || -> Result<(), String> { // Generates source files for examples @@ -306,7 +306,7 @@ 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, crate_types }; + FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates, bin_crate }; tcx.hir().visit_all_item_likes_in_crate(&mut finder); // The visitor might have found a type error, which we need to From 9499d2cce3bebef96d8ae64442602f87726a875a Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Wed, 7 Dec 2022 10:24:16 -0800 Subject: [PATCH 10/10] Improve calculation of scraped example minimized height --- src/librustdoc/html/static/css/rustdoc.css | 12 +++++++++--- src/librustdoc/html/static/js/scrape-examples.js | 8 ++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 6e5e293780d3d..afed3da9e91d2 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1931,18 +1931,24 @@ in storage.js } .scraped-example:not(.expanded) .code-wrapper { - max-height: 120px; + /* scrape-examples.js has a constant DEFAULT_MAX_LINES (call it N) for the number + * of lines shown in the un-expanded example code viewer. This pre needs to have + * a max-height equal to line-height * N. The line-height is currently 1.5em, + * and we include additional 10px for padding. */ + max-height: calc(1.5em * 5 + 10px); } .scraped-example:not(.expanded) .code-wrapper pre { overflow-y: hidden; - max-height: 120px; padding-bottom: 0; + /* See above comment, should be the same max-height. */ + max-height: calc(1.5em * 5 + 10px); } .more-scraped-examples .scraped-example:not(.expanded) .code-wrapper, .more-scraped-examples .scraped-example:not(.expanded) .code-wrapper pre { - max-height: 240px; + /* See above comment, except this height is based on HIDDEN_MAX_LINES. */ + max-height: calc(1.5em * 10 + 10px); } .scraped-example .code-wrapper .next, diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index 830b44d6bc0fd..7a3a9c5f34001 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -6,6 +6,8 @@ // Number of lines shown when code viewer is not expanded. // DEFAULT is the first example shown by default, while HIDDEN is // the examples hidden beneath the "More examples" toggle. + // + // NOTE: these values MUST be synchronized with certain rules in rustdoc.css! const DEFAULT_MAX_LINES = 5; const HIDDEN_MAX_LINES = 10; @@ -24,8 +26,10 @@ } else { const wrapper = elt.querySelector(".code-wrapper"); const halfHeight = wrapper.offsetHeight / 2; - const offsetMid = (lines.children[loc[0]].offsetTop - + lines.children[loc[1]].offsetTop) / 2; + const offsetTop = lines.children[loc[0]].offsetTop; + const lastLine = lines.children[loc[1]]; + const offsetBot = lastLine.offsetTop + lastLine.offsetHeight; + const offsetMid = (offsetTop + offsetBot) / 2; scrollOffset = offsetMid - halfHeight; }