diff options
| author | Matthias Krüger <matthias.krueger@famsik.de> | 2024-07-29 21:26:11 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-29 21:26:11 +0200 |
| commit | 9aedec9313dc8ecf9bdcb5f09c4eb0ad8b9a4875 (patch) | |
| tree | b9d4fc3ae4b3abc11c5a9dadf910dcd41c5fb660 /src | |
| parent | 66e5852c3bd022e2a589a089a0bc8392f8b291e1 (diff) | |
| parent | ac303df4e21eabfbf692f2d1959f7403f4887a31 (diff) | |
| download | rust-9aedec9313dc8ecf9bdcb5f09c4eb0ad8b9a4875.tar.gz rust-9aedec9313dc8ecf9bdcb5f09c4eb0ad8b9a4875.zip | |
Rollup merge of #126247 - notriddle:notriddle/word-wrap-item-table, r=GuillaumeGomez
rustdoc: word wrap CamelCase in the item list table and sidebar This is an alternative to https://github.com/rust-lang/rust/pull/126209. That is, it fixes the issue that affects the very long type names in https://docs.rs/async-stripe/0.31.0/stripe/index.html#structs. This is, necessarily, a pile of nasty heuristics. We need to balance a few issues: - Sometimes, there's no real word break. For example, `BTreeMap` should be `BTree<wbr>Map`, not `B<wbr>Tree<wbr>Map`. - Sometimes, there's a legit word break, but the name is tiny and the HTML overhead isn't worth it. For example, if we're typesetting `TyCtx`, writing `Ty<wbr>Ctx` would have an HTML overhead of 50%. Line breaking inside it makes no sense. # Screenshots | Before | After | | ------ | ----- | |  | 
Diffstat (limited to 'src')
| -rw-r--r-- | src/librustdoc/Cargo.toml | 1 | ||||
| -rw-r--r-- | src/librustdoc/html/escape.rs | 55 | ||||
| -rw-r--r-- | src/librustdoc/html/escape/tests.rs | 68 | ||||
| -rw-r--r-- | src/librustdoc/html/format.rs | 3 | ||||
| -rw-r--r-- | src/librustdoc/html/layout.rs | 2 | ||||
| -rw-r--r-- | src/librustdoc/html/render/mod.rs | 2 | ||||
| -rw-r--r-- | src/librustdoc/html/render/print_item.rs | 8 | ||||
| -rw-r--r-- | src/librustdoc/html/render/sidebar.rs | 16 | ||||
| -rw-r--r-- | src/librustdoc/html/static/css/rustdoc.css | 5 | ||||
| -rw-r--r-- | src/librustdoc/html/templates/page.html | 2 | ||||
| -rw-r--r-- | src/librustdoc/html/templates/sidebar.html | 8 | ||||
| -rw-r--r-- | src/tools/compiletest/src/runtest.rs | 1 |
12 files changed, 160 insertions, 11 deletions
diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index fe531f0ff59..dfd7414652f 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -23,6 +23,7 @@ tempfile = "3" tracing = "0.1" tracing-tree = "0.3.0" threadpool = "1.8.1" +unicode-segmentation = "1.9" [dependencies.tracing-subscriber] version = "0.3.3" diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs index ea4b573aeb9..691f86847b5 100644 --- a/src/librustdoc/html/escape.rs +++ b/src/librustdoc/html/escape.rs @@ -5,6 +5,8 @@ use std::fmt; +use unicode_segmentation::UnicodeSegmentation; + /// Wrapper struct which will emit the HTML-escaped version of the contained /// string when passed to a format string. pub(crate) struct Escape<'a>(pub &'a str); @@ -74,3 +76,56 @@ impl<'a> fmt::Display for EscapeBodyText<'a> { Ok(()) } } + +/// Wrapper struct which will emit the HTML-escaped version of the contained +/// string when passed to a format string. This function also word-breaks +/// CamelCase and snake_case word names. +/// +/// This is only safe to use for text nodes. If you need your output to be +/// safely contained in an attribute, use [`Escape`]. If you don't know the +/// difference, use [`Escape`]. +pub(crate) struct EscapeBodyTextWithWbr<'a>(pub &'a str); + +impl<'a> fmt::Display for EscapeBodyTextWithWbr<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let EscapeBodyTextWithWbr(text) = *self; + if text.len() < 8 { + return EscapeBodyText(text).fmt(fmt); + } + let mut last = 0; + let mut it = text.grapheme_indices(true).peekable(); + let _ = it.next(); // don't insert wbr before first char + while let Some((i, s)) = it.next() { + let pk = it.peek(); + if s.chars().all(|c| c.is_whitespace()) { + // don't need "First <wbr>Second"; the space is enough + EscapeBodyText(&text[last..i]).fmt(fmt)?; + last = i; + continue; + } + let is_uppercase = || s.chars().any(|c| c.is_uppercase()); + let next_is_uppercase = + || pk.map_or(true, |(_, t)| t.chars().any(|c| c.is_uppercase())); + let next_is_underscore = || pk.map_or(true, |(_, t)| t.contains('_')); + let next_is_colon = || pk.map_or(true, |(_, t)| t.contains(':')); + if i - last > 3 && is_uppercase() && !next_is_uppercase() { + EscapeBodyText(&text[last..i]).fmt(fmt)?; + fmt.write_str("<wbr>")?; + last = i; + } else if (s.contains(':') && !next_is_colon()) + || (s.contains('_') && !next_is_underscore()) + { + EscapeBodyText(&text[last..i + 1]).fmt(fmt)?; + fmt.write_str("<wbr>")?; + last = i + 1; + } + } + if last < text.len() { + EscapeBodyText(&text[last..]).fmt(fmt)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests; diff --git a/src/librustdoc/html/escape/tests.rs b/src/librustdoc/html/escape/tests.rs new file mode 100644 index 00000000000..a09649e9e18 --- /dev/null +++ b/src/librustdoc/html/escape/tests.rs @@ -0,0 +1,68 @@ +// basic examples +#[test] +fn escape_body_text_with_wbr() { + use super::EscapeBodyTextWithWbr as E; + // extreme corner cases + assert_eq!(&E("").to_string(), ""); + assert_eq!(&E("a").to_string(), "a"); + assert_eq!(&E("A").to_string(), "A"); + assert_eq!(&E("_").to_string(), "_"); + assert_eq!(&E(":").to_string(), ":"); + assert_eq!(&E(" ").to_string(), " "); + assert_eq!(&E("___________").to_string(), "___________"); + assert_eq!(&E(":::::::::::").to_string(), ":::::::::::"); + assert_eq!(&E(" ").to_string(), " "); + // real(istic) examples + assert_eq!(&E("FirstSecond").to_string(), "First<wbr>Second"); + assert_eq!(&E("First_Second").to_string(), "First_<wbr>Second"); + assert_eq!(&E("First Second").to_string(), "First Second"); + assert_eq!(&E("First HSecond").to_string(), "First HSecond"); + assert_eq!(&E("First HTTPSecond").to_string(), "First HTTP<wbr>Second"); + assert_eq!(&E("First SecondThird").to_string(), "First Second<wbr>Third"); + assert_eq!(&E("First<T>_Second").to_string(), "First<<wbr>T>_<wbr>Second"); + assert_eq!(&E("first_second").to_string(), "first_<wbr>second"); + assert_eq!(&E("first:second").to_string(), "first:<wbr>second"); + assert_eq!(&E("first::second").to_string(), "first::<wbr>second"); + assert_eq!(&E("MY_CONSTANT").to_string(), "MY_<wbr>CONSTANT"); + // a string won't get wrapped if it's less than 8 bytes + assert_eq!(&E("HashSet").to_string(), "HashSet"); + // an individual word won't get wrapped if it's less than 4 bytes + assert_eq!(&E("VecDequeue").to_string(), "VecDequeue"); + assert_eq!(&E("VecDequeueSet").to_string(), "VecDequeue<wbr>Set"); + // how to handle acronyms + assert_eq!(&E("BTreeMap").to_string(), "BTree<wbr>Map"); + assert_eq!(&E("HTTPSProxy").to_string(), "HTTPS<wbr>Proxy"); + // more corners + assert_eq!(&E("ṼẽçÑñéå").to_string(), "Ṽẽç<wbr>Ññéå"); + assert_eq!(&E("V\u{0300}e\u{0300}c\u{0300}D\u{0300}e\u{0300}q\u{0300}u\u{0300}e\u{0300}u\u{0300}e\u{0300}").to_string(), "V\u{0300}e\u{0300}c\u{0300}<wbr>D\u{0300}e\u{0300}q\u{0300}u\u{0300}e\u{0300}u\u{0300}e\u{0300}"); + assert_eq!(&E("LPFNACCESSIBLEOBJECTFROMWINDOW").to_string(), "LPFNACCESSIBLEOBJECTFROMWINDOW"); +} +// property test +#[test] +fn escape_body_text_with_wbr_makes_sense() { + use itertools::Itertools as _; + + use super::EscapeBodyTextWithWbr as E; + const C: [u8; 3] = [b'a', b'A', b'_']; + for chars in [ + C.into_iter(), + C.into_iter(), + C.into_iter(), + C.into_iter(), + C.into_iter(), + C.into_iter(), + C.into_iter(), + C.into_iter(), + ] + .into_iter() + .multi_cartesian_product() + { + let s = String::from_utf8(chars).unwrap(); + assert_eq!(s.len(), 8); + let esc = E(&s).to_string(); + assert!(!esc.contains("<wbr><wbr>")); + assert!(!esc.ends_with("<wbr>")); + assert!(!esc.starts_with("<wbr>")); + assert_eq!(&esc.replace("<wbr>", ""), &s); + } +} diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index d6aed75103d..bb5ac303ffd 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -32,7 +32,7 @@ use crate::clean::utils::find_nearest_parent_module; use crate::clean::{self, ExternalCrate, PrimitiveType}; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; -use crate::html::escape::Escape; +use crate::html::escape::{Escape, EscapeBodyText}; use crate::html::render::Context; use crate::passes::collect_intra_doc_links::UrlFragment; @@ -988,6 +988,7 @@ pub(crate) fn anchor<'a, 'cx: 'a>( f, r#"<a class="{short_ty}" href="{url}" title="{short_ty} {path}">{text}</a>"#, path = join_with_double_colon(&fqp), + text = EscapeBodyText(text.as_str()), ) } else { f.write_str(text.as_str()) diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index 7dfcc88398f..780cda9b1cd 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -69,6 +69,8 @@ struct PageLayout<'a> { display_krate_version_extra: &'a str, } +pub(crate) use crate::html::render::sidebar::filters; + pub(crate) fn render<T: Print, S: Print>( layout: &Layout, page: &Page<'_>, diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index aaac8678264..b5cc495ce41 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -30,7 +30,7 @@ mod tests; mod context; mod print_item; -mod sidebar; +pub(crate) mod sidebar; mod span_map; mod type_layout; mod write_shared; diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 24476e80778..eec6df9dd20 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -29,7 +29,7 @@ use crate::clean; use crate::config::ModuleSorting; use crate::formats::item_type::ItemType; use crate::formats::Impl; -use crate::html::escape::Escape; +use crate::html::escape::{Escape, EscapeBodyTextWithWbr}; use crate::html::format::{ display_fn, join_with_double_colon, print_abi_with_space, print_constness_with_space, print_where_clause, visibility_print_with_space, Buffer, Ending, PrintWithSpace, @@ -423,7 +423,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: "<div class=\"item-name\"><code>{}extern crate {} as {};", visibility_print_with_space(myitem, cx), anchor(myitem.item_id.expect_def_id(), src, cx), - myitem.name.unwrap(), + EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()), ), None => write!( w, @@ -520,7 +520,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: {stab_tags}\ </div>\ {docs_before}{docs}{docs_after}", - name = myitem.name.unwrap(), + name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()), visibility_and_hidden = visibility_and_hidden, stab_tags = extra_info_tags(myitem, item, tcx), class = myitem.type_(), @@ -558,7 +558,7 @@ fn extra_info_tags<'a, 'tcx: 'a>( display_fn(move |f| { write!( f, - r#"<span class="stab {class}" title="{title}">{contents}</span>"#, + r#"<wbr><span class="stab {class}" title="{title}">{contents}</span>"#, title = Escape(title), ) }) diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 6e826446c0e..101cc839f09 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -77,6 +77,22 @@ impl<'a> Link<'a> { } } +pub(crate) mod filters { + use std::fmt::Display; + + use rinja::filters::Safe; + + use crate::html::escape::EscapeBodyTextWithWbr; + use crate::html::render::display_fn; + pub(crate) fn wrapped<T>(v: T) -> rinja::Result<Safe<impl Display>> + where + T: Display, + { + let string = v.to_string(); + Ok(Safe(display_fn(move |f| EscapeBodyTextWithWbr(&string).fmt(f)))) + } +} + pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) { let blocks: Vec<LinkBlock<'_>> = match *it.kind { clean::StructItem(ref s) => sidebar_struct(cx, it, s), diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index e936e1ca07e..cafe5fe4c87 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -586,12 +586,15 @@ ul.block, .block li { } .sidebar h2 { + text-wrap: balance; overflow-wrap: anywhere; padding: 0; margin: 0.7rem 0; } .sidebar h3 { + text-wrap: balance; + overflow-wrap: anywhere; font-size: 1.125rem; /* 18px */ padding: 0; margin: 0; @@ -2222,7 +2225,7 @@ in src-script.js and main.js width: 33%; } .item-table > li > div { - word-break: break-all; + overflow-wrap: anywhere; } } diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html index cdf01fa7a97..65c4304e202 100644 --- a/src/librustdoc/html/templates/page.html +++ b/src/librustdoc/html/templates/page.html @@ -98,7 +98,7 @@ </a> {# #} {% endif %} <h2> {# #} - <a href="{{page.root_path|safe}}{{display_krate_with_trailing_slash|safe}}index.html">{{display_krate}}</a> {# #} + <a href="{{page.root_path|safe}}{{display_krate_with_trailing_slash|safe}}index.html">{{display_krate|wrapped|safe}}</a> {# #} {% if !display_krate_version_number.is_empty() %} <span class="version">{{+ display_krate_version_number}}</span> {% endif %} diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html index 3251b4c14c9..0990c2716b8 100644 --- a/src/librustdoc/html/templates/sidebar.html +++ b/src/librustdoc/html/templates/sidebar.html @@ -1,6 +1,6 @@ {% if !title.is_empty() %} <h2 class="location"> {# #} - <a href="#">{{title_prefix}}{{title}}</a> {# #} + <a href="#">{{title_prefix}}{{title|wrapped|safe}}</a> {# #} </h2> {% endif %} <div class="sidebar-elems"> @@ -15,7 +15,9 @@ {% for block in blocks %} {% if block.should_render() %} {% if !block.heading.name.is_empty() %} - <h3><a href="#{{block.heading.href|safe}}">{{block.heading.name}}</a></h3> + <h3><a href="#{{block.heading.href|safe}}"> {# #} + {{block.heading.name|wrapped|safe}} {# #} + </a></h3> {# #} {% endif %} {% if !block.links.is_empty() %} <ul class="block{% if !block.class.is_empty() +%} {{+block.class}}{% endif %}"> @@ -29,6 +31,6 @@ </section> {% endif %} {% if !path.is_empty() %} - <h2><a href="{% if is_mod %}../{% endif %}index.html">In {{+ path}}</a></h2> + <h2><a href="{% if is_mod %}../{% endif %}index.html">In {{+ path|wrapped|safe}}</a></h2> {% endif %} </div> diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index f6e8fdd6244..1f15605d8be 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -2731,6 +2731,7 @@ impl<'test> TestCx<'test> { #[rustfmt::skip] let tidy_args = [ + "--new-blocklevel-tags", "rustdoc-search", "--indent", "yes", "--indent-spaces", "2", "--wrap", "0", |
