diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index f9ea829e388..024ea62c31a 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; @@ -126,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(); } @@ -279,8 +280,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()) @@ -1306,6 +1305,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, @@ -1345,95 +1366,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, 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!(f, "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!(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(()) } }