diff options
Diffstat (limited to 'src/librustdoc/html/format.rs')
-rw-r--r-- | src/librustdoc/html/format.rs | 202 |
1 files changed, 104 insertions, 98 deletions
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 0e4c5ed68..1b445b898 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -1,13 +1,15 @@ //! HTML formatting module //! //! This module contains a large number of `fmt::Display` implementations for -//! various types in `rustdoc::clean`. These implementations all currently -//! assume that HTML output is desired, although it may be possible to redesign -//! them in the future to instead emit any format desired. +//! various types in `rustdoc::clean`. +//! +//! These implementations all emit HTML. As an internal implementation detail, +//! some of them support an alternate format that emits text, but that should +//! not be used external to this module. use std::borrow::Cow; use std::cell::Cell; -use std::fmt; +use std::fmt::{self, Write}; use std::iter::{self, once}; use rustc_ast as ast; @@ -19,7 +21,6 @@ use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_metadata::creader::{CStore, LoadedMacro}; use rustc_middle::ty; -use rustc_middle::ty::DefIdTree; use rustc_middle::ty::TyCtxt; use rustc_span::symbol::kw; use rustc_span::{sym, Symbol}; @@ -127,7 +128,6 @@ impl Buffer { // the fmt::Result return type imposed by fmt::Write (and avoiding the trait // import). pub(crate) fn write_fmt(&mut self, v: fmt::Arguments<'_>) { - use fmt::Write; self.buffer.write_fmt(v).unwrap(); } @@ -136,10 +136,6 @@ impl Buffer { self.into_inner() } - pub(crate) fn is_for_html(&self) -> bool { - self.for_html - } - pub(crate) fn reserve(&mut self, additional: usize) { self.buffer.reserve(additional) } @@ -280,8 +276,6 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( indent: usize, ending: Ending, ) -> impl fmt::Display + 'a + Captures<'tcx> { - use fmt::Write; - display_fn(move |f| { let mut where_predicates = gens.where_predicates.iter().filter(|pred| { !matches!(pred, clean::WherePredicate::BoundPredicate { bounds, .. } if bounds.is_empty()) @@ -309,13 +303,13 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( write!( f, "for<{:#}> {ty_cx:#}: {generic_bounds:#}", - comma_sep(bound_params.iter().map(|lt| lt.print()), true) + comma_sep(bound_params.iter().map(|lt| lt.print(cx)), true) ) } else { write!( f, "for<{}> {ty_cx}: {generic_bounds}", - comma_sep(bound_params.iter().map(|lt| lt.print()), true) + comma_sep(bound_params.iter().map(|lt| lt.print(cx)), true) ) } } @@ -355,10 +349,10 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( let mut br_with_padding = String::with_capacity(6 * indent + 28); br_with_padding.push_str("\n"); - let padding_amout = + let padding_amount = if ending == Ending::Newline { indent + 4 } else { indent + "fn where ".len() }; - for _ in 0..padding_amout { + for _ in 0..padding_amount { br_with_padding.push_str(" "); } let where_preds = where_preds.to_string().replace('\n', &br_with_padding); @@ -773,6 +767,12 @@ pub(crate) fn link_tooltip(did: DefId, fragment: &Option<UrlFragment>, cx: &Cont .or_else(|| cache.external_paths.get(&did)) else { return String::new() }; let mut buf = Buffer::new(); + let fqp = if *shortty == ItemType::Primitive { + // primitives are documented in a crate, but not actually part of it + &fqp[fqp.len() - 1..] + } else { + &fqp + }; if let &Some(UrlFragment::Item(id)) = fragment { write!(buf, "{} ", cx.tcx().def_descr(id)); for component in fqp { @@ -1138,22 +1138,21 @@ fn fmt_type<'cx>( // the ugliness comes from inlining across crates where // everything comes in as a fully resolved QPath (hard to // look at). - match href(trait_.def_id(), cx) { - Ok((ref url, _, ref path)) if !f.alternate() => { - write!( - f, - "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \ - title=\"type {path}::{name}\">{name}</a>{args}", - url = url, - shortty = ItemType::AssocType, - name = assoc.name, - path = join_with_double_colon(path), - args = assoc.args.print(cx), - )?; - } - _ => write!(f, "{}{:#}", assoc.name, assoc.args.print(cx))?, - } - Ok(()) + if !f.alternate() && let Ok((url, _, path)) = href(trait_.def_id(), cx) { + write!( + f, + "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \ + title=\"type {path}::{name}\">{name}</a>", + shortty = ItemType::AssocType, + name = assoc.name, + path = join_with_double_colon(&path), + ) + } else { + write!(f, "{}", assoc.name) + }?; + + // Carry `f.alternate()` into this display w/o branching manually. + fmt::Display::fmt(&assoc.args.print(cx), f) } } } @@ -1307,6 +1306,28 @@ impl clean::BareFunctionDecl { } } +// Implements Write but only counts the bytes "written". +struct WriteCounter(usize); + +impl std::fmt::Write for WriteCounter { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.0 += s.len(); + Ok(()) + } +} + +// Implements Display by emitting the given number of spaces. +struct Indent(usize); + +impl fmt::Display for Indent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (0..self.0).for_each(|_| { + f.write_char(' ').unwrap(); + }); + Ok(()) + } +} + impl clean::FnDecl { pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>( &'a self, @@ -1346,95 +1367,80 @@ impl clean::FnDecl { indent: usize, cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx> { - display_fn(move |f| self.inner_full_print(header_len, indent, f, cx)) + display_fn(move |f| { + // First, generate the text form of the declaration, with no line wrapping, and count the bytes. + let mut counter = WriteCounter(0); + write!(&mut counter, "{:#}", display_fn(|f| { self.inner_full_print(None, f, cx) })) + .unwrap(); + // If the text form was over 80 characters wide, we will line-wrap our output. + let line_wrapping_indent = + if header_len + counter.0 > 80 { Some(indent) } else { None }; + // Generate the final output. This happens to accept `{:#}` formatting to get textual + // output but in practice it is only formatted with `{}` to get HTML output. + self.inner_full_print(line_wrapping_indent, f, cx) + }) } fn inner_full_print( &self, - header_len: usize, - indent: usize, + // For None, the declaration will not be line-wrapped. For Some(n), + // the declaration will be line-wrapped, with an indent of n spaces. + line_wrapping_indent: Option<usize>, f: &mut fmt::Formatter<'_>, cx: &Context<'_>, ) -> fmt::Result { let amp = if f.alternate() { "&" } else { "&" }; - let mut args = Buffer::html(); - let mut args_plain = Buffer::new(); + + write!(f, "(")?; + if let Some(n) = line_wrapping_indent { + write!(f, "\n{}", Indent(n + 4))?; + } for (i, input) in self.inputs.values.iter().enumerate() { + if i > 0 { + match line_wrapping_indent { + None => write!(f, ", ")?, + Some(n) => write!(f, ",\n{}", Indent(n + 4))?, + }; + } if let Some(selfty) = input.to_self() { match selfty { clean::SelfValue => { - args.push_str("self"); - args_plain.push_str("self"); + write!(f, "self")?; } clean::SelfBorrowed(Some(ref lt), mtbl) => { - write!(args, "{}{} {}self", amp, lt.print(), mtbl.print_with_space()); - write!(args_plain, "&{} {}self", lt.print(), mtbl.print_with_space()); + write!(f, "{}{} {}self", amp, lt.print(), mtbl.print_with_space())?; } clean::SelfBorrowed(None, mtbl) => { - write!(args, "{}{}self", amp, mtbl.print_with_space()); - write!(args_plain, "&{}self", mtbl.print_with_space()); + write!(f, "{}{}self", amp, mtbl.print_with_space())?; } clean::SelfExplicit(ref typ) => { - if f.alternate() { - write!(args, "self: {:#}", typ.print(cx)); - } else { - write!(args, "self: {}", typ.print(cx)); - } - write!(args_plain, "self: {:#}", typ.print(cx)); + write!(f, "self: ")?; + fmt::Display::fmt(&typ.print(cx), f)?; } } } else { - if i > 0 { - args.push_str("\n"); - } if input.is_const { - args.push_str("const "); - args_plain.push_str("const "); - } - write!(args, "{}: ", input.name); - write!(args_plain, "{}: ", input.name); - - if f.alternate() { - write!(args, "{:#}", input.type_.print(cx)); - } else { - write!(args, "{}", input.type_.print(cx)); + write!(f, "const ")?; } - write!(args_plain, "{:#}", input.type_.print(cx)); - } - if i + 1 < self.inputs.values.len() { - args.push_str(","); - args_plain.push_str(","); + write!(f, "{}: ", input.name)?; + fmt::Display::fmt(&input.type_.print(cx), f)?; } } - let mut args_plain = format!("({})", args_plain.into_inner()); - let mut args = args.into_inner(); - if self.c_variadic { - args.push_str(",\n ..."); - args_plain.push_str(", ..."); + match line_wrapping_indent { + None => write!(f, ", ...")?, + Some(n) => write!(f, "\n{}...", Indent(n + 4))?, + }; } - let arrow_plain = format!("{:#}", self.output.print(cx)); - let arrow = - if f.alternate() { arrow_plain.clone() } else { format!("{}", self.output.print(cx)) }; - - let declaration_len = header_len + args_plain.len() + arrow_plain.len(); - let output = if declaration_len > 80 { - let full_pad = format!("\n{}", " ".repeat(indent + 4)); - let close_pad = format!("\n{}", " ".repeat(indent)); - format!( - "({pad}{args}{close}){arrow}", - pad = if self.inputs.values.is_empty() { "" } else { &full_pad }, - args = args.replace('\n', &full_pad), - close = close_pad, - arrow = arrow - ) - } else { - format!("({args}){arrow}", args = args.replace('\n', " "), arrow = arrow) + match line_wrapping_indent { + None => write!(f, ")")?, + Some(n) => write!(f, "\n{})", Indent(n))?, }; - write!(f, "{}", output) + fmt::Display::fmt(&self.output.print(cx), f)?; + Ok(()) } } @@ -1469,7 +1475,7 @@ pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>( debug!("path={:?}", path); // modified from `resolved_path()` to work with `DefPathData` let last_name = path.data.last().unwrap().data.get_opt_name().unwrap(); - let anchor = anchor(vis_did, last_name, cx).to_string(); + let anchor = anchor(vis_did, last_name, cx); let mut s = "pub(in ".to_owned(); for seg in &path.data[..path.data.len() - 1] { @@ -1491,9 +1497,9 @@ pub(crate) fn visibility_to_src_with_space<'a, 'tcx: 'a>( tcx: TyCtxt<'tcx>, item_did: DefId, ) -> impl fmt::Display + 'a + Captures<'tcx> { - let to_print = match visibility { - None => String::new(), - Some(ty::Visibility::Public) => "pub ".to_owned(), + let to_print: Cow<'static, str> = match visibility { + None => "".into(), + Some(ty::Visibility::Public) => "pub ".into(), Some(ty::Visibility::Restricted(vis_did)) => { // FIXME(camelid): This may not work correctly if `item_did` is a module. // However, rustdoc currently never displays a module's @@ -1501,17 +1507,17 @@ pub(crate) fn visibility_to_src_with_space<'a, 'tcx: 'a>( let parent_module = find_nearest_parent_module(tcx, item_did); if vis_did.is_crate_root() { - "pub(crate) ".to_owned() + "pub(crate) ".into() } else if parent_module == Some(vis_did) { // `pub(in foo)` where `foo` is the parent module // is the same as no visibility modifier - String::new() + "".into() } else if parent_module.and_then(|parent| find_nearest_parent_module(tcx, parent)) == Some(vis_did) { - "pub(super) ".to_owned() + "pub(super) ".into() } else { - format!("pub(in {}) ", tcx.def_path_str(vis_did)) + format!("pub(in {}) ", tcx.def_path_str(vis_did)).into() } } }; |