1
Fork 0

Move highlighting logic from JS to Rust

Continue migrating JS functionality

Cleanup

Fix compile error

Clean up the diff

Set toggle font to sans-serif
This commit is contained in:
Will Crichton 2021-08-26 14:43:12 -07:00
parent eea8f0a39a
commit 55bb51786e
10 changed files with 188 additions and 186 deletions

View file

@ -45,7 +45,7 @@ crate struct TestOptions {
crate attrs: Vec<String>, crate attrs: Vec<String>,
} }
crate fn make_rustc_config(options: &Options) -> interface::Config { crate fn run(options: Options) -> Result<(), ErrorReported> {
let input = config::Input::File(options.input.clone()); let input = config::Input::File(options.input.clone());
let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name; let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;
@ -87,7 +87,7 @@ crate fn make_rustc_config(options: &Options) -> interface::Config {
let mut cfgs = options.cfgs.clone(); let mut cfgs = options.cfgs.clone();
cfgs.push("doc".to_owned()); cfgs.push("doc".to_owned());
cfgs.push("doctest".to_owned()); cfgs.push("doctest".to_owned());
interface::Config { let config = interface::Config {
opts: sessopts, opts: sessopts,
crate_cfg: interface::parse_cfgspecs(cfgs), crate_cfg: interface::parse_cfgspecs(cfgs),
input, input,
@ -103,11 +103,7 @@ crate fn make_rustc_config(options: &Options) -> interface::Config {
override_queries: None, override_queries: None,
make_codegen_backend: None, make_codegen_backend: None,
registry: rustc_driver::diagnostics_registry(), registry: rustc_driver::diagnostics_registry(),
} };
}
crate fn run(options: Options) -> Result<(), ErrorReported> {
let config = make_rustc_config(&options);
let test_args = options.test_args.clone(); let test_args = options.test_args.clone();
let display_doctest_warnings = options.display_doctest_warnings; let display_doctest_warnings = options.display_doctest_warnings;

View file

@ -12,6 +12,7 @@ use crate::html::render::Context;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt::{Display, Write}; use std::fmt::{Display, Write};
use rustc_data_structures::fx::FxHashMap;
use rustc_lexer::{LiteralKind, TokenKind}; use rustc_lexer::{LiteralKind, TokenKind};
use rustc_span::edition::Edition; use rustc_span::edition::Edition;
use rustc_span::symbol::Symbol; use rustc_span::symbol::Symbol;
@ -30,6 +31,8 @@ crate struct ContextInfo<'a, 'b, 'c> {
crate root_path: &'c str, crate root_path: &'c str,
} }
crate type DecorationInfo = FxHashMap<&'static str, Vec<(u32, u32)>>;
/// Highlights `src`, returning the HTML output. /// Highlights `src`, returning the HTML output.
crate fn render_with_highlighting( crate fn render_with_highlighting(
src: &str, src: &str,
@ -40,6 +43,7 @@ crate fn render_with_highlighting(
edition: Edition, edition: Edition,
extra_content: Option<Buffer>, extra_content: Option<Buffer>,
context_info: Option<ContextInfo<'_, '_, '_>>, context_info: Option<ContextInfo<'_, '_, '_>>,
decoration_info: Option<DecorationInfo>,
) { ) {
debug!("highlighting: ================\n{}\n==============", src); debug!("highlighting: ================\n{}\n==============", src);
if let Some((edition_info, class)) = tooltip { if let Some((edition_info, class)) = tooltip {
@ -56,7 +60,7 @@ crate fn render_with_highlighting(
} }
write_header(out, class, extra_content); write_header(out, class, extra_content);
write_code(out, &src, edition, context_info); write_code(out, &src, edition, context_info, decoration_info);
write_footer(out, playground_button); write_footer(out, playground_button);
} }
@ -89,17 +93,23 @@ fn write_code(
src: &str, src: &str,
edition: Edition, edition: Edition,
context_info: Option<ContextInfo<'_, '_, '_>>, context_info: Option<ContextInfo<'_, '_, '_>>,
decoration_info: Option<DecorationInfo>,
) { ) {
// This replace allows to fix how the code source with DOS backline characters is displayed. // This replace allows to fix how the code source with DOS backline characters is displayed.
let src = src.replace("\r\n", "\n"); let src = src.replace("\r\n", "\n");
Classifier::new(&src, edition, context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP)) Classifier::new(
.highlight(&mut |highlight| { &src,
match highlight { edition,
Highlight::Token { text, class } => string(out, Escape(text), class, &context_info), context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
Highlight::EnterSpan { class } => enter_span(out, class), decoration_info,
Highlight::ExitSpan => exit_span(out), )
}; .highlight(&mut |highlight| {
}); match highlight {
Highlight::Token { text, class } => string(out, Escape(text), class, &context_info),
Highlight::EnterSpan { class } => enter_span(out, class),
Highlight::ExitSpan => exit_span(out),
};
});
} }
fn write_footer(out: &mut Buffer, playground_button: Option<&str>) { fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
@ -127,6 +137,7 @@ enum Class {
PreludeTy, PreludeTy,
PreludeVal, PreludeVal,
QuestionMark, QuestionMark,
Decoration(&'static str),
} }
impl Class { impl Class {
@ -150,6 +161,7 @@ impl Class {
Class::PreludeTy => "prelude-ty", Class::PreludeTy => "prelude-ty",
Class::PreludeVal => "prelude-val", Class::PreludeVal => "prelude-val",
Class::QuestionMark => "question-mark", Class::QuestionMark => "question-mark",
Class::Decoration(kind) => kind,
} }
} }
@ -244,7 +256,28 @@ impl Iterator for PeekIter<'a> {
type Item = (TokenKind, &'a str); type Item = (TokenKind, &'a str);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.peek_pos = 0; self.peek_pos = 0;
if let Some(first) = self.stored.pop_front() { Some(first) } else { self.iter.next() } if let Some(first) = self.stored.pop_front() {
Some(first)
} else {
self.iter.next()
}
}
}
/// Custom spans inserted into the source. Eg --scrape-examples uses this to highlight function calls
struct Decorations {
starts: Vec<(u32, &'static str)>,
ends: Vec<u32>,
}
impl Decorations {
fn new(info: DecorationInfo) -> Self {
let (starts, ends) = info
.into_iter()
.map(|(kind, ranges)| ranges.into_iter().map(move |(lo, hi)| ((lo, kind), hi)))
.flatten()
.unzip();
Decorations { starts, ends }
} }
} }
@ -259,12 +292,18 @@ struct Classifier<'a> {
byte_pos: u32, byte_pos: u32,
file_span: Span, file_span: Span,
src: &'a str, src: &'a str,
decorations: Option<Decorations>,
} }
impl<'a> Classifier<'a> { impl<'a> Classifier<'a> {
/// Takes as argument the source code to HTML-ify, the rust edition to use and the source code /// Takes as argument the source code to HTML-ify, the rust edition to use and the source code
/// file span which will be used later on by the `span_correspondance_map`. /// file span which will be used later on by the `span_correspondance_map`.
fn new(src: &str, edition: Edition, file_span: Span) -> Classifier<'_> { fn new(
src: &str,
edition: Edition,
file_span: Span,
decoration_info: Option<DecorationInfo>,
) -> Classifier<'_> {
let tokens = PeekIter::new(TokenIter { src }); let tokens = PeekIter::new(TokenIter { src });
Classifier { Classifier {
tokens, tokens,
@ -275,6 +314,7 @@ impl<'a> Classifier<'a> {
byte_pos: 0, byte_pos: 0,
file_span, file_span,
src, src,
decorations,
} }
} }
@ -356,6 +396,19 @@ impl<'a> Classifier<'a> {
/// token is used. /// token is used.
fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) { fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) {
loop { loop {
if let Some(decs) = self.decorations.as_mut() {
let byte_pos = self.byte_pos;
let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count();
for (_, kind) in decs.starts.drain(0..n_starts) {
sink(Highlight::EnterSpan { class: Class::Decoration(kind) });
}
let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count();
for _ in decs.ends.drain(0..n_ends) {
sink(Highlight::ExitSpan);
}
}
if self if self
.tokens .tokens
.peek() .peek()

View file

@ -22,7 +22,7 @@ fn test_html_highlighting() {
let src = include_str!("fixtures/sample.rs"); let src = include_str!("fixtures/sample.rs");
let html = { let html = {
let mut out = Buffer::new(); let mut out = Buffer::new();
write_code(&mut out, src, Edition::Edition2018, None); write_code(&mut out, src, Edition::Edition2018, None, None);
format!("{}<pre><code>{}</code></pre>\n", STYLE, out.into_inner()) format!("{}<pre><code>{}</code></pre>\n", STYLE, out.into_inner())
}; };
expect_file!["fixtures/sample.html"].assert_eq(&html); expect_file!["fixtures/sample.html"].assert_eq(&html);
@ -36,7 +36,7 @@ fn test_dos_backline() {
println!(\"foo\");\r\n\ println!(\"foo\");\r\n\
}\r\n"; }\r\n";
let mut html = Buffer::new(); let mut html = Buffer::new();
write_code(&mut html, src, Edition::Edition2018, None); write_code(&mut html, src, Edition::Edition2018, None, None);
expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner()); expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner());
}); });
} }
@ -50,7 +50,7 @@ let x = super::b::foo;
let y = Self::whatever;"; let y = Self::whatever;";
let mut html = Buffer::new(); let mut html = Buffer::new();
write_code(&mut html, src, Edition::Edition2018, None); write_code(&mut html, src, Edition::Edition2018, None, None);
expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner()); expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner());
}); });
} }

View file

@ -360,6 +360,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
edition, edition,
None, None,
None, None,
None,
); );
Some(Event::Html(s.into_inner().into())) Some(Event::Html(s.into_inner().into()))
} }

View file

@ -46,7 +46,7 @@ use std::string::ToString;
use rustc_ast_pretty::pprust; use rustc_ast_pretty::pprust;
use rustc_attr::{ConstStability, Deprecation, StabilityLevel}; use rustc_attr::{ConstStability, Deprecation, StabilityLevel};
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::CtorKind; use rustc_hir::def::CtorKind;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
@ -2496,23 +2496,28 @@ fn render_call_locations(
// To reduce file sizes, we only want to embed the source code needed to understand the example, not // To reduce file sizes, we only want to embed the source code needed to understand the example, not
// the entire file. So we find the smallest byte range that covers all items enclosing examples. // the entire file. So we find the smallest byte range that covers all items enclosing examples.
assert!(call_data.locations.len() > 0);
let min_loc = let min_loc =
call_data.locations.iter().min_by_key(|loc| loc.enclosing_item_span.0).unwrap(); call_data.locations.iter().min_by_key(|loc| loc.enclosing_item.byte_span.0).unwrap();
let min_byte = min_loc.enclosing_item_span.0; let min_byte = min_loc.enclosing_item.byte_span.0;
let min_line = min_loc.enclosing_item_lines.0; let min_line = min_loc.enclosing_item.line_span.0;
let max_byte = let max_byte =
call_data.locations.iter().map(|loc| loc.enclosing_item_span.1).max().unwrap(); call_data.locations.iter().map(|loc| loc.enclosing_item.byte_span.1).max().unwrap();
// The output code is limited to that byte range. // The output code is limited to that byte range.
let contents_subset = &contents[min_byte..max_byte]; let contents_subset = &contents[(min_byte as usize)..(max_byte as usize)];
// The call locations need to be updated to reflect that the size of the program has changed. // The call locations need to be updated to reflect that the size of the program has changed.
// Specifically, the ranges are all subtracted by `min_byte` since that's the new zero point. // Specifically, the ranges are all subtracted by `min_byte` since that's the new zero point.
let locations = call_data let (byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data
.locations .locations
.iter() .iter()
.map(|loc| (loc.call_span.0 - min_byte, loc.call_span.1 - min_byte)) .map(|loc| {
.collect::<Vec<_>>(); let (byte_lo, byte_hi) = loc.call_expr.byte_span;
let (line_lo, line_hi) = loc.call_expr.line_span;
((byte_lo - min_byte, byte_hi - min_byte), (line_lo - min_line, line_hi - min_line))
})
.unzip();
let edition = cx.shared.edition(); let edition = cx.shared.edition();
write!( write!(
@ -2524,7 +2529,7 @@ fn render_call_locations(
// The code and locations are encoded as data attributes, so they can be read // The code and locations are encoded as data attributes, so they can be read
// later by the JS for interactions. // later by the JS for interactions.
code = contents_subset.replace("\"", "&quot;"), code = contents_subset.replace("\"", "&quot;"),
locations = serde_json::to_string(&locations).unwrap(), locations = serde_json::to_string(&line_ranges).unwrap(),
); );
write!(w, r#"<span class="prev">&pr;</span> <span class="next">&sc;</span>"#); write!(w, r#"<span class="prev">&pr;</span> <span class="next">&sc;</span>"#);
write!(w, r#"<span class="expand">&varr;</span>"#); write!(w, r#"<span class="expand">&varr;</span>"#);
@ -2532,7 +2537,18 @@ fn render_call_locations(
// FIXME(wcrichto): where should file_span and root_path come from? // FIXME(wcrichto): where should file_span and root_path come from?
let file_span = rustc_span::DUMMY_SP; let file_span = rustc_span::DUMMY_SP;
let root_path = "".to_string(); let root_path = "".to_string();
sources::print_src(w, contents_subset, edition, file_span, cx, &root_path, Some(min_line)); let mut decoration_info = FxHashMap::default();
decoration_info.insert("highlight", byte_ranges);
sources::print_src(
w,
contents_subset,
edition,
file_span,
cx,
&root_path,
Some(min_line),
Some(decoration_info),
);
write!(w, "</div></div>"); write!(w, "</div></div>");
}; };
@ -2542,7 +2558,8 @@ fn render_call_locations(
// understand at a glance. // understand at a glance.
let ordered_locations = { let ordered_locations = {
let sort_criterion = |(_, call_data): &(_, &CallData)| { let sort_criterion = |(_, call_data): &(_, &CallData)| {
let (lo, hi) = call_data.locations[0].enclosing_item_span; // 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 hi - lo
}; };

View file

@ -1117,6 +1117,7 @@ fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Mac
it.span(cx.tcx()).inner().edition(), it.span(cx.tcx()).inner().edition(),
None, None,
None, None,
None,
); );
}); });
document(w, cx, it, None, HeadingOffset::H2) document(w, cx, it, None, HeadingOffset::H2)

View file

@ -212,6 +212,7 @@ impl SourceCollector<'_, 'tcx> {
&self.cx, &self.cx,
&root_path, &root_path,
None, None,
None,
) )
}, },
&self.cx.shared.style_files, &self.cx.shared.style_files,
@ -259,6 +260,7 @@ crate fn print_src(
context: &Context<'_>, context: &Context<'_>,
root_path: &str, root_path: &str,
offset: Option<usize>, offset: Option<usize>,
decoration_info: Option<highlight::DecorationInfo>,
) { ) {
let lines = s.lines().count(); let lines = s.lines().count();
let mut line_numbers = Buffer::empty_from(buf); let mut line_numbers = Buffer::empty_from(buf);
@ -283,5 +285,6 @@ crate fn print_src(
edition, edition,
Some(line_numbers), Some(line_numbers),
Some(highlight::ContextInfo { context, file_span, root_path }), Some(highlight::ContextInfo { context, file_span, root_path }),
decoration_info,
); );
} }

View file

@ -137,7 +137,7 @@ h1.fqn {
margin-top: 0; margin-top: 0;
/* workaround to keep flex from breaking below 700 px width due to the float: right on the nav /* workaround to keep flex from breaking below 700 px width due to the float: right on the nav
above the h1 */ above the h1 */
padding-left: 1px; padding-left: 1px;
} }
h1.fqn > .in-band > a:hover { h1.fqn > .in-band > a:hover {
@ -974,7 +974,7 @@ body.blur > :not(#help) {
text-shadow: text-shadow:
1px 0 0 black, 1px 0 0 black,
-1px 0 0 black, -1px 0 0 black,
0 1px 0 black, 0 1px 0 black,
0 -1px 0 black; 0 -1px 0 black;
} }
@ -1214,8 +1214,8 @@ a.test-arrow:hover{
.notable-traits-tooltip::after { .notable-traits-tooltip::after {
/* The margin on the tooltip does not capture hover events, /* The margin on the tooltip does not capture hover events,
this extends the area of hover enough so that mouse hover is not this extends the area of hover enough so that mouse hover is not
lost when moving the mouse to the tooltip */ lost when moving the mouse to the tooltip */
content: "\00a0\00a0\00a0"; content: "\00a0\00a0\00a0";
} }
@ -1715,7 +1715,7 @@ details.undocumented[open] > summary::before {
} }
/* We do NOT hide this element so that alternative device readers still have this information /* We do NOT hide this element so that alternative device readers still have this information
available. */ available. */
.sidebar-elems { .sidebar-elems {
position: fixed; position: fixed;
z-index: 1; z-index: 1;
@ -1971,7 +1971,8 @@ details.undocumented[open] > summary::before {
} }
} }
/* This part is for the new "examples" components */
/* Begin: styles for --scrape-examples feature */
.scraped-example-title { .scraped-example-title {
font-family: 'Fira Sans'; font-family: 'Fira Sans';
@ -2063,16 +2064,17 @@ details.undocumented[open] > summary::before {
overflow-y: hidden; overflow-y: hidden;
} }
.scraped-example .line-numbers span.highlight { .scraped-example .example-wrap .rust span.highlight {
background: #f6fdb0; background: #fcffd6;
} }
.scraped-example .example-wrap .rust span.highlight { .scraped-example .example-wrap .rust span.highlight.focus {
background: #f6fdb0; background: #f6fdb0;
} }
.more-examples-toggle summary { .more-examples-toggle summary {
color: #999; color: #999;
font-family: 'Fira Sans';
} }
.more-scraped-examples { .more-scraped-examples {
@ -2115,3 +2117,5 @@ h1 + .scraped-example {
.example-links ul { .example-links ul {
margin-bottom: 0; margin-bottom: 0;
} }
/* End: styles for --scrape-examples feature */

View file

@ -980,154 +980,55 @@ function hideThemeButtonState() {
window.addEventListener("hashchange", onHashChange); window.addEventListener("hashchange", onHashChange);
searchState.setup(); searchState.setup();
/////// EXAMPLE ANALYZER /////// --scrape-examples interactions
// Merge the full set of [from, to] offsets into a minimal set of non-overlapping
// [from, to] offsets.
// NB: This is such a archetypal software engineering interview question that
// I can't believe I actually had to write it. Yes, it's O(N) in the input length --
// but it does assume a sorted input!
function distinctRegions(locs) {
var start = -1;
var end = -1;
var output = [];
for (var i = 0; i < locs.length; i++) {
var loc = locs[i];
if (loc[0] > end) {
if (end > 0) {
output.push([start, end]);
}
start = loc[0];
end = loc[1];
} else {
end = Math.max(end, loc[1]);
}
}
if (end > 0) {
output.push([start, end]);
}
return output;
}
function convertLocsStartsToLineOffsets(code, locs) {
locs = distinctRegions(locs.slice(0).sort(function (a, b) {
return a[0] === b[0] ? a[1] - b[1] : a[0] - b[0];
})); // sort by start; use end if start is equal.
var codeLines = code.split("\n");
var lineIndex = 0;
var totalOffset = 0;
var output = [];
while (locs.length > 0 && lineIndex < codeLines.length) {
// +1 here and later is due to omitted \n
var lineLength = codeLines[lineIndex].length + 1;
while (locs.length > 0 && totalOffset + lineLength > locs[0][0]) {
var endIndex = lineIndex;
var charsRemaining = locs[0][1] - totalOffset;
while (endIndex < codeLines.length &&
charsRemaining > codeLines[endIndex].length + 1)
{
charsRemaining -= codeLines[endIndex].length + 1;
endIndex += 1;
}
output.push({
from: [lineIndex, locs[0][0] - totalOffset],
to: [endIndex, charsRemaining]
});
locs.shift();
}
lineIndex++;
totalOffset += lineLength;
}
return output;
}
// inserts str into html, *but* calculates idx by eliding anything in html that's not in raw.
// ideally this would work by walking the element tree...but this is good enough for now.
function insertStrAtRawIndex(raw, html, idx, str) {
if (idx > raw.length) {
return html;
}
if (idx == raw.length) {
return html + str;
}
var rawIdx = 0;
var htmlIdx = 0;
while (rawIdx < idx && rawIdx < raw.length) {
while (raw[rawIdx] !== html[htmlIdx] && htmlIdx < html.length) {
htmlIdx++;
}
rawIdx++;
htmlIdx++;
}
return html.substring(0, htmlIdx) + str + html.substr(htmlIdx);
}
// Scroll code block to put the given code location in the middle of the viewer // Scroll code block to put the given code location in the middle of the viewer
function scrollToLoc(elt, loc) { function scrollToLoc(elt, loc) {
var wrapper = elt.querySelector(".code-wrapper"); var wrapper = elt.querySelector(".code-wrapper");
var halfHeight = wrapper.offsetHeight / 2; var halfHeight = wrapper.offsetHeight / 2;
var lines = elt.querySelector('.line-numbers'); var lines = elt.querySelector('.line-numbers');
var offsetMid = (lines.children[loc.from[0]].offsetTop var offsetMid = (lines.children[loc[0]].offsetTop
+ lines.children[loc.to[0]].offsetTop) / 2; + lines.children[loc[1]].offsetTop) / 2;
var scrollOffset = offsetMid - halfHeight; var scrollOffset = offsetMid - halfHeight;
lines.scrollTo(0, scrollOffset); lines.scrollTo(0, scrollOffset);
elt.querySelector(".rust").scrollTo(0, scrollOffset); elt.querySelector(".rust").scrollTo(0, scrollOffset);
} }
function updateScrapedExample(example) { function updateScrapedExample(example) {
var code = example.attributes.getNamedItem("data-code").textContent;
var codeLines = code.split("\n");
var locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent); var locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent);
locs = convertLocsStartsToLineOffsets(code, locs);
// Add call-site highlights to code listings
var litParent = example.querySelector('.example-wrap pre.rust');
var litHtml = litParent.innerHTML.split("\n");
onEach(locs, function (loc) {
for (var i = loc.from[0]; i < loc.to[0] + 1; i++) {
addClass(example.querySelector('.line-numbers').children[i], "highlight");
}
litHtml[loc.to[0]] = insertStrAtRawIndex(
codeLines[loc.to[0]],
litHtml[loc.to[0]],
loc.to[1],
"</span>");
litHtml[loc.from[0]] = insertStrAtRawIndex(
codeLines[loc.from[0]],
litHtml[loc.from[0]],
loc.from[1],
'<span class="highlight" data-loc="' +
JSON.stringify(loc).replace(/"/g, "&quot;") +
'">');
}, true); // do this backwards to avoid shifting later offsets
litParent.innerHTML = litHtml.join('\n');
// Toggle through list of examples in a given file
var locIndex = 0; var locIndex = 0;
var highlights = example.querySelectorAll('.highlight');
addClass(highlights[0], 'focus');
if (locs.length > 1) { if (locs.length > 1) {
// Toggle through list of examples in a given file
var onChangeLoc = function(f) {
removeClass(highlights[locIndex], 'focus');
f();
scrollToLoc(example, locs[locIndex]);
addClass(highlights[locIndex], 'focus');
};
example.querySelector('.prev') example.querySelector('.prev')
.addEventListener('click', function () { .addEventListener('click', function() {
locIndex = (locIndex - 1 + locs.length) % locs.length; onChangeLoc(function() {
scrollToLoc(example, locs[locIndex]); locIndex = (locIndex - 1 + locs.length) % locs.length;
});
}); });
example.querySelector('.next') example.querySelector('.next')
.addEventListener('click', function () { .addEventListener('click', function() {
locIndex = (locIndex + 1) % locs.length; onChangeLoc(function() { locIndex = (locIndex + 1) % locs.length; });
scrollToLoc(example, locs[locIndex]);
}); });
} else { } else {
// Remove buttons if there's only one example in the file
example.querySelector('.prev').remove(); example.querySelector('.prev').remove();
example.querySelector('.next').remove(); example.querySelector('.next').remove();
} }
let codeEl = example.querySelector('.rust'); var codeEl = example.querySelector('.rust');
let expandButton = example.querySelector('.expand'); var codeOverflows = codeEl.scrollHeight > codeEl.clientHeight;
if (codeEl.scrollHeight == codeEl.clientHeight) { var expandButton = example.querySelector('.expand');
addClass(example, 'expanded'); if (codeOverflows) {
expandButton.remove(); // If file is larger than default height, give option to expand the viewer
} else {
// Show full code on expansion
expandButton.addEventListener('click', function () { expandButton.addEventListener('click', function () {
if (hasClass(example, "expanded")) { if (hasClass(example, "expanded")) {
removeClass(example, "expanded"); removeClass(example, "expanded");
@ -1136,6 +1037,10 @@ function hideThemeButtonState() {
addClass(example, "expanded"); addClass(example, "expanded");
} }
}); });
} else {
// Otherwise remove expansion buttons
addClass(example, 'expanded');
expandButton.remove();
} }
// Start with the first example in view // Start with the first example in view
@ -1146,6 +1051,8 @@ function hideThemeButtonState() {
var firstExamples = document.querySelectorAll('.scraped-example-list > .scraped-example'); var firstExamples = document.querySelectorAll('.scraped-example-list > .scraped-example');
onEach(firstExamples, updateScrapedExample); onEach(firstExamples, updateScrapedExample);
onEach(document.querySelectorAll('.more-examples-toggle'), function(toggle) { 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.querySelector('.toggle-line').addEventListener('click', function() {
toggle.open = false; toggle.open = false;
}); });

View file

@ -1,5 +1,4 @@
//! This module analyzes crates to find examples of uses for items in the //! This module analyzes crates to find call sites that can serve as examples in the documentation.
//! current crate being documented.
use crate::clean; use crate::clean;
use crate::config; use crate::config;
@ -11,20 +10,55 @@ use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{ use rustc_hir::{
self as hir, self as hir,
intravisit::{self, Visitor}, intravisit::{self, Visitor},
HirId,
}; };
use rustc_interface::interface; use rustc_interface::interface;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_middle::ty::{self, TyCtxt}; use rustc_middle::ty::{self, TyCtxt};
use rustc_span::{def_id::DefId, FileName}; use rustc_span::{def_id::DefId, BytePos, FileName, SourceFile};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Serialize, Deserialize, Debug, Clone)]
crate struct SyntaxRange {
crate byte_span: (u32, u32),
crate line_span: (usize, usize),
}
impl SyntaxRange {
fn new(span: rustc_span::Span, file: &SourceFile) -> Self {
let get_pos = |bytepos: BytePos| file.original_relative_byte_pos(bytepos).0;
let get_line = |bytepos: BytePos| file.lookup_line(bytepos).unwrap();
SyntaxRange {
byte_span: (get_pos(span.lo()), get_pos(span.hi())),
line_span: (get_line(span.lo()), get_line(span.hi())),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
crate struct CallLocation { crate struct CallLocation {
crate call_span: (usize, usize), crate call_expr: SyntaxRange,
crate enclosing_item_span: (usize, usize), crate enclosing_item: SyntaxRange,
crate enclosing_item_lines: (usize, usize), }
impl CallLocation {
fn new(
tcx: TyCtxt<'_>,
expr_span: rustc_span::Span,
expr_id: HirId,
source_file: &rustc_span::SourceFile,
) -> Self {
let enclosing_item_span = tcx.hir().span_with_body(tcx.hir().get_parent_item(expr_id));
assert!(enclosing_item_span.contains(expr_span));
CallLocation {
call_expr: SyntaxRange::new(expr_span, source_file),
enclosing_item: SyntaxRange::new(enclosing_item_span, source_file),
}
}
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@ -96,24 +130,10 @@ where
_ => None, _ => None,
}; };
let get_pos =
|bytepos: rustc_span::BytePos| file.original_relative_byte_pos(bytepos).0 as usize;
let get_range = |span: rustc_span::Span| (get_pos(span.lo()), get_pos(span.hi()));
let get_line = |bytepos: rustc_span::BytePos| file.lookup_line(bytepos).unwrap();
let get_lines = |span: rustc_span::Span| (get_line(span.lo()), get_line(span.hi()));
if let Some(file_path) = file_path { if let Some(file_path) = file_path {
let abs_path = fs::canonicalize(file_path.clone()).unwrap(); let abs_path = fs::canonicalize(file_path.clone()).unwrap();
let cx = &self.cx; let cx = &self.cx;
let enclosing_item_span = let location = CallLocation::new(self.tcx, span, ex.hir_id, &file);
self.tcx.hir().span_with_body(self.tcx.hir().get_parent_item(ex.hir_id));
assert!(enclosing_item_span.contains(span));
let location = CallLocation {
call_span: get_range(span),
enclosing_item_span: get_range(enclosing_item_span),
enclosing_item_lines: get_lines(enclosing_item_span),
};
entries entries
.entry(abs_path) .entry(abs_path)