diff options
Diffstat (limited to 'src/librustdoc/html')
23 files changed, 1208 insertions, 874 deletions
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index b6a73602a32..91b4b3ba1eb 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -30,120 +30,15 @@ use super::url_parts_builder::{UrlPartsBuilder, estimate_item_path_byte_length}; use crate::clean::types::ExternalLocation; use crate::clean::utils::find_nearest_parent_module; use crate::clean::{self, ExternalCrate, PrimitiveType}; +use crate::display::Joined as _; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::escape::{Escape, EscapeBodyText}; use crate::html::render::Context; -use crate::joined::Joined as _; use crate::passes::collect_intra_doc_links::UrlFragment; -pub(crate) trait Print { - fn print(self, buffer: &mut Buffer); -} - -impl<F> Print for F -where - F: FnOnce(&mut Buffer), -{ - fn print(self, buffer: &mut Buffer) { - (self)(buffer) - } -} - -impl Print for String { - fn print(self, buffer: &mut Buffer) { - buffer.write_str(&self); - } -} - -impl Print for &'_ str { - fn print(self, buffer: &mut Buffer) { - buffer.write_str(self); - } -} - -#[derive(Debug, Clone)] -pub(crate) struct Buffer { - for_html: bool, - buffer: String, -} - -impl core::fmt::Write for Buffer { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.buffer.write_str(s) - } - - #[inline] - fn write_char(&mut self, c: char) -> fmt::Result { - self.buffer.write_char(c) - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { - self.buffer.write_fmt(args) - } -} - -impl Buffer { - pub(crate) fn empty_from(v: &Buffer) -> Buffer { - Buffer { for_html: v.for_html, buffer: String::new() } - } - - pub(crate) fn html() -> Buffer { - Buffer { for_html: true, buffer: String::new() } - } - - pub(crate) fn new() -> Buffer { - Buffer { for_html: false, buffer: String::new() } - } - - pub(crate) fn is_empty(&self) -> bool { - self.buffer.is_empty() - } - - pub(crate) fn into_inner(self) -> String { - self.buffer - } - - pub(crate) fn push(&mut self, c: char) { - self.buffer.push(c); - } - - pub(crate) fn push_str(&mut self, s: &str) { - self.buffer.push_str(s); - } - - pub(crate) fn push_buffer(&mut self, other: Buffer) { - self.buffer.push_str(&other.buffer); - } - - // Intended for consumption by write! and writeln! (std::fmt) but without - // the fmt::Result return type imposed by fmt::Write (and avoiding the trait - // import). - pub(crate) fn write_str(&mut self, s: &str) { - self.buffer.push_str(s); - } - - // Intended for consumption by write! and writeln! (std::fmt) but without - // the fmt::Result return type imposed by fmt::Write (and avoiding the trait - // import). - pub(crate) fn write_fmt(&mut self, v: fmt::Arguments<'_>) { - self.buffer.write_fmt(v).unwrap(); - } - - pub(crate) fn to_display<T: Print>(mut self, t: T) -> String { - t.print(&mut self); - self.into_inner() - } - - pub(crate) fn reserve(&mut self, additional: usize) { - self.buffer.reserve(additional) - } - - pub(crate) fn len(&self) -> usize { - self.buffer.len() - } +pub(crate) fn write_str(s: &mut String, f: fmt::Arguments<'_>) { + s.write_fmt(f).unwrap(); } pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>( @@ -772,7 +667,7 @@ pub(crate) fn link_tooltip(did: DefId, fragment: &Option<UrlFragment>, cx: &Cont else { return String::new(); }; - let mut buf = Buffer::new(); + let mut buf = String::new(); let fqp = if *shortty == ItemType::Primitive { // primitives are documented in a crate, but not actually part of it &fqp[fqp.len() - 1..] @@ -780,19 +675,19 @@ pub(crate) fn link_tooltip(did: DefId, fragment: &Option<UrlFragment>, cx: &Cont fqp }; if let &Some(UrlFragment::Item(id)) = fragment { - write!(buf, "{} ", cx.tcx().def_descr(id)); + write_str(&mut buf, format_args!("{} ", cx.tcx().def_descr(id))); for component in fqp { - write!(buf, "{component}::"); + write_str(&mut buf, format_args!("{component}::")); } - write!(buf, "{}", cx.tcx().item_name(id)); + write_str(&mut buf, format_args!("{}", cx.tcx().item_name(id))); } else if !fqp.is_empty() { let mut fqp_it = fqp.iter(); - write!(buf, "{shortty} {}", fqp_it.next().unwrap()); + write_str(&mut buf, format_args!("{shortty} {}", fqp_it.next().unwrap())); for component in fqp_it { - write!(buf, "::{component}"); + write_str(&mut buf, format_args!("::{component}")); } } - buf.into_inner() + buf } /// Used to render a [`clean::Path`]. @@ -814,19 +709,22 @@ fn resolved_path( if w.alternate() { write!(w, "{}{:#}", last.name, last.args.print(cx))?; } else { - let path = if use_absolute { - if let Ok((_, _, fqp)) = href(did, cx) { - format!( - "{path}::{anchor}", - path = join_with_double_colon(&fqp[..fqp.len() - 1]), - anchor = anchor(did, *fqp.last().unwrap(), cx) - ) + let path = fmt::from_fn(|f| { + if use_absolute { + if let Ok((_, _, fqp)) = href(did, cx) { + write!( + f, + "{path}::{anchor}", + path = join_with_double_colon(&fqp[..fqp.len() - 1]), + anchor = anchor(did, *fqp.last().unwrap(), cx) + ) + } else { + write!(f, "{}", last.name) + } } else { - last.name.to_string() + write!(f, "{}", anchor(did, last.name, cx)) } - } else { - anchor(did, last.name, cx).to_string() - }; + }); write!(w, "{path}{args}", args = last.args.print(cx))?; } Ok(()) @@ -854,16 +752,20 @@ fn primitive_link_fragment( match m.primitive_locations.get(&prim) { Some(&def_id) if def_id.is_local() => { let len = cx.current.len(); - let path = if len == 0 { - let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx()); - format!("{cname_sym}/") - } else { - "../".repeat(len - 1) - }; + let path = fmt::from_fn(|f| { + if len == 0 { + let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx()); + write!(f, "{cname_sym}/")?; + } else { + for _ in 0..(len - 1) { + f.write_str("../")?; + } + } + Ok(()) + }); write!( f, - "<a class=\"primitive\" href=\"{}primitive.{}.html{fragment}\">", - path, + "<a class=\"primitive\" href=\"{path}primitive.{}.html{fragment}\">", prim.as_sym() )?; needs_termination = true; diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 7b2aee4b4a5..ed4b97d3625 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -14,7 +14,7 @@ use rustc_span::edition::Edition; use rustc_span::symbol::Symbol; use rustc_span::{BytePos, DUMMY_SP, Span}; -use super::format::{self, Buffer}; +use super::format::{self, write_str}; use crate::clean::PrimitiveType; use crate::html::escape::EscapeBodyText; use crate::html::render::{Context, LinkFromSrc}; @@ -48,64 +48,80 @@ pub(crate) enum Tooltip { /// Highlights `src` as an inline example, returning the HTML output. pub(crate) fn render_example_with_highlighting( src: &str, - out: &mut Buffer, + out: &mut String, tooltip: Tooltip, playground_button: Option<&str>, extra_classes: &[String], ) { write_header(out, "rust-example-rendered", None, tooltip, extra_classes); - write_code(out, src, None, None); + write_code(out, src, None, None, None); write_footer(out, playground_button); } fn write_header( - out: &mut Buffer, + out: &mut String, class: &str, - extra_content: Option<Buffer>, + extra_content: Option<&str>, tooltip: Tooltip, extra_classes: &[String], ) { - write!(out, "<div class=\"example-wrap{}\">", match tooltip { - Tooltip::Ignore => " ignore", - Tooltip::CompileFail => " compile_fail", - Tooltip::ShouldPanic => " should_panic", - Tooltip::Edition(_) => " edition", - Tooltip::None => "", - },); + write_str( + out, + format_args!( + "<div class=\"example-wrap{}\">", + match tooltip { + Tooltip::Ignore => " ignore", + Tooltip::CompileFail => " compile_fail", + Tooltip::ShouldPanic => " should_panic", + Tooltip::Edition(_) => " edition", + Tooltip::None => "", + } + ), + ); if tooltip != Tooltip::None { let edition_code; - write!(out, "<a href=\"#\" class=\"tooltip\" title=\"{}\">ⓘ</a>", match tooltip { - Tooltip::Ignore => "This example is not tested", - Tooltip::CompileFail => "This example deliberately fails to compile", - Tooltip::ShouldPanic => "This example panics", - Tooltip::Edition(edition) => { - edition_code = format!("This example runs with edition {edition}"); - &edition_code - } - Tooltip::None => unreachable!(), - },); + write_str( + out, + format_args!( + "<a href=\"#\" class=\"tooltip\" title=\"{}\">ⓘ</a>", + match tooltip { + Tooltip::Ignore => "This example is not tested", + Tooltip::CompileFail => "This example deliberately fails to compile", + Tooltip::ShouldPanic => "This example panics", + Tooltip::Edition(edition) => { + edition_code = format!("This example runs with edition {edition}"); + &edition_code + } + Tooltip::None => unreachable!(), + } + ), + ); } if let Some(extra) = extra_content { - out.push_buffer(extra); + out.push_str(&extra); } if class.is_empty() { - write!( + write_str( out, - "<pre class=\"rust{}{}\">", - if extra_classes.is_empty() { "" } else { " " }, - extra_classes.join(" "), + format_args!( + "<pre class=\"rust{}{}\">", + if extra_classes.is_empty() { "" } else { " " }, + extra_classes.join(" ") + ), ); } else { - write!( + write_str( out, - "<pre class=\"rust {class}{}{}\">", - if extra_classes.is_empty() { "" } else { " " }, - extra_classes.join(" "), + format_args!( + "<pre class=\"rust {class}{}{}\">", + if extra_classes.is_empty() { "" } else { " " }, + extra_classes.join(" ") + ), ); } - write!(out, "<code>"); + write_str(out, format_args!("<code>")); } /// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None` @@ -142,6 +158,7 @@ struct TokenHandler<'a, 'tcx, F: Write> { /// used to generate links. pending_elems: Vec<(&'a str, Option<Class>)>, href_context: Option<HrefContext<'a, 'tcx>>, + write_line_number: fn(&mut F, u32, &'static str), } impl<F: Write> TokenHandler<'_, '_, F> { @@ -174,7 +191,14 @@ impl<F: Write> TokenHandler<'_, '_, F> { && can_merge(current_class, Some(*parent_class), "") { for (text, class) in self.pending_elems.iter() { - string(self.out, EscapeBodyText(text), *class, &self.href_context, false); + string( + self.out, + EscapeBodyText(text), + *class, + &self.href_context, + false, + self.write_line_number, + ); } } else { // We only want to "open" the tag ourselves if we have more than one pending and if the @@ -196,6 +220,7 @@ impl<F: Write> TokenHandler<'_, '_, F> { *class, &self.href_context, close_tag.is_none(), + self.write_line_number, ); } if let Some(close_tag) = close_tag { @@ -205,6 +230,11 @@ impl<F: Write> TokenHandler<'_, '_, F> { self.pending_elems.clear(); true } + + #[inline] + fn write_line_number(&mut self, line: u32, extra: &'static str) { + (self.write_line_number)(&mut self.out, line, extra); + } } impl<F: Write> Drop for TokenHandler<'_, '_, F> { @@ -218,6 +248,43 @@ impl<F: Write> Drop for TokenHandler<'_, '_, F> { } } +fn write_scraped_line_number(out: &mut impl Write, line: u32, extra: &'static str) { + // https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr + // Do not show "1 2 3 4 5 ..." in web search results. + write!(out, "{extra}<span data-nosnippet>{line}</span>",).unwrap(); +} + +fn write_line_number(out: &mut impl Write, line: u32, extra: &'static str) { + // https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr + // Do not show "1 2 3 4 5 ..." in web search results. + write!(out, "{extra}<a href=#{line} id={line} data-nosnippet>{line}</a>",).unwrap(); +} + +fn empty_line_number(out: &mut impl Write, _: u32, extra: &'static str) { + out.write_str(extra).unwrap(); +} + +#[derive(Clone, Copy)] +pub(super) struct LineInfo { + pub(super) start_line: u32, + max_lines: u32, + pub(super) is_scraped_example: bool, +} + +impl LineInfo { + pub(super) fn new(max_lines: u32) -> Self { + Self { start_line: 1, max_lines: max_lines + 1, is_scraped_example: false } + } + + pub(super) fn new_scraped(max_lines: u32, start_line: u32) -> Self { + Self { + start_line: start_line + 1, + max_lines: max_lines + start_line + 1, + is_scraped_example: true, + } + } +} + /// Convert the given `src` source code into HTML by adding classes for highlighting. /// /// This code is used to render code blocks (in the documentation) as well as the source code pages. @@ -234,6 +301,7 @@ pub(super) fn write_code( src: &str, href_context: Option<HrefContext<'_, '_>>, decoration_info: Option<&DecorationInfo>, + line_info: Option<LineInfo>, ) { // This replace allows to fix how the code source with DOS backline characters is displayed. let src = src.replace("\r\n", "\n"); @@ -244,6 +312,23 @@ pub(super) fn write_code( current_class: None, pending_elems: Vec::new(), href_context, + write_line_number: match line_info { + Some(line_info) => { + if line_info.is_scraped_example { + write_scraped_line_number + } else { + write_line_number + } + } + None => empty_line_number, + }, + }; + + let (mut line, max_lines) = if let Some(line_info) = line_info { + token_handler.write_line_number(line_info.start_line, ""); + (line_info.start_line, line_info.max_lines) + } else { + (0, u32::MAX) }; Classifier::new( @@ -274,7 +359,14 @@ pub(super) fn write_code( if need_current_class_update { token_handler.current_class = class.map(Class::dummy); } - token_handler.pending_elems.push((text, class)); + if text == "\n" { + line += 1; + if line < max_lines { + token_handler.pending_elems.push((text, Some(Class::Backline(line)))); + } + } else { + token_handler.pending_elems.push((text, class)); + } } Highlight::EnterSpan { class } => { let mut should_add = true; @@ -314,8 +406,8 @@ pub(super) fn write_code( }); } -fn write_footer(out: &mut Buffer, playground_button: Option<&str>) { - writeln!(out, "</code></pre>{}</div>", playground_button.unwrap_or_default()); +fn write_footer(out: &mut String, playground_button: Option<&str>) { + write_str(out, format_args_nl!("</code></pre>{}</div>", playground_button.unwrap_or_default())); } /// How a span of text is classified. Mostly corresponds to token kinds. @@ -340,6 +432,7 @@ enum Class { PreludeVal(Span), QuestionMark, Decoration(&'static str), + Backline(u32), } impl Class { @@ -388,6 +481,7 @@ impl Class { Class::PreludeVal(_) => "prelude-val", Class::QuestionMark => "question-mark", Class::Decoration(kind) => kind, + Class::Backline(_) => "", } } @@ -411,7 +505,8 @@ impl Class { | Self::Bool | Self::Lifetime | Self::QuestionMark - | Self::Decoration(_) => None, + | Self::Decoration(_) + | Self::Backline(_) => None, } } } @@ -686,8 +781,13 @@ impl<'src> Classifier<'src> { ) { let lookahead = self.peek(); let no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None }); + let whitespace = |sink: &mut dyn FnMut(_)| { + for part in text.split('\n').intersperse("\n").filter(|s| !s.is_empty()) { + sink(Highlight::Token { text: part, class: None }); + } + }; let class = match token { - TokenKind::Whitespace => return no_highlight(sink), + TokenKind::Whitespace => return whitespace(sink), TokenKind::LineComment { doc_style } | TokenKind::BlockComment { doc_style, .. } => { if doc_style.is_some() { Class::DocComment @@ -708,7 +808,7 @@ impl<'src> Classifier<'src> { // or a reference or pointer type. Unless, of course, it looks like // a logical and or a multiplication operator: `&&` or `* `. TokenKind::Star => match self.tokens.peek() { - Some((TokenKind::Whitespace, _)) => return no_highlight(sink), + Some((TokenKind::Whitespace, _)) => return whitespace(sink), Some((TokenKind::Ident, "mut")) => { self.next(); sink(Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) }); @@ -732,7 +832,7 @@ impl<'src> Classifier<'src> { sink(Highlight::Token { text: "&=", class: None }); return; } - Some((TokenKind::Whitespace, _)) => return no_highlight(sink), + Some((TokenKind::Whitespace, _)) => return whitespace(sink), Some((TokenKind::Ident, "mut")) => { self.next(); sink(Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) }); @@ -879,7 +979,9 @@ impl<'src> Classifier<'src> { }; // Anything that didn't return above is the simple case where we the // class just spans a single token, so we can use the `string` method. - sink(Highlight::Token { text, class: Some(class) }); + for part in text.split('\n').intersperse("\n").filter(|s| !s.is_empty()) { + sink(Highlight::Token { text: part, class: Some(class) }); + } } fn peek(&mut self) -> Option<TokenKind> { @@ -931,14 +1033,18 @@ fn exit_span(out: &mut impl Write, closing_tag: &str) { /// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function /// will then try to find this `span` in the `span_correspondence_map`. If found, it'll then /// generate a link for this element (which corresponds to where its definition is located). -fn string<T: Display>( - out: &mut impl Write, +fn string<T: Display, W: Write>( + out: &mut W, text: T, klass: Option<Class>, href_context: &Option<HrefContext<'_, '_>>, open_tag: bool, + write_line_number_callback: fn(&mut W, u32, &'static str), ) { - if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context, open_tag) + if let Some(Class::Backline(line)) = klass { + write_line_number_callback(out, line, "\n"); + } else if let Some(closing_tag) = + string_without_closing_tag(out, text, klass, href_context, open_tag) { out.write_str(closing_tag).unwrap(); } diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs index fccbb98f80f..2603e887bea 100644 --- a/src/librustdoc/html/highlight/tests.rs +++ b/src/librustdoc/html/highlight/tests.rs @@ -3,7 +3,6 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_span::create_default_session_globals_then; use super::{DecorationInfo, write_code}; -use crate::html::format::Buffer; const STYLE: &str = r#" <style> @@ -22,9 +21,9 @@ fn test_html_highlighting() { create_default_session_globals_then(|| { let src = include_str!("fixtures/sample.rs"); let html = { - let mut out = Buffer::new(); - write_code(&mut out, src, None, None); - format!("{STYLE}<pre><code>{}</code></pre>\n", out.into_inner()) + let mut out = String::new(); + write_code(&mut out, src, None, None, None); + format!("{STYLE}<pre><code>{out}</code></pre>\n") }; expect_file!["fixtures/sample.html"].assert_eq(&html); }); @@ -36,9 +35,9 @@ fn test_dos_backline() { let src = "pub fn foo() {\r\n\ println!(\"foo\");\r\n\ }\r\n"; - let mut html = Buffer::new(); - write_code(&mut html, src, None, None); - expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner()); + let mut html = String::new(); + write_code(&mut html, src, None, None, None); + expect_file!["fixtures/dos_line.html"].assert_eq(&html); }); } @@ -50,9 +49,9 @@ use self::whatever; let x = super::b::foo; let y = Self::whatever;"; - let mut html = Buffer::new(); - write_code(&mut html, src, None, None); - expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner()); + let mut html = String::new(); + write_code(&mut html, src, None, None, None); + expect_file!["fixtures/highlight.html"].assert_eq(&html); }); } @@ -60,9 +59,9 @@ let y = Self::whatever;"; fn test_union_highlighting() { create_default_session_globals_then(|| { let src = include_str!("fixtures/union.rs"); - let mut html = Buffer::new(); - write_code(&mut html, src, None, None); - expect_file!["fixtures/union.html"].assert_eq(&html.into_inner()); + let mut html = String::new(); + write_code(&mut html, src, None, None, None); + expect_file!["fixtures/union.html"].assert_eq(&html); }); } @@ -77,8 +76,8 @@ let a = 4;"; decorations.insert("example", vec![(0, 10), (11, 21)]); decorations.insert("example2", vec![(22, 32)]); - let mut html = Buffer::new(); - write_code(&mut html, src, None, Some(&DecorationInfo(decorations))); - expect_file!["fixtures/decorations.html"].assert_eq(&html.into_inner()); + let mut html = String::new(); + write_code(&mut html, src, None, Some(&DecorationInfo(decorations)), None); + expect_file!["fixtures/decorations.html"].assert_eq(&html); }); } diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index d957cf1b569..df70df062fe 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -1,3 +1,4 @@ +use std::fmt::{self, Display}; use std::path::PathBuf; use rinja::Template; @@ -5,7 +6,6 @@ use rustc_data_structures::fx::FxIndexMap; use super::static_files::{STATIC_FILES, StaticFiles}; use crate::externalfiles::ExternalHtml; -use crate::html::format::{Buffer, Print}; use crate::html::render::{StylePath, ensure_trailing_slash}; #[derive(Clone)] @@ -71,7 +71,24 @@ struct PageLayout<'a> { pub(crate) use crate::html::render::sidebar::filters; -pub(crate) fn render<T: Print, S: Print>( +/// Implements [`Display`] for a function that accepts a mutable reference to a [`String`], and (optionally) writes to it. +/// +/// The wrapped function will receive an empty string, and can modify it, +/// and the `Display` implementation will write the contents of the string after the function has finished. +pub(crate) struct BufDisplay<F>(pub F); + +impl<F> Display for BufDisplay<F> +where + F: Fn(&mut String), +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut buf = String::new(); + self.0(&mut buf); + f.write_str(&buf) + } +} + +pub(crate) fn render<T: Display, S: Display>( layout: &Layout, page: &Page<'_>, sidebar: S, @@ -98,8 +115,8 @@ pub(crate) fn render<T: Print, S: Print>( let mut themes: Vec<String> = style_files.iter().map(|s| s.basename().unwrap()).collect(); themes.sort(); - let content = Buffer::html().to_display(t); // Note: This must happen before making the sidebar. - let sidebar = Buffer::html().to_display(sidebar); + let content = t.to_string(); // Note: This must happen before making the sidebar. + let sidebar = sidebar.to_string(); PageLayout { static_root_path, page, diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 7e835585b73..d9e49577d39 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -38,7 +38,7 @@ use std::sync::{Arc, Weak}; use pulldown_cmark::{ BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, TagEnd, html, }; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::{Diag, DiagMessage}; use rustc_hir::def_id::LocalDefId; use rustc_middle::ty::TyCtxt; @@ -52,7 +52,6 @@ use crate::clean::RenderedLink; use crate::doctest; use crate::doctest::GlobalTestOptions; use crate::html::escape::{Escape, EscapeBodyText}; -use crate::html::format::Buffer; use crate::html::highlight; use crate::html::length_limit::HtmlWithLimit; use crate::html::render::small_url_encode; @@ -140,7 +139,7 @@ impl ErrorCodes { /// Controls whether a line will be hidden or shown in HTML output. /// /// All lines are used in documentation tests. -enum Line<'a> { +pub(crate) enum Line<'a> { Hidden(&'a str), Shown(Cow<'a, str>), } @@ -153,7 +152,7 @@ impl<'a> Line<'a> { } } - fn for_code(self) -> Cow<'a, str> { + pub(crate) fn for_code(self) -> Cow<'a, str> { match self { Line::Shown(l) => l, Line::Hidden(l) => Cow::Borrowed(l), @@ -161,12 +160,14 @@ impl<'a> Line<'a> { } } +/// This function is used to handle the "hidden lines" (ie starting with `#`) in +/// doctests. It also transforms `##` back into `#`. // FIXME: There is a minor inconsistency here. For lines that start with ##, we // have no easy way of removing a potential single space after the hashes, which // is done in the single # case. This inconsistency seems okay, if non-ideal. In // order to fix it we'd have to iterate to find the first non-# character, and // then reallocate to remove it; which would make us return a String. -fn map_line(s: &str) -> Line<'_> { +pub(crate) fn map_line(s: &str) -> Line<'_> { let trimmed = s.trim(); if trimmed.starts_with("##") { Line::Shown(Cow::Owned(s.replacen("##", "#", 1))) @@ -329,7 +330,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> { // insert newline to clearly separate it from the // previous block so we can shorten the html output - let mut s = Buffer::new(); + let mut s = String::new(); s.push('\n'); highlight::render_example_with_highlighting( @@ -339,7 +340,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> { playground_button.as_deref(), &added_classes, ); - Some(Event::Html(s.into_inner().into())) + Some(Event::Html(s.into())) } } @@ -1762,6 +1763,46 @@ pub(crate) fn markdown_links<'md, R>( } }; + let span_for_refdef = |link: &CowStr<'_>, span: Range<usize>| { + // We want to underline the link's definition, but `span` will point at the entire refdef. + // Skip the label, then try to find the entire URL. + let mut square_brace_count = 0; + let mut iter = md.as_bytes()[span.start..span.end].iter().copied().enumerate(); + for (_i, c) in &mut iter { + match c { + b':' if square_brace_count == 0 => break, + b'[' => square_brace_count += 1, + b']' => square_brace_count -= 1, + _ => {} + } + } + while let Some((i, c)) = iter.next() { + if c == b'<' { + while let Some((j, c)) = iter.next() { + match c { + b'\\' => { + let _ = iter.next(); + } + b'>' => { + return MarkdownLinkRange::Destination( + i + 1 + span.start..j + span.start, + ); + } + _ => {} + } + } + } else if !c.is_ascii_whitespace() { + while let Some((j, c)) = iter.next() { + if c.is_ascii_whitespace() { + return MarkdownLinkRange::Destination(i + span.start..j + span.start); + } + } + return MarkdownLinkRange::Destination(i + span.start..span.end); + } + } + span_for_link(link, span) + }; + let span_for_offset_backward = |span: Range<usize>, open: u8, close: u8| { let mut open_brace = !0; let mut close_brace = !0; @@ -1843,9 +1884,16 @@ pub(crate) fn markdown_links<'md, R>( .into_offset_iter(); let mut links = Vec::new(); + let mut refdefs = FxIndexMap::default(); + for (label, refdef) in event_iter.reference_definitions().iter() { + refdefs.insert(label.to_string(), (false, refdef.dest.to_string(), refdef.span.clone())); + } + for (event, span) in event_iter { match event { - Event::Start(Tag::Link { link_type, dest_url, .. }) if may_be_doc_link(link_type) => { + Event::Start(Tag::Link { link_type, dest_url, id, .. }) + if may_be_doc_link(link_type) => + { let range = match link_type { // Link is pulled from the link itself. LinkType::ReferenceUnknown | LinkType::ShortcutUnknown => { @@ -1855,7 +1903,12 @@ pub(crate) fn markdown_links<'md, R>( LinkType::Inline => span_for_offset_backward(span, b'(', b')'), // Link is pulled from elsewhere in the document. LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => { - span_for_link(&dest_url, span) + if let Some((is_used, dest_url, span)) = refdefs.get_mut(&id[..]) { + *is_used = true; + span_for_refdef(&CowStr::from(&dest_url[..]), span.clone()) + } else { + span_for_link(&dest_url, span) + } } LinkType::Autolink | LinkType::Email => unreachable!(), }; @@ -1872,6 +1925,18 @@ pub(crate) fn markdown_links<'md, R>( } } + for (_label, (is_used, dest_url, span)) in refdefs.into_iter() { + if !is_used + && let Some(link) = preprocess_link(MarkdownLink { + kind: LinkType::Reference, + range: span_for_refdef(&CowStr::from(&dest_url[..]), span), + link: dest_url, + }) + { + links.push(link); + } + } + links } diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs index 2001a763c09..bb42b877a2c 100644 --- a/src/librustdoc/html/markdown/tests.rs +++ b/src/librustdoc/html/markdown/tests.rs @@ -275,14 +275,14 @@ fn test_lang_string_tokenizer() { case("foo", &[LangStringToken::LangToken("foo")]); case("foo,bar", &[LangStringToken::LangToken("foo"), LangStringToken::LangToken("bar")]); case(".foo,.bar", &[]); - case("{.foo,.bar}", &[ - LangStringToken::ClassAttribute("foo"), - LangStringToken::ClassAttribute("bar"), - ]); - case(" {.foo,.bar} ", &[ - LangStringToken::ClassAttribute("foo"), - LangStringToken::ClassAttribute("bar"), - ]); + case( + "{.foo,.bar}", + &[LangStringToken::ClassAttribute("foo"), LangStringToken::ClassAttribute("bar")], + ); + case( + " {.foo,.bar} ", + &[LangStringToken::ClassAttribute("foo"), LangStringToken::ClassAttribute("bar")], + ); case("foo bar", &[LangStringToken::LangToken("foo"), LangStringToken::LangToken("bar")]); case("foo\tbar", &[LangStringToken::LangToken("foo"), LangStringToken::LangToken("bar")]); case("foo\t, bar", &[LangStringToken::LangToken("foo"), LangStringToken::LangToken("bar")]); diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 1cefdf96bbc..146bdd34069 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; use std::collections::BTreeMap; +use std::fmt::{self, Write as _}; use std::io; use std::path::{Path, PathBuf}; use std::sync::mpsc::{Receiver, channel}; @@ -26,11 +27,12 @@ use crate::formats::FormatRenderer; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::escape::Escape; -use crate::html::format::{Buffer, join_with_double_colon}; +use crate::html::format::join_with_double_colon; +use crate::html::layout::{self, BufDisplay}; use crate::html::markdown::{self, ErrorCodes, IdMap, plain_text_summary}; use crate::html::render::write_shared::write_shared; use crate::html::url_parts_builder::UrlPartsBuilder; -use crate::html::{layout, sources, static_files}; +use crate::html::{sources, static_files}; use crate::scrape_examples::AllCallLocations; use crate::{DOC_RUST_LANG_ORG_VERSION, try_err}; @@ -235,7 +237,7 @@ impl<'tcx> Context<'tcx> { }; if !render_redirect_pages { - let mut page_buffer = Buffer::html(); + let mut page_buffer = String::new(); print_item(self, it, &mut page_buffer); let page = layout::Page { css_class: tyname_s, @@ -249,8 +251,10 @@ impl<'tcx> Context<'tcx> { layout::render( &self.shared.layout, &page, - |buf: &mut _| print_sidebar(self, it, buf), - move |buf: &mut Buffer| buf.push_buffer(page_buffer), + BufDisplay(|buf: &mut String| { + print_sidebar(self, it, buf); + }), + page_buffer, &self.shared.style_files, ) } else { @@ -262,12 +266,12 @@ impl<'tcx> Context<'tcx> { // preventing an infinite redirection loop in the generated // documentation. - let mut path = String::new(); - for name in &names[..names.len() - 1] { - path.push_str(name.as_str()); - path.push('/'); - } - path.push_str(&item_path(ty, names.last().unwrap().as_str())); + let path = fmt::from_fn(|f| { + for name in &names[..names.len() - 1] { + write!(f, "{name}/")?; + } + write!(f, "{}", item_path(ty, names.last().unwrap().as_str())) + }); match self.shared.redirections { Some(ref redirections) => { let mut current_path = String::new(); @@ -275,8 +279,12 @@ impl<'tcx> Context<'tcx> { current_path.push_str(name.as_str()); current_path.push('/'); } - current_path.push_str(&item_path(ty, names.last().unwrap().as_str())); - redirections.borrow_mut().insert(current_path, path); + let _ = write!( + current_path, + "{}", + item_path(ty, names.last().unwrap().as_str()) + ); + redirections.borrow_mut().insert(current_path, path.to_string()); } None => { return layout::redirect(&format!( @@ -627,7 +635,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), sym::rust_logo), }; let all = shared.all.replace(AllTypes::new()); - let mut sidebar = Buffer::html(); + let mut sidebar = String::new(); // all.html is not customizable, so a blank id map is fine let blocks = sidebar_module_like(all.item_sections(), &mut IdMap::new(), ModuleLike::Crate); @@ -646,8 +654,10 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { let v = layout::render( &shared.layout, &page, - sidebar.into_inner(), - |buf: &mut Buffer| all.print(buf), + sidebar, + BufDisplay(|buf: &mut String| { + all.print(buf); + }), &shared.style_files, ); shared.fs.write(final_file, v)?; @@ -665,7 +675,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { &shared.layout, &page, sidebar, - |buf: &mut Buffer| { + fmt::from_fn(|buf| { write!( buf, "<div class=\"main-heading\">\ @@ -684,7 +694,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { <script defer src=\"{static_root_path}{settings_js}\"></script>", static_root_path = page.get_static_root_path(), settings_js = static_files::STATIC_FILES.settings_js, - ); + )?; // Pre-load all theme CSS files, so that switching feels seamless. // // When loading settings.html as a popover, the equivalent HTML is @@ -697,10 +707,11 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { as=\"style\">", root_path = page.static_root_path.unwrap_or(""), suffix = page.resource_suffix, - ); + )?; } } - }, + Ok(()) + }), &shared.style_files, ); shared.fs.write(settings_file, v)?; @@ -716,25 +727,22 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { &shared.layout, &page, sidebar, - |buf: &mut Buffer| { - write!( - buf, - "<div class=\"main-heading\">\ - <h1>Rustdoc help</h1>\ - <span class=\"out-of-band\">\ - <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\ - Back\ - </a>\ - </span>\ - </div>\ - <noscript>\ - <section>\ - <p>You need to enable JavaScript to use keyboard commands or search.</p>\ - <p>For more information, browse the <a href=\"{DOC_RUST_LANG_ORG_VERSION}/rustdoc/\">rustdoc handbook</a>.</p>\ - </section>\ - </noscript>", - ) - }, + format_args!( + "<div class=\"main-heading\">\ + <h1>Rustdoc help</h1>\ + <span class=\"out-of-band\">\ + <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\ + Back\ + </a>\ + </span>\ + </div>\ + <noscript>\ + <section>\ + <p>You need to enable JavaScript to use keyboard commands or search.</p>\ + <p>For more information, browse the <a href=\"{DOC_RUST_LANG_ORG_VERSION}/rustdoc/\">rustdoc handbook</a>.</p>\ + </section>\ + </noscript>", + ), &shared.style_files, ); shared.fs.write(help_file, v)?; @@ -851,9 +859,9 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { if !buf.is_empty() { let name = item.name.as_ref().unwrap(); let item_type = item.type_(); - let file_name = &item_path(item_type, name.as_str()); + let file_name = item_path(item_type, name.as_str()).to_string(); self.shared.ensure_dir(&self.dst)?; - let joint_dst = self.dst.join(file_name); + let joint_dst = self.dst.join(&file_name); self.shared.fs.write(joint_dst, buf)?; if !self.info.render_redirect_pages { @@ -870,7 +878,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { format!("{crate_name}/{file_name}"), ); } else { - let v = layout::redirect(file_name); + let v = layout::redirect(&file_name); let redir_dst = self.dst.join(redir_name); self.shared.fs.write(redir_dst, v)?; } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index f7dcb87e4f3..204631063a2 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -63,15 +63,16 @@ pub(crate) use self::context::*; pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources}; pub(crate) use self::write_shared::*; use crate::clean::{self, ItemId, RenderedLink}; +use crate::display::{Joined as _, MaybeDisplay as _}; use crate::error::Error; use crate::formats::Impl; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::escape::Escape; use crate::html::format::{ - Buffer, Ending, HrefError, PrintWithSpace, href, join_with_double_colon, print_abi_with_space, + Ending, HrefError, PrintWithSpace, href, join_with_double_colon, print_abi_with_space, print_constness_with_space, print_default_space, print_generic_bounds, print_where_clause, - visibility_print_with_space, + visibility_print_with_space, write_str, }; use crate::html::markdown::{ HeadingOffset, IdMap, Markdown, MarkdownItemInfo, MarkdownSummaryLine, @@ -436,27 +437,29 @@ impl AllTypes { sections } - fn print(self, f: &mut Buffer) { - fn print_entries(f: &mut Buffer, e: &FxIndexSet<ItemEntry>, kind: ItemSection) { + fn print(&self, f: &mut String) { + fn print_entries(f: &mut String, e: &FxIndexSet<ItemEntry>, kind: ItemSection) { if !e.is_empty() { let mut e: Vec<&ItemEntry> = e.iter().collect(); e.sort(); - write!( + write_str( f, - "<h3 id=\"{id}\">{title}</h3><ul class=\"all-items\">", - id = kind.id(), - title = kind.name(), + format_args!( + "<h3 id=\"{id}\">{title}</h3><ul class=\"all-items\">", + id = kind.id(), + title = kind.name(), + ), ); for s in e.iter() { - write!(f, "<li>{}</li>", s.print()); + write_str(f, format_args!("<li>{}</li>", s.print())); } - f.write_str("</ul>"); + f.push_str("</ul>"); } } - f.write_str("<h1>List of all items</h1>"); + f.push_str("<h1>List of all items</h1>"); // Note: print_entries does not escape the title, because we know the current set of titles // doesn't require escaping. print_entries(f, &self.structs, ItemSection::Structs); @@ -566,17 +569,27 @@ fn document_short<'a, 'cx: 'a>( let (mut summary_html, has_more_content) = MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content(); - if has_more_content { - let link = format!(" <a{}>Read more</a>", assoc_href_attr(item, link, cx)); + let link = if has_more_content { + let link = fmt::from_fn(|f| { + write!( + f, + " <a{}>Read more</a>", + assoc_href_attr(item, link, cx).maybe_display() + ) + }); if let Some(idx) = summary_html.rfind("</p>") { - summary_html.insert_str(idx, &link); + summary_html.insert_str(idx, &link.to_string()); + None } else { - summary_html.push_str(&link); + Some(link) } + } else { + None } + .maybe_display(); - write!(f, "<div class='docblock'>{summary_html}</div>")?; + write!(f, "<div class='docblock'>{summary_html}{link}</div>")?; } Ok(()) }) @@ -761,7 +774,7 @@ pub(crate) fn render_impls( let did = i.trait_did().unwrap(); let provided_trait_methods = i.inner_impl().provided_trait_methods(cx.tcx()); let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_trait_methods); - let mut buffer = Buffer::new(); + let mut buffer = String::new(); render_impl( &mut buffer, cx, @@ -778,7 +791,7 @@ pub(crate) fn render_impls( toggle_open_by_default, }, ); - buffer.into_inner() + buffer }) .collect::<Vec<_>>(); rendered_impls.sort(); @@ -786,13 +799,23 @@ pub(crate) fn render_impls( } /// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item. -fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String { +fn assoc_href_attr<'a, 'tcx>( + it: &clean::Item, + link: AssocItemLink<'a>, + cx: &Context<'tcx>, +) -> Option<impl fmt::Display + 'a + Captures<'tcx>> { let name = it.name.unwrap(); let item_type = it.type_(); + enum Href<'a> { + AnchorId(&'a str), + Anchor(ItemType), + Url(String, ItemType), + } + let href = match link { - AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{id}")), - AssocItemLink::Anchor(None) => Some(format!("#{item_type}.{name}")), + AssocItemLink::Anchor(Some(ref id)) => Href::AnchorId(id), + AssocItemLink::Anchor(None) => Href::Anchor(item_type), AssocItemLink::GotoSource(did, provided_methods) => { // We're creating a link from the implementation of an associated item to its // declaration in the trait declaration. @@ -812,7 +835,7 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) }; match href(did.expect_def_id(), cx) { - Ok((url, ..)) => Some(format!("{url}#{item_type}.{name}")), + Ok((url, ..)) => Href::Url(url, item_type), // The link is broken since it points to an external crate that wasn't documented. // Do not create any link in such case. This is better than falling back to a // dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item @@ -824,15 +847,25 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) // those two items are distinct! // In this scenario, the actual `id` of this impl item would be // `#{item_type}.{name}-{n}` for some number `n` (a disambiguator). - Err(HrefError::DocumentationNotBuilt) => None, - Err(_) => Some(format!("#{item_type}.{name}")), + Err(HrefError::DocumentationNotBuilt) => return None, + Err(_) => Href::Anchor(item_type), } } }; + let href = fmt::from_fn(move |f| match &href { + Href::AnchorId(id) => write!(f, "#{id}"), + Href::Url(url, item_type) => { + write!(f, "{url}#{item_type}.{name}") + } + Href::Anchor(item_type) => { + write!(f, "#{item_type}.{name}") + } + }); + // If there is no `href` for the reason explained above, simply do not render it which is valid: // https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements - href.map(|href| format!(" href=\"{href}\"")).unwrap_or_default() + Some(fmt::from_fn(move |f| write!(f, " href=\"{href}\""))) } #[derive(Debug)] @@ -847,7 +880,7 @@ enum AssocConstValue<'a> { } fn assoc_const( - w: &mut Buffer, + w: &mut String, it: &clean::Item, generics: &clean::Generics, ty: &clean::Type, @@ -857,15 +890,17 @@ fn assoc_const( cx: &Context<'_>, ) { let tcx = cx.tcx(); - write!( + write_str( w, - "{indent}{vis}const <a{href} class=\"constant\">{name}</a>{generics}: {ty}", - indent = " ".repeat(indent), - vis = visibility_print_with_space(it, cx), - href = assoc_href_attr(it, link, cx), - name = it.name.as_ref().unwrap(), - generics = generics.print(cx), - ty = ty.print(cx), + format_args!( + "{indent}{vis}const <a{href} class=\"constant\">{name}</a>{generics}: {ty}", + indent = " ".repeat(indent), + vis = visibility_print_with_space(it, cx), + href = assoc_href_attr(it, link, cx).maybe_display(), + name = it.name.as_ref().unwrap(), + generics = generics.print(cx), + ty = ty.print(cx), + ), ); if let AssocConstValue::TraitDefault(konst) | AssocConstValue::Impl(konst) = value { // FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the @@ -879,14 +914,14 @@ fn assoc_const( AssocConstValue::Impl(_) => repr != "_", // show if there is a meaningful value to show AssocConstValue::None => unreachable!(), } { - write!(w, " = {}", Escape(&repr)); + write_str(w, format_args!(" = {}", Escape(&repr))); } } - write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline)); + write_str(w, format_args!("{}", print_where_clause(generics, cx, indent, Ending::NoNewline))); } fn assoc_type( - w: &mut Buffer, + w: &mut String, it: &clean::Item, generics: &clean::Generics, bounds: &[clean::GenericBound], @@ -895,27 +930,29 @@ fn assoc_type( indent: usize, cx: &Context<'_>, ) { - write!( + write_str( w, - "{indent}{vis}type <a{href} class=\"associatedtype\">{name}</a>{generics}", - indent = " ".repeat(indent), - vis = visibility_print_with_space(it, cx), - href = assoc_href_attr(it, link, cx), - name = it.name.as_ref().unwrap(), - generics = generics.print(cx), + format_args!( + "{indent}{vis}type <a{href} class=\"associatedtype\">{name}</a>{generics}", + indent = " ".repeat(indent), + vis = visibility_print_with_space(it, cx), + href = assoc_href_attr(it, link, cx).maybe_display(), + name = it.name.as_ref().unwrap(), + generics = generics.print(cx), + ), ); if !bounds.is_empty() { - write!(w, ": {}", print_generic_bounds(bounds, cx)) + write_str(w, format_args!(": {}", print_generic_bounds(bounds, cx))); } // 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_str(w, format_args!(" = {}", default.print(cx))); } - write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline)); + write_str(w, format_args!("{}", print_where_clause(generics, cx, indent, Ending::NoNewline))); } fn assoc_method( - w: &mut Buffer, + w: &mut String, meth: &clean::Item, g: &clean::Generics, d: &clean::FnDecl, @@ -942,7 +979,7 @@ fn assoc_method( let asyncness = header.asyncness.print_with_space(); let safety = header.safety.print_with_space(); let abi = print_abi_with_space(header.abi).to_string(); - let href = assoc_href_attr(meth, link, cx); + let href = assoc_href_attr(meth, link, cx).maybe_display(); // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`. let generics_len = format!("{:#}", g.print(cx)).len(); @@ -956,35 +993,36 @@ fn assoc_method( + name.as_str().len() + generics_len; - let notable_traits = notable_traits_button(&d.output, cx); + let notable_traits = notable_traits_button(&d.output, cx).maybe_display(); let (indent, indent_str, end_newline) = if parent == ItemType::Trait { header_len += 4; let indent_str = " "; - write!(w, "{}", render_attributes_in_pre(meth, indent_str, cx)); + write_str(w, format_args!("{}", render_attributes_in_pre(meth, indent_str, cx))); (4, indent_str, Ending::NoNewline) } else { render_attributes_in_code(w, meth, cx); (0, "", Ending::Newline) }; w.reserve(header_len + "<a href=\"\" class=\"fn\">{".len() + "</a>".len()); - write!( + write_str( w, - "{indent}{vis}{defaultness}{constness}{asyncness}{safety}{abi}fn \ + format_args!( + "{indent}{vis}{defaultness}{constness}{asyncness}{safety}{abi}fn \ <a{href} class=\"fn\">{name}</a>{generics}{decl}{notable_traits}{where_clause}", - indent = indent_str, - vis = vis, - defaultness = defaultness, - constness = constness, - asyncness = asyncness, - safety = safety, - abi = abi, - href = href, - name = name, - generics = g.print(cx), - decl = d.full_print(header_len, indent, cx), - notable_traits = notable_traits.unwrap_or_default(), - where_clause = print_where_clause(g, cx, indent, end_newline), + indent = indent_str, + vis = vis, + defaultness = defaultness, + constness = constness, + asyncness = asyncness, + safety = safety, + abi = abi, + href = href, + name = name, + generics = g.print(cx), + decl = d.full_print(header_len, indent, cx), + where_clause = print_where_clause(g, cx, indent, end_newline), + ), ); } @@ -1003,7 +1041,7 @@ fn assoc_method( /// will include the const-stable version, but no stable version will be emitted, as a natural /// consequence of the above rules. fn render_stability_since_raw_with_extra( - w: &mut Buffer, + w: &mut String, stable_version: Option<StableSince>, const_stability: Option<ConstStability>, extra_class: &str, @@ -1058,7 +1096,10 @@ fn render_stability_since_raw_with_extra( } if !stability.is_empty() { - write!(w, r#"<span class="since{extra_class}" title="{title}">{stability}</span>"#); + write_str( + w, + format_args!(r#"<span class="since{extra_class}" title="{title}">{stability}</span>"#), + ); } !stability.is_empty() @@ -1074,7 +1115,7 @@ fn since_to_string(since: &StableSince) -> Option<String> { #[inline] fn render_stability_since_raw( - w: &mut Buffer, + w: &mut String, ver: Option<StableSince>, const_stability: Option<ConstStability>, ) -> bool { @@ -1082,7 +1123,7 @@ fn render_stability_since_raw( } fn render_assoc_item( - w: &mut Buffer, + w: &mut String, item: &clean::Item, link: AssocItemLink<'_>, parent: ItemType, @@ -1222,9 +1263,11 @@ pub(crate) fn render_all_impls( synthetic: &[&Impl], blanket_impl: &[&Impl], ) { - let mut impls = Buffer::html(); - render_impls(cx, &mut impls, concrete, containing_item, true); - let impls = impls.into_inner(); + let impls = { + let mut buf = String::new(); + render_impls(cx, &mut buf, concrete, containing_item, true); + buf + }; if !impls.is_empty() { write_impl_section_heading(&mut w, "Trait Implementations", "trait-implementations"); write!(w, "<div id=\"trait-implementations-list\">{impls}</div>").unwrap(); @@ -1277,7 +1320,7 @@ fn render_assoc_items_inner( let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none()); if !non_trait.is_empty() { let mut close_tags = <Vec<&str>>::with_capacity(1); - let mut tmp_buf = Buffer::html(); + let mut tmp_buf = String::new(); let (render_mode, id, class_html) = match what { AssocItemRender::All => { write_impl_section_heading(&mut tmp_buf, "Implementations", "implementations"); @@ -1287,7 +1330,7 @@ fn render_assoc_items_inner( let id = cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx)))); let derived_id = cx.derive_id(&id); - tmp_buf.write_str("<details class=\"toggle big-toggle\" open><summary>"); + tmp_buf.push_str("<details class=\"toggle big-toggle\" open><summary>"); close_tags.push("</details>"); write_impl_section_heading( &mut tmp_buf, @@ -1298,14 +1341,14 @@ fn render_assoc_items_inner( ), &id, ); - tmp_buf.write_str("</summary>"); + tmp_buf.push_str("</summary>"); if let Some(def_id) = type_.def_id(cx.cache()) { cx.deref_id_map.borrow_mut().insert(def_id, id); } (RenderMode::ForDeref { mut_: deref_mut_ }, derived_id, r#" class="impl-items""#) } }; - let mut impls_buf = Buffer::html(); + let mut impls_buf = String::new(); for i in &non_trait { render_impl( &mut impls_buf, @@ -1325,13 +1368,7 @@ fn render_assoc_items_inner( ); } if !impls_buf.is_empty() { - write!( - w, - "{}<div id=\"{id}\"{class_html}>{}</div>", - tmp_buf.into_inner(), - impls_buf.into_inner() - ) - .unwrap(); + write!(w, "{tmp_buf}<div id=\"{id}\"{class_html}>{impls_buf}</div>").unwrap(); for tag in close_tags.into_iter().rev() { w.write_str(tag).unwrap(); } @@ -1431,7 +1468,10 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> } } -pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option<String> { +pub(crate) fn notable_traits_button<'a, 'tcx>( + ty: &'a clean::Type, + cx: &'a Context<'tcx>, +) -> Option<impl fmt::Display + 'a + Captures<'tcx>> { let mut has_notable_trait = false; if ty.is_unit() { @@ -1473,19 +1513,20 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Optio } } - if has_notable_trait { + has_notable_trait.then(|| { cx.types_with_notable_traits.borrow_mut().insert(ty.clone()); - Some(format!( - " <a href=\"#\" class=\"tooltip\" data-notable-ty=\"{ty}\">ⓘ</a>", - ty = Escape(&format!("{:#}", ty.print(cx))), - )) - } else { - None - } + fmt::from_fn(|f| { + write!( + f, + " <a href=\"#\" class=\"tooltip\" data-notable-ty=\"{ty}\">ⓘ</a>", + ty = Escape(&format!("{:#}", ty.print(cx))), + ) + }) + }) } fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { - let mut out = Buffer::html(); + let mut out = String::new(); let did = ty.def_id(cx.cache()).expect("notable_traits_button already checked this"); @@ -1507,15 +1548,20 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { if cx.cache().traits.get(&trait_did).is_some_and(|t| t.is_notable_trait(cx.tcx())) { if out.is_empty() { - write!( + write_str( &mut out, - "<h3>Notable traits for <code>{}</code></h3>\ - <pre><code>", - impl_.for_.print(cx) + format_args!( + "<h3>Notable traits for <code>{}</code></h3>\ + <pre><code>", + impl_.for_.print(cx) + ), ); } - write!(&mut out, "<div class=\"where\">{}</div>", impl_.print(false, cx)); + write_str( + &mut out, + format_args!("<div class=\"where\">{}</div>", impl_.print(false, cx)), + ); for it in &impl_.items { if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind { out.push_str("<div class=\"where\"> "); @@ -1538,10 +1584,10 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { } } if out.is_empty() { - out.write_str("</code></pre>"); + out.push_str("</code></pre>"); } - (format!("{:#}", ty.print(cx)), out.into_inner()) + (format!("{:#}", ty.print(cx)), out) } pub(crate) fn notable_traits_json<'a>( @@ -1577,7 +1623,7 @@ struct ImplRenderingParameters { } fn render_impl( - w: &mut Buffer, + w: &mut String, cx: &Context<'_>, i: &Impl, parent: &clean::Item, @@ -1598,8 +1644,8 @@ fn render_impl( // `containing_item` is used for rendering stability info. If the parent is a trait impl, // `containing_item` will the grandparent, since trait impls can't have stability attached. fn doc_impl_item( - boring: &mut Buffer, - interesting: &mut Buffer, + boring: &mut String, + interesting: &mut String, cx: &Context<'_>, item: &clean::Item, parent: &clean::Item, @@ -1622,8 +1668,8 @@ fn render_impl( let in_trait_class = if trait_.is_some() { " trait-impl" } else { "" }; - let mut doc_buffer = Buffer::empty_from(boring); - let mut info_buffer = Buffer::empty_from(boring); + let mut doc_buffer = String::new(); + let mut info_buffer = String::new(); let mut short_documented = true; if render_method_item { @@ -1638,25 +1684,26 @@ fn render_impl( document_item_info(cx, it, Some(parent)) .render_into(&mut info_buffer) .unwrap(); - write!( + write_str( &mut doc_buffer, - "{}", - document_full(item, cx, HeadingOffset::H5) + format_args!("{}", document_full(item, cx, HeadingOffset::H5)), ); short_documented = false; } else { // In case the item isn't documented, // provide short documentation from the trait. - write!( + write_str( &mut doc_buffer, - "{}", - document_short( - it, - cx, - link, - parent, - rendering_params.show_def_docs, - ) + format_args!( + "{}", + document_short( + it, + cx, + link, + parent, + rendering_params.show_def_docs, + ) + ), ); } } @@ -1665,15 +1712,20 @@ fn render_impl( .render_into(&mut info_buffer) .unwrap(); if rendering_params.show_def_docs { - write!(&mut doc_buffer, "{}", document_full(item, cx, HeadingOffset::H5)); + write_str( + &mut doc_buffer, + format_args!("{}", document_full(item, cx, HeadingOffset::H5)), + ); short_documented = false; } } } else { - write!( + write_str( &mut doc_buffer, - "{}", - document_short(item, cx, link, parent, rendering_params.show_def_docs) + format_args!( + "{}", + document_short(item, cx, link, parent, rendering_params.show_def_docs) + ), ); } } @@ -1682,7 +1734,10 @@ fn render_impl( let toggled = !doc_buffer.is_empty(); if toggled { let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" }; - write!(w, "<details class=\"toggle{method_toggle_class}\" open><summary>"); + write_str( + w, + format_args!("<details class=\"toggle{method_toggle_class}\" open><summary>"), + ); } match &item.kind { clean::MethodItem(..) | clean::RequiredMethodItem(_) => { @@ -1697,13 +1752,16 @@ fn render_impl( .find(|item| item.name.map(|n| n == *name).unwrap_or(false)) }) .map(|item| format!("{}.{name}", item.type_())); - write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">"); + write_str( + w, + format_args!("<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">"), + ); render_rightside(w, cx, item, render_mode); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>"); + write_str(w, format_args!("<a href=\"#{id}\" class=\"anchor\">§</a>")); } - w.write_str("<h4 class=\"code-header\">"); + w.push_str("<h4 class=\"code-header\">"); render_assoc_item( w, item, @@ -1712,19 +1770,22 @@ fn render_impl( cx, render_mode, ); - w.write_str("</h4></section>"); + w.push_str("</h4></section>"); } } clean::RequiredAssocConstItem(ref generics, ref ty) => { let source_id = format!("{item_type}.{name}"); let id = cx.derive_id(&source_id); - write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">"); + write_str( + w, + format_args!("<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">"), + ); render_rightside(w, cx, item, render_mode); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>"); + write_str(w, format_args!("<a href=\"#{id}\" class=\"anchor\">§</a>")); } - w.write_str("<h4 class=\"code-header\">"); + w.push_str("<h4 class=\"code-header\">"); assoc_const( w, item, @@ -1735,18 +1796,21 @@ fn render_impl( 0, cx, ); - w.write_str("</h4></section>"); + w.push_str("</h4></section>"); } clean::ProvidedAssocConstItem(ci) | clean::ImplAssocConstItem(ci) => { let source_id = format!("{item_type}.{name}"); let id = cx.derive_id(&source_id); - write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">"); + write_str( + w, + format_args!("<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">"), + ); render_rightside(w, cx, item, render_mode); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>"); + write_str(w, format_args!("<a href=\"#{id}\" class=\"anchor\">§</a>")); } - w.write_str("<h4 class=\"code-header\">"); + w.push_str("<h4 class=\"code-header\">"); assoc_const( w, item, @@ -1761,18 +1825,21 @@ fn render_impl( 0, cx, ); - w.write_str("</h4></section>"); + w.push_str("</h4></section>"); } clean::RequiredAssocTypeItem(ref generics, ref bounds) => { let source_id = format!("{item_type}.{name}"); let id = cx.derive_id(&source_id); - write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">"); + write_str( + w, + format_args!("<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">"), + ); render_rightside(w, cx, item, render_mode); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>"); + write_str(w, format_args!("<a href=\"#{id}\" class=\"anchor\">§</a>")); } - w.write_str("<h4 class=\"code-header\">"); + w.push_str("<h4 class=\"code-header\">"); assoc_type( w, item, @@ -1783,18 +1850,21 @@ fn render_impl( 0, cx, ); - w.write_str("</h4></section>"); + w.push_str("</h4></section>"); } clean::AssocTypeItem(tydef, _bounds) => { let source_id = format!("{item_type}.{name}"); let id = cx.derive_id(&source_id); - write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">"); + write_str( + w, + format_args!("<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">"), + ); render_rightside(w, cx, item, render_mode); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "<a href=\"#{id}\" class=\"anchor\">§</a>"); + write_str(w, format_args!("<a href=\"#{id}\" class=\"anchor\">§</a>")); } - w.write_str("<h4 class=\"code-header\">"); + w.push_str("<h4 class=\"code-header\">"); assoc_type( w, item, @@ -1805,22 +1875,22 @@ fn render_impl( 0, cx, ); - w.write_str("</h4></section>"); + w.push_str("</h4></section>"); } clean::StrippedItem(..) => return, _ => panic!("can't make docs for trait item with name {:?}", item.name), } - w.push_buffer(info_buffer); + w.push_str(&info_buffer); if toggled { - w.write_str("</summary>"); - w.push_buffer(doc_buffer); + w.push_str("</summary>"); + w.push_str(&doc_buffer); w.push_str("</details>"); } } - let mut impl_items = Buffer::empty_from(w); - let mut default_impl_items = Buffer::empty_from(w); + let mut impl_items = String::new(); + let mut default_impl_items = String::new(); let impl_ = i.inner_impl(); // Impl items are grouped by kinds: @@ -1894,8 +1964,8 @@ fn render_impl( } fn render_default_items( - boring: &mut Buffer, - interesting: &mut Buffer, + boring: &mut String, + interesting: &mut String, cx: &Context<'_>, t: &clean::Trait, i: &clean::Impl, @@ -1960,11 +2030,13 @@ fn render_impl( let toggled = !(impl_items.is_empty() && default_impl_items.is_empty()); if toggled { close_tags.push("</details>"); - write!( + write_str( w, - "<details class=\"toggle implementors-toggle\"{}>\ - <summary>", - if rendering_params.toggle_open_by_default { " open" } else { "" } + format_args!( + "<details class=\"toggle implementors-toggle\"{}>\ + <summary>", + if rendering_params.toggle_open_by_default { " open" } else { "" } + ), ); } @@ -1995,38 +2067,38 @@ fn render_impl( &before_dox, ); if toggled { - w.write_str("</summary>"); + w.push_str("</summary>"); } if before_dox.is_some() { if trait_.is_none() && impl_.items.is_empty() { - w.write_str( + w.push_str( "<div class=\"item-info\">\ <div class=\"stab empty-impl\">This impl block contains no items.</div>\ </div>", ); } if let Some(after_dox) = after_dox { - write!(w, "<div class=\"docblock\">{after_dox}</div>"); + write_str(w, format_args!("<div class=\"docblock\">{after_dox}</div>")); } } if !default_impl_items.is_empty() || !impl_items.is_empty() { - w.write_str("<div class=\"impl-items\">"); + w.push_str("<div class=\"impl-items\">"); close_tags.push("</div>"); } } if !default_impl_items.is_empty() || !impl_items.is_empty() { - w.push_buffer(default_impl_items); - w.push_buffer(impl_items); + w.push_str(&default_impl_items); + w.push_str(&impl_items); } for tag in close_tags.into_iter().rev() { - w.write_str(tag); + w.push_str(tag); } } // Render the items that appear on the right side of methods, impls, and // associated types. For example "1.0.0 (const: 1.39.0) · source". -fn render_rightside(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, render_mode: RenderMode) { +fn render_rightside(w: &mut String, cx: &Context<'_>, item: &clean::Item, render_mode: RenderMode) { let tcx = cx.tcx(); // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove @@ -2038,7 +2110,7 @@ fn render_rightside(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, render let src_href = cx.src_href(item); let has_src_ref = src_href.is_some(); - let mut rightside = Buffer::new(); + let mut rightside = String::new(); let has_stability = render_stability_since_raw_with_extra( &mut rightside, item.stable_since(tcx), @@ -2047,20 +2119,26 @@ fn render_rightside(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, render ); if let Some(link) = src_href { if has_stability { - write!(rightside, " · <a class=\"src\" href=\"{link}\">Source</a>") + write_str( + &mut rightside, + format_args!(" · <a class=\"src\" href=\"{link}\">Source</a>"), + ); } else { - write!(rightside, "<a class=\"src rightside\" href=\"{link}\">Source</a>") + write_str( + &mut rightside, + format_args!("<a class=\"src rightside\" href=\"{link}\">Source</a>"), + ); } } if has_stability && has_src_ref { - write!(w, "<span class=\"rightside\">{}</span>", rightside.into_inner()); + write_str(w, format_args!("<span class=\"rightside\">{rightside}</span>")); } else { - w.push_buffer(rightside); + w.push_str(&rightside); } } pub(crate) fn render_impl_summary( - w: &mut Buffer, + w: &mut String, cx: &Context<'_>, i: &Impl, parent: &clean::Item, @@ -2073,25 +2151,27 @@ pub(crate) fn render_impl_summary( ) { let inner_impl = i.inner_impl(); let id = cx.derive_id(get_id_for_impl(cx.tcx(), i.impl_item.item_id)); - let aliases = if aliases.is_empty() { - String::new() - } else { - format!(" data-aliases=\"{}\"", aliases.join(",")) - }; - write!(w, "<section id=\"{id}\" class=\"impl\"{aliases}>"); + let aliases = (!aliases.is_empty()) + .then_some(fmt::from_fn(|f| { + write!(f, " data-aliases=\"{}\"", fmt::from_fn(|f| aliases.iter().joined(",", f))) + })) + .maybe_display(); + write_str(w, format_args!("<section id=\"{id}\" class=\"impl\"{aliases}>")); render_rightside(w, cx, &i.impl_item, RenderMode::Normal); - write!( + write_str( w, - "<a href=\"#{id}\" class=\"anchor\">§</a>\ - <h3 class=\"code-header\">" + format_args!( + "<a href=\"#{id}\" class=\"anchor\">§</a>\ + <h3 class=\"code-header\">" + ), ); if let Some(use_absolute) = use_absolute { - write!(w, "{}", inner_impl.print(use_absolute, cx)); + write_str(w, format_args!("{}", inner_impl.print(use_absolute, cx))); if show_def_docs { for it in &inner_impl.items { if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind { - w.write_str("<div class=\"where\"> "); + w.push_str("<div class=\"where\"> "); assoc_type( w, it, @@ -2102,30 +2182,32 @@ pub(crate) fn render_impl_summary( 0, cx, ); - w.write_str(";</div>"); + w.push_str(";</div>"); } } } } else { - write!(w, "{}", inner_impl.print(false, cx)); + write_str(w, format_args!("{}", inner_impl.print(false, cx))); } - w.write_str("</h3>"); + w.push_str("</h3>"); let is_trait = inner_impl.trait_.is_some(); if is_trait && let Some(portability) = portability(&i.impl_item, Some(parent)) { - write!( + write_str( w, - "<span class=\"item-info\">\ + format_args!( + "<span class=\"item-info\">\ <div class=\"stab portability\">{portability}</div>\ </span>", + ), ); } if let Some(doc) = doc { - write!(w, "<div class=\"docblock\">{doc}</div>"); + write_str(w, format_args!("<div class=\"docblock\">{doc}</div>")); } - w.write_str("</section>"); + w.push_str("</section>"); } pub(crate) fn small_url_encode(s: String) -> String { @@ -2577,7 +2659,7 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &Context<'_>, item: &clean cx, &cx.root_path(), &highlight::DecorationInfo(decoration_info), - sources::SourceContext::Embedded(sources::ScrapedInfo { + &sources::SourceContext::Embedded(sources::ScrapedInfo { needs_expansion, offset: line_min, name: &call_data.display_name, diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 4f51b7a0108..f3201147039 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1,8 +1,7 @@ use std::cmp::Ordering; use std::fmt; -use std::fmt::{Display, Write}; +use std::fmt::Display; -use itertools::Itertools; use rinja::Template; use rustc_abi::VariantIdx; use rustc_data_structures::captures::Captures; @@ -27,17 +26,17 @@ use super::{ }; use crate::clean; use crate::config::ModuleSorting; +use crate::display::{Joined as _, MaybeDisplay as _}; use crate::formats::Impl; use crate::formats::item_type::ItemType; use crate::html::escape::{Escape, EscapeBodyTextWithWbr}; use crate::html::format::{ - Buffer, Ending, PrintWithSpace, join_with_double_colon, print_abi_with_space, - print_constness_with_space, print_where_clause, visibility_print_with_space, + Ending, PrintWithSpace, join_with_double_colon, print_abi_with_space, + print_constness_with_space, print_where_clause, visibility_print_with_space, write_str, }; use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine}; use crate::html::render::{document_full, document_item_info}; use crate::html::url_parts_builder::UrlPartsBuilder; -use crate::joined::Joined as _; /// Generates a Rinja template struct for rendering items with common methods. /// @@ -165,16 +164,16 @@ struct ItemVars<'a> { /// Calls `print_where_clause` and returns `true` if a `where` clause was generated. fn print_where_clause_and_check<'a, 'tcx: 'a>( - buffer: &mut Buffer, + buffer: &mut String, gens: &'a clean::Generics, cx: &'a Context<'tcx>, ) -> bool { let len_before = buffer.len(); - write!(buffer, "{}", print_where_clause(gens, cx, 0, Ending::Newline)); + write_str(buffer, format_args!("{}", print_where_clause(gens, cx, 0, Ending::Newline))); len_before != buffer.len() } -pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) { +pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut String) { debug_assert!(!item.is_stripped()); let typ = match item.kind { clean::ModuleItem(_) => { @@ -207,13 +206,15 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) unreachable!(); } }; - let mut stability_since_raw = Buffer::new(); - render_stability_since_raw( - &mut stability_since_raw, - item.stable_since(cx.tcx()), - item.const_stability(cx.tcx()), - ); - let stability_since_raw: String = stability_since_raw.into_inner(); + let stability_since_raw = { + let mut buf = String::new(); + render_stability_since_raw( + &mut buf, + item.stable_since(cx.tcx()), + item.const_stability(cx.tcx()), + ); + buf + }; // Write source tag // @@ -278,10 +279,12 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) // Render notable-traits.js used for all methods in this module. let mut types_with_notable_traits = cx.types_with_notable_traits.borrow_mut(); if !types_with_notable_traits.is_empty() { - write!( + write_str( buf, - r#"<script type="text/json" id="notable-traits-data">{}</script>"#, - notable_traits_json(types_with_notable_traits.iter(), cx) + format_args!( + r#"<script type="text/json" id="notable-traits-data">{}</script>"#, + notable_traits_json(types_with_notable_traits.iter(), cx) + ), ); types_with_notable_traits.clear(); } @@ -311,8 +314,8 @@ trait ItemTemplate<'a, 'cx: 'a>: rinja::Template + Display { fn item_and_cx(&self) -> (&'a clean::Item, &'a Context<'cx>); } -fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) { - write!(w, "{}", document(cx, item, None, HeadingOffset::H2)); +fn item_module(w: &mut String, cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) { + write_str(w, format_args!("{}", document(cx, item, None, HeadingOffset::H2))); let mut not_stripped_items = items.iter().filter(|i| !i.is_stripped()).enumerate().collect::<Vec<_>>(); @@ -398,7 +401,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl let my_section = item_ty_to_section(myitem.type_()); if Some(my_section) != last_section { if last_section.is_some() { - w.write_str(ITEM_TABLE_CLOSE); + w.push_str(ITEM_TABLE_CLOSE); } last_section = Some(my_section); let section_id = my_section.id(); @@ -412,21 +415,29 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl use crate::html::format::anchor; match *src { - Some(src) => write!( - w, - "<dt><code>{}extern crate {} as {};", - visibility_print_with_space(myitem, cx), - anchor(myitem.item_id.expect_def_id(), src, cx), - EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()), - ), - None => write!( - w, - "<dt><code>{}extern crate {};", - visibility_print_with_space(myitem, cx), - anchor(myitem.item_id.expect_def_id(), myitem.name.unwrap(), cx), - ), + Some(src) => { + write_str( + w, + format_args!( + "<dt><code>{}extern crate {} as {};", + visibility_print_with_space(myitem, cx), + anchor(myitem.item_id.expect_def_id(), src, cx), + EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()) + ), + ); + } + None => { + write_str( + w, + format_args!( + "<dt><code>{}extern crate {};", + visibility_print_with_space(myitem, cx), + anchor(myitem.item_id.expect_def_id(), myitem.name.unwrap(), cx) + ), + ); + } } - w.write_str("</code></dt>"); + w.push_str("</code></dt>"); } clean::ImportItem(ref import) => { @@ -440,13 +451,15 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl } clean::ImportKind::Glob => String::new(), }; - write!( + write_str( w, - "<dt{id}>\ - <code>{vis}{imp}</code>{stab_tags}\ - </dt>", - vis = visibility_print_with_space(myitem, cx), - imp = import.print(cx), + format_args!( + "<dt{id}>\ + <code>{vis}{imp}</code>{stab_tags}\ + </dt>", + vis = visibility_print_with_space(myitem, cx), + imp = import.print(cx) + ), ); } @@ -484,33 +497,31 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl MarkdownSummaryLine(&myitem.doc_value(), &myitem.links(cx)).into_string(); let (docs_before, docs_after) = if docs.is_empty() { ("", "") } else { ("<dd>", "</dd>") }; - write!( + write_str( w, - "<dt>\ - <a class=\"{class}\" href=\"{href}\" title=\"{title}\">{name}</a>\ - {visibility_and_hidden}\ - {unsafety_flag}\ - {stab_tags}\ - </dt>\ - {docs_before}{docs}{docs_after}", - name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()), - visibility_and_hidden = visibility_and_hidden, - stab_tags = extra_info_tags(tcx, myitem, item, None), - class = myitem.type_(), - unsafety_flag = unsafety_flag, - href = item_path(myitem.type_(), myitem.name.unwrap().as_str()), - title = [myitem.type_().to_string(), full_path(cx, myitem)] - .iter() - .filter_map(|s| if !s.is_empty() { Some(s.as_str()) } else { None }) - .collect::<Vec<_>>() - .join(" "), + format_args!( + "<dt>\ + <a class=\"{class}\" href=\"{href}\" title=\"{title}\">{name}</a>\ + {visibility_and_hidden}\ + {unsafety_flag}\ + {stab_tags}\ + </dt>\ + {docs_before}{docs}{docs_after}", + name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()), + visibility_and_hidden = visibility_and_hidden, + stab_tags = extra_info_tags(tcx, myitem, item, None), + class = myitem.type_(), + unsafety_flag = unsafety_flag, + href = item_path(myitem.type_(), myitem.name.unwrap().as_str()), + title = format_args!("{} {}", myitem.type_(), full_path(cx, myitem)), + ), ); } } } if last_section.is_some() { - w.write_str(ITEM_TABLE_CLOSE); + w.push_str(ITEM_TABLE_CLOSE); } } @@ -572,7 +583,7 @@ fn extra_info_tags<'a, 'tcx: 'a>( }) } -fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean::Function) { +fn item_function(w: &mut String, cx: &Context<'_>, it: &clean::Item, f: &clean::Function) { let tcx = cx.tcx(); let header = it.fn_header(tcx).expect("printing a function which isn't a function"); debug!( @@ -603,31 +614,32 @@ fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean:: + name.as_str().len() + generics_len; - let notable_traits = notable_traits_button(&f.decl.output, cx); + let notable_traits = notable_traits_button(&f.decl.output, cx).maybe_display(); wrap_item(w, |w| { w.reserve(header_len); - write!( + write_str( w, - "{attrs}{vis}{constness}{asyncness}{safety}{abi}fn \ + format_args!( + "{attrs}{vis}{constness}{asyncness}{safety}{abi}fn \ {name}{generics}{decl}{notable_traits}{where_clause}", - attrs = render_attributes_in_pre(it, "", cx), - vis = visibility, - constness = constness, - asyncness = asyncness, - safety = safety, - abi = abi, - name = name, - generics = f.generics.print(cx), - where_clause = print_where_clause(&f.generics, cx, 0, Ending::Newline), - decl = f.decl.full_print(header_len, 0, cx), - notable_traits = notable_traits.unwrap_or_default(), + attrs = render_attributes_in_pre(it, "", cx), + vis = visibility, + constness = constness, + asyncness = asyncness, + safety = safety, + abi = abi, + name = name, + generics = f.generics.print(cx), + where_clause = print_where_clause(&f.generics, cx, 0, Ending::Newline), + decl = f.decl.full_print(header_len, 0, cx), + ), ); }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); + write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); } -fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) { +fn item_trait(w: &mut String, cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) { let tcx = cx.tcx(); let bounds = bounds(&t.bounds, false, cx); let required_types = @@ -645,28 +657,33 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra // Output the trait definition wrap_item(w, |mut w| { - write!( + write_str( w, - "{attrs}{vis}{safety}{is_auto}trait {name}{generics}{bounds}", - attrs = render_attributes_in_pre(it, "", cx), - vis = visibility_print_with_space(it, cx), - safety = t.safety(tcx).print_with_space(), - is_auto = if t.is_auto(tcx) { "auto " } else { "" }, - name = it.name.unwrap(), - generics = t.generics.print(cx), + format_args!( + "{attrs}{vis}{safety}{is_auto}trait {name}{generics}{bounds}", + attrs = render_attributes_in_pre(it, "", cx), + vis = visibility_print_with_space(it, cx), + safety = t.safety(tcx).print_with_space(), + is_auto = if t.is_auto(tcx) { "auto " } else { "" }, + name = it.name.unwrap(), + generics = t.generics.print(cx), + ), ); if !t.generics.where_predicates.is_empty() { - write!(w, "{}", print_where_clause(&t.generics, cx, 0, Ending::Newline)); + write_str( + w, + format_args!("{}", print_where_clause(&t.generics, cx, 0, Ending::Newline)), + ); } else { - w.write_str(" "); + w.push_str(" "); } if t.items.is_empty() { - w.write_str("{ }"); + w.push_str("{ }"); } else { // FIXME: we should be using a derived_id for the Anchors here - w.write_str("{\n"); + w.push_str("{\n"); let mut toggle = false; // If there are too many associated types, hide _everything_ @@ -687,7 +704,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra cx, RenderMode::Normal, ); - w.write_str(";\n"); + w.push_str(";\n"); } } // If there are too many associated constants, hide everything after them @@ -707,7 +724,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra ); } if count_types != 0 && (count_consts != 0 || count_methods != 0) { - w.write_str("\n"); + w.push_str("\n"); } for consts in [&required_consts, &provided_consts] { for c in consts { @@ -719,7 +736,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra cx, RenderMode::Normal, ); - w.write_str(";\n"); + w.push_str(";\n"); } } if !toggle && should_hide_fields(count_methods) { @@ -727,11 +744,14 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra toggle_open(&mut w, format_args!("{count_methods} methods")); } if count_consts != 0 && count_methods != 0 { - w.write_str("\n"); + w.push_str("\n"); } if !required_methods.is_empty() { - writeln!(w, " // Required method{}", pluralize(required_methods.len())); + write_str( + w, + format_args_nl!(" // Required method{}", pluralize(required_methods.len())), + ); } for (pos, m) in required_methods.iter().enumerate() { render_assoc_item( @@ -742,18 +762,21 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra cx, RenderMode::Normal, ); - w.write_str(";\n"); + w.push_str(";\n"); if pos < required_methods.len() - 1 { - w.write_str("<span class=\"item-spacer\"></span>"); + w.push_str("<span class=\"item-spacer\"></span>"); } } if !required_methods.is_empty() && !provided_methods.is_empty() { - w.write_str("\n"); + w.push_str("\n"); } if !provided_methods.is_empty() { - writeln!(w, " // Provided method{}", pluralize(provided_methods.len())); + write_str( + w, + format_args_nl!(" // Provided method{}", pluralize(provided_methods.len())), + ); } for (pos, m) in provided_methods.iter().enumerate() { render_assoc_item( @@ -765,39 +788,42 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra RenderMode::Normal, ); - w.write_str(" { ... }\n"); + w.push_str(" { ... }\n"); if pos < provided_methods.len() - 1 { - w.write_str("<span class=\"item-spacer\"></span>"); + w.push_str("<span class=\"item-spacer\"></span>"); } } if toggle { toggle_close(&mut w); } - w.write_str("}"); + w.push_str("}"); } }); // Trait documentation - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); + write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); - fn trait_item(w: &mut Buffer, cx: &Context<'_>, m: &clean::Item, t: &clean::Item) { + fn trait_item(w: &mut String, cx: &Context<'_>, m: &clean::Item, t: &clean::Item) { let name = m.name.unwrap(); info!("Documenting {name} on {ty_name:?}", ty_name = t.name); let item_type = m.type_(); let id = cx.derive_id(format!("{item_type}.{name}")); - let mut content = Buffer::empty_from(w); - write!(content, "{}", document_full(m, cx, HeadingOffset::H5)); + let mut content = String::new(); + write_str(&mut content, format_args!("{}", document_full(m, cx, HeadingOffset::H5))); let toggled = !content.is_empty(); if toggled { let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" }; - write!(w, "<details class=\"toggle{method_toggle_class}\" open><summary>"); + write_str( + w, + format_args!("<details class=\"toggle{method_toggle_class}\" open><summary>"), + ); } - write!(w, "<section id=\"{id}\" class=\"method\">"); + write_str(w, format_args!("<section id=\"{id}\" class=\"method\">")); render_rightside(w, cx, m, RenderMode::Normal); - write!(w, "<h4 class=\"code-header\">"); + write_str(w, format_args!("<h4 class=\"code-header\">")); render_assoc_item( w, m, @@ -806,12 +832,12 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra cx, RenderMode::Normal, ); - w.write_str("</h4></section>"); + w.push_str("</h4></section>"); document_item_info(cx, m, Some(t)).render_into(w).unwrap(); if toggled { - write!(w, "</summary>"); - w.push_buffer(content); - write!(w, "</details>"); + write_str(w, format_args!("</summary>")); + w.push_str(&content); + write_str(w, format_args!("</details>")); } } @@ -826,7 +852,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra for t in required_consts { trait_item(w, cx, t, it); } - w.write_str("</div>"); + w.push_str("</div>"); } if !provided_consts.is_empty() { write_section_heading( @@ -839,7 +865,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra for t in provided_consts { trait_item(w, cx, t, it); } - w.write_str("</div>"); + w.push_str("</div>"); } if !required_types.is_empty() { @@ -853,7 +879,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra for t in required_types { trait_item(w, cx, t, it); } - w.write_str("</div>"); + w.push_str("</div>"); } if !provided_types.is_empty() { write_section_heading( @@ -866,7 +892,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra for t in provided_types { trait_item(w, cx, t, it); } - w.write_str("</div>"); + w.push_str("</div>"); } // Output the documentation for each function individually @@ -880,17 +906,19 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra ); if let Some(list) = must_implement_one_of_functions.as_deref() { - write!( + write_str( w, - "<div class=\"stab must_implement\">At least one of the `{}` methods is required.</div>", - list.iter().join("`, `") + format_args!( + "<div class=\"stab must_implement\">At least one of the `{}` methods is required.</div>", + fmt::from_fn(|f| list.iter().joined("`, `", f)) + ), ); } for m in required_methods { trait_item(w, cx, m, it); } - w.write_str("</div>"); + w.push_str("</div>"); } if !provided_methods.is_empty() { write_section_heading( @@ -903,11 +931,17 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra for m in provided_methods { trait_item(w, cx, m, it); } - w.write_str("</div>"); + w.push_str("</div>"); } // If there are methods directly on this trait object, render them here. - write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All)); + write_str( + w, + format_args!( + "{}", + render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All) + ), + ); let mut extern_crates = FxIndexSet::default(); @@ -997,7 +1031,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra for implementor in concrete { render_implementor(cx, implementor, it, w, &implementor_dups, &[]); } - w.write_str("</div>"); + w.push_str("</div>"); if t.is_auto(tcx) { write_section_heading( @@ -1020,7 +1054,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra ), ); } - w.write_str("</div>"); + w.push_str("</div>"); } } else { // even without any implementations to write in, we still want the heading and list, so the @@ -1129,17 +1163,20 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra js_src_path.extend(cx.current.iter().copied()); js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), it.name.unwrap())); } - let extern_crates = extern_crates - .into_iter() - .map(|cnum| tcx.crate_name(cnum).to_string()) - .collect::<Vec<_>>() - .join(","); - let (extern_before, extern_after) = - if extern_crates.is_empty() { ("", "") } else { (" data-ignore-extern-crates=\"", "\"") }; - write!( + let extern_crates = fmt::from_fn(|f| { + if !extern_crates.is_empty() { + f.write_str(" data-ignore-extern-crates=\"")?; + extern_crates.iter().map(|&cnum| tcx.crate_name(cnum)).joined(",", f)?; + f.write_str("\"")?; + } + Ok(()) + }); + write_str( w, - "<script src=\"{src}\"{extern_before}{extern_crates}{extern_after} async></script>", - src = js_src_path.finish(), + format_args!( + "<script src=\"{src}\"{extern_crates} async></script>", + src = js_src_path.finish() + ), ); } @@ -1171,21 +1208,23 @@ fn item_trait_alias( .unwrap(); } -fn item_type_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) { +fn item_type_alias(w: &mut String, cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) { wrap_item(w, |w| { - write!( + write_str( w, - "{attrs}{vis}type {name}{generics}{where_clause} = {type_};", - attrs = render_attributes_in_pre(it, "", cx), - vis = visibility_print_with_space(it, cx), - name = it.name.unwrap(), - generics = t.generics.print(cx), - where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), - type_ = t.type_.print(cx), + format_args!( + "{attrs}{vis}type {name}{generics}{where_clause} = {type_};", + attrs = render_attributes_in_pre(it, "", cx), + vis = visibility_print_with_space(it, cx), + name = it.name.unwrap(), + generics = t.generics.print(cx), + where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), + type_ = t.type_.print(cx), + ), ); }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); + write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); if let Some(inner_type) = &t.inner_type { write_section_heading(w, "Aliased Type", "aliased-type", None, ""); @@ -1201,7 +1240,7 @@ fn item_type_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean let variants_count = variants_iter().count(); let has_stripped_entries = variants_len != variants_count; - write!(w, "enum {}{}", it.name.unwrap(), t.generics.print(cx)); + write_str(w, format_args!("enum {}{}", it.name.unwrap(), t.generics.print(cx))); render_enum_fields( w, cx, @@ -1220,7 +1259,10 @@ fn item_type_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean let fields_count = fields.iter().filter(|i| !i.is_stripped()).count(); let has_stripped_fields = fields.len() != fields_count; - write!(w, "union {}{}", it.name.unwrap(), t.generics.print(cx)); + write_str( + w, + format_args!("union {}{}", it.name.unwrap(), t.generics.print(cx)), + ); render_struct_fields( w, Some(&t.generics), @@ -1239,7 +1281,10 @@ fn item_type_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean let fields_count = fields.iter().filter(|i| !i.is_stripped()).count(); let has_stripped_fields = fields.len() != fields_count; - write!(w, "struct {}{}", it.name.unwrap(), t.generics.print(cx)); + write_str( + w, + format_args!("struct {}{}", it.name.unwrap(), t.generics.print(cx)), + ); render_struct_fields( w, Some(&t.generics), @@ -1261,8 +1306,8 @@ fn item_type_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean // 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, def_id, AssocItemRender::All)); - write!(w, "{}", document_type_layout(cx, def_id)); + write_str(w, format_args!("{}", render_assoc_items(cx, it, def_id, AssocItemRender::All))); + write_str(w, format_args!("{}", document_type_layout(cx, def_id))); // [RUSTDOCIMPL] type.impl // @@ -1351,16 +1396,18 @@ fn item_type_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean .collect(); js_src_path.extend(target_fqp[..target_fqp.len() - 1].iter().copied()); js_src_path.push_fmt(format_args!("{target_type}.{}.js", target_fqp.last().unwrap())); - let self_path = self_fqp.iter().map(Symbol::as_str).collect::<Vec<&str>>().join("::"); - write!( + let self_path = fmt::from_fn(|f| self_fqp.iter().joined("::", f)); + write_str( w, - "<script src=\"{src}\" data-self-path=\"{self_path}\" async></script>", - src = js_src_path.finish(), + format_args!( + "<script src=\"{src}\" data-self-path=\"{self_path}\" async></script>", + src = js_src_path.finish() + ), ); } } -fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Union) { +fn item_union(w: &mut String, cx: &Context<'_>, it: &clean::Item, s: &clean::Union) { item_template!( #[template(path = "item_union.html")] struct ItemUnion<'a, 'cx> { @@ -1445,16 +1492,18 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>( }) } -fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) { +fn item_enum(w: &mut String, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) { let count_variants = e.variants().count(); wrap_item(w, |w| { render_attributes_in_code(w, it, cx); - write!( + write_str( w, - "{}enum {}{}", - visibility_print_with_space(it, cx), - it.name.unwrap(), - e.generics.print(cx), + format_args!( + "{}enum {}{}", + visibility_print_with_space(it, cx), + it.name.unwrap(), + e.generics.print(cx), + ), ); render_enum_fields( @@ -1469,14 +1518,14 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum ); }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); + write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); if count_variants != 0 { item_variants(w, cx, it, &e.variants, it.def_id().unwrap()); } let def_id = it.item_id.expect_def_id(); - write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); - write!(w, "{}", document_type_layout(cx, def_id)); + write_str(w, format_args!("{}", render_assoc_items(cx, it, def_id, AssocItemRender::All))); + write_str(w, format_args!("{}", document_type_layout(cx, def_id))); } /// It'll return false if any variant is not a C-like variant. Otherwise it'll return true if at @@ -1505,7 +1554,7 @@ fn should_show_enum_discriminant( } fn display_c_like_variant( - w: &mut Buffer, + w: &mut String, cx: &Context<'_>, item: &clean::Item, variant: &clean::Variant, @@ -1515,22 +1564,22 @@ fn display_c_like_variant( ) { let name = item.name.unwrap(); if let Some(ref value) = variant.discriminant { - write!(w, "{} = {}", name.as_str(), value.value(cx.tcx(), true)); + write_str(w, format_args!("{} = {}", name.as_str(), value.value(cx.tcx(), true))); } else if should_show_enum_discriminant { let adt_def = cx.tcx().adt_def(enum_def_id); let discr = adt_def.discriminant_for_variant(cx.tcx(), index); if discr.ty.is_signed() { - write!(w, "{} = {}", name.as_str(), discr.val as i128); + write_str(w, format_args!("{} = {}", name.as_str(), discr.val as i128)); } else { - write!(w, "{} = {}", name.as_str(), discr.val); + write_str(w, format_args!("{} = {}", name.as_str(), discr.val)); } } else { - w.write_str(name.as_str()); + w.push_str(name.as_str()); } } fn render_enum_fields( - mut w: &mut Buffer, + mut w: &mut String, cx: &Context<'_>, g: Option<&clean::Generics>, variants: &IndexVec<VariantIdx, clean::Item>, @@ -1542,14 +1591,14 @@ fn render_enum_fields( let should_show_enum_discriminant = should_show_enum_discriminant(cx, enum_def_id, variants); if !g.is_some_and(|g| print_where_clause_and_check(w, g, cx)) { // If there wasn't a `where` clause, we add a whitespace. - w.write_str(" "); + w.push_str(" "); } let variants_stripped = has_stripped_entries; if count_variants == 0 && !variants_stripped { - w.write_str("{}"); + w.push_str("{}"); } else { - w.write_str("{\n"); + w.push_str("{\n"); let toggle = should_hide_fields(count_variants); if toggle { toggle_open(&mut w, format_args!("{count_variants} variants")); @@ -1559,7 +1608,7 @@ fn render_enum_fields( if v.is_stripped() { continue; } - w.write_str(TAB); + w.push_str(TAB); match v.kind { clean::VariantItem(ref var) => match var.kind { clean::VariantKind::CLike => display_c_like_variant( @@ -1572,7 +1621,14 @@ fn render_enum_fields( enum_def_id, ), clean::VariantKind::Tuple(ref s) => { - write!(w, "{}({})", v.name.unwrap(), print_tuple_struct_fields(cx, s)); + write_str( + w, + format_args!( + "{}({})", + v.name.unwrap(), + print_tuple_struct_fields(cx, s) + ), + ); } clean::VariantKind::Struct(ref s) => { render_struct(w, v, None, None, &s.fields, TAB, false, cx); @@ -1580,21 +1636,21 @@ fn render_enum_fields( }, _ => unreachable!(), } - w.write_str(",\n"); + w.push_str(",\n"); } if variants_stripped && !is_non_exhaustive { - w.write_str(" <span class=\"comment\">// some variants omitted</span>\n"); + w.push_str(" <span class=\"comment\">// some variants omitted</span>\n"); } if toggle { toggle_close(&mut w); } - w.write_str("}"); + w.push_str("}"); } } fn item_variants( - w: &mut Buffer, + w: &mut String, cx: &Context<'_>, it: &clean::Item, variants: &IndexVec<VariantIdx, clean::Item>, @@ -1615,10 +1671,12 @@ fn item_variants( continue; } let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap())); - write!( + write_str( w, - "<section id=\"{id}\" class=\"variant\">\ - <a href=\"#{id}\" class=\"anchor\">§</a>", + format_args!( + "<section id=\"{id}\" class=\"variant\">\ + <a href=\"#{id}\" class=\"anchor\">§</a>" + ), ); render_stability_since_raw_with_extra( w, @@ -1626,7 +1684,7 @@ fn item_variants( variant.const_stability(tcx), " rightside", ); - w.write_str("<h3 class=\"code-header\">"); + w.push_str("<h3 class=\"code-header\">"); if let clean::VariantItem(ref var) = variant.kind && let clean::VariantKind::CLike = var.kind { @@ -1640,17 +1698,17 @@ fn item_variants( enum_def_id, ); } else { - w.write_str(variant.name.unwrap().as_str()); + w.push_str(variant.name.unwrap().as_str()); } let clean::VariantItem(variant_data) = &variant.kind else { unreachable!() }; if let clean::VariantKind::Tuple(ref s) = variant_data.kind { - write!(w, "({})", print_tuple_struct_fields(cx, s)); + write_str(w, format_args!("({})", print_tuple_struct_fields(cx, s))); } - w.write_str("</h3></section>"); + w.push_str("</h3></section>"); - write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4)); + write_str(w, format_args!("{}", document(cx, variant, Some(it), HeadingOffset::H4))); let heading_and_fields = match &variant_data.kind { clean::VariantKind::Struct(s) => { @@ -1676,12 +1734,14 @@ fn item_variants( if let Some((heading, fields)) = heading_and_fields { let variant_id = cx.derive_id(format!("{}.{}.fields", ItemType::Variant, variant.name.unwrap())); - write!( + write_str( w, - "<div class=\"sub-variant\" id=\"{variant_id}\">\ - <h4>{heading}</h4>\ - {}", - document_non_exhaustive(variant) + format_args!( + "<div class=\"sub-variant\" id=\"{variant_id}\">\ + <h4>{heading}</h4>\ + {}", + document_non_exhaustive(variant) + ), ); for field in fields { match field.kind { @@ -1692,40 +1752,44 @@ fn item_variants( variant.name.unwrap(), field.name.unwrap() )); - write!( + write_str( w, - "<div class=\"sub-variant-field\">\ - <span id=\"{id}\" class=\"section-header\">\ - <a href=\"#{id}\" class=\"anchor field\">§</a>\ - <code>{f}: {t}</code>\ - </span>", - f = field.name.unwrap(), - t = ty.print(cx), + format_args!( + "<div class=\"sub-variant-field\">\ + <span id=\"{id}\" class=\"section-header\">\ + <a href=\"#{id}\" class=\"anchor field\">§</a>\ + <code>{f}: {t}</code>\ + </span>", + f = field.name.unwrap(), + t = ty.print(cx), + ), ); - write!( + write_str( w, - "{}</div>", - document(cx, field, Some(variant), HeadingOffset::H5) + format_args!( + "{}</div>", + document(cx, field, Some(variant), HeadingOffset::H5), + ), ); } _ => unreachable!(), } } - w.write_str("</div>"); + w.push_str("</div>"); } } - write!(w, "</div>"); + write_str(w, format_args!("</div>")); } -fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) { +fn item_macro(w: &mut String, cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) { wrap_item(w, |w| { // FIXME: Also print `#[doc(hidden)]` for `macro_rules!` if it `is_doc_hidden`. if !t.macro_rules { - write!(w, "{}", visibility_print_with_space(it, cx)); + write_str(w, format_args!("{}", visibility_print_with_space(it, cx))); } - write!(w, "{}", Escape(&t.source)); + write_str(w, format_args!("{}", Escape(&t.source))); }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) + write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); } fn item_proc_macro( @@ -1779,7 +1843,7 @@ fn item_primitive(w: &mut impl fmt::Write, cx: &Context<'_>, it: &clean::Item) { } fn item_constant( - w: &mut Buffer, + w: &mut String, cx: &Context<'_>, it: &clean::Item, generics: &clean::Generics, @@ -1790,14 +1854,16 @@ fn item_constant( let tcx = cx.tcx(); render_attributes_in_code(w, it, cx); - write!( + write_str( w, - "{vis}const {name}{generics}: {typ}{where_clause}", - vis = visibility_print_with_space(it, cx), - name = it.name.unwrap(), - generics = generics.print(cx), - typ = ty.print(cx), - where_clause = print_where_clause(generics, cx, 0, Ending::NoNewline), + format_args!( + "{vis}const {name}{generics}: {typ}{where_clause}", + vis = visibility_print_with_space(it, cx), + name = it.name.unwrap(), + generics = generics.print(cx), + typ = ty.print(cx), + where_clause = print_where_clause(generics, cx, 0, Ending::NoNewline) + ), ); // FIXME: The code below now prints @@ -1813,9 +1879,9 @@ fn item_constant( let is_literal = c.is_literal(tcx); let expr = c.expr(tcx); if value.is_some() || is_literal { - write!(w, " = {expr};", expr = Escape(&expr)); + write_str(w, format_args!(" = {expr};", expr = Escape(&expr))); } else { - w.write_str(";"); + w.push_str(";"); } if !is_literal { @@ -1826,32 +1892,32 @@ fn item_constant( if value_lowercase != expr_lowercase && value_lowercase.trim_end_matches("i32") != expr_lowercase { - write!(w, " // {value}", value = Escape(value)); + write_str(w, format_args!(" // {value}", value = Escape(value))); } } } }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) + write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); } -fn item_struct(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) { +fn item_struct(w: &mut String, cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) { wrap_item(w, |w| { render_attributes_in_code(w, it, cx); render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx); }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); + write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); item_fields(w, cx, it, &s.fields, s.ctor_kind); let def_id = it.item_id.expect_def_id(); - write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); - write!(w, "{}", document_type_layout(cx, def_id)); + write_str(w, format_args!("{}", render_assoc_items(cx, it, def_id, AssocItemRender::All))); + write_str(w, format_args!("{}", document_type_layout(cx, def_id))); } fn item_fields( - w: &mut Buffer, + w: &mut String, cx: &Context<'_>, it: &clean::Item, fields: &[clean::Item], @@ -1876,16 +1942,18 @@ fn item_fields( let field_name = field.name.map_or_else(|| index.to_string(), |sym| sym.as_str().to_string()); let id = cx.derive_id(format!("{typ}.{field_name}", typ = ItemType::StructField)); - write!( + write_str( w, - "<span id=\"{id}\" class=\"{item_type} section-header\">\ - <a href=\"#{id}\" class=\"anchor field\">§</a>\ - <code>{field_name}: {ty}</code>\ - </span>", - item_type = ItemType::StructField, - ty = ty.print(cx) + format_args!( + "<span id=\"{id}\" class=\"{item_type} section-header\">\ + <a href=\"#{id}\" class=\"anchor field\">§</a>\ + <code>{field_name}: {ty}</code>\ + </span>", + item_type = ItemType::StructField, + ty = ty.print(cx) + ), ); - write!(w, "{}", document(cx, field, Some(it), HeadingOffset::H3)); + write_str(w, format_args!("{}", document(cx, field, Some(it), HeadingOffset::H3))); } } } @@ -1933,8 +2001,8 @@ fn item_foreign_type(w: &mut impl fmt::Write, cx: &Context<'_>, it: &clean::Item .unwrap(); } -fn item_keyword(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) +fn item_keyword(w: &mut String, cx: &Context<'_>, it: &clean::Item) { + write_str(w, format_args!("{}", document(cx, it, None, HeadingOffset::H2))); } /// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order). @@ -2043,34 +2111,33 @@ pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String { s } -pub(super) fn item_path(ty: ItemType, name: &str) -> String { - match ty { - ItemType::Module => format!("{}index.html", ensure_trailing_slash(name)), - _ => format!("{ty}.{name}.html"), - } +pub(super) fn item_path(ty: ItemType, name: &str) -> impl Display + '_ { + fmt::from_fn(move |f| match ty { + ItemType::Module => write!(f, "{}index.html", ensure_trailing_slash(name)), + _ => write!(f, "{ty}.{name}.html"), + }) } -fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>) -> String { - let mut bounds = String::new(); - if t_bounds.is_empty() { - return bounds; - } - let has_lots_of_bounds = t_bounds.len() > 2; - let inter_str = if has_lots_of_bounds { "\n + " } else { " + " }; - if !trait_alias { - if has_lots_of_bounds { - bounds.push_str(":\n "); - } else { - bounds.push_str(": "); - } - } - write!( - bounds, - "{}", - fmt::from_fn(|f| t_bounds.iter().map(|p| p.print(cx)).joined(inter_str, f)) - ) - .unwrap(); - bounds +fn bounds<'a, 'tcx>( + bounds: &'a [clean::GenericBound], + trait_alias: bool, + cx: &'a Context<'tcx>, +) -> impl Display + 'a + Captures<'tcx> { + (!bounds.is_empty()) + .then_some(fmt::from_fn(move |f| { + let has_lots_of_bounds = bounds.len() > 2; + let inter_str = if has_lots_of_bounds { "\n + " } else { " + " }; + if !trait_alias { + if has_lots_of_bounds { + f.write_str(":\n ")?; + } else { + f.write_str(": ")?; + } + } + + bounds.iter().map(|p| p.print(cx)).joined(inter_str, f) + })) + .maybe_display() } fn wrap_item<W, F>(w: &mut W, f: F) @@ -2108,7 +2175,7 @@ fn render_implementor( cx: &Context<'_>, implementor: &Impl, trait_: &clean::Item, - w: &mut Buffer, + w: &mut String, implementor_dups: &FxHashMap<Symbol, (DefId, bool)>, aliases: &[String], ) { @@ -2152,10 +2219,9 @@ fn render_union<'a, 'cx: 'a>( let where_displayed = g .map(|g| { - let mut buf = Buffer::html(); - write!(buf, "{}", g.print(cx)); + let mut buf = g.print(cx).to_string(); let where_displayed = print_where_clause_and_check(&mut buf, g, cx); - write!(f, "{buf}", buf = buf.into_inner()).unwrap(); + f.write_str(&buf).unwrap(); where_displayed }) .unwrap_or(false); @@ -2197,7 +2263,7 @@ fn render_union<'a, 'cx: 'a>( } fn render_struct( - w: &mut Buffer, + w: &mut String, it: &clean::Item, g: Option<&clean::Generics>, ty: Option<CtorKind>, @@ -2206,15 +2272,17 @@ fn render_struct( structhead: bool, cx: &Context<'_>, ) { - write!( + write_str( w, - "{}{}{}", - visibility_print_with_space(it, cx), - if structhead { "struct " } else { "" }, - it.name.unwrap() + format_args!( + "{}{}{}", + visibility_print_with_space(it, cx), + if structhead { "struct " } else { "" }, + it.name.unwrap() + ), ); if let Some(g) = g { - write!(w, "{}", g.print(cx)) + write_str(w, format_args!("{}", g.print(cx))); } render_struct_fields( w, @@ -2229,7 +2297,7 @@ fn render_struct( } fn render_struct_fields( - mut w: &mut Buffer, + mut w: &mut String, g: Option<&clean::Generics>, ty: Option<CtorKind>, fields: &[clean::Item], @@ -2245,9 +2313,9 @@ fn render_struct_fields( // If there wasn't a `where` clause, we add a whitespace. if !where_displayed { - w.write_str(" {"); + w.push_str(" {"); } else { - w.write_str("{"); + w.push_str("{"); } let count_fields = fields.iter().filter(|f| matches!(f.kind, clean::StructFieldItem(..))).count(); @@ -2258,66 +2326,82 @@ fn render_struct_fields( } for field in fields { if let clean::StructFieldItem(ref ty) = field.kind { - write!( + write_str( w, - "\n{tab} {vis}{name}: {ty},", - vis = visibility_print_with_space(field, cx), - name = field.name.unwrap(), - ty = ty.print(cx), + format_args!( + "\n{tab} {vis}{name}: {ty},", + vis = visibility_print_with_space(field, cx), + name = field.name.unwrap(), + ty = ty.print(cx) + ), ); } } if has_visible_fields { if has_stripped_entries { - write!(w, "\n{tab} <span class=\"comment\">/* private fields */</span>"); + write_str( + w, + format_args!( + "\n{tab} <span class=\"comment\">/* private fields */</span>" + ), + ); } - write!(w, "\n{tab}"); + write_str(w, format_args!("\n{tab}")); } else if has_stripped_entries { - write!(w, " <span class=\"comment\">/* private fields */</span> "); + write_str(w, format_args!(" <span class=\"comment\">/* private fields */</span> ")); } if toggle { toggle_close(&mut w); } - w.write_str("}"); + w.push_str("}"); } Some(CtorKind::Fn) => { - w.write_str("("); + w.push_str("("); if !fields.is_empty() && fields.iter().all(|field| { matches!(field.kind, clean::StrippedItem(box clean::StructFieldItem(..))) }) { - write!(w, "<span class=\"comment\">/* private fields */</span>"); + write_str(w, format_args!("<span class=\"comment\">/* private fields */</span>")); } else { for (i, field) in fields.iter().enumerate() { if i > 0 { - w.write_str(", "); + w.push_str(", "); } match field.kind { - clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"), + clean::StrippedItem(box clean::StructFieldItem(..)) => { + write_str(w, format_args!("_")); + } clean::StructFieldItem(ref ty) => { - write!(w, "{}{}", visibility_print_with_space(field, cx), ty.print(cx),) + write_str( + w, + format_args!( + "{}{}", + visibility_print_with_space(field, cx), + ty.print(cx) + ), + ); } _ => unreachable!(), } } } - w.write_str(")"); + w.push_str(")"); if let Some(g) = g { - write!(w, "{}", print_where_clause(g, cx, 0, Ending::NoNewline)); + write_str(w, format_args!("{}", print_where_clause(g, cx, 0, Ending::NoNewline))); } // We only want a ";" when we are displaying a tuple struct, not a variant tuple struct. if structhead { - w.write_str(";"); + w.push_str(";"); } } Some(CtorKind::Const) => { // Needed for PhantomData. if let Some(g) = g { - write!(w, "{}", print_where_clause(g, cx, 0, Ending::NoNewline)); + write_str(w, format_args!("{}", print_where_clause(g, cx, 0, Ending::NoNewline))); } - w.write_str(";"); + w.push_str(";"); } } } diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index e4a9a2b512e..95f617c9839 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -821,7 +821,7 @@ pub(crate) fn get_function_type_for_search( .map(|name| clean::PathSegment { name: *name, args: clean::GenericArgs::AngleBracketed { - args: Vec::new().into_boxed_slice(), + args: ThinVec::new(), constraints: ThinVec::new(), }, }) @@ -1265,13 +1265,14 @@ fn simplify_fn_type<'a, 'tcx>( *stored_bounds = type_bounds; } } - ty_constraints.push((RenderTypeId::AssociatedType(name), vec![ - RenderType { + ty_constraints.push(( + RenderTypeId::AssociatedType(name), + vec![RenderType { id: Some(RenderTypeId::Index(idx)), generics: None, bindings: None, - }, - ])) + }], + )) } } } diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 23ac568fdf8..64dbaf9083e 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::cmp::Ordering; use rinja::Template; use rustc_data_structures::fx::FxHashSet; @@ -11,8 +12,8 @@ use super::{Context, ItemSection, item_ty_to_section}; use crate::clean; use crate::formats::Impl; use crate::formats::item_type::ItemType; -use crate::html::format::Buffer; use crate::html::markdown::{IdMap, MarkdownWithToc}; +use crate::html::render::print_item::compare_names; #[derive(Clone, Copy)] pub(crate) enum ModuleLike { @@ -78,7 +79,7 @@ impl<'a> LinkBlock<'a> { } /// A link to an item. Content should not be escaped. -#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] +#[derive(Ord, PartialEq, Eq, Hash, Clone)] pub(crate) struct Link<'a> { /// The content for the anchor tag and title attr name: Cow<'a, str>, @@ -90,6 +91,20 @@ pub(crate) struct Link<'a> { children: Vec<Link<'a>>, } +impl PartialOrd for Link<'_> { + fn partial_cmp(&self, other: &Link<'_>) -> Option<Ordering> { + match compare_names(&self.name, &other.name) { + Ordering::Equal => (), + result => return Some(result), + } + (&self.name_html, &self.href, &self.children).partial_cmp(&( + &other.name_html, + &other.href, + &other.children, + )) + } +} + impl<'a> Link<'a> { pub fn new(href: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>) -> Self { Self { href: href.into(), name: name.into(), children: vec![], name_html: None } @@ -114,7 +129,7 @@ pub(crate) mod filters { } } -pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) { +pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut String) { let mut ids = IdMap::new(); let mut blocks: Vec<LinkBlock<'_>> = docblock_toc(cx, it, &mut ids).into_iter().collect(); let deref_id_map = cx.deref_id_map.borrow(); diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index a15ac155123..ce9c42c01cc 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -51,7 +51,7 @@ pub(crate) fn collect_spans_and_sources( let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() }; if generate_link_to_definition { - tcx.hir().walk_toplevel_module(&mut visitor); + tcx.hir_walk_toplevel_module(&mut visitor); } let sources = sources::collect_local_sources(tcx, src_root, krate); (sources, visitor.matches) @@ -173,18 +173,18 @@ impl SpanMapVisitor<'_> { } fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option<HirId>, span: Span) { - let hir = self.tcx.hir(); - let body_id = hir.enclosing_body_owner(hir_id); + let tcx = self.tcx; + let body_id = tcx.hir_enclosing_body_owner(hir_id); // FIXME: this is showing error messages for parts of the code that are not // compiled (because of cfg)! // // See discussion in https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352 - let typeck_results = self.tcx.typeck_body(hir.body_owned_by(body_id).id()); + let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id()); // Interestingly enough, for method calls, we need the whole expression whereas for static // method/function calls, we need the call expression specifically. if let Some(def_id) = typeck_results.type_dependent_def_id(expr_hir_id.unwrap_or(hir_id)) { let link = if def_id.as_local().is_some() { - LinkFromSrc::Local(rustc_span(def_id, self.tcx)) + LinkFromSrc::Local(rustc_span(def_id, tcx)) } else { LinkFromSrc::External(def_id) }; @@ -221,8 +221,8 @@ impl SpanMapVisitor<'_> { impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { type NestedFilter = nested_filter::All; - fn nested_visit_map(&mut self) -> Self::Map { - self.tcx.hir() + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { + self.tcx } fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) { @@ -288,7 +288,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { | ItemKind::Use(_, _) | ItemKind::ExternCrate(_) | ItemKind::ForeignMod { .. } - | ItemKind::GlobalAsm(_) + | ItemKind::GlobalAsm { .. } // We already have "visit_mod" above so no need to check it here. | ItemKind::Mod(_) => {} } diff --git a/src/librustdoc/html/render/tests.rs b/src/librustdoc/html/render/tests.rs index 4a9724a6f84..657cd3c82aa 100644 --- a/src/librustdoc/html/render/tests.rs +++ b/src/librustdoc/html/render/tests.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; +use super::AllTypes; use super::print_item::compare_names; -use super::{AllTypes, Buffer}; #[test] fn test_compare_names() { @@ -47,8 +47,8 @@ fn test_all_types_prints_header_once() { // Regression test for #82477 let all_types = AllTypes::new(); - let mut buffer = Buffer::new(); + let mut buffer = String::new(); all_types.print(&mut buffer); - assert_eq!(1, buffer.into_inner().matches("List of all items").count()); + assert_eq!(1, buffer.matches("List of all items").count()); } diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index fb6f3bc2c76..a4dec013fc0 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -44,7 +44,6 @@ use crate::docfs::PathError; use crate::error::Error; use crate::formats::Impl; use crate::formats::item_type::ItemType; -use crate::html::format::Buffer; use crate::html::layout; use crate::html::render::ordered_json::{EscapedJson, OrderedJson}; use crate::html::render::search_index::{SerializedSearchIndex, build_index}; @@ -207,14 +206,8 @@ fn write_static_files( if opt.emit.is_empty() || opt.emit.contains(&EmitType::Toolchain) { static_files::for_each(|f: &static_files::StaticFile| { let filename = static_dir.join(f.output_filename()); - let contents: &[u8]; - let contents_vec: Vec<u8>; - if opt.disable_minification { - contents = f.bytes; - } else { - contents_vec = f.minified(); - contents = &contents_vec; - }; + let contents: &[u8] = + if opt.disable_minification { f.src_bytes } else { f.minified_bytes }; fs::write(&filename, contents).map_err(|e| PathError::new(e, &filename)) })?; } @@ -628,7 +621,6 @@ impl TypeAliasPart { // to make that functionality work here, it needs to be called with // each type alias, and if it gives a different result, split the impl for &(type_alias_fqp, type_alias_item) in type_aliases { - let mut buf = Buffer::html(); cx.id_map.borrow_mut().clear(); cx.deref_id_map.borrow_mut().clear(); let target_did = impl_ @@ -644,23 +636,26 @@ impl TypeAliasPart { } else { AssocItemLink::Anchor(None) }; - super::render_impl( - &mut buf, - cx, - impl_, - type_alias_item, - assoc_link, - RenderMode::Normal, - None, - &[], - ImplRenderingParameters { - show_def_docs: true, - show_default_items: true, - show_non_assoc_items: true, - toggle_open_by_default: true, - }, - ); - let text = buf.into_inner(); + let text = { + let mut buf = String::new(); + super::render_impl( + &mut buf, + cx, + impl_, + type_alias_item, + assoc_link, + RenderMode::Normal, + None, + &[], + ImplRenderingParameters { + show_def_docs: true, + show_default_items: true, + show_non_assoc_items: true, + toggle_open_by_default: true, + }, + ); + buf + }; let type_alias_fqp = (*type_alias_fqp).iter().join("::"); if Some(&text) == ret.last().map(|s: &AliasSerializableImpl| &s.text) { ret.last_mut() diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index 1ac0c10c612..78c86a27632 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -1,6 +1,5 @@ use std::cell::RefCell; use std::ffi::OsStr; -use std::ops::RangeInclusive; use std::path::{Component, Path, PathBuf}; use std::{fmt, fs}; @@ -12,12 +11,13 @@ use rustc_session::Session; use rustc_span::{FileName, FileNameDisplayPreference, RealFileName, sym}; use tracing::info; +use super::highlight; +use super::layout::{self, BufDisplay}; +use super::render::Context; use crate::clean; use crate::clean::utils::has_doc_flag; use crate::docfs::PathError; use crate::error::Error; -use crate::html::render::Context; -use crate::html::{highlight, layout}; use crate::visit::DocVisitor; pub(crate) fn render(cx: &mut Context<'_>, krate: &clean::Crate) -> Result<(), Error> { @@ -238,11 +238,12 @@ impl SourceCollector<'_, '_> { resource_suffix: &shared.resource_suffix, rust_logo: has_doc_flag(self.cx.tcx(), LOCAL_CRATE.as_def_id(), sym::rust_logo), }; + let source_context = SourceContext::Standalone { file_path }; let v = layout::render( &shared.layout, &page, "", - |buf: &mut _| { + BufDisplay(|buf: &mut String| { print_src( buf, contents, @@ -250,9 +251,9 @@ impl SourceCollector<'_, '_> { self.cx, &root_path, &highlight::DecorationInfo::default(), - SourceContext::Standalone { file_path }, - ) - }, + &source_context, + ); + }), &shared.style_files, ); shared.fs.write(cur, v)?; @@ -302,17 +303,17 @@ pub(crate) struct ScrapedInfo<'a> { #[derive(Template)] #[template(path = "scraped_source.html")] struct ScrapedSource<'a, Code: std::fmt::Display> { - info: ScrapedInfo<'a>, - lines: RangeInclusive<usize>, + info: &'a ScrapedInfo<'a>, code_html: Code, + max_nb_digits: u32, } #[derive(Template)] #[template(path = "source.html")] struct Source<Code: std::fmt::Display> { - lines: RangeInclusive<usize>, code_html: Code, file_path: Option<(String, String)>, + max_nb_digits: u32, } pub(crate) enum SourceContext<'a> { @@ -329,8 +330,17 @@ pub(crate) fn print_src( context: &Context<'_>, root_path: &str, decoration_info: &highlight::DecorationInfo, - source_context: SourceContext<'_>, + source_context: &SourceContext<'_>, ) { + let mut lines = s.lines().count(); + let line_info = if let SourceContext::Embedded(ref info) = source_context { + highlight::LineInfo::new_scraped(lines as u32, info.offset as u32) + } else { + highlight::LineInfo::new(lines as u32) + }; + if line_info.is_scraped_example { + lines += line_info.start_line as usize; + } let code = fmt::from_fn(move |fmt| { let current_href = context .href_from_span(clean::Span::new(file_span), false) @@ -340,13 +350,13 @@ pub(crate) fn print_src( s, Some(highlight::HrefContext { context, file_span, root_path, current_href }), Some(decoration_info), + Some(line_info), ); Ok(()) }); - let lines = s.lines().count(); + let max_nb_digits = if lines > 0 { lines.ilog(10) + 1 } else { 1 }; match source_context { SourceContext::Standalone { file_path } => Source { - lines: (1..=lines), code_html: code, file_path: if let Some(file_name) = file_path.file_name() && let Some(file_path) = file_path.parent() @@ -355,12 +365,14 @@ pub(crate) fn print_src( } else { None }, + max_nb_digits, } .render_into(&mut writer) .unwrap(), SourceContext::Embedded(info) => { - let lines = (1 + info.offset)..=(lines + info.offset); - ScrapedSource { info, lines, code_html: code }.render_into(&mut writer).unwrap(); + ScrapedSource { info, code_html: code, max_nb_digits } + .render_into(&mut writer) + .unwrap(); } }; } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index d0612e997fd..4f5f8f92264 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -40,6 +40,20 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ --docblock-indent: 24px; --font-family: "Source Serif 4", NanumBarunGothic, serif; --font-family-code: "Source Code Pro", monospace; + --line-number-padding: 4px; + /* scraped examples icons (34x33px) */ + --prev-arrow-image: url('data:image/svg+xml,<svg width="16" height="16" viewBox="0 0 16 16" \ + enable-background="new 0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="none" \ + d="M8,3l-4,5l4,5m-4,-5h10" stroke="black" stroke-width="2"/></svg>'); + --next-arrow-image: url('data:image/svg+xml,<svg width="16" height="16" viewBox="0 0 16 16" \ + enable-background="new 0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="none" \ + d="M8,3l4,5l-4,5m4,-5h-10" stroke="black" stroke-width="2"/></svg>'); + --expand-arrow-image: url('data:image/svg+xml,<svg width="16" height="16" viewBox="0 0 16 16" \ + enable-background="new 0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="none" \ + d="M3,10l4,4l4,-4m-4,4M3,7l4,-4l4,4" stroke="black" stroke-width="2"/></svg>'); + --collapse-arrow-image: url('data:image/svg+xml,<svg width="16" height="16" viewBox="0 0 16 16" \ + enable-background="new 0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="none" \ + d="M3,8l4,4l4,-4m-4,4M3,4l4,4l4,-4" stroke="black" stroke-width="2"/></svg>'); } :root.sans-serif { @@ -450,9 +464,7 @@ pre.item-decl { .src .content pre { padding: 20px; -} -.rustdoc.src .example-wrap .src-line-numbers { - padding: 20px 0 20px 4px; + padding-left: 16px; } img { @@ -901,29 +913,58 @@ both the code example and the line numbers, so we need to remove the radius in t min-width: fit-content; /* prevent collapsing into nothing in truncated scraped examples */ flex-grow: 0; text-align: right; + -moz-user-select: none; -webkit-user-select: none; + -ms-user-select: none; user-select: none; padding: 14px 8px; padding-right: 2px; color: var(--src-line-numbers-span-color); } -.rustdoc .scraped-example .example-wrap .src-line-numbers { - padding: 0; +.example-wrap.digits-1 [data-nosnippet] { + width: calc(1ch + var(--line-number-padding) * 2); +} +.example-wrap.digits-2 [data-nosnippet] { + width: calc(2ch + var(--line-number-padding) * 2); +} +.example-wrap.digits-3 [data-nosnippet] { + width: calc(3ch + var(--line-number-padding) * 2); +} +.example-wrap.digits-4 [data-nosnippet] { + width: calc(4ch + var(--line-number-padding) * 2); +} +.example-wrap.digits-5 [data-nosnippet] { + width: calc(5ch + var(--line-number-padding) * 2); } -.rustdoc .src-line-numbers pre { - padding: 14px 0; +.example-wrap.digits-6 [data-nosnippet] { + width: calc(6ch + var(--line-number-padding) * 2); } -.src-line-numbers a, .src-line-numbers span { +.example-wrap.digits-7 [data-nosnippet] { + width: calc(7ch + var(--line-number-padding) * 2); +} +.example-wrap.digits-8 [data-nosnippet] { + width: calc(8ch + var(--line-number-padding) * 2); +} +.example-wrap.digits-9 [data-nosnippet] { + width: calc(9ch + var(--line-number-padding) * 2); +} + +.example-wrap [data-nosnippet] { color: var(--src-line-numbers-span-color); - padding: 0 8px; + text-align: right; + display: inline-block; + margin-right: 20px; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + padding: 0 4px; } -.src-line-numbers :target { - background-color: transparent; +.example-wrap [data-nosnippet]:target { border-right: none; - padding: 0 8px; } -.src-line-numbers .line-highlighted { +.example-wrap .line-highlighted[data-nosnippet] { background-color: var(--src-line-number-highlighted-background-color); } @@ -1110,7 +1151,7 @@ because of the `[-]` element which would overlap with it. */ } .main-heading a:hover, -.example-wrap .rust a:hover, +.example-wrap .rust a:hover:not([data-nosnippet]), .all-items a:hover, .docblock a:not(.scrape-help):not(.tooltip):hover:not(.doc-anchor), .item-table dd a:not(.scrape-help):not(.tooltip):hover, @@ -1568,7 +1609,7 @@ pre.rust .doccomment { color: var(--code-highlight-doc-comment-color); } -.rustdoc.src .example-wrap pre.rust a { +.rustdoc.src .example-wrap pre.rust a:not([data-nosnippet]) { background: var(--codeblock-link-background); } @@ -1701,7 +1742,10 @@ instead, we check that it's not a "finger" cursor. padding: 2px 0 0 4px; } .example-wrap .button-holder .copy-button::before, -.example-wrap .test-arrow::before { +.example-wrap .test-arrow::before, +.example-wrap .button-holder .prev::before, +.example-wrap .button-holder .next::before, +.example-wrap .button-holder .expand::before { filter: var(--copy-path-img-filter); } .example-wrap .button-holder .copy-button::before { @@ -1716,6 +1760,24 @@ instead, we check that it's not a "finger" cursor. padding-right: 5px; } +.example-wrap .button-holder .prev, +.example-wrap .button-holder .next, +.example-wrap .button-holder .expand { + line-height: 0px; +} +.example-wrap .button-holder .prev::before { + content: var(--prev-arrow-image); +} +.example-wrap .button-holder .next::before { + content: var(--next-arrow-image); +} +.example-wrap .button-holder .expand::before { + content: var(--expand-arrow-image); +} +.example-wrap .button-holder .expand.collapse::before { + content: var(--collapse-arrow-image); +} + .code-attribute { font-weight: 300; color: var(--code-attribute-color); @@ -1759,8 +1821,7 @@ instead, we check that it's not a "finger" cursor. } } -:target { - padding-right: 3px; +:target:not([data-nosnippet]) { background-color: var(--target-background-color); border-right: 3px solid var(--target-border-color); } @@ -1985,6 +2046,13 @@ button#toggle-all-docs:before { filter: var(--settings-menu-filter); } +button#toggle-all-docs.will-expand:before { + /* Custom arrow icon */ + content: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 12 12" \ + enable-background="new 0 0 12 12" xmlns="http://www.w3.org/2000/svg">\ + <path d="M2,5l4,-4l4,4M2,7l4,4l4,-4" stroke="black" fill="none" stroke-width="2px"/></svg>'); +} + #help-button > a:before { /* Question mark with circle */ content: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 12 12" \ @@ -3153,7 +3221,7 @@ Original by Dempfi (https://github.com/dempfi/ayu) color: #ff7733; } -:root[data-theme="ayu"] .src-line-numbers .line-highlighted { +:root[data-theme="ayu"] a[data-nosnippet].line-highlighted { color: #708090; padding-right: 7px; border-right: 1px solid #ffb44c; diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index a348c6c5678..e46cc1897e9 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -2039,7 +2039,10 @@ function preLoadCss(cssUrl) { // Most page titles are '<Item> in <path::to::module> - Rust', except // modules (which don't have the first part) and keywords/primitives // (which don't have a module path) - const [item, module] = document.title.split(" in "); + const titleElement = document.querySelector("title"); + const title = titleElement && titleElement.textContent ? + titleElement.textContent.replace(" - Rust", "") : ""; + const [item, module] = title.split(" in "); const path = [item]; if (module !== undefined) { path.unshift(module); diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index d08f15a5bfa..d641405c875 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -16,7 +16,7 @@ // Scroll code block to the given code location function scrollToLoc(elt, loc, isHidden) { - const lines = elt.querySelector(".src-line-numbers > pre"); + const lines = elt.querySelectorAll("[data-nosnippet]"); let scrollOffset; // If the block is greater than the size of the viewer, @@ -25,24 +25,24 @@ const maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES; if (loc[1] - loc[0] > maxLines) { const line = Math.max(0, loc[0] - 1); - scrollOffset = lines.children[line].offsetTop; + scrollOffset = lines[line].offsetTop; } else { const halfHeight = elt.offsetHeight / 2; - const offsetTop = lines.children[loc[0]].offsetTop; - const lastLine = lines.children[loc[1]]; + const offsetTop = lines[loc[0]].offsetTop; + const lastLine = lines[loc[1]]; const offsetBot = lastLine.offsetTop + lastLine.offsetHeight; const offsetMid = (offsetTop + offsetBot) / 2; scrollOffset = offsetMid - halfHeight; } - lines.parentElement.scrollTo(0, scrollOffset); + lines[0].parentElement.scrollTo(0, scrollOffset); elt.querySelector(".rust").scrollTo(0, scrollOffset); } function createScrapeButton(parent, className, content) { const button = document.createElement("button"); button.className = className; - button.innerText = content; + button.title = content; parent.insertBefore(button, parent.firstChild); return button; } @@ -54,14 +54,14 @@ let expandButton = null; if (!example.classList.contains("expanded")) { - expandButton = createScrapeButton(buttonHolder, "expand", "↕"); + expandButton = createScrapeButton(buttonHolder, "expand", "Show all"); } const isHidden = example.parentElement.classList.contains("more-scraped-examples"); const locs = example.locs; if (locs.length > 1) { - const next = createScrapeButton(buttonHolder, "next", "≻"); - const prev = createScrapeButton(buttonHolder, "prev", "≺"); + const next = createScrapeButton(buttonHolder, "next", "Next usage"); + const prev = createScrapeButton(buttonHolder, "prev", "Previous usage"); // Toggle through list of examples in a given file const onChangeLoc = changeIndex => { @@ -94,9 +94,13 @@ expandButton.addEventListener("click", () => { if (hasClass(example, "expanded")) { removeClass(example, "expanded"); + removeClass(expandButton, "collapse"); + expandButton.title = "Show all"; scrollToLoc(example, locs[0][0], isHidden); } else { addClass(example, "expanded"); + addClass(expandButton, "collapse"); + expandButton.title = "Show single example"; } }); } diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 121a43e3d92..ccbd6811b07 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -5318,8 +5318,9 @@ function registerSearchEvents() { // @ts-expect-error searchState.input.addEventListener("blur", () => { - // @ts-expect-error - searchState.input.placeholder = searchState.input.origPlaceholder; + if (window.searchState.input) { + window.searchState.input.placeholder = window.searchState.origPlaceholder; + } }); // Push and pop states are used to add search results to the browser diff --git a/src/librustdoc/html/static/js/src-script.js b/src/librustdoc/html/static/js/src-script.js index 8f712f4c20c..fc27241334b 100644 --- a/src/librustdoc/html/static/js/src-script.js +++ b/src/librustdoc/html/static/js/src-script.js @@ -138,10 +138,8 @@ function highlightSrcLines() { if (x) { x.scrollIntoView(); } - onEachLazy(document.getElementsByClassName("src-line-numbers"), e => { - onEachLazy(e.getElementsByTagName("a"), i_e => { - removeClass(i_e, "line-highlighted"); - }); + onEachLazy(document.querySelectorAll("a[data-nosnippet]"), e => { + removeClass(e, "line-highlighted"); }); for (let i = from; i <= to; ++i) { elem = document.getElementById(i); @@ -200,7 +198,7 @@ const handleSrcHighlight = (function() { window.addEventListener("hashchange", highlightSrcLines); -onEachLazy(document.getElementsByClassName("src-line-numbers"), el => { +onEachLazy(document.querySelectorAll("a[data-nosnippet]"), el => { el.addEventListener("click", handleSrcHighlight); }); diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index 0bcaf11da0c..45589a37069 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -8,26 +8,18 @@ use std::{fmt, str}; pub(crate) struct StaticFile { pub(crate) filename: PathBuf, - pub(crate) bytes: &'static [u8], + pub(crate) src_bytes: &'static [u8], + pub(crate) minified_bytes: &'static [u8], } impl StaticFile { - fn new(filename: &str, bytes: &'static [u8], sha256: &'static str) -> StaticFile { - Self { filename: static_filename(filename, sha256), bytes } - } - - pub(crate) fn minified(&self) -> Vec<u8> { - let extension = match self.filename.extension() { - Some(e) => e, - None => return self.bytes.to_owned(), - }; - if extension == "css" { - minifier::css::minify(str::from_utf8(self.bytes).unwrap()).unwrap().to_string().into() - } else if extension == "js" { - minifier::js::minify(str::from_utf8(self.bytes).unwrap()).to_string().into() - } else { - self.bytes.to_owned() - } + fn new( + filename: &str, + src_bytes: &'static [u8], + minified_bytes: &'static [u8], + sha256: &'static str, + ) -> StaticFile { + Self { filename: static_filename(filename, sha256), src_bytes, minified_bytes } } pub(crate) fn output_filename(&self) -> &Path { @@ -68,7 +60,7 @@ macro_rules! static_files { // sha256 files are generated in build.rs pub(crate) static STATIC_FILES: std::sync::LazyLock<StaticFiles> = std::sync::LazyLock::new(|| StaticFiles { - $($field: StaticFile::new($file_path, include_bytes!($file_path), include_str!(concat!(env!("OUT_DIR"), "/", $file_path, ".sha256"))),)+ + $($field: StaticFile::new($file_path, include_bytes!($file_path), include_bytes!(concat!(env!("OUT_DIR"), "/", $file_path, ".min")), include_str!(concat!(env!("OUT_DIR"), "/", $file_path, ".sha256"))),)+ }); pub(crate) fn for_each<E>(f: impl Fn(&StaticFile) -> Result<(), E>) -> Result<(), E> { diff --git a/src/librustdoc/html/templates/scraped_source.html b/src/librustdoc/html/templates/scraped_source.html index bd54bbf58d5..3e69f1c8cad 100644 --- a/src/librustdoc/html/templates/scraped_source.html +++ b/src/librustdoc/html/templates/scraped_source.html @@ -2,17 +2,7 @@ <div class="scraped-example-title"> {{info.name +}} (<a href="{{info.url}}">{{info.title}}</a>) {# #} </div> {# #} - <div class="example-wrap"> - {# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr - Do not show "1 2 3 4 5 ..." in web search results. #} - <div class="src-line-numbers" data-nosnippet> {# #} - <pre> - {% for line in lines.clone() %} - {# ~#} - <span>{{line|safe}}</span> - {% endfor %} - </pre> {# #} - </div> {# #} + <div class="example-wrap digits-{{max_nb_digits}}"> {# #} <pre class="rust"> {# #} <code> {{code_html|safe}} diff --git a/src/librustdoc/html/templates/source.html b/src/librustdoc/html/templates/source.html index ea530087e6f..454d4c27f1a 100644 --- a/src/librustdoc/html/templates/source.html +++ b/src/librustdoc/html/templates/source.html @@ -9,15 +9,7 @@ </div> {% else %} {% endmatch %} -<div class="example-wrap"> - {# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr - Do not show "1 2 3 4 5 ..." in web search results. #} - <div data-nosnippet><pre class="src-line-numbers"> - {% for line in lines.clone() %} - {# ~#} - <a href="#{{line|safe}}" id="{{line|safe}}">{{line|safe}}</a> - {% endfor %} - </pre></div> {# #} +<div class="example-wrap digits-{{max_nb_digits}}"> {# #} <pre class="rust"> {# #} <code> {{code_html|safe}} |
