Rollup merge of #93217 - willcrichton:example-analyzer, r=GuillaumeGomez
Improve Rustdoc UI for scraped examples with multiline arguments, fix overflow in line numbers This PR improves a few aspects of the scrape examples feature in Rustdoc. * Only function names and not the full call expression are highlighted. * For call-sites with multiline arguments, the minimized code viewer will scroll to the top of the call-site rather than the middle if the argument is larger than the viewer size, ensuring that the function name is visible. * This fixes an issue where the line numbers column had a visible x-scroll bar. r? `@GuillaumeGomez`
This commit is contained in:
commit
db61452b7a
13 changed files with 342 additions and 88 deletions
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -75,8 +75,10 @@ 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;
|
||||
use crate::DOC_RUST_LANG_ORG_CHANNEL;
|
||||
|
||||
/// A pair of name and its optional document.
|
||||
crate type NameDoc = (String, Option<String>);
|
||||
|
|
@ -460,6 +462,34 @@ fn settings(root_path: &str, suffix: &str, theme_names: Vec<String>) -> Result<S
|
|||
))
|
||||
}
|
||||
|
||||
fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
|
||||
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\">\
|
||||
<h1 class=\"fqn\">\
|
||||
<span class=\"in-band\">About scraped examples</span>\
|
||||
</h1>\
|
||||
</div>\
|
||||
<div>{}</div>",
|
||||
Markdown {
|
||||
content: &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<'_>,
|
||||
|
|
@ -2743,7 +2773,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=\"{root_path}scrape-examples-help.html\">?</a>\
|
||||
</h5>",
|
||||
root_path = cx.root_path(),
|
||||
id = id
|
||||
);
|
||||
|
||||
|
|
@ -2795,9 +2827,10 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
|
|||
.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 line_range = (line_lo - line_min, line_hi - line_min);
|
||||
let (line_url, line_title) = link_to_loc(call_data, loc);
|
||||
|
||||
|
|
@ -2913,6 +2946,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\">"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -840,8 +840,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;
|
||||
}
|
||||
|
||||
|
|
@ -2038,21 +2038,45 @@ details.rustdoc-toggle[open] > summary.hideme::after {
|
|||
|
||||
/* Begin: styles for --scrape-examples feature */
|
||||
|
||||
.scraped-example-list .scrape-help {
|
||||
margin-left: 10px;
|
||||
padding: 0 4px;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
bottom: 1px;
|
||||
background: transparent;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.scraped-example-title {
|
||||
font-family: 'Fira Sans';
|
||||
}
|
||||
|
||||
.scraped-example:not(.expanded) .code-wrapper pre.line-numbers {
|
||||
overflow: hidden;
|
||||
.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 .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;
|
||||
|
|
@ -2077,14 +2101,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%;
|
||||
|
|
@ -2092,7 +2108,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 {
|
||||
|
|
@ -2102,12 +2117,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 {
|
||||
|
|
@ -2126,34 +2135,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 summary {
|
||||
color: #999;
|
||||
.more-examples-toggle .hide-more {
|
||||
margin-left: 25px;
|
||||
margin-bottom: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
|
@ -2169,13 +2181,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%;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -611,6 +611,18 @@ input:checked + .slider {
|
|||
background-color: #ffb454 !important;
|
||||
}
|
||||
|
||||
|
||||
.scraped-example-list .scrape-help {
|
||||
border-color: #aaa;
|
||||
color: #eee;
|
||||
}
|
||||
.scraped-example-list .scrape-help:hover {
|
||||
border-color: white;
|
||||
color: white;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -478,6 +478,17 @@ div.files > .selected {
|
|||
border-bottom-color: #ddd;
|
||||
}
|
||||
|
||||
.scraped-example-list .scrape-help {
|
||||
border-color: #aaa;
|
||||
color: #eee;
|
||||
}
|
||||
.scraped-example-list .scrape-help:hover {
|
||||
border-color: white;
|
||||
color: white;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -462,3 +462,33 @@ div.files > .selected {
|
|||
.setting-line > .title {
|
||||
border-bottom-color: #D5D5D5;
|
||||
}
|
||||
|
||||
.scraped-example-list .scrape-help {
|
||||
border-color: #555;
|
||||
color: #333;
|
||||
}
|
||||
.scraped-example-list .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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -70,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');
|
||||
|
|
|
|||
34
src/librustdoc/html/static/scrape-examples-help.md
Normal file
34
src/librustdoc/html/static/scrape-examples-help.md
Normal file
|
|
@ -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 ↕ in the top-right. If a file contains multiple instances of an item, you can use the ≺ and ≻ 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.
|
||||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue