summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/html
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/html')
-rw-r--r--src/librustdoc/html/format.rs58
-rw-r--r--src/librustdoc/html/layout.rs2
-rw-r--r--src/librustdoc/html/markdown.rs18
-rw-r--r--src/librustdoc/html/render/context.rs41
-rw-r--r--src/librustdoc/html/render/mod.rs29
-rw-r--r--src/librustdoc/html/render/print_item.rs267
-rw-r--r--src/librustdoc/html/render/search_index.rs23
-rw-r--r--src/librustdoc/html/render/type_layout.rs2
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css54
-rw-r--r--src/librustdoc/html/static/js/externs.js4
-rw-r--r--src/librustdoc/html/static/js/main.js170
-rw-r--r--src/librustdoc/html/static/js/search.js894
-rw-r--r--src/librustdoc/html/static/js/source-script.js4
-rw-r--r--src/librustdoc/html/static/js/storage.js2
-rw-r--r--src/librustdoc/html/templates/item_info.html2
-rw-r--r--src/librustdoc/html/templates/item_union.html19
-rw-r--r--src/librustdoc/html/templates/page.html4
-rw-r--r--src/librustdoc/html/templates/print_item.html4
18 files changed, 1063 insertions, 534 deletions
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index d963d6092..54c0cd2ef 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -347,13 +347,19 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
}
} else {
let mut br_with_padding = String::with_capacity(6 * indent + 28);
- br_with_padding.push_str("\n");
+ br_with_padding.push('\n');
- let padding_amount =
- if ending == Ending::Newline { indent + 4 } else { indent + "fn where ".len() };
+ let where_indent = 3;
+ let padding_amount = if ending == Ending::Newline {
+ indent + 4
+ } else if indent == 0 {
+ 4
+ } else {
+ indent + where_indent + "where ".len()
+ };
for _ in 0..padding_amount {
- br_with_padding.push_str(" ");
+ br_with_padding.push(' ');
}
let where_preds = where_preds.to_string().replace('\n', &br_with_padding);
@@ -370,7 +376,8 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
let where_preds = where_preds.replacen(&br_with_padding, " ", 1);
let mut clause = br_with_padding;
- clause.truncate(clause.len() - "where ".len());
+ // +1 is for `\n`.
+ clause.truncate(indent + 1 + where_indent);
write!(clause, "<span class=\"where\">where{where_preds}</span>")?;
clause
@@ -1257,9 +1264,9 @@ impl clean::Impl {
};
primitive_link_fragment(f, PrimitiveType::Tuple, &format!("fn ({name}₁, {name}₂, …, {name}ₙ{ellipsis})"), "#trait-implementations-1", cx)?;
// Write output.
- if let clean::FnRetTy::Return(ty) = &bare_fn.decl.output {
+ if !bare_fn.decl.output.is_unit() {
write!(f, " -> ")?;
- fmt_type(ty, f, use_absolute, cx)?;
+ fmt_type(&bare_fn.decl.output, f, use_absolute, cx)?;
}
} else if let Some(ty) = self.kind.as_blanket_ty() {
fmt_type(ty, f, use_absolute, cx)?;
@@ -1296,22 +1303,6 @@ impl clean::Arguments {
}
}
-impl clean::FnRetTy {
- pub(crate) fn print<'a, 'tcx: 'a>(
- &'a self,
- cx: &'a Context<'tcx>,
- ) -> impl fmt::Display + 'a + Captures<'tcx> {
- display_fn(move |f| match self {
- clean::Return(clean::Tuple(tys)) if tys.is_empty() => Ok(()),
- clean::Return(ty) if f.alternate() => {
- write!(f, " -> {:#}", ty.print(cx))
- }
- clean::Return(ty) => write!(f, " -&gt; {}", ty.print(cx)),
- clean::DefaultReturn => Ok(()),
- })
- }
-}
-
impl clean::BareFunctionDecl {
fn print_hrtb_with_space<'a, 'tcx: 'a>(
&'a self,
@@ -1366,7 +1357,7 @@ impl clean::FnDecl {
"({args:#}{ellipsis}){arrow:#}",
args = self.inputs.print(cx),
ellipsis = ellipsis,
- arrow = self.output.print(cx)
+ arrow = self.print_output(cx)
)
} else {
write!(
@@ -1374,7 +1365,7 @@ impl clean::FnDecl {
"({args}{ellipsis}){arrow}",
args = self.inputs.print(cx),
ellipsis = ellipsis,
- arrow = self.output.print(cx)
+ arrow = self.print_output(cx)
)
}
})
@@ -1417,7 +1408,7 @@ impl clean::FnDecl {
let amp = if f.alternate() { "&" } else { "&amp;" };
write!(f, "(")?;
- if let Some(n) = line_wrapping_indent {
+ if let Some(n) = line_wrapping_indent && !self.inputs.values.is_empty() {
write!(f, "\n{}", Indent(n + 4))?;
}
for (i, input) in self.inputs.values.iter().enumerate() {
@@ -1464,9 +1455,22 @@ impl clean::FnDecl {
Some(n) => write!(f, "\n{})", Indent(n))?,
};
- fmt::Display::fmt(&self.output.print(cx), f)?;
+ fmt::Display::fmt(&self.print_output(cx), f)?;
Ok(())
}
+
+ fn print_output<'a, 'tcx: 'a>(
+ &'a self,
+ cx: &'a Context<'tcx>,
+ ) -> impl fmt::Display + 'a + Captures<'tcx> {
+ display_fn(move |f| match &self.output {
+ clean::Tuple(tys) if tys.is_empty() => Ok(()),
+ ty if f.alternate() => {
+ write!(f, " -> {:#}", ty.print(cx))
+ }
+ ty => write!(f, " -&gt; {}", ty.print(cx)),
+ })
+ }
}
pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>(
diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs
index 6ab849c92..8c5871d91 100644
--- a/src/librustdoc/html/layout.rs
+++ b/src/librustdoc/html/layout.rs
@@ -55,6 +55,7 @@ struct PageLayout<'a> {
sidebar: String,
content: String,
krate_with_trailing_slash: String,
+ rust_channel: &'static str,
pub(crate) rustdoc_version: &'a str,
}
@@ -82,6 +83,7 @@ pub(crate) fn render<T: Print, S: Print>(
sidebar,
content,
krate_with_trailing_slash,
+ rust_channel: *crate::clean::utils::DOC_CHANNEL,
rustdoc_version,
}
.render()
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 9bb20022c..fd00277e2 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -381,7 +381,6 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
Some(Event::Code(text)) => {
trace!("saw code {}", text);
if let Some(link) = self.shortcut_link {
- trace!("original text was {}", link.original_text);
// NOTE: this only replaces if the code block is the *entire* text.
// If only part of the link has code highlighting, the disambiguator will not be removed.
// e.g. [fn@`f`]
@@ -390,8 +389,11 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
// So we could never be sure we weren't replacing too much:
// [fn@my_`f`unc] is treated the same as [my_func()] in that pass.
//
- // NOTE: &[1..len() - 1] is to strip the backticks
- if **text == link.original_text[1..link.original_text.len() - 1] {
+ // NOTE: .get(1..len() - 1) is to strip the backticks
+ if let Some(link) = self.links.iter().find(|l| {
+ l.href == link.href
+ && Some(&**text) == l.original_text.get(1..l.original_text.len() - 1)
+ }) {
debug!("replacing {} with {}", text, link.new_text);
*text = CowStr::Borrowed(&link.new_text);
}
@@ -402,9 +404,12 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
Some(Event::Text(text)) => {
trace!("saw text {}", text);
if let Some(link) = self.shortcut_link {
- trace!("original text was {}", link.original_text);
// NOTE: same limitations as `Event::Code`
- if **text == *link.original_text {
+ if let Some(link) = self
+ .links
+ .iter()
+ .find(|l| l.href == link.href && **text == *l.original_text)
+ {
debug!("replacing {} with {}", text, link.new_text);
*text = CowStr::Borrowed(&link.new_text);
}
@@ -781,7 +786,7 @@ impl<'tcx> ExtraInfo<'tcx> {
ExtraInfo { def_id, sp, tcx }
}
- fn error_invalid_codeblock_attr(&self, msg: String, help: &str) {
+ fn error_invalid_codeblock_attr(&self, msg: String, help: &'static str) {
if let Some(def_id) = self.def_id.as_local() {
self.tcx.struct_span_lint_hir(
crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
@@ -1520,7 +1525,6 @@ fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {
map.insert("toggle-all-docs".into(), 1);
map.insert("all-types".into(), 1);
map.insert("default-settings".into(), 1);
- map.insert("rustdoc-vars".into(), 1);
map.insert("sidebar-vars".into(), 1);
map.insert("copy-path".into(), 1);
map.insert("TOC".into(), 1);
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 56af257fd..4c4762636 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -73,6 +73,8 @@ pub(crate) struct Context<'tcx> {
pub(crate) include_sources: bool,
/// Collection of all types with notable traits referenced in the current module.
pub(crate) types_with_notable_traits: FxHashSet<clean::Type>,
+ /// Field used during rendering, to know if we're inside an inlined item.
+ pub(crate) is_inside_inlined_module: bool,
}
// `Context` is cloned a lot, so we don't want the size to grow unexpectedly.
@@ -171,6 +173,19 @@ impl<'tcx> Context<'tcx> {
}
fn render_item(&mut self, it: &clean::Item, is_module: bool) -> String {
+ let mut render_redirect_pages = self.render_redirect_pages;
+ // If the item is stripped but inlined, links won't point to the item so no need to generate
+ // a file for it.
+ if it.is_stripped() &&
+ let Some(def_id) = it.def_id() &&
+ def_id.is_local()
+ {
+ if self.is_inside_inlined_module || self.shared.cache.inlined_items.contains(&def_id) {
+ // For now we're forced to generate a redirect page for stripped items until
+ // `record_extern_fqn` correctly points to external items.
+ render_redirect_pages = true;
+ }
+ }
let mut title = String::new();
if !is_module {
title.push_str(it.name.unwrap().as_str());
@@ -205,7 +220,7 @@ impl<'tcx> Context<'tcx> {
tyname.as_str()
};
- if !self.render_redirect_pages {
+ if !render_redirect_pages {
let clone_shared = Rc::clone(&self.shared);
let page = layout::Page {
css_class: tyname_s,
@@ -545,6 +560,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
shared: Rc::new(scx),
include_sources,
types_with_notable_traits: FxHashSet::default(),
+ is_inside_inlined_module: false,
};
if emit_crate {
@@ -574,6 +590,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
shared: Rc::clone(&self.shared),
include_sources: self.include_sources,
types_with_notable_traits: FxHashSet::default(),
+ is_inside_inlined_module: self.is_inside_inlined_module,
}
}
@@ -768,12 +785,22 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
info!("Recursing into {}", self.dst.display());
- let buf = self.render_item(item, true);
- // buf will be empty if the module is stripped and there is no redirect for it
- if !buf.is_empty() {
- self.shared.ensure_dir(&self.dst)?;
- let joint_dst = self.dst.join("index.html");
- self.shared.fs.write(joint_dst, buf)?;
+ if !item.is_stripped() {
+ let buf = self.render_item(item, true);
+ // buf will be empty if the module is stripped and there is no redirect for it
+ if !buf.is_empty() {
+ self.shared.ensure_dir(&self.dst)?;
+ let joint_dst = self.dst.join("index.html");
+ self.shared.fs.write(joint_dst, buf)?;
+ }
+ }
+ if !self.is_inside_inlined_module {
+ if let Some(def_id) = item.def_id() && self.cache().inlined_items.contains(&def_id) {
+ self.is_inside_inlined_module = true;
+ }
+ } else if item.is_doc_hidden() {
+ // We're not inside an inlined module anymore since this one cannot be re-exported.
+ self.is_inside_inlined_module = false;
}
// Render sidebar-items.js used throughout this module.
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 9e3b5d10a..f923f9054 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -421,11 +421,10 @@ fn document<'a, 'cx: 'a>(
display_fn(move |f| {
document_item_info(cx, item, parent).render_into(f).unwrap();
if parent.is_none() {
- write!(f, "{}", document_full_collapsible(item, cx, heading_offset))?;
+ write!(f, "{}", document_full_collapsible(item, cx, heading_offset))
} else {
- write!(f, "{}", document_full(item, cx, heading_offset))?;
+ write!(f, "{}", document_full(item, cx, heading_offset))
}
- Ok(())
})
}
@@ -787,10 +786,12 @@ fn assoc_type(
indent: usize,
cx: &Context<'_>,
) {
+ let tcx = cx.tcx();
write!(
w,
- "{indent}type <a{href} class=\"associatedtype\">{name}</a>{generics}",
+ "{indent}{vis}type <a{href} class=\"associatedtype\">{name}</a>{generics}",
indent = " ".repeat(indent),
+ vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
href = assoc_href_attr(it, link, cx),
name = it.name.as_ref().unwrap(),
generics = generics.print(cx),
@@ -798,10 +799,11 @@ fn assoc_type(
if !bounds.is_empty() {
write!(w, ": {}", print_generic_bounds(bounds, cx))
}
- write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline));
+ // Render the default before the where-clause which aligns with the new recommended style. See #89122.
if let Some(default) = default {
write!(w, " = {}", default.print(cx))
}
+ write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline));
}
fn assoc_method(
@@ -844,7 +846,7 @@ fn assoc_method(
+ name.as_str().len()
+ generics_len;
- let notable_traits = d.output.as_return().and_then(|output| notable_traits_button(output, cx));
+ let notable_traits = notable_traits_button(&d.output, cx);
let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
header_len += 4;
@@ -858,8 +860,8 @@ fn assoc_method(
w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len());
write!(
w,
- "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a{href} class=\"fn\">{name}</a>\
- {generics}{decl}{notable_traits}{where_clause}",
+ "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn \
+ <a{href} class=\"fn\">{name}</a>{generics}{decl}{notable_traits}{where_clause}",
indent = indent_str,
vis = vis,
constness = constness,
@@ -1038,9 +1040,9 @@ fn render_attributes_in_pre<'a, 'b: 'a>(
// When an attribute is rendered inside a <code> tag, it is formatted using
// a div to produce a newline after it.
-fn render_attributes_in_code(w: &mut Buffer, it: &clean::Item, tcx: TyCtxt<'_>) {
- for a in it.attributes(tcx, false) {
- write!(w, "<div class=\"code-attribute\">{}</div>", a);
+fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, tcx: TyCtxt<'_>) {
+ for attr in it.attributes(tcx, false) {
+ write!(w, "<div class=\"code-attribute\">{attr}</div>").unwrap();
}
}
@@ -1282,6 +1284,11 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) ->
pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> Option<String> {
let mut has_notable_trait = false;
+ if ty.is_unit() {
+ // Very common fast path.
+ return None;
+ }
+
let did = ty.def_id(cx.cache())?;
// Box has pass-through impls for Read, Write, Iterator, and Future when the
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 62027a3fa..383e3c170 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -9,7 +9,6 @@ use rustc_middle::middle::stability;
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::hygiene::MacroKind;
use rustc_span::symbol::{kw, sym, Symbol};
-use std::borrow::Borrow;
use std::cell::{RefCell, RefMut};
use std::cmp::Ordering;
use std::fmt;
@@ -40,6 +39,110 @@ use crate::html::{highlight, static_files};
use askama::Template;
use itertools::Itertools;
+/// Generates an Askama template struct for rendering items with common methods.
+///
+/// Usage:
+/// ```ignore (illustrative)
+/// item_template!(
+/// #[template(path = "<template.html>", /* additional values */)]
+/// /* additional meta items */
+/// struct MyItem<'a, 'cx> {
+/// cx: RefCell<&'a mut Context<'cx>>,
+/// it: &'a clean::Item,
+/// /* additional fields */
+/// },
+/// methods = [ /* method names (comma separated; refer to macro definition of `item_template_methods!()`) */ ]
+/// )
+/// ```
+///
+/// NOTE: ensure that the generic lifetimes (`'a`, `'cx`) and
+/// required fields (`cx`, `it`) are identical (in terms of order and definition).
+macro_rules! item_template {
+ (
+ $(#[$meta:meta])*
+ struct $name:ident<'a, 'cx> {
+ cx: RefCell<&'a mut Context<'cx>>,
+ it: &'a clean::Item,
+ $($field_name:ident: $field_ty:ty),*,
+ },
+ methods = [$($methods:tt),* $(,)?]
+ ) => {
+ #[derive(Template)]
+ $(#[$meta])*
+ struct $name<'a, 'cx> {
+ cx: RefCell<&'a mut Context<'cx>>,
+ it: &'a clean::Item,
+ $($field_name: $field_ty),*
+ }
+
+ impl<'a, 'cx: 'a> ItemTemplate<'a, 'cx> for $name<'a, 'cx> {
+ fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>) {
+ (&self.it, self.cx.borrow_mut())
+ }
+ }
+
+ impl<'a, 'cx: 'a> $name<'a, 'cx> {
+ item_template_methods!($($methods)*);
+ }
+ };
+}
+
+/// Implement common methods for item template structs generated by `item_template!()`.
+///
+/// NOTE: this macro is intended to be used only by `item_template!()`.
+macro_rules! item_template_methods {
+ () => {};
+ (document $($rest:tt)*) => {
+ fn document<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let (item, mut cx) = self.item_and_mut_cx();
+ let v = document(*cx, item, None, HeadingOffset::H2);
+ write!(f, "{v}")
+ })
+ }
+ item_template_methods!($($rest)*);
+ };
+ (document_type_layout $($rest:tt)*) => {
+ fn document_type_layout<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let (item, cx) = self.item_and_mut_cx();
+ let def_id = item.item_id.expect_def_id();
+ let v = document_type_layout(*cx, def_id);
+ write!(f, "{v}")
+ })
+ }
+ item_template_methods!($($rest)*);
+ };
+ (render_attributes_in_pre $($rest:tt)*) => {
+ fn render_attributes_in_pre<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let (item, cx) = self.item_and_mut_cx();
+ let tcx = cx.tcx();
+ let v = render_attributes_in_pre(item, "", tcx);
+ write!(f, "{v}")
+ })
+ }
+ item_template_methods!($($rest)*);
+ };
+ (render_assoc_items $($rest:tt)*) => {
+ fn render_assoc_items<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
+ display_fn(move |f| {
+ let (item, mut cx) = self.item_and_mut_cx();
+ let def_id = item.item_id.expect_def_id();
+ let v = render_assoc_items(*cx, item, def_id, AssocItemRender::All);
+ write!(f, "{v}")
+ })
+ }
+ item_template_methods!($($rest)*);
+ };
+ ($method:ident $($rest:tt)*) => {
+ compile_error!(concat!("unknown method: ", stringify!($method)));
+ };
+ ($token:tt $($rest:tt)*) => {
+ compile_error!(concat!("unexpected token: ", stringify!($token)));
+ };
+}
+
const ITEM_TABLE_OPEN: &str = "<ul class=\"item-table\">";
const ITEM_TABLE_CLOSE: &str = "</ul>";
const ITEM_TABLE_ROW_OPEN: &str = "<li>";
@@ -222,49 +325,6 @@ trait ItemTemplate<'a, 'cx: 'a>: askama::Template + fmt::Display {
fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>);
}
-fn item_template_document<'a: 'b, 'b, 'cx: 'a>(
- templ: &'b impl ItemTemplate<'a, 'cx>,
-) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
- display_fn(move |f| {
- let (item, mut cx) = templ.item_and_mut_cx();
- let v = document(*cx, item, None, HeadingOffset::H2);
- write!(f, "{v}")
- })
-}
-
-fn item_template_document_type_layout<'a: 'b, 'b, 'cx: 'a>(
- templ: &'b impl ItemTemplate<'a, 'cx>,
-) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
- display_fn(move |f| {
- let (item, cx) = templ.item_and_mut_cx();
- let def_id = item.item_id.expect_def_id();
- let v = document_type_layout(*cx, def_id);
- write!(f, "{v}")
- })
-}
-
-fn item_template_render_attributes_in_pre<'a: 'b, 'b, 'cx: 'a>(
- templ: &'b impl ItemTemplate<'a, 'cx>,
-) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
- display_fn(move |f| {
- let (item, cx) = templ.item_and_mut_cx();
- let tcx = cx.tcx();
- let v = render_attributes_in_pre(item, "", tcx);
- write!(f, "{v}")
- })
-}
-
-fn item_template_render_assoc_items<'a: 'b, 'b, 'cx: 'a>(
- templ: &'b impl ItemTemplate<'a, 'cx>,
-) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
- display_fn(move |f| {
- let (item, mut cx) = templ.item_and_mut_cx();
- let def_id = item.item_id.expect_def_id();
- let v = render_assoc_items(*cx, item, def_id, AssocItemRender::All);
- write!(f, "{v}")
- })
-}
-
fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: &[clean::Item]) {
write!(w, "{}", document(cx, item, None, HeadingOffset::H2));
@@ -587,8 +647,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
+ name.as_str().len()
+ generics_len;
- let notable_traits =
- f.decl.output.as_return().and_then(|output| notable_traits_button(output, cx));
+ let notable_traits = notable_traits_button(&f.decl.output, cx);
wrap_item(w, |w| {
w.reserve(header_len);
@@ -1102,7 +1161,12 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
);
}
-fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::TraitAlias) {
+fn item_trait_alias(
+ w: &mut impl fmt::Write,
+ cx: &mut Context<'_>,
+ it: &clean::Item,
+ t: &clean::TraitAlias,
+) {
wrap_item(w, |w| {
write!(
w,
@@ -1112,19 +1176,25 @@ fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &
print_where_clause(&t.generics, cx, 0, Ending::Newline),
bounds(&t.bounds, true, cx),
attrs = render_attributes_in_pre(it, "", cx.tcx()),
- );
+ )
+ .unwrap();
});
- write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
-
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
// Render any items associated directly to this alias, as otherwise they
// won't be visible anywhere in the docs. It would be nice to also show
// associated items from the aliased type (see discussion in #32077), but
// we need #14072 to make sense of the generics.
write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
+ .unwrap();
}
-fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) {
+fn item_opaque_ty(
+ w: &mut impl fmt::Write,
+ cx: &mut Context<'_>,
+ it: &clean::Item,
+ t: &clean::OpaqueTy,
+) {
wrap_item(w, |w| {
write!(
w,
@@ -1134,16 +1204,18 @@ fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &cl
where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline),
bounds = bounds(&t.bounds, false, cx),
attrs = render_attributes_in_pre(it, "", cx.tcx()),
- );
+ )
+ .unwrap();
});
- write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
// Render any items associated directly to this alias, as otherwise they
// won't be visible anywhere in the docs. It would be nice to also show
// associated items from the aliased type (see discussion in #32077), but
// we need #14072 to make sense of the generics.
write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
+ .unwrap();
}
fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Typedef) {
@@ -1176,19 +1248,15 @@ fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clea
}
fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) {
- #[derive(Template)]
- #[template(path = "item_union.html")]
- struct ItemUnion<'a, 'cx> {
- cx: RefCell<&'a mut Context<'cx>>,
- it: &'a clean::Item,
- s: &'a clean::Union,
- }
-
- impl<'a, 'cx: 'a> ItemTemplate<'a, 'cx> for ItemUnion<'a, 'cx> {
- fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>) {
- (self.it, self.cx.borrow_mut())
- }
- }
+ item_template!(
+ #[template(path = "item_union.html")]
+ struct ItemUnion<'a, 'cx> {
+ cx: RefCell<&'a mut Context<'cx>>,
+ it: &'a clean::Item,
+ s: &'a clean::Union,
+ },
+ methods = [document, document_type_layout, render_attributes_in_pre, render_assoc_items]
+ );
impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
fn render_union<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
@@ -1198,6 +1266,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean:
write!(f, "{v}")
})
}
+
fn document_field<'b>(
&'b self,
field: &'a clean::Item,
@@ -1208,10 +1277,12 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean:
write!(f, "{v}")
})
}
+
fn stability_field(&self, field: &clean::Item) -> Option<String> {
let cx = self.cx.borrow();
field.stability_class(cx.tcx())
}
+
fn print_ty<'b>(
&'b self,
ty: &'a clean::Type,
@@ -1420,37 +1491,41 @@ fn item_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
-fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &clean::ProcMacro) {
- wrap_item(w, |w| {
+fn item_proc_macro(
+ w: &mut impl fmt::Write,
+ cx: &mut Context<'_>,
+ it: &clean::Item,
+ m: &clean::ProcMacro,
+) {
+ wrap_item(w, |buffer| {
let name = it.name.expect("proc-macros always have names");
match m.kind {
MacroKind::Bang => {
- write!(w, "{}!() {{ /* proc-macro */ }}", name);
+ write!(buffer, "{name}!() {{ /* proc-macro */ }}").unwrap();
}
MacroKind::Attr => {
- write!(w, "#[{}]", name);
+ write!(buffer, "#[{name}]").unwrap();
}
MacroKind::Derive => {
- write!(w, "#[derive({})]", name);
+ write!(buffer, "#[derive({name})]").unwrap();
if !m.helpers.is_empty() {
- w.push_str("\n{\n");
- w.push_str(" // Attributes available to this derive:\n");
+ buffer.write_str("\n{\n // Attributes available to this derive:\n").unwrap();
for attr in &m.helpers {
- writeln!(w, " #[{}]", attr);
+ writeln!(buffer, " #[{attr}]").unwrap();
}
- w.push_str("}\n");
+ buffer.write_str("}\n").unwrap();
}
}
}
});
- write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
}
-fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
+fn item_primitive(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item) {
let def_id = it.item_id.expect_def_id();
- write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) {
- write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
+ write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)).unwrap();
} else {
// We handle the "reference" primitive type on its own because we only want to list
// implementations on generic types.
@@ -1560,8 +1635,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
}
fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) {
- let mut buffer = Buffer::new();
- wrap_item(&mut buffer, |buffer| {
+ wrap_item(w, |buffer| {
render_attributes_in_code(buffer, it, cx.tcx());
write!(
buffer,
@@ -1570,29 +1644,29 @@ fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item,
mutability = s.mutability.print_with_space(),
name = it.name.unwrap(),
typ = s.type_.print(cx)
- );
+ )
+ .unwrap();
});
- write!(w, "{}", buffer.into_inner()).unwrap();
-
write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
}
-fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
- wrap_item(w, |w| {
- w.write_str("extern {\n");
- render_attributes_in_code(w, it, cx.tcx());
+fn item_foreign_type(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item) {
+ wrap_item(w, |buffer| {
+ buffer.write_str("extern {\n").unwrap();
+ render_attributes_in_code(buffer, it, cx.tcx());
write!(
- w,
+ buffer,
" {}type {};\n}}",
visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx),
it.name.unwrap(),
- );
+ )
+ .unwrap();
});
- write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
-
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap();
write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
+ .unwrap();
}
fn item_keyword(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
@@ -1666,13 +1740,14 @@ fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>)
bounds
}
-fn wrap_item<F>(w: &mut Buffer, f: F)
+fn wrap_item<W, F>(w: &mut W, f: F)
where
- F: FnOnce(&mut Buffer),
+ W: fmt::Write,
+ F: FnOnce(&mut W),
{
- w.write_str(r#"<pre class="rust item-decl"><code>"#);
+ write!(w, r#"<pre class="rust item-decl"><code>"#).unwrap();
f(w);
- w.write_str("</code></pre>");
+ write!(w, "</code></pre>").unwrap();
}
#[derive(PartialEq, Eq)]
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index 846299f02..f34be120d 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -7,7 +7,7 @@ use rustc_span::symbol::Symbol;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use crate::clean;
-use crate::clean::types::{FnRetTy, Function, Generics, ItemId, Type, WherePredicate};
+use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate};
use crate::formats::cache::{Cache, OrphanImplItem};
use crate::formats::item_type::ItemType;
use crate::html::format::join_with_double_colon;
@@ -656,22 +656,9 @@ fn get_fn_inputs_and_outputs<'tcx>(
}
let mut ret_types = Vec::new();
- match decl.output {
- FnRetTy::Return(ref return_type) => {
- add_generics_and_bounds_as_types(
- self_,
- generics,
- return_type,
- tcx,
- 0,
- &mut ret_types,
- cache,
- );
- if ret_types.is_empty() {
- ret_types.push(get_index_type(return_type, vec![]));
- }
- }
- _ => {}
- };
+ add_generics_and_bounds_as_types(self_, generics, &decl.output, tcx, 0, &mut ret_types, cache);
+ if ret_types.is_empty() {
+ ret_types.push(get_index_type(&decl.output, vec![]));
+ }
(all_types, ret_types)
}
diff --git a/src/librustdoc/html/render/type_layout.rs b/src/librustdoc/html/render/type_layout.rs
index c9b95b1e6..0bc32ea5a 100644
--- a/src/librustdoc/html/render/type_layout.rs
+++ b/src/librustdoc/html/render/type_layout.rs
@@ -17,7 +17,7 @@ use crate::html::render::Context;
#[template(path = "type_layout.html")]
struct TypeLayout<'cx> {
variants: Vec<(Symbol, TypeLayoutSize)>,
- type_layout_size: Result<TypeLayoutSize, LayoutError<'cx>>,
+ type_layout_size: Result<TypeLayoutSize, &'cx LayoutError<'cx>>,
}
#[derive(Template)]
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index a7d5f4977..b7f455259 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -8,6 +8,7 @@
:root {
--nav-sub-mobile-padding: 8px;
+ --search-typename-width: 6.75rem;
}
/* See FiraSans-LICENSE.txt for the Fira Sans license. */
@@ -213,7 +214,7 @@ a.anchor,
h1 a,
.search-results a,
.stab,
-.result-name .primitive > i, .result-name .keyword > i {
+.result-name i {
color: var(--main-color);
}
@@ -869,14 +870,11 @@ so that we can apply CSS-filters to change the arrow color in themes */
gap: 1em;
}
-.search-results > a > div {
- flex: 1;
-}
-
.search-results > a > div.desc {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
+ flex: 2;
}
.search-results a:hover,
@@ -884,12 +882,28 @@ so that we can apply CSS-filters to change the arrow color in themes */
background-color: var(--search-result-link-focus-background-color);
}
+.search-results .result-name {
+ display: flex;
+ align-items: center;
+ justify-content: start;
+ flex: 3;
+}
.search-results .result-name span.alias {
color: var(--search-results-alias-color);
}
-.search-results .result-name span.grey {
+.search-results .result-name .grey {
color: var(--search-results-grey-color);
}
+.search-results .result-name .typename {
+ color: var(--search-results-grey-color);
+ font-size: 0.875rem;
+ width: var(--search-typename-width);
+}
+.search-results .result-name .path {
+ word-break: break-all;
+ max-width: calc(100% - var(--search-typename-width));
+ display: inline-block;
+}
.popover {
position: absolute;
@@ -957,6 +971,8 @@ so that we can apply CSS-filters to change the arrow color in themes */
display: flex;
padding: 3px;
margin-bottom: 5px;
+ align-items: center;
+ vertical-align: text-bottom;
}
.item-name .stab {
margin-left: 0.3125em;
@@ -968,11 +984,9 @@ so that we can apply CSS-filters to change the arrow color in themes */
color: var(--main-color);
background-color: var(--stab-background-color);
width: fit-content;
- align-items: center;
white-space: pre-wrap;
border-radius: 3px;
- display: inline-flex;
- vertical-align: text-bottom;
+ display: inline;
}
.stab.portability > code {
@@ -1179,6 +1193,10 @@ a.test-arrow:hover {
position: relative;
}
+.code-header a.tooltip:hover {
+ color: var(--link-color);
+}
+
/* placeholder thunk so that the mouse can easily travel from "(i)" to popover
the resulting "hover tunnel" is a stepped triangle, approximating
https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown */
@@ -1191,6 +1209,14 @@ a.tooltip:hover::after {
content: "\00a0";
}
+/* This animation is layered onto the mistake-proofing delay for dismissing
+ a hovered tooltip, to ensure it feels responsive even with the delay.
+ */
+.fade-out {
+ opacity: 0;
+ transition: opacity 0.45s cubic-bezier(0, 0, 0.1, 1.0);
+}
+
.popover.tooltip .content {
margin: 0.25em 0.5em;
}
@@ -1712,6 +1738,16 @@ in source-script.js
.search-results > a > div.desc, .item-table > li > div.desc {
padding-left: 2em;
}
+ .search-results .result-name {
+ display: block;
+ }
+ .search-results .result-name .typename {
+ width: initial;
+ margin-right: 0;
+ }
+ .search-results .result-name .typename, .search-results .result-name .path {
+ display: inline;
+ }
.source-sidebar-expanded .source .sidebar {
max-width: 100vw;
diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js
index 8b931f74e..f697abd07 100644
--- a/src/librustdoc/html/static/js/externs.js
+++ b/src/librustdoc/html/static/js/externs.js
@@ -53,7 +53,7 @@ let ParsedQuery;
* parent: (Object|null|undefined),
* path: string,
* ty: (Number|null|number),
- * type: (Array<?>|null)
+ * type: FunctionSearchType?
* }}
*/
let Row;
@@ -135,7 +135,7 @@ let RawFunctionType;
/**
* @typedef {{
* inputs: Array<FunctionType>,
- * outputs: Array<FunctionType>,
+ * output: Array<FunctionType>,
* }}
*/
let FunctionSearchType;
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index bccf675c1..254b0d8bf 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -4,6 +4,13 @@
"use strict";
+// The amount of time that the cursor must remain still over a hover target before
+// revealing a tooltip.
+//
+// https://www.nngroup.com/articles/timing-exposing-content/
+window.RUSTDOC_TOOLTIP_HOVER_MS = 300;
+window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS = 450;
+
// Given a basename (e.g. "storage") and an extension (e.g. ".js"), return a URL
// for a resource under the root-path, with the resource-suffix.
function resourcePath(basename, extension) {
@@ -270,14 +277,18 @@ function preLoadCss(cssUrl) {
searchState.mouseMovedAfterSearch = false;
document.title = searchState.title;
},
- hideResults: () => {
- switchDisplayedElement(null);
+ removeQueryParameters: () => {
+ // We change the document title.
document.title = searchState.titleBeforeSearch;
- // We also remove the query parameter from the URL.
if (browserSupportsHistoryApi()) {
history.replaceState(null, "", getNakedUrl() + window.location.hash);
}
},
+ hideResults: () => {
+ switchDisplayedElement(null);
+ // We also remove the query parameter from the URL.
+ searchState.removeQueryParameters();
+ },
getQueryStringParams: () => {
const params = {};
window.location.search.substring(1).split("&").
@@ -772,6 +783,13 @@ function preLoadCss(cssUrl) {
});
});
+ /**
+ * Show a tooltip immediately.
+ *
+ * @param {DOMElement} e - The tooltip's anchor point. The DOM is consulted to figure
+ * out what the tooltip should contain, and where it should be
+ * positioned.
+ */
function showTooltip(e) {
const notable_ty = e.getAttribute("data-notable-ty");
if (!window.NOTABLE_TRAITS && notable_ty) {
@@ -782,8 +800,10 @@ function preLoadCss(cssUrl) {
throw new Error("showTooltip() called with notable without any notable traits!");
}
}
+ // Make this function idempotent. If the tooltip is already shown, avoid doing extra work
+ // and leave it alone.
if (window.CURRENT_TOOLTIP_ELEMENT && window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE === e) {
- // Make this function idempotent.
+ clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
return;
}
window.hideAllModals(false);
@@ -791,11 +811,18 @@ function preLoadCss(cssUrl) {
if (notable_ty) {
wrapper.innerHTML = "<div class=\"content\">" +
window.NOTABLE_TRAITS[notable_ty] + "</div>";
- } else if (e.getAttribute("title") !== undefined) {
- const titleContent = document.createElement("div");
- titleContent.className = "content";
- titleContent.appendChild(document.createTextNode(e.getAttribute("title")));
- wrapper.appendChild(titleContent);
+ } else {
+ // Replace any `title` attribute with `data-title` to avoid double tooltips.
+ if (e.getAttribute("title") !== null) {
+ e.setAttribute("data-title", e.getAttribute("title"));
+ e.removeAttribute("title");
+ }
+ if (e.getAttribute("data-title") !== null) {
+ const titleContent = document.createElement("div");
+ titleContent.className = "content";
+ titleContent.appendChild(document.createTextNode(e.getAttribute("data-title")));
+ wrapper.appendChild(titleContent);
+ }
}
wrapper.className = "tooltip popover";
const focusCatcher = document.createElement("div");
@@ -824,17 +851,77 @@ function preLoadCss(cssUrl) {
wrapper.style.visibility = "";
window.CURRENT_TOOLTIP_ELEMENT = wrapper;
window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE = e;
+ clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
+ wrapper.onpointerenter = function(ev) {
+ // If this is a synthetic touch event, ignore it. A click event will be along shortly.
+ if (ev.pointerType !== "mouse") {
+ return;
+ }
+ clearTooltipHoverTimeout(e);
+ };
wrapper.onpointerleave = function(ev) {
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
if (ev.pointerType !== "mouse") {
return;
}
- if (!e.TOOLTIP_FORCE_VISIBLE && !elemIsInParent(event.relatedTarget, e)) {
- hideTooltip(true);
+ if (!e.TOOLTIP_FORCE_VISIBLE && !elemIsInParent(ev.relatedTarget, e)) {
+ // See "Tooltip pointer leave gesture" below.
+ setTooltipHoverTimeout(e, false);
+ addClass(wrapper, "fade-out");
}
};
}
+ /**
+ * Show or hide the tooltip after a timeout. If a timeout was already set before this function
+ * was called, that timeout gets cleared. If the tooltip is already in the requested state,
+ * this function will still clear any pending timeout, but otherwise do nothing.
+ *
+ * @param {DOMElement} element - The tooltip's anchor point. The DOM is consulted to figure
+ * out what the tooltip should contain, and where it should be
+ * positioned.
+ * @param {boolean} show - If true, the tooltip will be made visible. If false, it will
+ * be hidden.
+ */
+ function setTooltipHoverTimeout(element, show) {
+ clearTooltipHoverTimeout(element);
+ if (!show && !window.CURRENT_TOOLTIP_ELEMENT) {
+ // To "hide" an already hidden element, just cancel its timeout.
+ return;
+ }
+ if (show && window.CURRENT_TOOLTIP_ELEMENT) {
+ // To "show" an already visible element, just cancel its timeout.
+ return;
+ }
+ if (window.CURRENT_TOOLTIP_ELEMENT &&
+ window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE !== element) {
+ // Don't do anything if another tooltip is already visible.
+ return;
+ }
+ element.TOOLTIP_HOVER_TIMEOUT = setTimeout(() => {
+ if (show) {
+ showTooltip(element);
+ } else if (!element.TOOLTIP_FORCE_VISIBLE) {
+ hideTooltip(false);
+ }
+ }, show ? window.RUSTDOC_TOOLTIP_HOVER_MS : window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS);
+ }
+
+ /**
+ * If a show/hide timeout was set by `setTooltipHoverTimeout`, cancel it. If none exists,
+ * do nothing.
+ *
+ * @param {DOMElement} element - The tooltip's anchor point,
+ * as passed to `setTooltipHoverTimeout`.
+ */
+ function clearTooltipHoverTimeout(element) {
+ if (element.TOOLTIP_HOVER_TIMEOUT !== undefined) {
+ removeClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");
+ clearTimeout(element.TOOLTIP_HOVER_TIMEOUT);
+ delete element.TOOLTIP_HOVER_TIMEOUT;
+ }
+ }
+
function tooltipBlurHandler(event) {
if (window.CURRENT_TOOLTIP_ELEMENT &&
!elemIsInParent(document.activeElement, window.CURRENT_TOOLTIP_ELEMENT) &&
@@ -854,6 +941,12 @@ function preLoadCss(cssUrl) {
}
}
+ /**
+ * Hide the current tooltip immediately.
+ *
+ * @param {boolean} focus - If set to `true`, move keyboard focus to the tooltip anchor point.
+ * If set to `false`, leave keyboard focus alone.
+ */
function hideTooltip(focus) {
if (window.CURRENT_TOOLTIP_ELEMENT) {
if (window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE) {
@@ -864,6 +957,7 @@ function preLoadCss(cssUrl) {
}
const body = document.getElementsByTagName("body")[0];
body.removeChild(window.CURRENT_TOOLTIP_ELEMENT);
+ clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
window.CURRENT_TOOLTIP_ELEMENT = null;
}
}
@@ -886,7 +980,14 @@ function preLoadCss(cssUrl) {
if (ev.pointerType !== "mouse") {
return;
}
- showTooltip(this);
+ setTooltipHoverTimeout(this, true);
+ };
+ e.onpointermove = function(ev) {
+ // If this is a synthetic touch event, ignore it. A click event will be along shortly.
+ if (ev.pointerType !== "mouse") {
+ return;
+ }
+ setTooltipHoverTimeout(this, true);
};
e.onpointerleave = function(ev) {
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
@@ -895,7 +996,38 @@ function preLoadCss(cssUrl) {
}
if (!this.TOOLTIP_FORCE_VISIBLE &&
!elemIsInParent(ev.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT)) {
- hideTooltip(true);
+ // Tooltip pointer leave gesture:
+ //
+ // Designing a good hover microinteraction is a matter of guessing user
+ // intent from what are, literally, vague gestures. In this case, guessing if
+ // hovering in or out of the tooltip base is intentional or not.
+ //
+ // To figure this out, a few different techniques are used:
+ //
+ // * When the mouse pointer enters a tooltip anchor point, its hitbox is grown
+ // on the bottom, where the popover is/will appear. Search "hover tunnel" in
+ // rustdoc.css for the implementation.
+ // * There's a delay when the mouse pointer enters the popover base anchor, in
+ // case the mouse pointer was just passing through and the user didn't want
+ // to open it.
+ // * Similarly, a delay is added when exiting the anchor, or the popover
+ // itself, before hiding it.
+ // * A fade-out animation is layered onto the pointer exit delay to immediately
+ // inform the user that they successfully dismissed the popover, while still
+ // providing a way for them to cancel it if it was a mistake and they still
+ // wanted to interact with it.
+ // * No animation is used for revealing it, because we don't want people to try
+ // to interact with an element while it's in the middle of fading in: either
+ // they're allowed to interact with it while it's fading in, meaning it can't
+ // serve as mistake-proofing for the popover, or they can't, but
+ // they might try and be frustrated.
+ //
+ // See also:
+ // * https://www.nngroup.com/articles/timing-exposing-content/
+ // * https://www.nngroup.com/articles/tooltip-guidelines/
+ // * https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown
+ setTooltipHoverTimeout(e, false);
+ addClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");
}
};
});
@@ -918,9 +1050,10 @@ function preLoadCss(cssUrl) {
function buildHelpMenu() {
const book_info = document.createElement("span");
+ const channel = getVar("channel");
book_info.className = "top";
- book_info.innerHTML = "You can find more information in \
- <a href=\"https://doc.rust-lang.org/rustdoc/\">the rustdoc book</a>.";
+ book_info.innerHTML = `You can find more information in \
+<a href="https://doc.rust-lang.org/${channel}/rustdoc/">the rustdoc book</a>.`;
const shortcuts = [
["?", "Show this help dialog"],
@@ -940,6 +1073,9 @@ function preLoadCss(cssUrl) {
div_shortcuts.innerHTML = "<h2>Keyboard Shortcuts</h2><dl>" + shortcuts + "</dl></div>";
const infos = [
+ `For a full list of all search features, take a look <a \
+href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\
+#the-search-interface">here</a>.`,
"Prefix searches with a type followed by a colon (e.g., <code>fn:</code>) to \
restrict the search to a given item kind.",
"Accepted kinds are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \
@@ -949,6 +1085,10 @@ function preLoadCss(cssUrl) {
<code>-&gt; vec</code> or <code>String, enum:Cow -&gt; bool</code>)",
"You can look for items with an exact name by putting double quotes around \
your request: <code>\"string\"</code>",
+ "Look for functions that accept or return \
+ <a href=\"https://doc.rust-lang.org/std/primitive.slice.html\">slices</a> and \
+ <a href=\"https://doc.rust-lang.org/std/primitive.array.html\">arrays</a> by writing \
+ square brackets (e.g., <code>-&gt; [u8]</code> or <code>[] -&gt; Option</code>)",
"Look for items inside another one by searching for a path: <code>vec::Vec</code>",
].map(x => "<p>" + x + "</p>").join("");
const div_infos = document.createElement("div");
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 62afe40bb..51d8e81ca 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -35,6 +35,35 @@ const itemTypes = [
"traitalias",
];
+const longItemTypes = [
+ "module",
+ "extern crate",
+ "re-export",
+ "struct",
+ "enum",
+ "function",
+ "type alias",
+ "static",
+ "trait",
+ "",
+ "trait method",
+ "method",
+ "struct field",
+ "enum variant",
+ "macro",
+ "primitive type",
+ "assoc type",
+ "constant",
+ "assoc const",
+ "union",
+ "foreign type",
+ "keyword",
+ "existential type",
+ "attribute macro",
+ "derive macro",
+ "trait alias",
+];
+
// used for special search precedence
const TY_PRIMITIVE = itemTypes.indexOf("primitive");
const TY_KEYWORD = itemTypes.indexOf("keyword");
@@ -208,6 +237,46 @@ function initSearch(rawSearchIndex) {
let typeNameIdMap;
const ALIASES = new Map();
+ /**
+ * Special type name IDs for searching by array.
+ */
+ let typeNameIdOfArray;
+ /**
+ * Special type name IDs for searching by slice.
+ */
+ let typeNameIdOfSlice;
+ /**
+ * Special type name IDs for searching by both array and slice (`[]` syntax).
+ */
+ let typeNameIdOfArrayOrSlice;
+
+ /**
+ * Add an item to the type Name->ID map, or, if one already exists, use it.
+ * Returns the number. If name is "" or null, return -1 (pure generic).
+ *
+ * This is effectively string interning, so that function matching can be
+ * done more quickly. Two types with the same name but different item kinds
+ * get the same ID.
+ *
+ * @param {string} name
+ *
+ * @returns {integer}
+ */
+ function buildTypeMapIndex(name) {
+
+ if (name === "" || name === null) {
+ return -1;
+ }
+
+ if (typeNameIdMap.has(name)) {
+ return typeNameIdMap.get(name);
+ } else {
+ const id = typeNameIdMap.size;
+ typeNameIdMap.set(name, id);
+ return id;
+ }
+ }
+
function isWhitespace(c) {
return " \t\n\r".indexOf(c) !== -1;
}
@@ -217,11 +286,11 @@ function initSearch(rawSearchIndex) {
}
function isEndCharacter(c) {
- return ",>-".indexOf(c) !== -1;
+ return ",>-]".indexOf(c) !== -1;
}
function isStopCharacter(c) {
- return isWhitespace(c) || isEndCharacter(c);
+ return isEndCharacter(c);
}
function isErrorCharacter(c) {
@@ -317,18 +386,69 @@ function initSearch(rawSearchIndex) {
* @return {boolean}
*/
function isSeparatorCharacter(c) {
- return c === "," || isWhitespaceCharacter(c);
+ return c === ",";
}
- /**
- * Returns `true` if the given `c` character is a whitespace.
+/**
+ * Returns `true` if the given `c` character is a path separator. For example
+ * `:` in `a::b` or a whitespace in `a b`.
*
* @param {string} c
*
* @return {boolean}
*/
- function isWhitespaceCharacter(c) {
- return c === " " || c === "\t";
+ function isPathSeparator(c) {
+ return c === ":" || isWhitespace(c);
+ }
+
+ /**
+ * Returns `true` if the previous character is `lookingFor`.
+ *
+ * @param {ParserState} parserState
+ * @param {String} lookingFor
+ *
+ * @return {boolean}
+ */
+ function prevIs(parserState, lookingFor) {
+ let pos = parserState.pos;
+ while (pos > 0) {
+ const c = parserState.userQuery[pos - 1];
+ if (c === lookingFor) {
+ return true;
+ } else if (!isWhitespace(c)) {
+ break;
+ }
+ pos -= 1;
+ }
+ return false;
+ }
+
+ /**
+ * Returns `true` if the last element in the `elems` argument has generics.
+ *
+ * @param {Array<QueryElement>} elems
+ * @param {ParserState} parserState
+ *
+ * @return {boolean}
+ */
+ function isLastElemGeneric(elems, parserState) {
+ return (elems.length > 0 && elems[elems.length - 1].generics.length > 0) ||
+ prevIs(parserState, ">");
+ }
+
+ /**
+ * Increase current parser position until it doesn't find a whitespace anymore.
+ *
+ * @param {ParserState} parserState
+ */
+ function skipWhitespace(parserState) {
+ while (parserState.pos < parserState.userQuery.length) {
+ const c = parserState.userQuery[parserState.pos];
+ if (!isWhitespace(c)) {
+ break;
+ }
+ parserState.pos += 1;
+ }
}
/**
@@ -340,39 +460,78 @@ function initSearch(rawSearchIndex) {
* @return {QueryElement} - The newly created `QueryElement`.
*/
function createQueryElement(query, parserState, name, generics, isInGenerics) {
- if (name === "*" || (name.length === 0 && generics.length === 0)) {
- return;
+ const path = name.trim();
+ if (path.length === 0 && generics.length === 0) {
+ throw ["Unexpected ", parserState.userQuery[parserState.pos]];
+ } else if (path === "*") {
+ throw ["Unexpected ", "*"];
}
if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {
- throw ["You cannot have more than one element if you use quotes"];
- }
- const pathSegments = name.split("::");
- if (pathSegments.length > 1) {
- for (let i = 0, len = pathSegments.length; i < len; ++i) {
- const pathSegment = pathSegments[i];
-
- if (pathSegment.length === 0) {
- if (i === 0) {
- throw ["Paths cannot start with ", "::"];
- } else if (i + 1 === len) {
- throw ["Paths cannot end with ", "::"];
- }
- throw ["Unexpected ", "::::"];
- }
+ throw ["Cannot have more than one element if you use quotes"];
+ }
+ const typeFilter = parserState.typeFilter;
+ parserState.typeFilter = null;
+ if (name === "!") {
+ if (typeFilter !== null && typeFilter !== "primitive") {
+ throw [
+ "Invalid search type: primitive never type ",
+ "!",
+ " and ",
+ typeFilter,
+ " both specified",
+ ];
}
+ if (generics.length !== 0) {
+ throw [
+ "Never type ",
+ "!",
+ " does not accept generic parameters",
+ ];
+ }
+ return {
+ name: "never",
+ id: -1,
+ fullPath: ["never"],
+ pathWithoutLast: [],
+ pathLast: "never",
+ generics: [],
+ typeFilter: "primitive",
+ };
}
+ if (path.startsWith("::")) {
+ throw ["Paths cannot start with ", "::"];
+ } else if (path.endsWith("::")) {
+ throw ["Paths cannot end with ", "::"];
+ } else if (path.includes("::::")) {
+ throw ["Unexpected ", "::::"];
+ } else if (path.includes(" ::")) {
+ throw ["Unexpected ", " ::"];
+ } else if (path.includes(":: ")) {
+ throw ["Unexpected ", ":: "];
+ }
+ const pathSegments = path.split(/::|\s+/);
// In case we only have something like `<p>`, there is no name.
if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === "")) {
- throw ["Found generics without a path"];
+ if (generics.length > 0 || prevIs(parserState, ">")) {
+ throw ["Found generics without a path"];
+ } else {
+ throw ["Unexpected ", parserState.userQuery[parserState.pos]];
+ }
+ }
+ for (const [i, pathSegment] of pathSegments.entries()) {
+ if (pathSegment === "!") {
+ if (i !== 0) {
+ throw ["Never type ", "!", " is not associated item"];
+ }
+ pathSegments[i] = "never";
+ }
}
parserState.totalElems += 1;
if (isInGenerics) {
parserState.genericsElems += 1;
}
- const typeFilter = parserState.typeFilter;
- parserState.typeFilter = null;
return {
- name: name,
+ name: name.trim(),
id: -1,
fullPath: pathSegments,
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
@@ -408,28 +567,40 @@ function initSearch(rawSearchIndex) {
foundExclamation = parserState.pos;
} else if (isErrorCharacter(c)) {
throw ["Unexpected ", c];
- } else if (
- isStopCharacter(c) ||
- isSpecialStartCharacter(c) ||
- isSeparatorCharacter(c)
- ) {
- break;
- } else if (c === ":") { // If we allow paths ("str::string" for example).
- if (!isPathStart(parserState)) {
- break;
+ } else if (isPathSeparator(c)) {
+ if (c === ":") {
+ if (!isPathStart(parserState)) {
+ break;
+ }
+ // Skip current ":".
+ parserState.pos += 1;
+ } else {
+ while (parserState.pos + 1 < parserState.length) {
+ const next_c = parserState.userQuery[parserState.pos + 1];
+ if (!isWhitespace(next_c)) {
+ break;
+ }
+ parserState.pos += 1;
+ }
}
if (foundExclamation !== -1) {
- if (start <= (end - 2)) {
+ if (foundExclamation !== start &&
+ isIdentCharacter(parserState.userQuery[foundExclamation - 1])
+ ) {
throw ["Cannot have associated items in macros"];
} else {
- // if start == end - 1, we got the never type
// while the never type has no associated macros, we still
// can parse a path like that
foundExclamation = -1;
}
}
- // Skip current ":".
- parserState.pos += 1;
+ } else if (
+ c === "[" ||
+ isStopCharacter(c) ||
+ isSpecialStartCharacter(c) ||
+ isSeparatorCharacter(c)
+ ) {
+ break;
} else {
throw ["Unexpected ", c];
}
@@ -438,7 +609,10 @@ function initSearch(rawSearchIndex) {
end = parserState.pos;
}
// if start == end - 1, we got the never type
- if (foundExclamation !== -1 && start <= (end - 2)) {
+ if (foundExclamation !== -1 &&
+ foundExclamation !== start &&
+ isIdentCharacter(parserState.userQuery[foundExclamation - 1])
+ ) {
if (parserState.typeFilter === null) {
parserState.typeFilter = "macro";
} else if (parserState.typeFilter !== "macro") {
@@ -464,37 +638,71 @@ function initSearch(rawSearchIndex) {
function getNextElem(query, parserState, elems, isInGenerics) {
const generics = [];
+ skipWhitespace(parserState);
let start = parserState.pos;
let end;
- // We handle the strings on their own mostly to make code easier to follow.
- if (parserState.userQuery[parserState.pos] === "\"") {
- start += 1;
- getStringElem(query, parserState, isInGenerics);
- end = parserState.pos - 1;
+ if (parserState.userQuery[parserState.pos] === "[") {
+ parserState.pos += 1;
+ getItemsBefore(query, parserState, generics, "]");
+ const typeFilter = parserState.typeFilter;
+ if (typeFilter !== null && typeFilter !== "primitive") {
+ throw [
+ "Invalid search type: primitive ",
+ "[]",
+ " and ",
+ typeFilter,
+ " both specified",
+ ];
+ }
+ parserState.typeFilter = null;
+ parserState.totalElems += 1;
+ if (isInGenerics) {
+ parserState.genericsElems += 1;
+ }
+ elems.push({
+ name: "[]",
+ id: -1,
+ fullPath: ["[]"],
+ pathWithoutLast: [],
+ pathLast: "[]",
+ generics,
+ typeFilter: "primitive",
+ });
} else {
- end = getIdentEndPosition(parserState);
- }
- if (parserState.pos < parserState.length &&
- parserState.userQuery[parserState.pos] === "<"
- ) {
- if (start >= end) {
- throw ["Found generics without a path"];
+ const isStringElem = parserState.userQuery[start] === "\"";
+ // We handle the strings on their own mostly to make code easier to follow.
+ if (isStringElem) {
+ start += 1;
+ getStringElem(query, parserState, isInGenerics);
+ end = parserState.pos - 1;
+ } else {
+ end = getIdentEndPosition(parserState);
}
- parserState.pos += 1;
- getItemsBefore(query, parserState, generics, ">");
- }
- if (start >= end && generics.length === 0) {
- return;
+ if (parserState.pos < parserState.length &&
+ parserState.userQuery[parserState.pos] === "<"
+ ) {
+ if (start >= end) {
+ throw ["Found generics without a path"];
+ }
+ parserState.pos += 1;
+ getItemsBefore(query, parserState, generics, ">");
+ }
+ if (isStringElem) {
+ skipWhitespace(parserState);
+ }
+ if (start >= end && generics.length === 0) {
+ return;
+ }
+ elems.push(
+ createQueryElement(
+ query,
+ parserState,
+ parserState.userQuery.slice(start, end),
+ generics,
+ isInGenerics
+ )
+ );
}
- elems.push(
- createQueryElement(
- query,
- parserState,
- parserState.userQuery.slice(start, end),
- generics,
- isInGenerics
- )
- );
}
/**
@@ -518,6 +726,17 @@ function initSearch(rawSearchIndex) {
const oldTypeFilter = parserState.typeFilter;
parserState.typeFilter = null;
+ let extra = "";
+ if (endChar === ">") {
+ extra = "<";
+ } else if (endChar === "]") {
+ extra = "[";
+ } else if (endChar === "") {
+ extra = "->";
+ } else {
+ extra = endChar;
+ }
+
while (parserState.pos < parserState.length) {
const c = parserState.userQuery[parserState.pos];
if (c === endChar) {
@@ -535,7 +754,7 @@ function initSearch(rawSearchIndex) {
if (elems.length === 0) {
throw ["Expected type filter before ", ":"];
} else if (query.literalSearch) {
- throw ["You cannot use quotes on type filter"];
+ throw ["Cannot use quotes on type filter"];
}
// The type filter doesn't count as an element since it's a modifier.
const typeFilterElem = elems.pop();
@@ -547,43 +766,39 @@ function initSearch(rawSearchIndex) {
foundStopChar = true;
continue;
} else if (isEndCharacter(c)) {
- let extra = "";
- if (endChar === ">") {
- extra = "<";
- } else if (endChar === "") {
- extra = "->";
- } else {
- extra = endChar;
- }
throw ["Unexpected ", c, " after ", extra];
}
if (!foundStopChar) {
+ let extra = [];
+ if (isLastElemGeneric(query.elems, parserState)) {
+ extra = [" after ", ">"];
+ } else if (prevIs(parserState, "\"")) {
+ throw ["Cannot have more than one element if you use quotes"];
+ }
if (endChar !== "") {
throw [
"Expected ",
- ",", // comma
- ", ",
- "&nbsp;", // whitespace
+ ",",
" or ",
endChar,
+ ...extra,
", found ",
c,
];
}
throw [
"Expected ",
- ",", // comma
- " or ",
- "&nbsp;", // whitespace
+ ",",
+ ...extra,
", found ",
c,
];
}
const posBefore = parserState.pos;
start = parserState.pos;
- getNextElem(query, parserState, elems, endChar === ">");
+ getNextElem(query, parserState, elems, endChar !== "");
if (endChar !== "" && parserState.pos >= parserState.length) {
- throw ["Unclosed ", "<"];
+ throw ["Unclosed ", extra];
}
// This case can be encountered if `getNextElem` encountered a "stop character" right
// from the start. For example if you have `,,` or `<>`. In this case, we simply move up
@@ -594,7 +809,7 @@ function initSearch(rawSearchIndex) {
foundStopChar = false;
}
if (parserState.pos >= parserState.length && endChar !== "") {
- throw ["Unclosed ", "<"];
+ throw ["Unclosed ", extra];
}
// We are either at the end of the string or on the `endChar` character, let's move forward
// in any case.
@@ -610,11 +825,17 @@ function initSearch(rawSearchIndex) {
* @param {ParserState} parserState
*/
function checkExtraTypeFilterCharacters(start, parserState) {
- const query = parserState.userQuery;
+ const query = parserState.userQuery.slice(start, parserState.pos).trim();
- for (let pos = start; pos < parserState.pos; ++pos) {
- if (!isIdentCharacter(query[pos]) && !isWhitespaceCharacter(query[pos])) {
- throw ["Unexpected ", query[pos], " in type filter"];
+ for (const c in query) {
+ if (!isIdentCharacter(query[c])) {
+ throw [
+ "Unexpected ",
+ query[c],
+ " in type filter (before ",
+ ":",
+ ")",
+ ];
}
}
}
@@ -646,12 +867,17 @@ function initSearch(rawSearchIndex) {
throw ["Unexpected ", c];
} else if (c === ":" && !isPathStart(parserState)) {
if (parserState.typeFilter !== null) {
- throw ["Unexpected ", ":"];
- }
- if (query.elems.length === 0) {
+ throw [
+ "Unexpected ",
+ ":",
+ " (expected path after type filter ",
+ parserState.typeFilter + ":",
+ ")",
+ ];
+ } else if (query.elems.length === 0) {
throw ["Expected type filter before ", ":"];
} else if (query.literalSearch) {
- throw ["You cannot use quotes on type filter"];
+ throw ["Cannot use quotes on type filter"];
}
// The type filter doesn't count as an element since it's a modifier.
const typeFilterElem = query.elems.pop();
@@ -662,29 +888,36 @@ function initSearch(rawSearchIndex) {
query.literalSearch = false;
foundStopChar = true;
continue;
+ } else if (isWhitespace(c)) {
+ skipWhitespace(parserState);
+ continue;
}
if (!foundStopChar) {
+ let extra = "";
+ if (isLastElemGeneric(query.elems, parserState)) {
+ extra = [" after ", ">"];
+ } else if (prevIs(parserState, "\"")) {
+ throw ["Cannot have more than one element if you use quotes"];
+ }
if (parserState.typeFilter !== null) {
throw [
"Expected ",
- ",", // comma
- ", ",
- "&nbsp;", // whitespace
+ ",",
" or ",
- "->", // arrow
+ "->",
+ ...extra,
", found ",
c,
];
}
throw [
"Expected ",
- ",", // comma
+ ",",
", ",
- "&nbsp;", // whitespace
- ", ",
- ":", // colon
+ ":",
" or ",
- "->", // arrow
+ "->",
+ ...extra,
", found ",
c,
];
@@ -699,11 +932,18 @@ function initSearch(rawSearchIndex) {
foundStopChar = false;
}
if (parserState.typeFilter !== null) {
- throw ["Unexpected ", ":", " (expected path after type filter)"];
+ throw [
+ "Unexpected ",
+ ":",
+ " (expected path after type filter ",
+ parserState.typeFilter + ":",
+ ")",
+ ];
}
while (parserState.pos < parserState.length) {
if (isReturnArrow(parserState)) {
parserState.pos += 2;
+ skipWhitespace(parserState);
// Get returned elements.
getItemsBefore(query, parserState, query.returned, "");
// Nothing can come afterward!
@@ -778,9 +1018,10 @@ function initSearch(rawSearchIndex) {
* The supported syntax by this parser is as follow:
*
* ident = *(ALPHA / DIGIT / "_")
- * path = ident *(DOUBLE-COLON ident) [!]
- * arg = [type-filter *WS COLON *WS] path [generics]
- * type-sep = COMMA/WS *(COMMA/WS)
+ * path = ident *(DOUBLE-COLON/{WS} ident) [!]
+ * slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
+ * arg = [type-filter *WS COLON *WS] (path [generics] / slice)
+ * type-sep = *WS COMMA *(COMMA)
* nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
* generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
* CLOSE-ANGLE-BRACKET
@@ -821,6 +1062,8 @@ function initSearch(rawSearchIndex) {
*
* OPEN-ANGLE-BRACKET = "<"
* CLOSE-ANGLE-BRACKET = ">"
+ * OPEN-SQUARE-BRACKET = "["
+ * CLOSE-SQUARE-BRACKET = "]"
* COLON = ":"
* DOUBLE-COLON = "::"
* QUOTE = %x22
@@ -1103,98 +1346,182 @@ function initSearch(rawSearchIndex) {
}
/**
- * This function checks if the object (`row`) generics match the given type (`elem`)
- * generics. If there are no generics on `row`, `defaultDistance` is returned.
+ * This function checks generics in search query `queryElem` can all be found in the
+ * search index (`fnType`),
*
- * @param {Row} row - The object to check.
- * @param {QueryElement} elem - The element from the parsed query.
+ * @param {FunctionType} fnType - The object to check.
+ * @param {QueryElement} queryElem - The element from the parsed query.
*
- * @return {boolean} - Returns true if a match, false otherwise.
+ * @return {boolean} - Returns true if a match, false otherwise.
*/
- function checkGenerics(row, elem) {
- if (row.generics.length === 0 || elem.generics.length === 0) {
- return false;
- }
- // This function is called if the names match, but we need to make
- // sure that all generics match as well.
- //
+ function checkGenerics(fnType, queryElem) {
+ return unifyFunctionTypes(fnType.generics, queryElem.generics);
+ }
+ /**
+ * This function checks if a list of search query `queryElems` can all be found in the
+ * search index (`fnTypes`).
+ *
+ * @param {Array<FunctionType>} fnTypes - The objects to check.
+ * @param {Array<QueryElement>} queryElems - The elements from the parsed query.
+ *
+ * @return {boolean} - Returns true if a match, false otherwise.
+ */
+ function unifyFunctionTypes(fnTypes, queryElems) {
// This search engine implements order-agnostic unification. There
// should be no missing duplicates (generics have "bag semantics"),
// and the row is allowed to have extras.
- if (elem.generics.length > 0 && row.generics.length >= elem.generics.length) {
- const elems = new Map();
- const addEntryToElems = function addEntryToElems(entry) {
- if (entry.id === -1) {
- // Pure generic, needs to check into it.
- for (const inner_entry of entry.generics) {
- addEntryToElems(inner_entry);
- }
- return;
+ if (queryElems.length === 0) {
+ return true;
+ }
+ if (!fnTypes || fnTypes.length === 0) {
+ return false;
+ }
+ /**
+ * @type Map<integer, QueryElement[]>
+ */
+ const queryElemSet = new Map();
+ const addQueryElemToQueryElemSet = function addQueryElemToQueryElemSet(queryElem) {
+ let currentQueryElemList;
+ if (queryElemSet.has(queryElem.id)) {
+ currentQueryElemList = queryElemSet.get(queryElem.id);
+ } else {
+ currentQueryElemList = [];
+ queryElemSet.set(queryElem.id, currentQueryElemList);
+ }
+ currentQueryElemList.push(queryElem);
+ };
+ for (const queryElem of queryElems) {
+ addQueryElemToQueryElemSet(queryElem);
+ }
+ /**
+ * @type Map<integer, FunctionType[]>
+ */
+ const fnTypeSet = new Map();
+ const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) {
+ // Pure generic, or an item that's not matched by any query elems.
+ // Try [unboxing] it.
+ //
+ // [unboxing]:
+ // http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
+ const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice);
+ if (fnType.id === -1 || !(
+ queryElemSet.has(fnType.id) ||
+ (fnType.id === typeNameIdOfSlice && queryContainsArrayOrSliceElem) ||
+ (fnType.id === typeNameIdOfArray && queryContainsArrayOrSliceElem)
+ )) {
+ for (const innerFnType of fnType.generics) {
+ addFnTypeToFnTypeSet(innerFnType);
}
- let currentEntryElems;
- if (elems.has(entry.id)) {
- currentEntryElems = elems.get(entry.id);
- } else {
- currentEntryElems = [];
- elems.set(entry.id, currentEntryElems);
+ return;
+ }
+ let currentQueryElemList = queryElemSet.get(fnType.id) || [];
+ let matchIdx = currentQueryElemList.findIndex(queryElem => {
+ return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
+ checkGenerics(fnType, queryElem);
+ });
+ if (matchIdx === -1 &&
+ (fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) &&
+ queryContainsArrayOrSliceElem
+ ) {
+ currentQueryElemList = queryElemSet.get(typeNameIdOfArrayOrSlice) || [];
+ matchIdx = currentQueryElemList.findIndex(queryElem => {
+ return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
+ checkGenerics(fnType, queryElem);
+ });
+ }
+ // None of the query elems match the function type.
+ // Try [unboxing] it.
+ if (matchIdx === -1) {
+ for (const innerFnType of fnType.generics) {
+ addFnTypeToFnTypeSet(innerFnType);
}
- currentEntryElems.push(entry);
- };
- for (const entry of row.generics) {
- addEntryToElems(entry);
- }
- // We need to find the type that matches the most to remove it in order
- // to move forward.
- const handleGeneric = generic => {
- if (!elems.has(generic.id)) {
- return false;
+ return;
+ }
+ let currentFnTypeList;
+ if (fnTypeSet.has(fnType.id)) {
+ currentFnTypeList = fnTypeSet.get(fnType.id);
+ } else {
+ currentFnTypeList = [];
+ fnTypeSet.set(fnType.id, currentFnTypeList);
+ }
+ currentFnTypeList.push(fnType);
+ };
+ for (const fnType of fnTypes) {
+ addFnTypeToFnTypeSet(fnType);
+ }
+ const doHandleQueryElemList = (currentFnTypeList, queryElemList) => {
+ if (queryElemList.length === 0) {
+ return true;
+ }
+ // Multiple items in one list might match multiple items in another.
+ // Since an item with fewer generics can match an item with more, we
+ // need to check all combinations for a potential match.
+ const queryElem = queryElemList.pop();
+ const l = currentFnTypeList.length;
+ for (let i = 0; i < l; i += 1) {
+ const fnType = currentFnTypeList[i];
+ if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
+ continue;
}
- const matchElems = elems.get(generic.id);
- const matchIdx = matchElems.findIndex(tmp_elem => {
- if (generic.generics.length > 0 && !checkGenerics(tmp_elem, generic)) {
- return false;
+ if (queryElem.generics.length === 0 || checkGenerics(fnType, queryElem)) {
+ currentFnTypeList.splice(i, 1);
+ const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
+ if (result) {
+ return true;
}
- return typePassesFilter(generic.typeFilter, tmp_elem.ty);
- });
- if (matchIdx === -1) {
- return false;
+ currentFnTypeList.splice(i, 0, fnType);
}
- matchElems.splice(matchIdx, 1);
- if (matchElems.length === 0) {
- elems.delete(generic.id);
+ }
+ return false;
+ };
+ const handleQueryElemList = (id, queryElemList) => {
+ if (!fnTypeSet.has(id)) {
+ if (id === typeNameIdOfArrayOrSlice) {
+ return handleQueryElemList(typeNameIdOfSlice, queryElemList) ||
+ handleQueryElemList(typeNameIdOfArray, queryElemList);
}
- return true;
- };
- // To do the right thing with type filters, we first process generics
- // that have them, removing matching ones from the "bag," then do the
- // ones with no type filter, which can match any entry regardless of its
- // own type.
- for (const generic of elem.generics) {
- if (generic.typeFilter !== -1 && !handleGeneric(generic)) {
- return false;
+ return false;
+ }
+ const currentFnTypeList = fnTypeSet.get(id);
+ if (currentFnTypeList.length < queryElemList.length) {
+ // It's not possible for all the query elems to find a match.
+ return false;
+ }
+ const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
+ if (result) {
+ // Found a solution.
+ // Any items that weren't used for it can be unboxed, and might form
+ // part of the solution for another item.
+ for (const innerFnType of currentFnTypeList) {
+ addFnTypeToFnTypeSet(innerFnType);
}
+ fnTypeSet.delete(id);
}
- for (const generic of elem.generics) {
- if (generic.typeFilter === -1 && !handleGeneric(generic)) {
- return false;
+ return result;
+ };
+ let queryElemSetSize = -1;
+ while (queryElemSetSize !== queryElemSet.size) {
+ queryElemSetSize = queryElemSet.size;
+ for (const [id, queryElemList] of queryElemSet) {
+ if (handleQueryElemList(id, queryElemList)) {
+ queryElemSet.delete(id);
}
}
- return true;
}
- return false;
+ return queryElemSetSize === 0;
}
/**
* This function checks if the object (`row`) matches the given type (`elem`) and its
* generics (if any).
*
- * @param {Row} row
+ * @param {Array<FunctionType>} list
* @param {QueryElement} elem - The element from the parsed query.
*
* @return {boolean} - Returns true if found, false otherwise.
*/
- function checkIfInGenerics(row, elem) {
- for (const entry of row.generics) {
+ function checkIfInList(list, elem) {
+ for (const entry of list) {
if (checkType(entry, elem)) {
return true;
}
@@ -1214,10 +1541,15 @@ function initSearch(rawSearchIndex) {
function checkType(row, elem) {
if (row.id === -1) {
// This is a pure "generic" search, no need to run other checks.
- return row.generics.length > 0 ? checkIfInGenerics(row, elem) : false;
+ return row.generics.length > 0 ? checkIfInList(row.generics, elem) : false;
}
- if (row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty)) {
+ const matchesExact = row.id === elem.id;
+ const matchesArrayOrSlice = elem.id === typeNameIdOfArrayOrSlice &&
+ (row.id === typeNameIdOfSlice || row.id === typeNameIdOfArray);
+
+ if ((matchesExact || matchesArrayOrSlice) &&
+ typePassesFilter(elem.typeFilter, row.ty)) {
if (elem.generics.length > 0) {
return checkGenerics(row, elem);
}
@@ -1227,59 +1559,7 @@ function initSearch(rawSearchIndex) {
// If the current item does not match, try [unboxing] the generic.
// [unboxing]:
// https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
- return checkIfInGenerics(row, elem);
- }
-
- /**
- * This function checks if the object (`row`) has an argument with the given type (`elem`).
- *
- * @param {Row} row
- * @param {QueryElement} elem - The element from the parsed query.
- * @param {Array<integer>} skipPositions - Do not return one of these positions.
- *
- * @return {integer} - Returns the position of the match, or -1 if none.
- */
- function findArg(row, elem, skipPositions) {
- if (row && row.type && row.type.inputs && row.type.inputs.length > 0) {
- let i = 0;
- for (const input of row.type.inputs) {
- if (skipPositions.indexOf(i) !== -1) {
- i += 1;
- continue;
- }
- if (checkType(input, elem)) {
- return i;
- }
- i += 1;
- }
- }
- return -1;
- }
-
- /**
- * This function checks if the object (`row`) returns the given type (`elem`).
- *
- * @param {Row} row
- * @param {QueryElement} elem - The element from the parsed query.
- * @param {Array<integer>} skipPositions - Do not return one of these positions.
- *
- * @return {integer} - Returns the position of the matching item, or -1 if none.
- */
- function checkReturned(row, elem, skipPositions) {
- if (row && row.type && row.type.output.length > 0) {
- let i = 0;
- for (const ret_ty of row.type.output) {
- if (skipPositions.indexOf(i) !== -1) {
- i += 1;
- continue;
- }
- if (checkType(ret_ty, elem)) {
- return i;
- }
- i += 1;
- }
- }
- return -1;
+ return checkIfInList(row.generics, elem);
}
function checkPath(contains, ty, maxEditDistance) {
@@ -1480,14 +1760,14 @@ function initSearch(rawSearchIndex) {
const fullId = row.id;
const searchWord = searchWords[pos];
- const in_args = findArg(row, elem, []);
- if (in_args !== -1) {
+ const in_args = row.type && row.type.inputs && checkIfInList(row.type.inputs, elem);
+ if (in_args) {
// path_dist is 0 because no parent path information is currently stored
// in the search index
addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance);
}
- const returned = checkReturned(row, elem, []);
- if (returned !== -1) {
+ const returned = row.type && row.type.output && checkIfInList(row.type.output, elem);
+ if (returned) {
addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance);
}
@@ -1543,32 +1823,15 @@ function initSearch(rawSearchIndex) {
* @param {Object} results
*/
function handleArgs(row, pos, results) {
- if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
+ if (!row || (filterCrates !== null && row.crate !== filterCrates) || !row.type) {
return;
}
// If the result is too "bad", we return false and it ends this search.
- function checkArgs(elems, callback) {
- const skipPositions = [];
- for (const elem of elems) {
- // There is more than one parameter to the query so all checks should be "exact"
- const position = callback(
- row,
- elem,
- skipPositions
- );
- if (position !== -1) {
- skipPositions.push(position);
- } else {
- return false;
- }
- }
- return true;
- }
- if (!checkArgs(parsedQuery.elems, findArg)) {
+ if (!unifyFunctionTypes(row.type.inputs, parsedQuery.elems)) {
return;
}
- if (!checkArgs(parsedQuery.returned, checkReturned)) {
+ if (!unifyFunctionTypes(row.type.output, parsedQuery.returned)) {
return;
}
@@ -1655,12 +1918,9 @@ function initSearch(rawSearchIndex) {
elem = parsedQuery.returned[0];
for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
row = searchIndex[i];
- in_returned = checkReturned(
- row,
- elem,
- []
- );
- if (in_returned !== -1) {
+ in_returned = row.type &&
+ unifyFunctionTypes(row.type.output, parsedQuery.returned);
+ if (in_returned) {
addIntoResults(
results_others,
row.id,
@@ -1836,16 +2096,11 @@ function initSearch(rawSearchIndex) {
array.forEach(item => {
const name = item.name;
const type = itemTypes[item.ty];
+ const longType = longItemTypes[item.ty];
+ const typeName = longType.length !== 0 ? `${longType}` : "?";
length += 1;
- let extra = "";
- if (type === "primitive") {
- extra = " <i>(primitive type)</i>";
- } else if (type === "keyword") {
- extra = " <i>(keyword)</i>";
- }
-
const link = document.createElement("a");
link.className = "result-" + type;
link.href = item.href;
@@ -1863,13 +2118,18 @@ function initSearch(rawSearchIndex) {
alias.insertAdjacentHTML(
"beforeend",
- "<span class=\"grey\"><i>&nbsp;- see&nbsp;</i></span>");
+ "<i class=\"grey\">&nbsp;- see&nbsp;</i>");
resultName.appendChild(alias);
}
+
resultName.insertAdjacentHTML(
"beforeend",
- item.displayPath + "<span class=\"" + type + "\">" + name + extra + "</span>");
+ `\
+<span class="typename">${typeName}</span>\
+<div class="path">\
+ ${item.displayPath}<span class="${type}">${name}</span>\
+</div>`);
link.appendChild(resultName);
const description = document.createElement("div");
@@ -1916,6 +2176,20 @@ function initSearch(rawSearchIndex) {
if (go_to_first || (results.others.length === 1
&& getSettingValue("go-to-only-result") === "true")
) {
+ // Needed to force re-execution of JS when coming back to a page. Let's take this
+ // scenario as example:
+ //
+ // 1. You have the "Directly go to item in search if there is only one result" option
+ // enabled.
+ // 2. You make a search which results only one result, leading you automatically to
+ // this result.
+ // 3. You go back to previous page.
+ //
+ // Now, without the call below, the JS will not be re-executed and the previous state
+ // will be used, starting search again since the search input is not empty, leading you
+ // back to the previous page again.
+ window.onunload = () => {};
+ searchState.removeQueryParameters();
const elem = document.createElement("a");
elem.href = results.others[0].href;
removeClass(elem, "active");
@@ -1967,7 +2241,7 @@ function initSearch(rawSearchIndex) {
error.forEach((value, index) => {
value = value.split("<").join("&lt;").split(">").join("&gt;");
if (index % 2 !== 0) {
- error[index] = `<code>${value}</code>`;
+ error[index] = `<code>${value.replaceAll(" ", "&nbsp;")}</code>`;
} else {
error[index] = value;
}
@@ -2030,6 +2304,18 @@ function initSearch(rawSearchIndex) {
printTab(currentTab);
}
+ function updateSearchHistory(url) {
+ if (!browserSupportsHistoryApi()) {
+ return;
+ }
+ const params = searchState.getQueryStringParams();
+ if (!history.state && !params.search) {
+ history.pushState(null, "", url);
+ } else {
+ history.replaceState(null, "", url);
+ }
+ }
+
/**
* Perform a search based on the current state of the search input element
* and display the results.
@@ -2040,7 +2326,6 @@ function initSearch(rawSearchIndex) {
if (e) {
e.preventDefault();
}
-
const query = parseQuery(searchState.input.value.trim());
let filterCrates = getFilterCrates();
@@ -2066,15 +2351,7 @@ function initSearch(rawSearchIndex) {
// Because searching is incremental by character, only the most
// recent search query is added to the browser history.
- if (browserSupportsHistoryApi()) {
- const newURL = buildUrl(query.original, filterCrates);
-
- if (!history.state && !params.search) {
- history.pushState(null, "", newURL);
- } else {
- history.replaceState(null, "", newURL);
- }
- }
+ updateSearchHistory(buildUrl(query.original, filterCrates));
showResults(
execQuery(query, searchWords, filterCrates, window.currentCrate),
@@ -2083,34 +2360,6 @@ function initSearch(rawSearchIndex) {
}
/**
- * Add an item to the type Name->ID map, or, if one already exists, use it.
- * Returns the number. If name is "" or null, return -1 (pure generic).
- *
- * This is effectively string interning, so that function matching can be
- * done more quickly. Two types with the same name but different item kinds
- * get the same ID.
- *
- * @param {Map<string, integer>} typeNameIdMap
- * @param {string} name
- *
- * @returns {integer}
- */
- function buildTypeMapIndex(typeNameIdMap, name) {
-
- if (name === "" || name === null) {
- return -1;
- }
-
- if (typeNameIdMap.has(name)) {
- return typeNameIdMap.get(name);
- } else {
- const id = typeNameIdMap.size;
- typeNameIdMap.set(name, id);
- return id;
- }
- }
-
- /**
* Convert a list of RawFunctionType / ID to object-based FunctionType.
*
* Crates often have lots of functions in them, and it's common to have a large number of
@@ -2128,7 +2377,7 @@ function initSearch(rawSearchIndex) {
*
* @return {Array<FunctionSearchType>}
*/
- function buildItemSearchTypeAll(types, lowercasePaths, typeNameIdMap) {
+ function buildItemSearchTypeAll(types, lowercasePaths) {
const PATH_INDEX_DATA = 0;
const GENERICS_DATA = 1;
return types.map(type => {
@@ -2140,15 +2389,14 @@ function initSearch(rawSearchIndex) {
pathIndex = type[PATH_INDEX_DATA];
generics = buildItemSearchTypeAll(
type[GENERICS_DATA],
- lowercasePaths,
- typeNameIdMap
+ lowercasePaths
);
}
return {
// `0` is used as a sentinel because it's fewer bytes than `null`
id: pathIndex === 0
? -1
- : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
+ : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
generics: generics,
};
@@ -2171,7 +2419,7 @@ function initSearch(rawSearchIndex) {
*
* @return {null|FunctionSearchType}
*/
- function buildFunctionSearchType(functionSearchType, lowercasePaths, typeNameIdMap) {
+ function buildFunctionSearchType(functionSearchType, lowercasePaths) {
const INPUTS_DATA = 0;
const OUTPUT_DATA = 1;
// `0` is used as a sentinel because it's fewer bytes than `null`
@@ -2184,15 +2432,14 @@ function initSearch(rawSearchIndex) {
inputs = [{
id: pathIndex === 0
? -1
- : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
+ : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
generics: [],
}];
} else {
inputs = buildItemSearchTypeAll(
functionSearchType[INPUTS_DATA],
- lowercasePaths,
- typeNameIdMap
+ lowercasePaths
);
}
if (functionSearchType.length > 1) {
@@ -2201,15 +2448,14 @@ function initSearch(rawSearchIndex) {
output = [{
id: pathIndex === 0
? -1
- : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
+ : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name),
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
generics: [],
}];
} else {
output = buildItemSearchTypeAll(
functionSearchType[OUTPUT_DATA],
- lowercasePaths,
- typeNameIdMap
+ lowercasePaths
);
}
} else {
@@ -2233,6 +2479,12 @@ function initSearch(rawSearchIndex) {
let currentIndex = 0;
let id = 0;
+ // Initialize type map indexes for primitive list types
+ // that can be searched using `[]` syntax.
+ typeNameIdOfArray = buildTypeMapIndex("array");
+ typeNameIdOfSlice = buildTypeMapIndex("slice");
+ typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
+
for (const crate in rawSearchIndex) {
if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) {
continue;
@@ -2363,8 +2615,7 @@ function initSearch(rawSearchIndex) {
parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
type: buildFunctionSearchType(
itemFunctionSearchTypes[i],
- lowercasePaths,
- typeNameIdMap
+ lowercasePaths
),
id: id,
normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
@@ -2566,13 +2817,8 @@ function initSearch(rawSearchIndex) {
function updateCrate(ev) {
if (ev.target.value === "all crates") {
// If we don't remove it from the URL, it'll be picked up again by the search.
- const params = searchState.getQueryStringParams();
const query = searchState.input.value.trim();
- if (!history.state && !params.search) {
- history.pushState(null, "", buildUrl(query, null));
- } else {
- history.replaceState(null, "", buildUrl(query, null));
- }
+ updateSearchHistory(buildUrl(query, null));
}
// In case you "cut" the entry from the search input, then change the crate filter
// before paste back the previous search, you get the old search results without
diff --git a/src/librustdoc/html/static/js/source-script.js b/src/librustdoc/html/static/js/source-script.js
index d999f3b36..6eb991360 100644
--- a/src/librustdoc/html/static/js/source-script.js
+++ b/src/librustdoc/html/static/js/source-script.js
@@ -3,13 +3,13 @@
// Local js definitions:
/* global addClass, getCurrentValue, onEachLazy, removeClass, browserSupportsHistoryApi */
-/* global updateLocalStorage */
+/* global updateLocalStorage, getVar */
"use strict";
(function() {
-const rootPath = document.getElementById("rustdoc-vars").attributes["data-root-path"].value;
+const rootPath = getVar("root-path");
const NAME_OFFSET = 0;
const DIRS_OFFSET = 1;
diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js
index 93979a944..71961f6f2 100644
--- a/src/librustdoc/html/static/js/storage.js
+++ b/src/librustdoc/html/static/js/storage.js
@@ -108,7 +108,7 @@ function getCurrentValue(name) {
// Get a value from the rustdoc-vars div, which is used to convey data from
// Rust to the JS. If there is no such element, return null.
const getVar = (function getVar(name) {
- const el = document.getElementById("rustdoc-vars");
+ const el = document.querySelector("head > meta[name='rustdoc-vars']");
return el ? el.attributes["data-" + name].value : null;
});
diff --git a/src/librustdoc/html/templates/item_info.html b/src/librustdoc/html/templates/item_info.html
index d2ea9bdae..9e65ae95e 100644
--- a/src/librustdoc/html/templates/item_info.html
+++ b/src/librustdoc/html/templates/item_info.html
@@ -1,5 +1,5 @@
{% if !items.is_empty() %}
- <span class="item-info"> {# #}
+ <span class="item-info">
{% for item in items %}
{{item|safe}} {# #}
{% endfor %}
diff --git a/src/librustdoc/html/templates/item_union.html b/src/librustdoc/html/templates/item_union.html
index c21967005..f6d2fa348 100644
--- a/src/librustdoc/html/templates/item_union.html
+++ b/src/librustdoc/html/templates/item_union.html
@@ -1,17 +1,18 @@
<pre class="rust item-decl"><code>
- {{ self::item_template_render_attributes_in_pre(self.borrow()) | safe }}
+ {{ self.render_attributes_in_pre() | safe }}
{{ self.render_union() | safe }}
</code></pre>
-{{ self::item_template_document(self.borrow()) | safe }}
+{{ self.document() | safe }}
{% if self.fields_iter().peek().is_some() %}
- <h2 id="fields" class="fields small-section-header">
- Fields<a href="#fields" class="anchor">§</a>
+ <h2 id="fields" class="fields small-section-header"> {# #}
+ Fields<a href="#fields" class="anchor">§</a> {# #}
</h2>
{% for (field, ty) in self.fields_iter() %}
{% let name = field.name.expect("union field name") %}
- <span id="structfield.{{ name }}" class="{{ ItemType::StructField }} small-section-header">
- <a href="#structfield.{{ name }}" class="anchor field">§</a>
- <code>{{ name }}: {{ self.print_ty(ty) | safe }}</code>
+ <span id="structfield.{{ name }}" {#+ #}
+ class="{{ ItemType::StructField +}} small-section-header"> {# #}
+ <a href="#structfield.{{ name }}" class="anchor field">§</a> {# #}
+ <code>{{ name }}: {{+ self.print_ty(ty) | safe }}</code> {# #}
</span>
{% if let Some(stability_class) = self.stability_field(field) %}
<span class="stab {{ stability_class }}"></span>
@@ -19,5 +20,5 @@
{{ self.document_field(field) | safe }}
{% endfor %}
{% endif %}
-{{ self::item_template_render_assoc_items(self.borrow()) | safe }}
-{{ self::item_template_document_type_layout(self.borrow()) | safe }}
+{{ self.render_assoc_items() | safe }}
+{{ self.document_type_layout() | safe }}
diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html
index 9133f899a..d4ec9c34b 100644
--- a/src/librustdoc/html/templates/page.html
+++ b/src/librustdoc/html/templates/page.html
@@ -24,13 +24,14 @@
{% endfor %}
></script> {# #}
{% endif %}
- <div id="rustdoc-vars" {#+ #}
+ <meta name="rustdoc-vars" {#+ #}
data-root-path="{{page.root_path|safe}}" {#+ #}
data-static-root-path="{{static_root_path|safe}}" {#+ #}
data-current-crate="{{layout.krate}}" {#+ #}
data-themes="{{themes|join(",") }}" {#+ #}
data-resource-suffix="{{page.resource_suffix}}" {#+ #}
data-rustdoc-version="{{rustdoc_version}}" {#+ #}
+ data-channel="{{rust_channel}}" {#+ #}
data-search-js="{{files.search_js}}" {#+ #}
data-settings-js="{{files.settings_js}}" {#+ #}
data-settings-css="{{files.settings_css}}" {#+ #}
@@ -38,7 +39,6 @@
data-theme-dark-css="{{files.theme_dark_css}}" {#+ #}
data-theme-ayu-css="{{files.theme_ayu_css}}" {#+ #}
> {# #}
- </div> {# #}
<script src="{{static_root_path|safe}}{{files.storage_js}}"></script> {# #}
{% if page.css_class.contains("crate") %}
<script defer src="{{page.root_path|safe}}crates{{page.resource_suffix}}.js"></script> {# #}
diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html
index edabac9a0..68a295ae0 100644
--- a/src/librustdoc/html/templates/print_item.html
+++ b/src/librustdoc/html/templates/print_item.html
@@ -1,5 +1,5 @@
<div class="main-heading"> {# #}
- <h1> {# #}
+ <h1>
{{typ}}
{# The breadcrumbs of the item path, like std::string #}
{% for component in path_components %}
@@ -12,7 +12,7 @@
alt="Copy item path"> {# #}
</button> {# #}
</h1> {# #}
- <span class="out-of-band"> {# #}
+ <span class="out-of-band">
{% if !stability_since_raw.is_empty() %}
{{ stability_since_raw|safe +}} · {#+ #}
{% endif %}