Add links on source types to go to definition
This commit is contained in:
parent
2f07ae408f
commit
023231a709
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)]
|
||||
|
@ -330,6 +330,9 @@ fn dont_escape(c: u8) -> bool {
|
||||
tooltip,
|
||||
edition,
|
||||
None,
|
||||
0,
|
||||
None,
|
||||
"",
|
||||
);
|
||||
Some(Event::Html(s.into_inner().into()))
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
110
src/librustdoc/html/render/span_map.rs
Normal file
110
src/librustdoc/html/render/span_map.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user