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())
}
/// 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 {
self.0
}

View File

@ -494,7 +494,10 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_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) {
Some(&(ref fqp, shortty)) => (fqp, shortty, {
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 crate::clean;
use crate::html::escape::Escape;
use crate::html::render::Context;
use std::fmt::Display;
use std::fmt::{Display, Write};
use std::iter::Peekable;
use rustc_lexer::{LiteralKind, TokenKind};
use rustc_span::edition::Edition;
use rustc_span::symbol::Symbol;
use super::format::Buffer;
use super::format::{self, Buffer};
use super::render::LinkFromSrc;
/// Highlights `src`, returning the HTML output.
crate fn render_with_highlighting(
@ -25,6 +28,9 @@
tooltip: Option<(Option<Edition>, &str)>,
edition: Edition,
extra_content: Option<Buffer>,
file_span_lo: u32,
context: Option<&Context<'_>>,
root_path: &str,
) {
debug!("highlighting: ================\n{}\n==============", src);
if let Some((edition_info, class)) = tooltip {
@ -41,7 +47,7 @@
}
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);
}
@ -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.
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 {
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::ExitSpan => exit_span(out),
};
@ -82,14 +97,14 @@ enum Class {
KeyWord,
// Keywords that do pointer/reference stuff.
RefKeyWord,
Self_,
Self_((u32, u32)),
Op,
Macro,
MacroNonTerminal,
String,
Number,
Bool,
Ident,
Ident((u32, u32)),
Lifetime,
PreludeTy,
PreludeVal,
@ -105,20 +120,27 @@ fn as_html(self) -> &'static str {
Class::Attribute => "attribute",
Class::KeyWord => "kw",
Class::RefKeyWord => "kw-2",
Class::Self_ => "self",
Class::Self_(_) => "self",
Class::Op => "op",
Class::Macro => "macro",
Class::MacroNonTerminal => "macro-nonterminal",
Class::String => "string",
Class::Number => "number",
Class::Bool => "bool-val",
Class::Ident => "ident",
Class::Ident(_) => "ident",
Class::Lifetime => "lifetime",
Class::PreludeTy => "prelude-ty",
Class::PreludeVal => "prelude-val",
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> {
@ -144,14 +166,23 @@ fn next(&mut self) -> Option<(TokenKind, &'a str)> {
}
}
fn get_real_ident_class(text: &str, edition: Edition) -> Class {
match text {
/// Returns `None` if this is a `Class::Ident`.
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,
"self" | "Self" => Class::Self_,
"false" | "true" => Class::Bool,
_ 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
@ -163,11 +194,12 @@ struct Classifier<'a> {
in_macro_nonterminal: bool,
edition: Edition,
byte_pos: u32,
file_span_lo: u32,
src: &'a str,
}
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();
Classifier {
tokens,
@ -176,6 +208,7 @@ fn new(src: &str, edition: Edition) -> Classifier<'_> {
in_macro_nonterminal: false,
edition,
byte_pos: 0,
file_span_lo,
src,
}
}
@ -201,17 +234,17 @@ fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> {
if has_ident {
return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
} 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 {
let class = get_real_ident_class(text, edition);
let class = get_real_ident_class(text, edition, true);
(class, text)
} else {
// 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.
@ -221,7 +254,7 @@ fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> {
} else if nb > 0 && has_ident {
return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
} else if nb > 0 {
return vec![(TokenKind::Colon, pos, pos + nb)];
return vec![(TokenKind::Colon, start, start + nb)];
} else if has_ident {
return vec![(TokenKind::Ident, start, pos)];
} else {
@ -231,10 +264,11 @@ fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> {
}
/// 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() {
let before = self.byte_pos;
self.byte_pos += text.len() as u32;
Some((kind, text))
Some((kind, text, before))
} else {
None
}
@ -254,14 +288,18 @@ fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) {
.unwrap_or(false)
{
let tokens = self.get_full_ident_path();
let skip = !tokens.is_empty();
for (token, start, end) in tokens {
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;
}
if skip {
continue;
}
}
if let Some((token, text)) = self.next() {
self.advance(token, text, sink);
if let Some((token, text, before)) = self.next() {
self.advance(token, text, sink, before);
} else {
break;
}
@ -270,7 +308,13 @@ fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) {
/// Single step of highlighting. This will classify `token`, but maybe also
/// 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 no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None });
let class = match token {
@ -401,19 +445,30 @@ fn advance(&mut self, token: TokenKind, text: &'a str, sink: &mut dyn FnMut(High
sink(Highlight::Token { text, class: None });
return;
}
TokenKind::Ident => match get_real_ident_class(text, self.edition) {
Class::Ident => match text {
TokenKind::Ident => match get_real_ident_class(text, self.edition, false) {
None => match text {
"Option" | "Result" => Class::PreludeTy,
"Some" | "None" | "Ok" | "Err" => Class::PreludeVal,
_ if self.in_macro_nonterminal => {
self.in_macro_nonterminal = false;
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,
};
// 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
/// 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 {
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)]

View File

@ -330,6 +330,9 @@ fn dont_escape(c: u8) -> bool {
tooltip,
edition,
None,
0,
None,
"",
);
Some(Event::Html(s.into_inner().into()))
}

View File

@ -17,7 +17,10 @@
use super::cache::{build_index, ExternalLocation};
use super::print_item::{full_path, item_path, print_item};
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::ExternalCrate;
@ -46,7 +49,7 @@
pub(crate) current: Vec<String>,
/// The current destination folder of where HTML artifacts should be placed.
/// 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
/// real location of an item. This is used to allow external links to
/// publicly reused items to redirect to the right location.
@ -58,7 +61,7 @@
/// Issue for improving the situation: [#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.
///
/// Ideally the cache would be in [`SharedContext`], but it's mutated
@ -68,7 +71,11 @@
/// It's immutable once in `Context`, so it's not as bad that it's not in
/// `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.
@ -84,10 +91,6 @@
/// This describes the layout of each page, and is not modified after
/// creation of the context (contains info like the favicon and added html).
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.
crate local_sources: FxHashMap<PathBuf, String>,
/// Show the memory layout of types in the docs.
@ -125,6 +128,10 @@
redirections: Option<RefCell<FxHashMap<String, String>>>,
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<'_> {
@ -293,15 +300,19 @@ fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<NameDoc
/// may happen, for example, with externally inlined items where the source
/// of their crate documentation isn't known.
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;
}
let mut root = self.root_path();
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.
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(),
_ => return None,
};
@ -339,8 +350,8 @@ pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> {
(&*symbol, &path)
};
let loline = item.span(self.tcx()).lo(self.sess()).line;
let hiline = item.span(self.tcx()).hi(self.sess()).line;
let loline = span.lo(self.sess()).line;
let hiline = span.hi(self.sess()).line;
let lines =
if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) };
Some(format!(
@ -362,9 +373,9 @@ fn descr() -> &'static str {
const RUN_ON_MODULE: bool = true;
fn init(
mut krate: clean::Crate,
krate: clean::Crate,
options: RenderOptions,
mut cache: Cache,
cache: Cache,
tcx: TyCtxt<'tcx>,
) -> Result<(Self, clean::Crate), Error> {
// need to save a copy of the options for rendering the index page
@ -444,13 +455,16 @@ fn init(
_ => {}
}
}
let (mut krate, local_sources, matches) =
collect_spans_and_sources(tcx, krate, &src_root, include_sources);
let (sender, receiver) = channel();
let mut scx = SharedContext {
tcx,
collapsed: krate.collapsed,
src_root,
include_sources,
local_sources: Default::default(),
local_sources,
issue_tracker_base_url,
layout,
created_dirs: Default::default(),
@ -466,6 +480,7 @@ fn init(
redirections: if generate_redirect_map { Some(Default::default()) } else { None },
show_type_layout,
templates,
span_correspondance_map: matches,
};
// Add the default themes to the `Vec` of stylepaths
@ -483,12 +498,6 @@ fn init(
let dst = output;
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 {
current: Vec::new(),
@ -497,8 +506,16 @@ fn init(
id_map: RefCell::new(id_map),
shared: Rc::new(scx),
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.
Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
write_shared(&cx, &krate, index, &md_opts)?;
@ -514,6 +531,7 @@ fn make_child_renderer(&self) -> Self {
id_map: RefCell::new(IdMap::new()),
shared: Rc::clone(&self.shared),
cache: Rc::clone(&self.cache),
include_sources: self.include_sources,
}
}

View File

@ -30,9 +30,11 @@
mod context;
mod print_item;
mod span_map;
mod write_shared;
crate use context::*;
crate use span_map::{collect_spans_and_sources, LinkFromSrc};
use std::collections::VecDeque;
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
// this page, and this link will be auto-clicked. The `id` attribute is
// 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);
}
@ -1081,6 +1081,9 @@ fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Mac
None,
it.span(cx.tcx()).inner().edition(),
None,
0,
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 @@ fn add_background_image_to_css(
write_minify("search.js", static_files::SEARCH_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)?;
}
@ -398,7 +398,7 @@ fn to_json_string(&self) -> String {
}
}
if cx.shared.include_sources {
if cx.include_sources {
let mut hierarchy = Hierarchy::new(OsString::new());
for source in cx
.shared

View File

@ -5,8 +5,10 @@
use crate::html::format::Buffer;
use crate::html::highlight;
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_middle::ty::TyCtxt;
use rustc_session::Session;
use rustc_span::edition::Edition;
use rustc_span::source_map::FileName;
@ -14,52 +16,116 @@
use std::fs;
use std::path::{Component, Path, PathBuf};
crate fn render(
dst: &Path,
scx: &mut SharedContext<'_>,
krate: clean::Crate,
) -> Result<clean::Crate, Error> {
crate fn render(cx: &mut Context<'_>, krate: clean::Crate) -> Result<clean::Crate, Error> {
info!("emitting source files");
let dst = dst.join("src").join(&*krate.name.as_str());
scx.ensure_dir(&dst)?;
let mut folder = SourceCollector { dst, scx };
let dst = cx.dst.join("src").join(&*krate.name.as_str());
cx.shared.ensure_dir(&dst)?;
let mut folder = SourceCollector { dst, cx, emitted_local_sources: FxHashSet::default() };
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
struct SourceCollector<'a, 'tcx> {
scx: &'a mut SharedContext<'tcx>,
cx: &'a mut Context<'tcx>,
/// Root destination to place all HTML output into
dst: PathBuf,
emitted_local_sources: FxHashSet<PathBuf>,
}
impl DocFolder for SourceCollector<'_, '_> {
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 including source files, and we haven't seen this file yet,
// then we need to render it out to the filesystem.
if self.scx.include_sources
// skip all synthetic "files"
&& item.span(self.scx.tcx).filename(self.sess()).is_real()
// skip non-local files
&& item.span(self.scx.tcx).cnum(self.sess()) == LOCAL_CRATE
{
let filename = item.span(self.scx.tcx).filename(self.sess());
if self.cx.include_sources && is_real_and_local(span, sess) {
let filename = span.filename(sess);
let span = span.inner();
let start_pos = sess.source_map().lookup_source_file(span.lo()).start_pos;
// 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
// something like that), so just don't include sources for the
// entire crate. The other option is maintaining this mapping on a
// 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,
Err(e) => {
self.scx.tcx.sess.span_err(
item.span(self.scx.tcx).inner(),
self.cx.shared.tcx.sess.span_err(
span,
&format!(
"failed to render source code for `{}`: {}",
filename.prefer_local(),
e
e,
),
);
false
@ -73,12 +139,8 @@ fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
}
impl SourceCollector<'_, 'tcx> {
fn sess(&self) -> &'tcx Session {
&self.scx.tcx.sess
}
/// 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 {
FileName::Real(ref file) => {
if let Some(local_path) = file.local_path() {
@ -89,7 +151,7 @@ fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
}
_ => return Ok(()),
};
if self.scx.local_sources.contains_key(&*p) {
if self.emitted_local_sources.contains(&*p) {
// We've already emitted this source
return Ok(());
}
@ -107,20 +169,17 @@ fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
// Create the intermediate directories
let mut cur = self.dst.clone();
let mut root_path = String::from("../../");
let mut href = String::new();
clean_path(&self.scx.src_root, &p, false, |component| {
clean_path(&self.cx.shared.src_root, &p, false, |component| {
cur.push(component);
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 mut fname = src_fname.clone();
fname.push(".html");
cur.push(&fname);
href.push_str(&fname.to_string_lossy());
let title = format!("{} - source", src_fname.to_string_lossy());
let desc = format!("Source of the Rust file `{}`.", filename.prefer_remapped());
@ -128,23 +187,32 @@ fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
title: &title,
css_class: "source",
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,
keywords: BASIC_KEYWORDS,
resource_suffix: &self.scx.resource_suffix,
extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
resource_suffix: &self.cx.shared.resource_suffix,
extra_scripts: &[&format!("source-files{}", self.cx.shared.resource_suffix)],
static_extra_scripts: &[&format!("source-script{}", self.cx.shared.resource_suffix)],
};
let v = layout::render(
&self.scx.templates,
&self.scx.layout,
&self.cx.shared.templates,
&self.cx.shared.layout,
&page,
"",
|buf: &mut _| print_src(buf, contents, self.scx.edition()),
&self.scx.style_files,
|buf: &mut _| {
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.scx.local_sources.insert(p, href);
self.cx.shared.fs.write(&cur, v.as_bytes())?;
self.emitted_local_sources.insert(p);
Ok(())
}
}
@ -178,7 +246,14 @@ fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
/// Wrapper struct to render the source code of a file. This will do things like
/// 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 mut line_numbers = Buffer::empty_from(buf);
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);
}
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,
);
}