1
Fork 0

Add links on source types to go to definition

This commit is contained in:
Guillaume Gomez 2021-04-06 00:07:46 +02:00 committed by Guillaume Gomez
parent 2f07ae408f
commit 023231a709
10 changed files with 454 additions and 106 deletions

View file

@ -1950,6 +1950,11 @@ impl Span {
Self(sp.source_callsite()) Self(sp.source_callsite())
} }
/// Unless you know what you're doing, use [`Self::from_rustc_span`] instead!
crate fn wrap(sp: rustc_span::Span) -> Span {
Self(sp)
}
crate fn inner(&self) -> rustc_span::Span { crate fn inner(&self) -> rustc_span::Span {
self.0 self.0
} }

View file

@ -494,7 +494,10 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<Str
if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private { if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private {
return Err(HrefError::Private); return Err(HrefError::Private);
} }
// href_with_depth_inner(did, cache, || {
// let depth = CURRENT_DEPTH.with(|l| l.get());
// "../".repeat(depth)
// })
let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) { let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
Some(&(ref fqp, shortty)) => (fqp, shortty, { Some(&(ref fqp, shortty)) => (fqp, shortty, {
let module_fqp = to_module_fqp(shortty, fqp); let module_fqp = to_module_fqp(shortty, fqp);

View file

@ -5,16 +5,19 @@
//! //!
//! Use the `render_with_highlighting` to highlight some rust code. //! Use the `render_with_highlighting` to highlight some rust code.
use crate::clean;
use crate::html::escape::Escape; use crate::html::escape::Escape;
use crate::html::render::Context;
use std::fmt::Display; use std::fmt::{Display, Write};
use std::iter::Peekable; use std::iter::Peekable;
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;
use super::format::Buffer; use super::format::{self, Buffer};
use super::render::LinkFromSrc;
/// Highlights `src`, returning the HTML output. /// Highlights `src`, returning the HTML output.
crate fn render_with_highlighting( crate fn render_with_highlighting(
@ -25,6 +28,9 @@ crate fn render_with_highlighting(
tooltip: Option<(Option<Edition>, &str)>, tooltip: Option<(Option<Edition>, &str)>,
edition: Edition, edition: Edition,
extra_content: Option<Buffer>, extra_content: Option<Buffer>,
file_span_lo: u32,
context: Option<&Context<'_>>,
root_path: &str,
) { ) {
debug!("highlighting: ================\n{}\n==============", src); debug!("highlighting: ================\n{}\n==============", src);
if let Some((edition_info, class)) = tooltip { if let Some((edition_info, class)) = tooltip {
@ -41,7 +47,7 @@ crate fn render_with_highlighting(
} }
write_header(out, class, extra_content); write_header(out, class, extra_content);
write_code(out, &src, edition); write_code(out, &src, edition, file_span_lo, context, root_path);
write_footer(out, playground_button); write_footer(out, playground_button);
} }
@ -57,12 +63,21 @@ fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option<Buf
} }
} }
fn write_code(out: &mut Buffer, src: &str, edition: Edition) { fn write_code(
out: &mut Buffer,
src: &str,
edition: Edition,
file_span_lo: u32,
context: Option<&Context<'_>>,
root_path: &str,
) {
// 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).highlight(&mut |highlight| { Classifier::new(&src, edition, file_span_lo).highlight(&mut |highlight| {
match highlight { match highlight {
Highlight::Token { text, class } => string(out, Escape(text), class), Highlight::Token { text, class } => {
string(out, Escape(text), class, context, root_path)
}
Highlight::EnterSpan { class } => enter_span(out, class), Highlight::EnterSpan { class } => enter_span(out, class),
Highlight::ExitSpan => exit_span(out), Highlight::ExitSpan => exit_span(out),
}; };
@ -82,14 +97,14 @@ enum Class {
KeyWord, KeyWord,
// Keywords that do pointer/reference stuff. // Keywords that do pointer/reference stuff.
RefKeyWord, RefKeyWord,
Self_, Self_((u32, u32)),
Op, Op,
Macro, Macro,
MacroNonTerminal, MacroNonTerminal,
String, String,
Number, Number,
Bool, Bool,
Ident, Ident((u32, u32)),
Lifetime, Lifetime,
PreludeTy, PreludeTy,
PreludeVal, PreludeVal,
@ -105,20 +120,27 @@ impl Class {
Class::Attribute => "attribute", Class::Attribute => "attribute",
Class::KeyWord => "kw", Class::KeyWord => "kw",
Class::RefKeyWord => "kw-2", Class::RefKeyWord => "kw-2",
Class::Self_ => "self", Class::Self_(_) => "self",
Class::Op => "op", Class::Op => "op",
Class::Macro => "macro", Class::Macro => "macro",
Class::MacroNonTerminal => "macro-nonterminal", Class::MacroNonTerminal => "macro-nonterminal",
Class::String => "string", Class::String => "string",
Class::Number => "number", Class::Number => "number",
Class::Bool => "bool-val", Class::Bool => "bool-val",
Class::Ident => "ident", Class::Ident(_) => "ident",
Class::Lifetime => "lifetime", Class::Lifetime => "lifetime",
Class::PreludeTy => "prelude-ty", Class::PreludeTy => "prelude-ty",
Class::PreludeVal => "prelude-val", Class::PreludeVal => "prelude-val",
Class::QuestionMark => "question-mark", Class::QuestionMark => "question-mark",
} }
} }
fn get_span(self) -> Option<(u32, u32)> {
match self {
Self::Ident(sp) | Self::Self_(sp) => Some(sp),
_ => None,
}
}
} }
enum Highlight<'a> { enum Highlight<'a> {
@ -144,14 +166,23 @@ impl Iterator for TokenIter<'a> {
} }
} }
fn get_real_ident_class(text: &str, edition: Edition) -> Class { /// Returns `None` if this is a `Class::Ident`.
match text { fn get_real_ident_class(text: &str, edition: Edition, allow_path_keywords: bool) -> Option<Class> {
let ignore: &[&str] =
if allow_path_keywords { &["self", "Self", "super", "crate"] } else { &["self", "Self"] };
if ignore.iter().any(|k| *k == text) {
return None;
}
Some(match text {
"ref" | "mut" => Class::RefKeyWord, "ref" | "mut" => Class::RefKeyWord,
"self" | "Self" => Class::Self_,
"false" | "true" => Class::Bool, "false" | "true" => Class::Bool,
_ if Symbol::intern(text).is_reserved(|| edition) => Class::KeyWord, _ if Symbol::intern(text).is_reserved(|| edition) => Class::KeyWord,
_ => Class::Ident, _ => return None,
} })
}
fn move_span(file_span_lo: u32, start: u32, end: u32) -> (u32, u32) {
(start + file_span_lo, end + file_span_lo)
} }
/// Processes program tokens, classifying strings of text by highlighting /// Processes program tokens, classifying strings of text by highlighting
@ -163,11 +194,12 @@ struct Classifier<'a> {
in_macro_nonterminal: bool, in_macro_nonterminal: bool,
edition: Edition, edition: Edition,
byte_pos: u32, byte_pos: u32,
file_span_lo: u32,
src: &'a str, src: &'a str,
} }
impl<'a> Classifier<'a> { impl<'a> Classifier<'a> {
fn new(src: &str, edition: Edition) -> Classifier<'_> { fn new(src: &str, edition: Edition, file_span_lo: u32) -> Classifier<'_> {
let tokens = TokenIter { src }.peekable(); let tokens = TokenIter { src }.peekable();
Classifier { Classifier {
tokens, tokens,
@ -176,6 +208,7 @@ impl<'a> Classifier<'a> {
in_macro_nonterminal: false, in_macro_nonterminal: false,
edition, edition,
byte_pos: 0, byte_pos: 0,
file_span_lo,
src, src,
} }
} }
@ -201,17 +234,17 @@ impl<'a> Classifier<'a> {
if has_ident { if has_ident {
return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)]; return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
} else { } else {
return vec![(TokenKind::Colon, pos, pos + nb)]; return vec![(TokenKind::Colon, start, pos + nb)];
} }
} }
if let Some((Class::Ident, text)) = self.tokens.peek().map(|(token, text)| { if let Some((None, text)) = self.tokens.peek().map(|(token, text)| {
if *token == TokenKind::Ident { if *token == TokenKind::Ident {
let class = get_real_ident_class(text, edition); let class = get_real_ident_class(text, edition, true);
(class, text) (class, text)
} else { } else {
// Doesn't matter which Class we put in here... // Doesn't matter which Class we put in here...
(Class::Comment, text) (Some(Class::Comment), text)
} }
}) { }) {
// We only "add" the colon if there is an ident behind. // We only "add" the colon if there is an ident behind.
@ -221,7 +254,7 @@ impl<'a> Classifier<'a> {
} else if nb > 0 && has_ident { } else if nb > 0 && has_ident {
return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)]; return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
} else if nb > 0 { } else if nb > 0 {
return vec![(TokenKind::Colon, pos, pos + nb)]; return vec![(TokenKind::Colon, start, start + nb)];
} else if has_ident { } else if has_ident {
return vec![(TokenKind::Ident, start, pos)]; return vec![(TokenKind::Ident, start, pos)];
} else { } else {
@ -231,10 +264,11 @@ impl<'a> Classifier<'a> {
} }
/// Wraps the tokens iteration to ensure that the byte_pos is always correct. /// Wraps the tokens iteration to ensure that the byte_pos is always correct.
fn next(&mut self) -> Option<(TokenKind, &'a str)> { fn next(&mut self) -> Option<(TokenKind, &'a str, u32)> {
if let Some((kind, text)) = self.tokens.next() { if let Some((kind, text)) = self.tokens.next() {
let before = self.byte_pos;
self.byte_pos += text.len() as u32; self.byte_pos += text.len() as u32;
Some((kind, text)) Some((kind, text, before))
} else { } else {
None None
} }
@ -254,14 +288,18 @@ impl<'a> Classifier<'a> {
.unwrap_or(false) .unwrap_or(false)
{ {
let tokens = self.get_full_ident_path(); let tokens = self.get_full_ident_path();
let skip = !tokens.is_empty();
for (token, start, end) in tokens { for (token, start, end) in tokens {
let text = &self.src[start..end]; let text = &self.src[start..end];
self.advance(token, text, sink); self.advance(token, text, sink, start as u32);
self.byte_pos += text.len() as u32; self.byte_pos += text.len() as u32;
} }
if skip {
continue;
}
} }
if let Some((token, text)) = self.next() { if let Some((token, text, before)) = self.next() {
self.advance(token, text, sink); self.advance(token, text, sink, before);
} else { } else {
break; break;
} }
@ -270,7 +308,13 @@ impl<'a> Classifier<'a> {
/// Single step of highlighting. This will classify `token`, but maybe also /// Single step of highlighting. This will classify `token`, but maybe also
/// a couple of following ones as well. /// a couple of following ones as well.
fn advance(&mut self, token: TokenKind, text: &'a str, sink: &mut dyn FnMut(Highlight<'a>)) { fn advance(
&mut self,
token: TokenKind,
text: &'a str,
sink: &mut dyn FnMut(Highlight<'a>),
before: u32,
) {
let lookahead = self.peek(); let lookahead = self.peek();
let no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None }); let no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None });
let class = match token { let class = match token {
@ -401,19 +445,30 @@ impl<'a> Classifier<'a> {
sink(Highlight::Token { text, class: None }); sink(Highlight::Token { text, class: None });
return; return;
} }
TokenKind::Ident => match get_real_ident_class(text, self.edition) { TokenKind::Ident => match get_real_ident_class(text, self.edition, false) {
Class::Ident => match text { None => match text {
"Option" | "Result" => Class::PreludeTy, "Option" | "Result" => Class::PreludeTy,
"Some" | "None" | "Ok" | "Err" => Class::PreludeVal, "Some" | "None" | "Ok" | "Err" => Class::PreludeVal,
_ if self.in_macro_nonterminal => { _ if self.in_macro_nonterminal => {
self.in_macro_nonterminal = false; self.in_macro_nonterminal = false;
Class::MacroNonTerminal Class::MacroNonTerminal
} }
_ => Class::Ident, "self" | "Self" => Class::Self_(move_span(
self.file_span_lo,
before,
before + text.len() as u32,
)),
_ => Class::Ident(move_span(
self.file_span_lo,
before,
before + text.len() as u32,
)),
}, },
c => c, Some(c) => c,
}, },
TokenKind::RawIdent | TokenKind::UnknownPrefix => Class::Ident, TokenKind::RawIdent | TokenKind::UnknownPrefix => {
Class::Ident(move_span(self.file_span_lo, before, before + text.len() as u32))
}
TokenKind::Lifetime { .. } => Class::Lifetime, TokenKind::Lifetime { .. } => Class::Lifetime,
}; };
// Anything that didn't return above is the simple case where we the // Anything that didn't return above is the simple case where we the
@ -448,11 +503,74 @@ fn exit_span(out: &mut Buffer) {
/// ``` /// ```
/// The latter can be thought of as a shorthand for the former, which is more /// The latter can be thought of as a shorthand for the former, which is more
/// flexible. /// flexible.
fn string<T: Display>(out: &mut Buffer, text: T, klass: Option<Class>) { fn string<T: Display>(
out: &mut Buffer,
text: T,
klass: Option<Class>,
context: Option<&Context<'_>>,
root_path: &str,
) {
match klass { match klass {
None => write!(out, "{}", text), None => write!(out, "{}", text),
Some(klass) => write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text), Some(klass) => {
if let Some(def_span) = klass.get_span() {
let mut text = text.to_string();
if text.contains("::") {
text =
text.split("::").enumerate().fold(String::new(), |mut path, (pos, t)| {
let pre = if pos != 0 { "::" } else { "" };
match t {
"self" | "Self" => write!(
&mut path,
"{}<span class=\"{}\">{}</span>",
pre,
Class::Self_((0, 0)).as_html(),
t
),
"crate" | "super" => write!(
&mut path,
"{}<span class=\"{}\">{}</span>",
pre,
Class::KeyWord.as_html(),
t
),
t => write!(&mut path, "{}{}", pre, t),
}
.expect("Failed to build source HTML path");
path
});
}
if let Some(context) = context {
if let Some(href) =
context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
match href {
LinkFromSrc::Local(span) => {
eprintln!("==> {:?}:{:?}", span.lo(), span.hi());
context
.href_from_span(clean::Span::wrap(*span))
.map(|s| format!("{}{}", root_path, s))
},
LinkFromSrc::External(def_id) => {
format::href(*def_id, context).map(|(url, _, _)| url)
}
}
})
{
write!(
out,
"<a class=\"{}\" href=\"{}\">{}</a>",
klass.as_html(),
href,
text
);
return;
}
}
}
write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text);
}
} }
write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text);
} }
#[cfg(test)] #[cfg(test)]

View file

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

View file

@ -17,7 +17,10 @@ use rustc_span::symbol::sym;
use super::cache::{build_index, ExternalLocation}; use super::cache::{build_index, ExternalLocation};
use super::print_item::{full_path, item_path, print_item}; use super::print_item::{full_path, item_path, print_item};
use super::write_shared::write_shared; use super::write_shared::write_shared;
use super::{print_sidebar, settings, AllTypes, NameDoc, StylePath, BASIC_KEYWORDS}; use super::{
collect_spans_and_sources, print_sidebar, settings, AllTypes, LinkFromSrc, NameDoc, StylePath,
BASIC_KEYWORDS,
};
use crate::clean; use crate::clean;
use crate::clean::ExternalCrate; use crate::clean::ExternalCrate;
@ -46,7 +49,7 @@ crate struct Context<'tcx> {
pub(crate) current: Vec<String>, pub(crate) current: Vec<String>,
/// The current destination folder of where HTML artifacts should be placed. /// The current destination folder of where HTML artifacts should be placed.
/// This changes as the context descends into the module hierarchy. /// This changes as the context descends into the module hierarchy.
pub(super) dst: PathBuf, crate dst: PathBuf,
/// A flag, which when `true`, will render pages which redirect to the /// A flag, which when `true`, will render pages which redirect to the
/// real location of an item. This is used to allow external links to /// real location of an item. This is used to allow external links to
/// publicly reused items to redirect to the right location. /// publicly reused items to redirect to the right location.
@ -58,7 +61,7 @@ crate struct Context<'tcx> {
/// Issue for improving the situation: [#82381][] /// Issue for improving the situation: [#82381][]
/// ///
/// [#82381]: https://github.com/rust-lang/rust/issues/82381 /// [#82381]: https://github.com/rust-lang/rust/issues/82381
pub(super) shared: Rc<SharedContext<'tcx>>, crate shared: Rc<SharedContext<'tcx>>,
/// The [`Cache`] used during rendering. /// The [`Cache`] used during rendering.
/// ///
/// Ideally the cache would be in [`SharedContext`], but it's mutated /// Ideally the cache would be in [`SharedContext`], but it's mutated
@ -68,7 +71,11 @@ crate struct Context<'tcx> {
/// It's immutable once in `Context`, so it's not as bad that it's not in /// It's immutable once in `Context`, so it's not as bad that it's not in
/// `SharedContext`. /// `SharedContext`.
// FIXME: move `cache` to `SharedContext` // FIXME: move `cache` to `SharedContext`
pub(super) cache: Rc<Cache>, crate cache: Rc<Cache>,
/// This flag indicates whether `[src]` links should be generated or not. If
/// the source files are present in the html rendering, then this will be
/// `true`.
crate include_sources: bool,
} }
// `Context` is cloned a lot, so we don't want the size to grow unexpectedly. // `Context` is cloned a lot, so we don't want the size to grow unexpectedly.
@ -84,10 +91,6 @@ crate struct SharedContext<'tcx> {
/// This describes the layout of each page, and is not modified after /// This describes the layout of each page, and is not modified after
/// creation of the context (contains info like the favicon and added html). /// creation of the context (contains info like the favicon and added html).
crate layout: layout::Layout, crate layout: layout::Layout,
/// This flag indicates whether `[src]` links should be generated or not. If
/// the source files are present in the html rendering, then this will be
/// `true`.
crate include_sources: bool,
/// The local file sources we've emitted and their respective url-paths. /// The local file sources we've emitted and their respective url-paths.
crate local_sources: FxHashMap<PathBuf, String>, crate local_sources: FxHashMap<PathBuf, String>,
/// Show the memory layout of types in the docs. /// Show the memory layout of types in the docs.
@ -125,6 +128,10 @@ crate struct SharedContext<'tcx> {
redirections: Option<RefCell<FxHashMap<String, String>>>, redirections: Option<RefCell<FxHashMap<String, String>>>,
pub(crate) templates: tera::Tera, pub(crate) templates: tera::Tera,
/// Correspondance map used to link types used in the source code pages to allow to click on
/// links to jump to the type's definition.
crate span_correspondance_map: FxHashMap<(u32, u32), LinkFromSrc>,
} }
impl SharedContext<'_> { impl SharedContext<'_> {
@ -293,15 +300,19 @@ impl<'tcx> Context<'tcx> {
/// may happen, for example, with externally inlined items where the source /// may happen, for example, with externally inlined items where the source
/// of their crate documentation isn't known. /// of their crate documentation isn't known.
pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> { pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> {
if item.span(self.tcx()).is_dummy() { self.href_from_span(item.span(self.tcx()))
}
crate fn href_from_span(&self, span: clean::Span) -> Option<String> {
if span.is_dummy() {
return None; return None;
} }
let mut root = self.root_path(); let mut root = self.root_path();
let mut path = String::new(); let mut path = String::new();
let cnum = item.span(self.tcx()).cnum(self.sess()); let cnum = span.cnum(self.sess());
// We can safely ignore synthetic `SourceFile`s. // We can safely ignore synthetic `SourceFile`s.
let file = match item.span(self.tcx()).filename(self.sess()) { let file = match span.filename(self.sess()) {
FileName::Real(ref path) => path.local_path_if_available().to_path_buf(), FileName::Real(ref path) => path.local_path_if_available().to_path_buf(),
_ => return None, _ => return None,
}; };
@ -339,8 +350,8 @@ impl<'tcx> Context<'tcx> {
(&*symbol, &path) (&*symbol, &path)
}; };
let loline = item.span(self.tcx()).lo(self.sess()).line; let loline = span.lo(self.sess()).line;
let hiline = item.span(self.tcx()).hi(self.sess()).line; let hiline = span.hi(self.sess()).line;
let lines = let lines =
if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) }; if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) };
Some(format!( Some(format!(
@ -362,9 +373,9 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
const RUN_ON_MODULE: bool = true; const RUN_ON_MODULE: bool = true;
fn init( fn init(
mut krate: clean::Crate, krate: clean::Crate,
options: RenderOptions, options: RenderOptions,
mut cache: Cache, cache: Cache,
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
) -> Result<(Self, clean::Crate), Error> { ) -> Result<(Self, clean::Crate), Error> {
// need to save a copy of the options for rendering the index page // need to save a copy of the options for rendering the index page
@ -444,13 +455,16 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
_ => {} _ => {}
} }
} }
let (mut krate, local_sources, matches) =
collect_spans_and_sources(tcx, krate, &src_root, include_sources);
let (sender, receiver) = channel(); let (sender, receiver) = channel();
let mut scx = SharedContext { let mut scx = SharedContext {
tcx, tcx,
collapsed: krate.collapsed, collapsed: krate.collapsed,
src_root, src_root,
include_sources, local_sources,
local_sources: Default::default(),
issue_tracker_base_url, issue_tracker_base_url,
layout, layout,
created_dirs: Default::default(), created_dirs: Default::default(),
@ -466,6 +480,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
redirections: if generate_redirect_map { Some(Default::default()) } else { None }, redirections: if generate_redirect_map { Some(Default::default()) } else { None },
show_type_layout, show_type_layout,
templates, templates,
span_correspondance_map: matches,
}; };
// Add the default themes to the `Vec` of stylepaths // Add the default themes to the `Vec` of stylepaths
@ -483,12 +498,6 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
let dst = output; let dst = output;
scx.ensure_dir(&dst)?; scx.ensure_dir(&dst)?;
if emit_crate {
krate = sources::render(&dst, &mut scx, krate)?;
}
// Build our search index
let index = build_index(&krate, &mut cache, tcx);
let mut cx = Context { let mut cx = Context {
current: Vec::new(), current: Vec::new(),
@ -497,8 +506,16 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
id_map: RefCell::new(id_map), id_map: RefCell::new(id_map),
shared: Rc::new(scx), shared: Rc::new(scx),
cache: Rc::new(cache), cache: Rc::new(cache),
include_sources,
}; };
if emit_crate {
krate = sources::render(&mut cx, krate)?;
}
// Build our search index
let index = build_index(&krate, Rc::get_mut(&mut cx.cache).unwrap(), tcx);
// Write shared runs within a flock; disable thread dispatching of IO temporarily. // Write shared runs within a flock; disable thread dispatching of IO temporarily.
Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true); Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
write_shared(&cx, &krate, index, &md_opts)?; write_shared(&cx, &krate, index, &md_opts)?;
@ -514,6 +531,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
id_map: RefCell::new(IdMap::new()), id_map: RefCell::new(IdMap::new()),
shared: Rc::clone(&self.shared), shared: Rc::clone(&self.shared),
cache: Rc::clone(&self.cache), cache: Rc::clone(&self.cache),
include_sources: self.include_sources,
} }
} }

View file

@ -30,9 +30,11 @@ mod tests;
mod context; mod context;
mod print_item; mod print_item;
mod span_map;
mod write_shared; mod write_shared;
crate use context::*; crate use context::*;
crate use span_map::{collect_spans_and_sources, LinkFromSrc};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::default::Default; use std::default::Default;

View file

@ -119,7 +119,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer,
// [src] link in the downstream documentation will actually come back to // [src] link in the downstream documentation will actually come back to
// this page, and this link will be auto-clicked. The `id` attribute is // this page, and this link will be auto-clicked. The `id` attribute is
// used to find the link to auto-click. // used to find the link to auto-click.
if cx.shared.include_sources && !item.is_primitive() { if cx.include_sources && !item.is_primitive() {
write_srclink(cx, item, buf); write_srclink(cx, item, buf);
} }
@ -1081,6 +1081,9 @@ fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Mac
None, None,
it.span(cx.tcx()).inner().edition(), it.span(cx.tcx()).inner().edition(),
None, None,
0,
None,
"",
); );
}); });
document(w, cx, it, None) document(w, cx, it, None)

View file

@ -0,0 +1,110 @@
use crate::clean;
use crate::html::sources;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::{/*ExprKind, */ GenericParam, GenericParamKind, HirId};
use rustc_middle::ty::TyCtxt;
use rustc_span::Span;
#[derive(Debug)]
crate enum LinkFromSrc {
Local(Span),
External(DefId),
}
crate fn collect_spans_and_sources(
tcx: TyCtxt<'_>,
krate: clean::Crate,
src_root: &std::path::Path,
include_sources: bool,
) -> (clean::Crate, FxHashMap<std::path::PathBuf, String>, FxHashMap<(u32, u32), LinkFromSrc>) {
let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
if include_sources {
intravisit::walk_crate(&mut visitor, tcx.hir().krate());
let (krate, sources) = sources::collect_local_sources(tcx, src_root, krate);
(krate, sources, visitor.matches)
} else {
(krate, Default::default(), Default::default())
}
}
fn span_to_tuple(span: Span) -> (u32, u32) {
(span.lo().0, span.hi().0)
}
struct SpanMapVisitor<'tcx> {
crate tcx: TyCtxt<'tcx>,
crate matches: FxHashMap<(u32, u32), LinkFromSrc>,
}
impl<'tcx> SpanMapVisitor<'tcx> {
fn handle_path(&mut self, path: &rustc_hir::Path<'_>, path_span: Option<Span>) -> bool {
let info = match path.res {
Res::Def(kind, def_id) if kind != DefKind::TyParam => {
if matches!(kind, DefKind::Macro(_)) {
return false;
}
Some(def_id)
}
Res::Local(_) => None,
_ => return true,
};
if let Some(span) = self.tcx.hir().res_span(path.res) {
self.matches.insert(
path_span.map(span_to_tuple).unwrap_or_else(|| span_to_tuple(path.span)),
LinkFromSrc::Local(span),
);
} else if let Some(def_id) = info {
self.matches.insert(
path_span.map(span_to_tuple).unwrap_or_else(|| span_to_tuple(path.span)),
LinkFromSrc::External(def_id),
);
}
true
}
}
impl Visitor<'tcx> for SpanMapVisitor<'tcx> {
type Map = rustc_middle::hir::map::Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::All(self.tcx.hir())
}
fn visit_generic_param(&mut self, p: &'tcx GenericParam<'tcx>) {
if !matches!(p.kind, GenericParamKind::Type { .. }) {
return;
}
for bound in p.bounds {
if let Some(trait_ref) = bound.trait_ref() {
self.handle_path(&trait_ref.path, None);
}
}
}
fn visit_path(&mut self, path: &'tcx rustc_hir::Path<'tcx>, _id: HirId) {
self.handle_path(path, None);
intravisit::walk_path(self, path);
}
// fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
// match expr.kind {
// ExprKind::MethodCall(segment, method_span, _, _) => {
// if let Some(hir_id) = segment.hir_id {
// // call https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.type_dependent_def_id
// }
// }
// _ => {}
// }
// intravisit::walk_expr(self, expr);
// }
fn visit_use(&mut self, path: &'tcx rustc_hir::Path<'tcx>, id: HirId) {
self.handle_path(path, None);
intravisit::walk_use(self, path, id);
}
}

View file

@ -272,7 +272,7 @@ pub(super) fn write_shared(
write_minify("search.js", static_files::SEARCH_JS)?; write_minify("search.js", static_files::SEARCH_JS)?;
write_minify("settings.js", static_files::SETTINGS_JS)?; write_minify("settings.js", static_files::SETTINGS_JS)?;
if cx.shared.include_sources { if cx.include_sources {
write_minify("source-script.js", static_files::sidebar::SOURCE_SCRIPT)?; write_minify("source-script.js", static_files::sidebar::SOURCE_SCRIPT)?;
} }
@ -398,7 +398,7 @@ pub(super) fn write_shared(
} }
} }
if cx.shared.include_sources { if cx.include_sources {
let mut hierarchy = Hierarchy::new(OsString::new()); let mut hierarchy = Hierarchy::new(OsString::new());
for source in cx for source in cx
.shared .shared

View file

@ -5,8 +5,10 @@ use crate::fold::DocFolder;
use crate::html::format::Buffer; use crate::html::format::Buffer;
use crate::html::highlight; use crate::html::highlight;
use crate::html::layout; use crate::html::layout;
use crate::html::render::{SharedContext, BASIC_KEYWORDS}; use crate::html::render::{Context, BASIC_KEYWORDS};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::def_id::LOCAL_CRATE;
use rustc_middle::ty::TyCtxt;
use rustc_session::Session; use rustc_session::Session;
use rustc_span::edition::Edition; use rustc_span::edition::Edition;
use rustc_span::source_map::FileName; use rustc_span::source_map::FileName;
@ -14,52 +16,116 @@ use std::ffi::OsStr;
use std::fs; use std::fs;
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
crate fn render( crate fn render(cx: &mut Context<'_>, krate: clean::Crate) -> Result<clean::Crate, Error> {
dst: &Path,
scx: &mut SharedContext<'_>,
krate: clean::Crate,
) -> Result<clean::Crate, Error> {
info!("emitting source files"); info!("emitting source files");
let dst = dst.join("src").join(&*krate.name.as_str()); let dst = cx.dst.join("src").join(&*krate.name.as_str());
scx.ensure_dir(&dst)?; cx.shared.ensure_dir(&dst)?;
let mut folder = SourceCollector { dst, scx }; let mut folder = SourceCollector { dst, cx, emitted_local_sources: FxHashSet::default() };
Ok(folder.fold_crate(krate)) Ok(folder.fold_crate(krate))
} }
crate fn collect_local_sources<'tcx>(
tcx: TyCtxt<'tcx>,
src_root: &Path,
krate: clean::Crate,
) -> (clean::Crate, FxHashMap<PathBuf, String>) {
let mut lsc = LocalSourcesCollector { tcx, local_sources: FxHashMap::default(), src_root };
let krate = lsc.fold_crate(krate);
(krate, lsc.local_sources)
}
struct LocalSourcesCollector<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
local_sources: FxHashMap<PathBuf, String>,
src_root: &'a Path,
}
fn is_real_and_local(span: clean::Span, sess: &Session) -> bool {
span.filename(sess).is_real() && span.cnum(sess) == LOCAL_CRATE
}
impl LocalSourcesCollector<'_, '_> {
fn add_local_source(&mut self, item: &clean::Item) {
let sess = self.tcx.sess;
let span = item.span(self.tcx);
// skip all synthetic "files"
if !is_real_and_local(span, sess) {
return;
}
let filename = span.filename(sess);
let p = match filename {
FileName::Real(ref file) => match file.local_path() {
Some(p) => p.to_path_buf(),
_ => return,
},
_ => return,
};
if self.local_sources.contains_key(&*p) {
// We've already emitted this source
return;
}
let mut href = String::new();
clean_path(&self.src_root, &p, false, |component| {
href.push_str(&component.to_string_lossy());
href.push('/');
});
let src_fname = p.file_name().expect("source has no filename").to_os_string();
let mut fname = src_fname.clone();
fname.push(".html");
href.push_str(&fname.to_string_lossy());
self.local_sources.insert(p, href);
}
}
impl DocFolder for LocalSourcesCollector<'_, '_> {
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
self.add_local_source(&item);
// FIXME: if `include_sources` isn't set and DocFolder didn't require consuming the crate by value,
// we could return None here without having to walk the rest of the crate.
Some(self.fold_item_recur(item))
}
}
/// Helper struct to render all source code to HTML pages /// Helper struct to render all source code to HTML pages
struct SourceCollector<'a, 'tcx> { struct SourceCollector<'a, 'tcx> {
scx: &'a mut SharedContext<'tcx>, cx: &'a mut Context<'tcx>,
/// Root destination to place all HTML output into /// Root destination to place all HTML output into
dst: PathBuf, dst: PathBuf,
emitted_local_sources: FxHashSet<PathBuf>,
} }
impl DocFolder for SourceCollector<'_, '_> { impl DocFolder for SourceCollector<'_, '_> {
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> { fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
let tcx = self.cx.tcx();
let span = item.span(tcx);
let sess = tcx.sess;
// If we're not rendering sources, there's nothing to do. // If we're not rendering sources, there's nothing to do.
// If we're including source files, and we haven't seen this file yet, // If we're including source files, and we haven't seen this file yet,
// then we need to render it out to the filesystem. // then we need to render it out to the filesystem.
if self.scx.include_sources if self.cx.include_sources && is_real_and_local(span, sess) {
// skip all synthetic "files" let filename = span.filename(sess);
&& item.span(self.scx.tcx).filename(self.sess()).is_real() let span = span.inner();
// skip non-local files let start_pos = sess.source_map().lookup_source_file(span.lo()).start_pos;
&& item.span(self.scx.tcx).cnum(self.sess()) == LOCAL_CRATE
{
let filename = item.span(self.scx.tcx).filename(self.sess());
// If it turns out that we couldn't read this file, then we probably // If it turns out that we couldn't read this file, then we probably
// can't read any of the files (generating html output from json or // can't read any of the files (generating html output from json or
// something like that), so just don't include sources for the // something like that), so just don't include sources for the
// entire crate. The other option is maintaining this mapping on a // entire crate. The other option is maintaining this mapping on a
// per-file basis, but that's probably not worth it... // per-file basis, but that's probably not worth it...
self.scx.include_sources = match self.emit_source(&filename) { self.cx.include_sources = match self.emit_source(&filename, start_pos.0) {
Ok(()) => true, Ok(()) => true,
Err(e) => { Err(e) => {
self.scx.tcx.sess.span_err( self.cx.shared.tcx.sess.span_err(
item.span(self.scx.tcx).inner(), span,
&format!( &format!(
"failed to render source code for `{}`: {}", "failed to render source code for `{}`: {}",
filename.prefer_local(), filename.prefer_local(),
e e,
), ),
); );
false false
@ -73,12 +139,8 @@ impl DocFolder for SourceCollector<'_, '_> {
} }
impl SourceCollector<'_, 'tcx> { impl SourceCollector<'_, 'tcx> {
fn sess(&self) -> &'tcx Session {
&self.scx.tcx.sess
}
/// Renders the given filename into its corresponding HTML source file. /// Renders the given filename into its corresponding HTML source file.
fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> { fn emit_source(&mut self, filename: &FileName, file_span_lo: u32) -> Result<(), Error> {
let p = match *filename { let p = match *filename {
FileName::Real(ref file) => { FileName::Real(ref file) => {
if let Some(local_path) = file.local_path() { if let Some(local_path) = file.local_path() {
@ -89,7 +151,7 @@ impl SourceCollector<'_, 'tcx> {
} }
_ => return Ok(()), _ => return Ok(()),
}; };
if self.scx.local_sources.contains_key(&*p) { if self.emitted_local_sources.contains(&*p) {
// We've already emitted this source // We've already emitted this source
return Ok(()); return Ok(());
} }
@ -107,20 +169,17 @@ impl SourceCollector<'_, 'tcx> {
// Create the intermediate directories // Create the intermediate directories
let mut cur = self.dst.clone(); let mut cur = self.dst.clone();
let mut root_path = String::from("../../"); let mut root_path = String::from("../../");
let mut href = String::new(); clean_path(&self.cx.shared.src_root, &p, false, |component| {
clean_path(&self.scx.src_root, &p, false, |component| {
cur.push(component); cur.push(component);
root_path.push_str("../"); root_path.push_str("../");
href.push_str(&component.to_string_lossy());
href.push('/');
}); });
self.scx.ensure_dir(&cur)?;
self.cx.shared.ensure_dir(&cur)?;
let src_fname = p.file_name().expect("source has no filename").to_os_string(); let src_fname = p.file_name().expect("source has no filename").to_os_string();
let mut fname = src_fname.clone(); let mut fname = src_fname.clone();
fname.push(".html"); fname.push(".html");
cur.push(&fname); cur.push(&fname);
href.push_str(&fname.to_string_lossy());
let title = format!("{} - source", src_fname.to_string_lossy()); let title = format!("{} - source", src_fname.to_string_lossy());
let desc = format!("Source of the Rust file `{}`.", filename.prefer_remapped()); let desc = format!("Source of the Rust file `{}`.", filename.prefer_remapped());
@ -128,23 +187,32 @@ impl SourceCollector<'_, 'tcx> {
title: &title, title: &title,
css_class: "source", css_class: "source",
root_path: &root_path, root_path: &root_path,
static_root_path: self.scx.static_root_path.as_deref(), static_root_path: self.cx.shared.static_root_path.as_deref(),
description: &desc, description: &desc,
keywords: BASIC_KEYWORDS, keywords: BASIC_KEYWORDS,
resource_suffix: &self.scx.resource_suffix, resource_suffix: &self.cx.shared.resource_suffix,
extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)], extra_scripts: &[&format!("source-files{}", self.cx.shared.resource_suffix)],
static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)], static_extra_scripts: &[&format!("source-script{}", self.cx.shared.resource_suffix)],
}; };
let v = layout::render( let v = layout::render(
&self.scx.templates, &self.cx.shared.templates,
&self.scx.layout, &self.cx.shared.layout,
&page, &page,
"", "",
|buf: &mut _| print_src(buf, contents, self.scx.edition()), |buf: &mut _| {
&self.scx.style_files, print_src(
buf,
contents,
self.cx.shared.edition(),
file_span_lo,
&self.cx,
&root_path,
)
},
&self.cx.shared.style_files,
); );
self.scx.fs.write(&cur, v.as_bytes())?; self.cx.shared.fs.write(&cur, v.as_bytes())?;
self.scx.local_sources.insert(p, href); self.emitted_local_sources.insert(p);
Ok(()) Ok(())
} }
} }
@ -178,7 +246,14 @@ where
/// Wrapper struct to render the source code of a file. This will do things like /// Wrapper struct to render the source code of a file. This will do things like
/// adding line numbers to the left-hand side. /// adding line numbers to the left-hand side.
fn print_src(buf: &mut Buffer, s: &str, edition: Edition) { fn print_src(
buf: &mut Buffer,
s: &str,
edition: Edition,
file_span_lo: u32,
context: &Context<'_>,
root_path: &str,
) {
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);
let mut cols = 0; let mut cols = 0;
@ -192,5 +267,16 @@ fn print_src(buf: &mut Buffer, s: &str, edition: Edition) {
writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i, cols); writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i, cols);
} }
line_numbers.write_str("</pre>"); line_numbers.write_str("</pre>");
highlight::render_with_highlighting(s, buf, None, None, None, edition, Some(line_numbers)); highlight::render_with_highlighting(
s,
buf,
None,
None,
None,
edition,
Some(line_numbers),
file_span_lo,
Some(context),
root_path,
);
} }