From 7e81b0a3173470cbc77273b2f984913dd3c02ef8 Mon Sep 17 00:00:00 2001
From: Will Crichton <wcrichto@cs.stanford.edu>
Date: Sat, 22 Jan 2022 15:32:19 -0800
Subject: [PATCH 01/10] Improve Rustdoc UI for scraped examples with multiline
 arguments, fix overflow in line numbers

---
 src/librustdoc/html/render/mod.rs             | 32 +++++++++++++++++--
 src/librustdoc/html/static/css/rustdoc.css    | 11 +++----
 .../html/static/js/scrape-examples.js         | 26 +++++++++++----
 3 files changed, 54 insertions(+), 15 deletions(-)

diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 3666767a9d9cb..23ce3b131bbd7 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -2717,6 +2717,30 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
         // The output code is limited to that byte range.
         let contents_subset = &contents[(byte_min as usize)..(byte_max as usize)];
 
+        // Given a call-site range, return the set of sub-ranges that exclude leading whitespace
+        // when the range spans multiple lines.
+        let strip_leading_whitespace = |(lo, hi): (u32, u32)| -> Vec<(u32, u32)> {
+            let contents_range = &contents_subset[(lo as usize)..(hi as usize)];
+            let mut ignoring_whitespace = false;
+            let mut ranges = Vec::new();
+            let mut cur_lo = 0;
+            for (idx, chr) in contents_range.char_indices() {
+                let idx = idx as u32;
+                if ignoring_whitespace {
+                    if !chr.is_whitespace() {
+                        ignoring_whitespace = false;
+                        cur_lo = idx;
+                    }
+                } else if chr == '\n' {
+                    ranges.push((lo + cur_lo, lo + idx));
+                    cur_lo = idx;
+                    ignoring_whitespace = true;
+                }
+            }
+            ranges.push((lo + cur_lo, hi));
+            ranges
+        };
+
         // The call locations need to be updated to reflect that the size of the program has changed.
         // Specifically, the ranges are all subtracted by `byte_min` since that's the new zero point.
         let (mut byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data
@@ -2726,10 +2750,12 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
                 let (byte_lo, byte_hi) = loc.call_expr.byte_span;
                 let (line_lo, line_hi) = loc.call_expr.line_span;
                 let byte_range = (byte_lo - byte_min, byte_hi - byte_min);
+                let byte_ranges = strip_leading_whitespace(byte_range);
+
                 let line_range = (line_lo - line_min, line_hi - line_min);
                 let (line_url, line_title) = link_to_loc(call_data, loc);
 
-                (byte_range, (line_range, line_url, line_title))
+                (byte_ranges, (line_range, line_url, line_title))
             })
             .unzip();
 
@@ -2784,8 +2810,8 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
         let root_path = vec!["../"; cx.current.len() - 1].join("");
 
         let mut decoration_info = FxHashMap::default();
-        decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]);
-        decoration_info.insert("highlight", byte_ranges);
+        decoration_info.insert("highlight focus", byte_ranges.remove(0));
+        decoration_info.insert("highlight", byte_ranges.into_iter().flatten().collect());
 
         sources::print_src(
             w,
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index ee265b8c4b543..42b66c70c4cb4 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -2038,17 +2038,16 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	font-family: 'Fira Sans';
 }
 
-.scraped-example:not(.expanded) .code-wrapper pre.line-numbers {
-	overflow: hidden;
-	max-height: 240px;
-}
-
-.scraped-example:not(.expanded) .code-wrapper .example-wrap pre.rust {
+.scraped-example:not(.expanded) .code-wrapper pre {
 	overflow-y: hidden;
 	max-height: 240px;
 	padding-bottom: 0;
 }
 
+.scraped-example:not(.expanded) .code-wrapper pre.line-numbers {
+	overflow-x: hidden;
+}
+
 .scraped-example .code-wrapper .prev {
 	position: absolute;
 	top: 0.25em;
diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js
index 664b187e33e9f..383ae001bc21f 100644
--- a/src/librustdoc/html/static/js/scrape-examples.js
+++ b/src/librustdoc/html/static/js/scrape-examples.js
@@ -1,14 +1,28 @@
 /* global addClass, hasClass, removeClass, onEach */
 
 (function () {
-    // Scroll code block to put the given code location in the middle of the viewer
+    // Number of lines shown when code viewer is not expanded
+    const MAX_LINES = 10;
+
+    // Scroll code block to the given code location
     function scrollToLoc(elt, loc) {
-        var wrapper = elt.querySelector(".code-wrapper");
-        var halfHeight = wrapper.offsetHeight / 2;
         var lines = elt.querySelector('.line-numbers');
-        var offsetMid = (lines.children[loc[0]].offsetTop
-                         + lines.children[loc[1]].offsetTop) / 2;
-        var scrollOffset = offsetMid - halfHeight;
+        var 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) {
+            var line = Math.max(0, loc[0] - 1);
+            scrollOffset = lines.children[line].offsetTop;
+        } else {
+            var wrapper = elt.querySelector(".code-wrapper");
+            var halfHeight = wrapper.offsetHeight / 2;
+            var offsetMid = (lines.children[loc[0]].offsetTop
+                             + lines.children[loc[1]].offsetTop) / 2;
+            scrollOffset = offsetMid - halfHeight;
+        }
+
         lines.scrollTo(0, scrollOffset);
         elt.querySelector(".rust").scrollTo(0, scrollOffset);
     }

From d58c9dfdb910089afc1296142bae21634ce5eb4b Mon Sep 17 00:00:00 2001
From: Will Crichton <wcrichto@cs.stanford.edu>
Date: Mon, 24 Jan 2022 17:32:33 -0800
Subject: [PATCH 02/10] Only highlight identifier in scraped examples, not
 arguments

---
 src/librustdoc/html/render/mod.rs             | 33 ++---------
 src/librustdoc/scrape_examples.rs             | 55 +++++++++++++------
 .../src/lib.rs                                |  5 +-
 3 files changed, 44 insertions(+), 49 deletions(-)

diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 23ce3b131bbd7..6b57ff5eeba32 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -2717,45 +2717,20 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
         // The output code is limited to that byte range.
         let contents_subset = &contents[(byte_min as usize)..(byte_max as usize)];
 
-        // Given a call-site range, return the set of sub-ranges that exclude leading whitespace
-        // when the range spans multiple lines.
-        let strip_leading_whitespace = |(lo, hi): (u32, u32)| -> Vec<(u32, u32)> {
-            let contents_range = &contents_subset[(lo as usize)..(hi as usize)];
-            let mut ignoring_whitespace = false;
-            let mut ranges = Vec::new();
-            let mut cur_lo = 0;
-            for (idx, chr) in contents_range.char_indices() {
-                let idx = idx as u32;
-                if ignoring_whitespace {
-                    if !chr.is_whitespace() {
-                        ignoring_whitespace = false;
-                        cur_lo = idx;
-                    }
-                } else if chr == '\n' {
-                    ranges.push((lo + cur_lo, lo + idx));
-                    cur_lo = idx;
-                    ignoring_whitespace = true;
-                }
-            }
-            ranges.push((lo + cur_lo, hi));
-            ranges
-        };
-
         // The call locations need to be updated to reflect that the size of the program has changed.
         // Specifically, the ranges are all subtracted by `byte_min` since that's the new zero point.
         let (mut byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data
             .locations
             .iter()
             .map(|loc| {
-                let (byte_lo, byte_hi) = loc.call_expr.byte_span;
+                let (byte_lo, byte_hi) = loc.call_ident.byte_span;
                 let (line_lo, line_hi) = loc.call_expr.line_span;
                 let byte_range = (byte_lo - byte_min, byte_hi - byte_min);
-                let byte_ranges = strip_leading_whitespace(byte_range);
 
                 let line_range = (line_lo - line_min, line_hi - line_min);
                 let (line_url, line_title) = link_to_loc(call_data, loc);
 
-                (byte_ranges, (line_range, line_url, line_title))
+                (byte_range, (line_range, line_url, line_title))
             })
             .unzip();
 
@@ -2810,8 +2785,8 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
         let root_path = vec!["../"; cx.current.len() - 1].join("");
 
         let mut decoration_info = FxHashMap::default();
-        decoration_info.insert("highlight focus", byte_ranges.remove(0));
-        decoration_info.insert("highlight", byte_ranges.into_iter().flatten().collect());
+        decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]);
+        decoration_info.insert("highlight", byte_ranges);
 
         sources::print_src(
             w,
diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs
index 7cf0ea9e84e4e..21af26a1c913e 100644
--- a/src/librustdoc/scrape_examples.rs
+++ b/src/librustdoc/scrape_examples.rs
@@ -85,17 +85,20 @@ impl SyntaxRange {
 #[derive(Encodable, Decodable, Debug, Clone)]
 crate struct CallLocation {
     crate call_expr: SyntaxRange,
+    crate call_ident: SyntaxRange,
     crate enclosing_item: SyntaxRange,
 }
 
 impl CallLocation {
     fn new(
         expr_span: rustc_span::Span,
+        ident_span: rustc_span::Span,
         enclosing_item_span: rustc_span::Span,
         source_file: &SourceFile,
     ) -> Self {
         CallLocation {
             call_expr: SyntaxRange::new(expr_span, source_file),
+            call_ident: SyntaxRange::new(ident_span, source_file),
             enclosing_item: SyntaxRange::new(enclosing_item_span, source_file),
         }
     }
@@ -146,24 +149,39 @@ where
         }
 
         // Get type of function if expression is a function call
-        let (ty, span) = match ex.kind {
+        let (ty, call_span, ident_span) = match ex.kind {
             hir::ExprKind::Call(f, _) => {
                 let types = tcx.typeck(ex.hir_id.owner);
 
                 if let Some(ty) = types.node_type_opt(f.hir_id) {
-                    (ty, ex.span)
+                    (ty, ex.span, f.span)
                 } else {
                     trace!("node_type_opt({}) = None", f.hir_id);
                     return;
                 }
             }
-            hir::ExprKind::MethodCall(_, _, span) => {
+            hir::ExprKind::MethodCall(_, args, call_span) => {
                 let types = tcx.typeck(ex.hir_id.owner);
                 let Some(def_id) = types.type_dependent_def_id(ex.hir_id) else {
                     trace!("type_dependent_def_id({}) = None", ex.hir_id);
                     return;
                 };
-                (tcx.type_of(def_id), span)
+
+                // The MethodCall node doesn't directly contain a span for the
+                // method identifier, so we have to compute it by trimming the full
+                // span based on the arguments.
+                let ident_span = match args.get(1) {
+                    // If there is an argument, e.g. "f(x)", then
+                    // get the span "f(" and delete the lparen.
+                    Some(arg) => {
+                        let with_paren = call_span.until(arg.span);
+                        with_paren.with_hi(with_paren.hi() - BytePos(1))
+                    }
+                    // Otherwise, just delete both parens directly.
+                    None => call_span.with_hi(call_span.hi() - BytePos(2)),
+                };
+
+                (tcx.type_of(def_id), call_span, ident_span)
             }
             _ => {
                 return;
@@ -172,8 +190,8 @@ where
 
         // If this span comes from a macro expansion, then the source code may not actually show
         // a use of the given item, so it would be a poor example. Hence, we skip all uses in macros.
-        if span.from_expansion() {
-            trace!("Rejecting expr from macro: {:?}", span);
+        if call_span.from_expansion() {
+            trace!("Rejecting expr from macro: {:?}", call_span);
             return;
         }
 
@@ -183,26 +201,29 @@ where
             .hir()
             .span_with_body(tcx.hir().local_def_id_to_hir_id(tcx.hir().get_parent_item(ex.hir_id)));
         if enclosing_item_span.from_expansion() {
-            trace!("Rejecting expr ({:?}) from macro item: {:?}", span, enclosing_item_span);
+            trace!("Rejecting expr ({:?}) from macro item: {:?}", call_span, enclosing_item_span);
             return;
         }
 
         assert!(
-            enclosing_item_span.contains(span),
-            "Attempted to scrape call at [{:?}] whose enclosing item [{:?}] doesn't contain the span of the call.",
-            span,
-            enclosing_item_span
+            enclosing_item_span.contains(call_span),
+            "Attempted to scrape call at [{call_span:?}] whose enclosing item [{enclosing_item_span:?}] doesn't contain the span of the call.",
+        );
+
+        assert!(
+            call_span.contains(ident_span),
+            "Attempted to scrape call at [{call_span:?}] whose identifier [{ident_span:?}] was not contained in the span of the call."
         );
 
         // Save call site if the function resolves to a concrete definition
         if let ty::FnDef(def_id, _) = ty.kind() {
             if self.target_crates.iter().all(|krate| *krate != def_id.krate) {
-                trace!("Rejecting expr from crate not being documented: {:?}", span);
+                trace!("Rejecting expr from crate not being documented: {call_span:?}");
                 return;
             }
 
             let source_map = tcx.sess.source_map();
-            let file = source_map.lookup_char_pos(span.lo()).file;
+            let file = source_map.lookup_char_pos(call_span.lo()).file;
             let file_path = match file.name.clone() {
                 FileName::Real(real_filename) => real_filename.into_local_path(),
                 _ => None,
@@ -212,20 +233,20 @@ where
                 let abs_path = fs::canonicalize(file_path.clone()).unwrap();
                 let cx = &self.cx;
                 let mk_call_data = || {
-                    let clean_span = crate::clean::types::Span::new(span);
+                    let clean_span = crate::clean::types::Span::new(call_span);
                     let url = cx.href_from_span(clean_span, false).unwrap();
                     let display_name = file_path.display().to_string();
-                    let edition = span.edition();
+                    let edition = call_span.edition();
                     CallData { locations: Vec::new(), url, display_name, edition }
                 };
 
                 let fn_key = tcx.def_path_hash(*def_id);
                 let fn_entries = self.calls.entry(fn_key).or_default();
 
-                trace!("Including expr: {:?}", span);
+                trace!("Including expr: {:?}", call_span);
                 let enclosing_item_span =
                     source_map.span_extend_to_prev_char(enclosing_item_span, '\n', false);
-                let location = CallLocation::new(span, enclosing_item_span, &file);
+                let location = CallLocation::new(call_span, ident_span, enclosing_item_span, &file);
                 fn_entries.entry(abs_path).or_insert_with(mk_call_data).locations.push(location);
             }
         }
diff --git a/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs b/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs
index 5afffffdf9976..7bd57609fa224 100644
--- a/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs
+++ b/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs
@@ -1,7 +1,6 @@
 // @has foobar/fn.ok.html '//*[@class="docblock scraped-example-list"]' 'ex2'
 // @has foobar/fn.ok.html '//*[@class="more-scraped-examples"]' 'ex1'
-// @has foobar/fn.ok.html '//*[@class="highlight focus"]' '1'
-// @has foobar/fn.ok.html '//*[@class="highlight"]' '2'
-// @has foobar/fn.ok.html '//*[@class="highlight focus"]' '0'
+// @has foobar/fn.ok.html '//*[@class="highlight focus"]' ''
+// @has foobar/fn.ok.html '//*[@class="highlight"]' ''
 
 pub fn ok(_x: i32) {}

From f1e3e2c366b05f232c6b2225af7fc155925029df Mon Sep 17 00:00:00 2001
From: Will Crichton <wcrichto@cs.stanford.edu>
Date: Tue, 25 Jan 2022 12:11:44 -0800
Subject: [PATCH 03/10] Use PathSegment::ident for scraping method calls

---
 src/librustdoc/scrape_examples.rs | 17 ++---------------
 1 file changed, 2 insertions(+), 15 deletions(-)

diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs
index 21af26a1c913e..fee42de746d57 100644
--- a/src/librustdoc/scrape_examples.rs
+++ b/src/librustdoc/scrape_examples.rs
@@ -160,27 +160,14 @@ where
                     return;
                 }
             }
-            hir::ExprKind::MethodCall(_, args, call_span) => {
+            hir::ExprKind::MethodCall(path, _, call_span) => {
                 let types = tcx.typeck(ex.hir_id.owner);
                 let Some(def_id) = types.type_dependent_def_id(ex.hir_id) else {
                     trace!("type_dependent_def_id({}) = None", ex.hir_id);
                     return;
                 };
 
-                // The MethodCall node doesn't directly contain a span for the
-                // method identifier, so we have to compute it by trimming the full
-                // span based on the arguments.
-                let ident_span = match args.get(1) {
-                    // If there is an argument, e.g. "f(x)", then
-                    // get the span "f(" and delete the lparen.
-                    Some(arg) => {
-                        let with_paren = call_span.until(arg.span);
-                        with_paren.with_hi(with_paren.hi() - BytePos(1))
-                    }
-                    // Otherwise, just delete both parens directly.
-                    None => call_span.with_hi(call_span.hi() - BytePos(2)),
-                };
-
+                let ident_span = path.ident.span;
                 (tcx.type_of(def_id), call_span, ident_span)
             }
             _ => {

From ae5d0cbe74a07baef9eb92dde82b28feea8961cd Mon Sep 17 00:00:00 2001
From: Will Crichton <wcrichto@cs.stanford.edu>
Date: Thu, 27 Jan 2022 17:00:31 -0800
Subject: [PATCH 04/10] Improve alignment of additional scraped examples, add
 scrape examples help page

---
 src/doc/rustdoc/src/SUMMARY.md                |  1 +
 src/doc/rustdoc/src/scraped-examples.md       | 55 +++++++++++++
 src/librustdoc/html/render/mod.rs             |  2 +
 src/librustdoc/html/static/css/rustdoc.css    | 77 +++++++++++--------
 src/librustdoc/html/static/css/themes/ayu.css | 16 +++-
 .../html/static/css/themes/dark.css           | 15 +++-
 .../html/static/css/themes/light.css          | 30 ++++++++
 .../html/static/js/scrape-examples.js         |  6 +-
 .../src/lib.rs                                |  4 +-
 9 files changed, 164 insertions(+), 42 deletions(-)
 create mode 100644 src/doc/rustdoc/src/scraped-examples.md

diff --git a/src/doc/rustdoc/src/SUMMARY.md b/src/doc/rustdoc/src/SUMMARY.md
index d627f5b0389f3..747cc629ba44b 100644
--- a/src/doc/rustdoc/src/SUMMARY.md
+++ b/src/doc/rustdoc/src/SUMMARY.md
@@ -9,6 +9,7 @@
     - [Linking to items by name](write-documentation/linking-to-items-by-name.md)
     - [Documentation tests](write-documentation/documentation-tests.md)
 - [Rustdoc-specific lints](lints.md)
+- [Scraped examples](scraped-examples.md)
 - [Advanced features](advanced-features.md)
 - [Unstable features](unstable-features.md)
 - [Deprecated features](deprecated-features.md)
diff --git a/src/doc/rustdoc/src/scraped-examples.md b/src/doc/rustdoc/src/scraped-examples.md
new file mode 100644
index 0000000000000..bab992ccc50ea
--- /dev/null
+++ b/src/doc/rustdoc/src/scraped-examples.md
@@ -0,0 +1,55 @@
+# Scraped examples
+
+Rustdoc can automatically scrape examples of items being documented from the `examples/` directory of a Cargo workspace. These examples will be included within the generated documentation for that item. For example, if your library contains a public function:
+
+```rust,ignore(needs-other-file)
+// a_crate/src/lib.rs
+pub fn a_func() {}
+```
+
+And you have an example calling this function:
+
+```rust,ignore(needs-other-file)
+// a_crate/examples/ex.rs
+fn main() {
+  a_crate::a_func();
+}
+```
+
+Then this code snippet will be included in the documentation for `a_func`. This documentation is inserted by Rustdoc and cannot be manually edited by the crate author.
+
+
+## How to use this feature
+
+This feature is unstable, so you can enable it by calling Rustdoc with the unstable `rustdoc-scrape-examples` flag:
+
+```bash
+cargo doc -Zunstable-options -Zrustdoc-scrape-examples=examples
+```
+
+To enable this feature on [docs.rs](https://docs.rs), add this to your Cargo.toml:
+
+```toml
+[package.metadata.docs.rs]
+cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples=examples"]
+```
+
+
+## How it works
+
+When you run `cargo doc`, Rustdoc will analyze all the crates that match Cargo's `--examples` filter for instances of items being documented. Then Rustdoc will include the source code of these instances in the generated documentation.
+
+Rustdoc has a few techniques to ensure these examples don't overwhelm documentation readers, and that it doesn't blow up the page size:
+
+1. For a given item, a maximum of 5 examples are included in the page. The remaining examples are just links to source code.
+2. Only one example is shown by default, and the remaining examples are hidden behind a toggle.
+3. For a given file that contains examples, only the item containing the examples will be included in the generated documentation.
+
+For a given item, Rustdoc sorts its examples based on the size of the example &mdash; smaller ones are shown first.
+
+
+## FAQ
+
+### My example is not showing up in the documentation
+
+This feature uses Cargo's convention for finding examples. You should ensure that `cargo check --examples` includes your example file.
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 6b57ff5eeba32..b5536dc930faa 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -2671,6 +2671,7 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
           <span></span>\
           <h5 id=\"{id}\">\
              <a href=\"#{id}\">Examples found in repository</a>\
+             <a class=\"scrape-help\" href=\"https://doc.rust-lang.org/rustdoc/scraped-examples.html\" target=\"_blank\">?</a>\
           </h5>",
         id = id
     );
@@ -2842,6 +2843,7 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
                   <summary class=\"hideme\">\
                      <span>More examples</span>\
                   </summary>\
+                  <div class=\"hide-more\">Hide additional examples</div>\
                   <div class=\"more-scraped-examples\">\
                     <div class=\"toggle-line\"><div class=\"toggle-line-inner\"></div></div>\
                     <div class=\"more-scraped-examples-inner\">"
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 42b66c70c4cb4..b278ab6b24d3b 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -618,7 +618,7 @@ h2.location a {
 	position: relative;
 }
 
-.docblock > :not(.information) {
+.docblock > :not(.information):not(.more-examples-toggle) {
 	max-width: 100%;
 	overflow-x: auto;
 }
@@ -836,8 +836,8 @@ h2.small-section-header > .anchor {
 	content: 'ยง';
 }
 
-.docblock a:not(.srclink):not(.test-arrow):hover,
-.docblock-short a:not(.srclink):not(.test-arrow):hover, .item-info a {
+.docblock a:not(.srclink):not(.test-arrow):not(.scrape-help):hover,
+.docblock-short a:not(.srclink):not(.test-arrow):not(.scrape-help):hover, .item-info a {
 	text-decoration: underline;
 }
 
@@ -2034,10 +2034,36 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 
 /* Begin: styles for --scrape-examples feature */
 
+.scraped-example-list .section-header .scrape-help {
+	cursor: pointer;
+	border-radius: 2px;
+	margin-left: 10px;
+	padding: 0 4px;
+	font-weight: normal;
+	font-size: 12px;
+	position: relative;
+	bottom: 1px;
+	background: transparent;
+	border-width: 1px;
+	border-style: solid;
+}
+
 .scraped-example-title {
 	font-family: 'Fira Sans';
 }
 
+.scraped-example .code-wrapper {
+	position: relative;
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+	width: 100%;
+}
+
+.scraped-example:not(.expanded) .code-wrapper {
+	max-height: 240px;
+}
+
 .scraped-example:not(.expanded) .code-wrapper pre {
 	overflow-y: hidden;
 	max-height: 240px;
@@ -2072,14 +2098,6 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	cursor: pointer;
 }
 
-.scraped-example .code-wrapper {
-	position: relative;
-	display: flex;
-	flex-direction: row;
-	flex-wrap: wrap;
-	width: 100%;
-}
-
 .scraped-example:not(.expanded) .code-wrapper:before {
 	content: " ";
 	width: 100%;
@@ -2087,7 +2105,6 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	position: absolute;
 	z-index: 100;
 	top: 0;
-	background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
 }
 
 .scraped-example:not(.expanded) .code-wrapper:after {
@@ -2097,12 +2114,6 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	position: absolute;
 	z-index: 100;
 	bottom: 0;
-	background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
-}
-
-.scraped-example:not(.expanded) .code-wrapper {
-	overflow: hidden;
-	max-height: 240px;
 }
 
 .scraped-example .code-wrapper .line-numbers {
@@ -2121,34 +2132,37 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	margin-bottom: 0;
 }
 
+.scraped-example:not(.expanded) .code-wrapper .example-wrap {
+	overflow-x: hidden;
+}
+
 .scraped-example .code-wrapper .example-wrap pre.rust {
 	overflow-x: inherit;
 	width: inherit;
 	overflow-y: hidden;
 }
 
-.scraped-example .example-wrap .rust span.highlight {
-	background: #fcffd6;
-}
-
-.scraped-example .example-wrap .rust span.highlight.focus {
-	background: #f6fdb0;
-}
 
 .more-examples-toggle {
+	max-width: calc(100% + 25px);
 	margin-top: 10px;
+	margin-left: -25px;
+}
+
+.more-examples-toggle .hide-more {
+	margin-left: 25px;
+	margin-bottom: 5px;
+	cursor: pointer;
 }
 
-.more-examples-toggle summary {
-	color: #999;
+.more-examples-toggle summary, .more-examples-toggle .hide-more {
 	font-family: 'Fira Sans';
 }
 
 .more-scraped-examples {
-	margin-left: 25px;
+	margin-left: 5px;
 	display: flex;
 	flex-direction: row;
-	width: calc(100% - 25px);
 }
 
 .more-scraped-examples-inner {
@@ -2164,13 +2178,8 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	cursor: pointer;
 }
 
-.toggle-line:hover .toggle-line-inner {
-	background: #aaa;
-}
-
 .toggle-line-inner {
 	min-width: 2px;
-	background: #ddd;
 	height: 100%;
 }
 
diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css
index e402b3583f399..0fee0ba54fa82 100644
--- a/src/librustdoc/html/static/css/themes/ayu.css
+++ b/src/librustdoc/html/static/css/themes/ayu.css
@@ -611,6 +611,18 @@ input:checked + .slider {
 	background-color: #ffb454 !important;
 }
 
+
+.scraped-example-list .section-header .scrape-help {
+	border-color: #999;
+	color: #999;
+}
+.scraped-example-list .section-header .scrape-help:hover {
+	border-color: #c5c5c5;
+	color: #c5c5c5;
+}
+.more-examples-toggle summary, .more-examples-toggle .hide-more {
+	color: #999;
+}
 .scraped-example .example-wrap .rust span.highlight {
 	background: rgb(91, 59, 1);
 }
@@ -624,8 +636,8 @@ input:checked + .slider {
 	background: linear-gradient(to top, rgba(15, 20, 25, 1), rgba(15, 20, 25, 0));
 }
 .toggle-line-inner {
-	background: #616161;
+	background: #999;
 }
 .toggle-line:hover .toggle-line-inner {
-	background: #898989;
+	background: #c5c5c5;
 }
diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css
index 0a56055b8cbf6..e121c984dbddf 100644
--- a/src/librustdoc/html/static/css/themes/dark.css
+++ b/src/librustdoc/html/static/css/themes/dark.css
@@ -478,6 +478,17 @@ div.files > .selected {
 	border-bottom-color: #ddd;
 }
 
+.scraped-example-list .section-header .scrape-help {
+	border-color: #999;
+	color: #999;
+}
+.scraped-example-list .section-header .scrape-help:hover {
+	border-color: #c5c5c5;
+	color: #c5c5c5;
+}
+.more-examples-toggle summary, .more-examples-toggle .hide-more {
+	color: #999;
+}
 .scraped-example .example-wrap .rust span.highlight {
 	background: rgb(91, 59, 1);
 }
@@ -491,8 +502,8 @@ div.files > .selected {
 	background: linear-gradient(to top, rgba(53, 53, 53, 1), rgba(53, 53, 53, 0));
 }
 .toggle-line-inner {
-	background: #616161;
+	background: #999;
 }
 .toggle-line:hover .toggle-line-inner {
-	background: #898989;
+	background: #c5c5c5;
 }
diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css
index dc1715b2a78f3..be1da8df2b511 100644
--- a/src/librustdoc/html/static/css/themes/light.css
+++ b/src/librustdoc/html/static/css/themes/light.css
@@ -462,3 +462,33 @@ div.files > .selected {
 .setting-line > .title {
 	border-bottom-color: #D5D5D5;
 }
+
+.scraped-example-list .section-header .scrape-help {
+	border-color: #999;
+	color: #999;
+}
+.scraped-example-list .section-header .scrape-help:hover {
+	border-color: black;
+	color: black;
+}
+.more-examples-toggle summary, .more-examples-toggle .hide-more {
+	color: #999;
+}
+.scraped-example .example-wrap .rust span.highlight {
+	background: #fcffd6;
+}
+.scraped-example .example-wrap .rust span.highlight.focus {
+	background: #f6fdb0;
+}
+.scraped-example:not(.expanded) .code-wrapper:before {
+	background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
+}
+.scraped-example:not(.expanded) .code-wrapper:after {
+	background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
+}
+.toggle-line-inner {
+	background: #ccc;
+}
+.toggle-line:hover .toggle-line-inner {
+	background: #999;
+}
diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js
index 383ae001bc21f..a28fb46172990 100644
--- a/src/librustdoc/html/static/js/scrape-examples.js
+++ b/src/librustdoc/html/static/js/scrape-examples.js
@@ -84,8 +84,10 @@
     onEach(document.querySelectorAll('.more-examples-toggle'), function(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.
-        toggle.querySelector('.toggle-line').addEventListener('click', function() {
-            toggle.open = false;
+        toggle.querySelectorAll('.toggle-line, .hide-more').forEach(button => {
+            button.addEventListener('click', function() {
+                toggle.open = false;
+            });
         });
 
         var moreExamples = toggle.querySelectorAll('.scraped-example');
diff --git a/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs b/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs
index 7bd57609fa224..c53c987a7cbf0 100644
--- a/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs
+++ b/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs
@@ -1,6 +1,6 @@
 // @has foobar/fn.ok.html '//*[@class="docblock scraped-example-list"]' 'ex2'
 // @has foobar/fn.ok.html '//*[@class="more-scraped-examples"]' 'ex1'
-// @has foobar/fn.ok.html '//*[@class="highlight focus"]' ''
-// @has foobar/fn.ok.html '//*[@class="highlight"]' ''
+// @has foobar/fn.ok.html '//*[@class="highlight focus"]' 'ok'
+// @has foobar/fn.ok.html '//*[@class="highlight"]' 'ok'
 
 pub fn ok(_x: i32) {}

From 7cca69342a3f662041362bc988da851942dadb6c Mon Sep 17 00:00:00 2001
From: Will Crichton <wcrichto@cs.stanford.edu>
Date: Thu, 27 Jan 2022 18:39:10 -0800
Subject: [PATCH 05/10] Fix markdown issue, remove hard-coded rust-lang.org url

---
 src/doc/rustdoc/src/scraped-examples.md | 4 ++--
 src/librustdoc/html/render/mod.rs       | 4 +++-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/doc/rustdoc/src/scraped-examples.md b/src/doc/rustdoc/src/scraped-examples.md
index bab992ccc50ea..08f220af2a0c4 100644
--- a/src/doc/rustdoc/src/scraped-examples.md
+++ b/src/doc/rustdoc/src/scraped-examples.md
@@ -2,14 +2,14 @@
 
 Rustdoc can automatically scrape examples of items being documented from the `examples/` directory of a Cargo workspace. These examples will be included within the generated documentation for that item. For example, if your library contains a public function:
 
-```rust,ignore(needs-other-file)
+```rust,ignore (needs-other-file)
 // a_crate/src/lib.rs
 pub fn a_func() {}
 ```
 
 And you have an example calling this function:
 
-```rust,ignore(needs-other-file)
+```rust,ignore (needs-other-file)
 // a_crate/examples/ex.rs
 fn main() {
   a_crate::a_func();
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index b5536dc930faa..c3e2eb81e45d2 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -62,6 +62,7 @@ use rustc_span::{
 use serde::ser::SerializeSeq;
 use serde::{Serialize, Serializer};
 
+use crate::clean::utils::DOC_RUST_LANG_ORG_CHANNEL;
 use crate::clean::{self, ItemId, RenderedLink, SelfTy};
 use crate::error::Error;
 use crate::formats::cache::Cache;
@@ -2671,8 +2672,9 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
           <span></span>\
           <h5 id=\"{id}\">\
              <a href=\"#{id}\">Examples found in repository</a>\
-             <a class=\"scrape-help\" href=\"https://doc.rust-lang.org/rustdoc/scraped-examples.html\" target=\"_blank\">?</a>\
+             <a class=\"scrape-help\" href=\"{doc_prefix}/rustdoc/scraped-examples.html\" target=\"_blank\">?</a>\
           </h5>",
+        doc_prefix = DOC_RUST_LANG_ORG_CHANNEL,
         id = id
     );
 

From bb3ed6f7d6caddd3f2c26f2b5b218b2d750e8c3d Mon Sep 17 00:00:00 2001
From: Will Crichton <wcrichto@cs.stanford.edu>
Date: Thu, 27 Jan 2022 18:51:34 -0800
Subject: [PATCH 06/10] Improve styling on  scrape examples help button

---
 src/librustdoc/html/static/css/rustdoc.css      | 3 +--
 src/librustdoc/html/static/css/themes/ayu.css   | 8 ++++----
 src/librustdoc/html/static/css/themes/dark.css  | 8 ++++----
 src/librustdoc/html/static/css/themes/light.css | 4 ++--
 4 files changed, 11 insertions(+), 12 deletions(-)

diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index b278ab6b24d3b..55a11652c2353 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -2035,8 +2035,6 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 /* Begin: styles for --scrape-examples feature */
 
 .scraped-example-list .section-header .scrape-help {
-	cursor: pointer;
-	border-radius: 2px;
 	margin-left: 10px;
 	padding: 0 4px;
 	font-weight: normal;
@@ -2046,6 +2044,7 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 	background: transparent;
 	border-width: 1px;
 	border-style: solid;
+	border-radius: 50px;
 }
 
 .scraped-example-title {
diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css
index 0fee0ba54fa82..e073057528c79 100644
--- a/src/librustdoc/html/static/css/themes/ayu.css
+++ b/src/librustdoc/html/static/css/themes/ayu.css
@@ -613,12 +613,12 @@ input:checked + .slider {
 
 
 .scraped-example-list .section-header .scrape-help {
-	border-color: #999;
-	color: #999;
+	border-color: #aaa;
+	color: #eee;
 }
 .scraped-example-list .section-header .scrape-help:hover {
-	border-color: #c5c5c5;
-	color: #c5c5c5;
+	border-color: white;
+	color: white;
 }
 .more-examples-toggle summary, .more-examples-toggle .hide-more {
 	color: #999;
diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css
index e121c984dbddf..a10029b637177 100644
--- a/src/librustdoc/html/static/css/themes/dark.css
+++ b/src/librustdoc/html/static/css/themes/dark.css
@@ -479,12 +479,12 @@ div.files > .selected {
 }
 
 .scraped-example-list .section-header .scrape-help {
-	border-color: #999;
-	color: #999;
+	border-color: #aaa;
+	color: #eee;
 }
 .scraped-example-list .section-header .scrape-help:hover {
-	border-color: #c5c5c5;
-	color: #c5c5c5;
+	border-color: white;
+	color: white;
 }
 .more-examples-toggle summary, .more-examples-toggle .hide-more {
 	color: #999;
diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css
index be1da8df2b511..a30dcac66dbc5 100644
--- a/src/librustdoc/html/static/css/themes/light.css
+++ b/src/librustdoc/html/static/css/themes/light.css
@@ -464,8 +464,8 @@ div.files > .selected {
 }
 
 .scraped-example-list .section-header .scrape-help {
-	border-color: #999;
-	color: #999;
+	border-color: #555;
+	color: #333;
 }
 .scraped-example-list .section-header .scrape-help:hover {
 	border-color: black;

From 318e45767f068cd3e04d110a0451f31169ac119c Mon Sep 17 00:00:00 2001
From: Will Crichton <wcrichto@cs.stanford.edu>
Date: Fri, 28 Jan 2022 18:18:52 -0800
Subject: [PATCH 07/10] Clarify that scrape examples is unstable

---
 src/doc/rustdoc/src/scraped-examples.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/doc/rustdoc/src/scraped-examples.md b/src/doc/rustdoc/src/scraped-examples.md
index 08f220af2a0c4..d75f6d522e8ed 100644
--- a/src/doc/rustdoc/src/scraped-examples.md
+++ b/src/doc/rustdoc/src/scraped-examples.md
@@ -1,6 +1,6 @@
 # Scraped examples
 
-Rustdoc can automatically scrape examples of items being documented from the `examples/` directory of a Cargo workspace. These examples will be included within the generated documentation for that item. For example, if your library contains a public function:
+Rustdoc has an unstable feature where it can automatically scrape examples of items being documented from the `examples/` directory of a Cargo workspace. These examples will be included within the generated documentation for that item. For example, if your library contains a public function:
 
 ```rust,ignore (needs-other-file)
 // a_crate/src/lib.rs

From d1416d528aa17623c15d51839a5cfeee7bfdbc35 Mon Sep 17 00:00:00 2001
From: Will Crichton <wcrichto@cs.stanford.edu>
Date: Mon, 14 Feb 2022 19:49:39 -0800
Subject: [PATCH 08/10] Add scrape examples help page

---
 src/librustdoc/html/render/context.rs         | 19 +++++++++--
 src/librustdoc/html/render/mod.rs             | 29 ++++++++++++++--
 src/librustdoc/html/static/css/rustdoc.css    |  2 +-
 src/librustdoc/html/static/css/themes/ayu.css |  4 +--
 .../html/static/css/themes/dark.css           |  4 +--
 .../html/static/css/themes/light.css          |  4 +--
 .../html/static/scrape-examples-help.md       | 34 +++++++++++++++++++
 src/librustdoc/html/static_files.rs           |  2 ++
 8 files changed, 86 insertions(+), 12 deletions(-)
 create mode 100644 src/librustdoc/html/static/scrape-examples-help.md

diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 34784bbed0cf5..90123655758cc 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -17,8 +17,8 @@ use super::print_item::{full_path, item_path, print_item};
 use super::search_index::build_index;
 use super::write_shared::write_shared;
 use super::{
-    collect_spans_and_sources, print_sidebar, settings, AllTypes, LinkFromSrc, NameDoc, StylePath,
-    BASIC_KEYWORDS,
+    collect_spans_and_sources, print_sidebar, scrape_examples_help, settings, AllTypes,
+    LinkFromSrc, NameDoc, StylePath, BASIC_KEYWORDS,
 };
 
 use crate::clean::{self, types::ExternalLocation, ExternalCrate};
@@ -551,6 +551,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         let crate_name = self.tcx().crate_name(LOCAL_CRATE);
         let final_file = self.dst.join(crate_name.as_str()).join("all.html");
         let settings_file = self.dst.join("settings.html");
+        let scrape_examples_help_file = self.dst.join("scrape-examples-help.html");
 
         let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
         if !root_path.ends_with('/') {
@@ -606,6 +607,20 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             &self.shared.style_files,
         );
         self.shared.fs.write(settings_file, v)?;
+
+        if self.shared.layout.scrape_examples_extension {
+            page.title = "About scraped examples";
+            page.description = "How the scraped examples feature works in Rustdoc";
+            let v = layout::render(
+                &self.shared.layout,
+                &page,
+                "",
+                scrape_examples_help(&*self.shared),
+                &self.shared.style_files,
+            );
+            self.shared.fs.write(scrape_examples_help_file, v)?;
+        }
+
         if let Some(ref redirections) = self.shared.redirections {
             if !redirections.borrow().is_empty() {
                 let redirect_map_path =
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index c3e2eb81e45d2..80a18038272c7 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -62,7 +62,6 @@ use rustc_span::{
 use serde::ser::SerializeSeq;
 use serde::{Serialize, Serializer};
 
-use crate::clean::utils::DOC_RUST_LANG_ORG_CHANNEL;
 use crate::clean::{self, ItemId, RenderedLink, SelfTy};
 use crate::error::Error;
 use crate::formats::cache::Cache;
@@ -77,6 +76,7 @@ use crate::html::format::{
 use crate::html::highlight;
 use crate::html::markdown::{HeadingOffset, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine};
 use crate::html::sources;
+use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD;
 use crate::scrape_examples::{CallData, CallLocation};
 use crate::try_none;
 
@@ -462,6 +462,29 @@ fn settings(root_path: &str, suffix: &str, theme_names: Vec<String>) -> Result<S
     ))
 }
 
+fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
+    let content = SCRAPE_EXAMPLES_HELP_MD;
+    let mut ids = IdMap::default();
+    format!(
+        "<div class=\"main-heading\">
+            <h1 class=\"fqn\">\
+                <span class=\"in-band\">About scraped examples</span>\
+            </h1>\
+        </div>\
+        <div>{}</div>",
+        Markdown {
+            content,
+            links: &[],
+            ids: &mut ids,
+            error_codes: shared.codes,
+            edition: shared.edition(),
+            playground: &shared.playground,
+            heading_offset: HeadingOffset::H1
+        }
+        .into_string()
+    )
+}
+
 fn document(
     w: &mut Buffer,
     cx: &Context<'_>,
@@ -2672,9 +2695,9 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
           <span></span>\
           <h5 id=\"{id}\">\
              <a href=\"#{id}\">Examples found in repository</a>\
-             <a class=\"scrape-help\" href=\"{doc_prefix}/rustdoc/scraped-examples.html\" target=\"_blank\">?</a>\
+             <a class=\"scrape-help\" href=\"{root_path}scrape-examples-help.html\">?</a>\
           </h5>",
-        doc_prefix = DOC_RUST_LANG_ORG_CHANNEL,
+        root_path = cx.root_path(),
         id = id
     );
 
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 55a11652c2353..e3c3d614f015c 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -2034,7 +2034,7 @@ details.rustdoc-toggle[open] > summary.hideme::after {
 
 /* Begin: styles for --scrape-examples feature */
 
-.scraped-example-list .section-header .scrape-help {
+.scraped-example-list .scrape-help {
 	margin-left: 10px;
 	padding: 0 4px;
 	font-weight: normal;
diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css
index e073057528c79..b1bf06c1865c7 100644
--- a/src/librustdoc/html/static/css/themes/ayu.css
+++ b/src/librustdoc/html/static/css/themes/ayu.css
@@ -612,11 +612,11 @@ input:checked + .slider {
 }
 
 
-.scraped-example-list .section-header .scrape-help {
+.scraped-example-list .scrape-help {
 	border-color: #aaa;
 	color: #eee;
 }
-.scraped-example-list .section-header .scrape-help:hover {
+.scraped-example-list .scrape-help:hover {
 	border-color: white;
 	color: white;
 }
diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css
index a10029b637177..236304ccc9f1b 100644
--- a/src/librustdoc/html/static/css/themes/dark.css
+++ b/src/librustdoc/html/static/css/themes/dark.css
@@ -478,11 +478,11 @@ div.files > .selected {
 	border-bottom-color: #ddd;
 }
 
-.scraped-example-list .section-header .scrape-help {
+.scraped-example-list .scrape-help {
 	border-color: #aaa;
 	color: #eee;
 }
-.scraped-example-list .section-header .scrape-help:hover {
+.scraped-example-list .scrape-help:hover {
 	border-color: white;
 	color: white;
 }
diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css
index a30dcac66dbc5..c923902aba2d3 100644
--- a/src/librustdoc/html/static/css/themes/light.css
+++ b/src/librustdoc/html/static/css/themes/light.css
@@ -463,11 +463,11 @@ div.files > .selected {
 	border-bottom-color: #D5D5D5;
 }
 
-.scraped-example-list .section-header .scrape-help {
+.scraped-example-list .scrape-help {
 	border-color: #555;
 	color: #333;
 }
-.scraped-example-list .section-header .scrape-help:hover {
+.scraped-example-list .scrape-help:hover {
 	border-color: black;
 	color: black;
 }
diff --git a/src/librustdoc/html/static/scrape-examples-help.md b/src/librustdoc/html/static/scrape-examples-help.md
new file mode 100644
index 0000000000000..035b2e18b00eb
--- /dev/null
+++ b/src/librustdoc/html/static/scrape-examples-help.md
@@ -0,0 +1,34 @@
+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:
+
+```rust
+// src/lib.rs
+pub fn a_func() {}
+```
+
+And you have an example calling this function:
+
+```rust
+// examples/ex.rs
+fn main() {
+  a_crate::a_func();
+}
+```
+
+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 &varr; in the top-right. If a file contains multiple instances of an item, you can use the &pr; and &sc; buttons to toggle through each instance.
+
+If there is more than one file that contains examples, then you should click "More examples" to see these examples.
+
+
+## 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.
+
+Rustdoc has a few techniques to ensure this doesn't overwhelm documentation readers, and that it doesn't blow up the page size:
+
+1. For a given item, a maximum of 5 examples are included in the page. The remaining examples are just links to source code.
+2. Only one example is shown by default, and the remaining examples are hidden behind a toggle.
+3. For a given file that contains examples, only the item containing the examples will be included in the generated documentation.
diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs
index cd369a93d8283..1837e4a3b650a 100644
--- a/src/librustdoc/html/static_files.rs
+++ b/src/librustdoc/html/static_files.rs
@@ -39,6 +39,8 @@ crate static STORAGE_JS: &str = include_str!("static/js/storage.js");
 /// --scrape-examples flag that inserts automatically-found examples of usages of items.
 crate static SCRAPE_EXAMPLES_JS: &str = include_str!("static/js/scrape-examples.js");
 
+crate static SCRAPE_EXAMPLES_HELP_MD: &str = include_str!("static/scrape-examples-help.md");
+
 /// The file contents of `brush.svg`, the icon used for the theme-switch button.
 crate static BRUSH_SVG: &[u8] = include_bytes!("static/images/brush.svg");
 

From b9ecdca0d5543cbb3c40042305ae315bf3454c60 Mon Sep 17 00:00:00 2001
From: Will Crichton <wcrichto@cs.stanford.edu>
Date: Sun, 27 Mar 2022 19:36:22 -0700
Subject: [PATCH 09/10] Don't panic when scraping invalid calls

---
 src/librustdoc/scrape_examples.rs | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs
index fee42de746d57..bddc4326b7468 100644
--- a/src/librustdoc/scrape_examples.rs
+++ b/src/librustdoc/scrape_examples.rs
@@ -192,15 +192,22 @@ where
             return;
         }
 
-        assert!(
-            enclosing_item_span.contains(call_span),
-            "Attempted to scrape call at [{call_span:?}] whose enclosing item [{enclosing_item_span:?}] doesn't contain the span of the call.",
-        );
-
-        assert!(
-            call_span.contains(ident_span),
-            "Attempted to scrape call at [{call_span:?}] whose identifier [{ident_span:?}] was not contained in the span of the call."
-        );
+        // If the enclosing item doesn't actually enclose the call, this means we probably have a weird
+        // macro issue even though the spans aren't tagged as being from an expansion.
+        if !enclosing_item_span.contains(call_span) {
+            warn!(
+                "Attempted to scrape call at [{call_span:?}] whose enclosing item [{enclosing_item_span:?}] doesn't contain the span of the call."
+            );
+            return;
+        }
+
+        // Similarly for the call w/ the function ident.
+        if !call_span.contains(ident_span) {
+            warn!(
+                "Attempted to scrape call at [{call_span:?}] whose identifier [{ident_span:?}] was not contained in the span of the call."
+            );
+            return;
+        }
 
         // Save call site if the function resolves to a concrete definition
         if let ty::FnDef(def_id, _) = ty.kind() {

From 6a18b6865590704d42777fa7432436e55773d46b Mon Sep 17 00:00:00 2001
From: Will Crichton <wcrichto@cs.stanford.edu>
Date: Tue, 12 Apr 2022 11:05:07 -0700
Subject: [PATCH 10/10] Add Rustdoc book link to scrape examples help. Remove
 remaining panic locations in scrape examples.

---
 src/librustdoc/html/render/mod.rs | 12 ++++--
 src/librustdoc/scrape_examples.rs | 61 +++++++++++++++++++++----------
 2 files changed, 51 insertions(+), 22 deletions(-)

diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 80a18038272c7..4825ddc5a2034 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -79,6 +79,7 @@ use crate::html::sources;
 use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD;
 use crate::scrape_examples::{CallData, CallLocation};
 use crate::try_none;
+use crate::DOC_RUST_LANG_ORG_CHANNEL;
 
 /// A pair of name and its optional document.
 crate type NameDoc = (String, Option<String>);
@@ -463,17 +464,22 @@ fn settings(root_path: &str, suffix: &str, theme_names: Vec<String>) -> Result<S
 }
 
 fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
-    let content = SCRAPE_EXAMPLES_HELP_MD;
+    let mut content = SCRAPE_EXAMPLES_HELP_MD.to_owned();
+    content.push_str(&format!(
+      "## More information\n\n\
+      If you want more information about this feature, please read the [corresponding chapter in the Rustdoc book]({}/rustdoc/scraped-examples.html).",
+      DOC_RUST_LANG_ORG_CHANNEL));
+
     let mut ids = IdMap::default();
     format!(
-        "<div class=\"main-heading\">
+        "<div class=\"main-heading\">\
             <h1 class=\"fqn\">\
                 <span class=\"in-band\">About scraped examples</span>\
             </h1>\
         </div>\
         <div>{}</div>",
         Markdown {
-            content,
+            content: &content,
             links: &[],
             ids: &mut ids,
             error_codes: shared.codes,
diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs
index bddc4326b7468..0da490f3cd6c8 100644
--- a/src/librustdoc/scrape_examples.rs
+++ b/src/librustdoc/scrape_examples.rs
@@ -71,14 +71,14 @@ crate struct SyntaxRange {
 }
 
 impl SyntaxRange {
-    fn new(span: rustc_span::Span, file: &SourceFile) -> Self {
+    fn new(span: rustc_span::Span, file: &SourceFile) -> Option<Self> {
         let get_pos = |bytepos: BytePos| file.original_relative_byte_pos(bytepos).0;
-        let get_line = |bytepos: BytePos| file.lookup_line(bytepos).unwrap();
+        let get_line = |bytepos: BytePos| file.lookup_line(bytepos);
 
-        SyntaxRange {
+        Some(SyntaxRange {
             byte_span: (get_pos(span.lo()), get_pos(span.hi())),
-            line_span: (get_line(span.lo()), get_line(span.hi())),
-        }
+            line_span: (get_line(span.lo())?, get_line(span.hi())?),
+        })
     }
 }
 
@@ -95,12 +95,12 @@ impl CallLocation {
         ident_span: rustc_span::Span,
         enclosing_item_span: rustc_span::Span,
         source_file: &SourceFile,
-    ) -> Self {
-        CallLocation {
-            call_expr: SyntaxRange::new(expr_span, source_file),
-            call_ident: SyntaxRange::new(ident_span, source_file),
-            enclosing_item: SyntaxRange::new(enclosing_item_span, source_file),
-        }
+    ) -> Option<Self> {
+        Some(CallLocation {
+            call_expr: SyntaxRange::new(expr_span, source_file)?,
+            call_ident: SyntaxRange::new(ident_span, source_file)?,
+            enclosing_item: SyntaxRange::new(enclosing_item_span, source_file)?,
+        })
     }
 }
 
@@ -178,7 +178,7 @@ where
         // If this span comes from a macro expansion, then the source code may not actually show
         // a use of the given item, so it would be a poor example. Hence, we skip all uses in macros.
         if call_span.from_expansion() {
-            trace!("Rejecting expr from macro: {:?}", call_span);
+            trace!("Rejecting expr from macro: {call_span:?}");
             return;
         }
 
@@ -188,7 +188,7 @@ where
             .hir()
             .span_with_body(tcx.hir().local_def_id_to_hir_id(tcx.hir().get_parent_item(ex.hir_id)));
         if enclosing_item_span.from_expansion() {
-            trace!("Rejecting expr ({:?}) from macro item: {:?}", call_span, enclosing_item_span);
+            trace!("Rejecting expr ({call_span:?}) from macro item: {enclosing_item_span:?}");
             return;
         }
 
@@ -224,11 +224,27 @@ where
             };
 
             if let Some(file_path) = file_path {
-                let abs_path = fs::canonicalize(file_path.clone()).unwrap();
+                let abs_path = match fs::canonicalize(file_path.clone()) {
+                    Ok(abs_path) => abs_path,
+                    Err(_) => {
+                        trace!("Could not canonicalize file path: {}", file_path.display());
+                        return;
+                    }
+                };
+
                 let cx = &self.cx;
+                let clean_span = crate::clean::types::Span::new(call_span);
+                let url = match cx.href_from_span(clean_span, false) {
+                    Some(url) => url,
+                    None => {
+                        trace!(
+                            "Rejecting expr ({call_span:?}) whose clean span ({clean_span:?}) cannot be turned into a link"
+                        );
+                        return;
+                    }
+                };
+
                 let mk_call_data = || {
-                    let clean_span = crate::clean::types::Span::new(call_span);
-                    let url = cx.href_from_span(clean_span, false).unwrap();
                     let display_name = file_path.display().to_string();
                     let edition = call_span.edition();
                     CallData { locations: Vec::new(), url, display_name, edition }
@@ -240,7 +256,14 @@ where
                 trace!("Including expr: {:?}", call_span);
                 let enclosing_item_span =
                     source_map.span_extend_to_prev_char(enclosing_item_span, '\n', false);
-                let location = CallLocation::new(call_span, ident_span, enclosing_item_span, &file);
+                let location =
+                    match CallLocation::new(call_span, ident_span, enclosing_item_span, &file) {
+                        Some(location) => location,
+                        None => {
+                            trace!("Could not get serializable call location for {call_span:?}");
+                            return;
+                        }
+                    };
                 fn_entries.entry(abs_path).or_insert_with(mk_call_data).locations.push(location);
             }
         }
@@ -274,8 +297,8 @@ crate fn run(
             .map(|(crate_num, _)| **crate_num)
             .collect::<Vec<_>>();
 
-        debug!("All crates in TyCtxt: {:?}", all_crates);
-        debug!("Scrape examples target_crates: {:?}", target_crates);
+        debug!("All crates in TyCtxt: {all_crates:?}");
+        debug!("Scrape examples target_crates: {target_crates:?}");
 
         // Run call-finder on all items
         let mut calls = FxHashMap::default();