Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rustdoc: Merge source code pages HTML elements together #100429

Merged
merged 3 commits into from
Aug 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 172 additions & 12 deletions src/librustdoc/html/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,70 @@ fn write_header(out: &mut Buffer, class: &str, extra_content: Option<Buffer>) {
write!(out, "<code>");
}

/// Write all the pending elements sharing a same (or at mergeable) `Class`.
///
/// If there is a "parent" (if a `EnterSpan` event was encountered) and the parent can be merged
/// with the elements' class, then we simply write the elements since the `ExitSpan` event will
/// close the tag.
///
/// Otherwise, if there is only one pending element, we let the `string` function handle both
/// opening and closing the tag, otherwise we do it into this function.
fn write_pending_elems(
out: &mut Buffer,
href_context: &Option<HrefContext<'_, '_, '_>>,
pending_elems: &mut Vec<(&str, Option<Class>)>,
current_class: &mut Option<Class>,
closing_tags: &[(&str, Class)],
) {
if pending_elems.is_empty() {
return;
}
let mut done = false;
if let Some((_, parent_class)) = closing_tags.last() {
if can_merge(*current_class, Some(*parent_class), "") {
for (text, class) in pending_elems.iter() {
string(out, Escape(text), *class, &href_context, false);
}
done = true;
}
}
if !done {
// We only want to "open" the tag ourselves if we have more than one pending and if the current
// parent tag is not the same as our pending content.
let open_tag_ourselves = pending_elems.len() > 1;
let close_tag = if open_tag_ourselves {
enter_span(out, current_class.unwrap(), &href_context)
} else {
""
};
for (text, class) in pending_elems.iter() {
string(out, Escape(text), *class, &href_context, !open_tag_ourselves);
}
if open_tag_ourselves {
exit_span(out, close_tag);
}
}
pending_elems.clear();
*current_class = None;
}

/// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
/// basically (since it's `Option<Class>`). The following rules apply:
///
/// * If two `Class` have the same variant, then they can be merged.
/// * If the other `Class` is unclassified and only contains white characters (backline,
/// whitespace, etc), it can be merged.
/// * `Class::Ident` is considered the same as unclassified (because it doesn't have an associated
/// CSS class).
fn can_merge(class1: Option<Class>, class2: Option<Class>, text: &str) -> bool {
match (class1, class2) {
(Some(c1), Some(c2)) => c1.is_equal_to(c2),
(Some(Class::Ident(_)), None) | (None, Some(Class::Ident(_))) => true,
(Some(_), None) | (None, Some(_)) => text.trim().is_empty(),
_ => false,
}
}

/// 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.
Expand All @@ -130,23 +194,64 @@ fn write_code(
) {
// This replace allows to fix how the code source with DOS backline characters is displayed.
let src = src.replace("\r\n", "\n");
let mut closing_tags: Vec<&'static str> = Vec::new();
// It contains the closing tag and the associated `Class`.
let mut closing_tags: Vec<(&'static str, Class)> = Vec::new();
// The following two variables are used to group HTML elements with same `class` attributes
// to reduce the DOM size.
let mut current_class: Option<Class> = None;
// We need to keep the `Class` for each element because it could contain a `Span` which is
// used to generate links.
let mut pending_elems: Vec<(&str, Option<Class>)> = Vec::new();

Classifier::new(
&src,
href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
decoration_info,
)
.highlight(&mut |highlight| {
match highlight {
Highlight::Token { text, class } => string(out, Escape(text), class, &href_context),
Highlight::Token { text, class } => {
// If the two `Class` are different, time to flush the current content and start
// a new one.
if !can_merge(current_class, class, text) {
write_pending_elems(
out,
&href_context,
&mut pending_elems,
&mut current_class,
&closing_tags,
);
current_class = class.map(Class::dummy);
} else if current_class.is_none() {
current_class = class.map(Class::dummy);
}
pending_elems.push((text, class));
}
Highlight::EnterSpan { class } => {
closing_tags.push(enter_span(out, class, &href_context))
// We flush everything just in case...
write_pending_elems(
out,
&href_context,
&mut pending_elems,
&mut current_class,
&closing_tags,
);
closing_tags.push((enter_span(out, class, &href_context), class))
}
Highlight::ExitSpan => {
exit_span(out, closing_tags.pop().expect("ExitSpan without EnterSpan"))
// We flush everything just in case...
write_pending_elems(
out,
&href_context,
&mut pending_elems,
&mut current_class,
&closing_tags,
);
exit_span(out, closing_tags.pop().expect("ExitSpan without EnterSpan").0)
}
};
});
write_pending_elems(out, &href_context, &mut pending_elems, &mut current_class, &closing_tags);
}

fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
Expand All @@ -160,14 +265,15 @@ enum Class {
DocComment,
Attribute,
KeyWord,
// Keywords that do pointer/reference stuff.
/// Keywords that do pointer/reference stuff.
RefKeyWord,
Self_(Span),
Macro(Span),
MacroNonTerminal,
String,
Number,
Bool,
/// `Ident` isn't rendered in the HTML but we still need it for the `Span` it contains.
Ident(Span),
Lifetime,
PreludeTy,
Expand All @@ -177,6 +283,31 @@ enum Class {
}

impl Class {
/// It is only looking at the variant, not the variant content.
///
/// It is used mostly to group multiple similar HTML elements into one `<span>` instead of
/// multiple ones.
fn is_equal_to(self, other: Self) -> bool {
match (self, other) {
(Self::Self_(_), Self::Self_(_))
| (Self::Macro(_), Self::Macro(_))
| (Self::Ident(_), Self::Ident(_))
| (Self::Decoration(_), Self::Decoration(_)) => true,
(x, y) => x == y,
}
}

/// If `self` contains a `Span`, it'll be replaced with `DUMMY_SP` to prevent creating links
/// on "empty content" (because of the attributes merge).
fn dummy(self) -> Self {
match self {
Self::Self_(_) => Self::Self_(DUMMY_SP),
Self::Macro(_) => Self::Macro(DUMMY_SP),
Self::Ident(_) => Self::Ident(DUMMY_SP),
s => s,
}
}

/// Returns the css class expected by rustdoc for each `Class`.
fn as_html(self) -> &'static str {
match self {
Expand All @@ -191,7 +322,7 @@ impl Class {
Class::String => "string",
Class::Number => "number",
Class::Bool => "bool-val",
Class::Ident(_) => "ident",
Class::Ident(_) => "",
Class::Lifetime => "lifetime",
Class::PreludeTy => "prelude-ty",
Class::PreludeVal => "prelude-val",
Expand Down Expand Up @@ -630,7 +761,7 @@ impl<'a> Classifier<'a> {
TokenKind::CloseBracket => {
if self.in_attribute {
self.in_attribute = false;
sink(Highlight::Token { text: "]", class: None });
sink(Highlight::Token { text: "]", class: Some(Class::Attribute) });
sink(Highlight::ExitSpan);
return;
}
Expand Down Expand Up @@ -701,7 +832,7 @@ fn enter_span(
klass: Class,
href_context: &Option<HrefContext<'_, '_, '_>>,
) -> &'static str {
string_without_closing_tag(out, "", Some(klass), href_context).expect(
string_without_closing_tag(out, "", Some(klass), href_context, true).expect(
"internal error: enter_span was called with Some(klass) but did not return a \
closing HTML tag",
)
Expand Down Expand Up @@ -733,8 +864,10 @@ fn string<T: Display>(
text: T,
klass: Option<Class>,
href_context: &Option<HrefContext<'_, '_, '_>>,
open_tag: bool,
) {
if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context) {
if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context, open_tag)
{
out.write_str(closing_tag);
}
}
Expand All @@ -753,6 +886,7 @@ fn string_without_closing_tag<T: Display>(
text: T,
klass: Option<Class>,
href_context: &Option<HrefContext<'_, '_, '_>>,
open_tag: bool,
) -> Option<&'static str> {
let Some(klass) = klass
else {
Expand All @@ -761,6 +895,10 @@ fn string_without_closing_tag<T: Display>(
};
let Some(def_span) = klass.get_span()
else {
if !open_tag {
write!(out, "{}", text);
return None;
}
write!(out, "<span class=\"{}\">{}", klass.as_html(), text);
return Some("</span>");
};
Expand All @@ -784,6 +922,7 @@ fn string_without_closing_tag<T: Display>(
path
});
}

if let Some(href_context) = href_context {
if let Some(href) =
href_context.context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
Expand Down Expand Up @@ -812,12 +951,33 @@ fn string_without_closing_tag<T: Display>(
}
})
{
write!(out, "<a class=\"{}\" href=\"{}\">{}", klass.as_html(), href, text_s);
if !open_tag {
// We're already inside an element which has the same klass, no need to give it
// again.
write!(out, "<a href=\"{}\">{}", href, text_s);
} else {
let klass_s = klass.as_html();
if klass_s.is_empty() {
write!(out, "<a href=\"{}\">{}", href, text_s);
} else {
write!(out, "<a class=\"{}\" href=\"{}\">{}", klass_s, href, text_s);
}
}
return Some("</a>");
}
}
write!(out, "<span class=\"{}\">{}", klass.as_html(), text_s);
Some("</span>")
if !open_tag {
write!(out, "{}", text_s);
return None;
}
let klass_s = klass.as_html();
if klass_s.is_empty() {
write!(out, "{}", text_s);
Some("")
} else {
write!(out, "<span class=\"{}\">{}", klass_s, text_s);
Some("</span>")
}
}

#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions src/librustdoc/html/highlight/fixtures/decorations.html
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<span class="example"><span class="kw">let</span> <span class="ident">x</span> = <span class="number">1</span>;</span>
<span class="kw">let</span> <span class="ident">y</span> = <span class="number">2</span>;
<span class="example"><span class="kw">let </span>x = <span class="number">1</span>;</span>
<span class="kw">let </span>y = <span class="number">2</span>;
2 changes: 1 addition & 1 deletion src/librustdoc/html/highlight/fixtures/dos_line.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">foo</span>() {
<span class="kw">pub fn </span>foo() {
<span class="macro">println!</span>(<span class="string">&quot;foo&quot;</span>);
}
8 changes: 4 additions & 4 deletions src/librustdoc/html/highlight/fixtures/highlight.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<span class="kw">use</span> <span class="ident"><span class="kw">crate</span>::a::foo</span>;
<span class="kw">use</span> <span class="ident"><span class="self">self</span>::whatever</span>;
<span class="kw">let</span> <span class="ident">x</span> = <span class="ident"><span class="kw">super</span>::b::foo</span>;
<span class="kw">let</span> <span class="ident">y</span> = <span class="ident"><span class="self">Self</span>::whatever</span>;
<span class="kw">use </span><span class="kw">crate</span>::a::foo;
<span class="kw">use </span><span class="self">self</span>::whatever;
<span class="kw">let </span>x = <span class="kw">super</span>::b::foo;
<span class="kw">let </span>y = <span class="self">Self</span>::whatever;
38 changes: 19 additions & 19 deletions src/librustdoc/html/highlight/fixtures/sample.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@
.lifetime { color: #B76514; }
.question-mark { color: #ff9011; }
</style>
<pre><code><span class="attribute">#![<span class="ident">crate_type</span> = <span class="string">&quot;lib&quot;</span>]</span>
<pre><code><span class="attribute">#![crate_type = <span class="string">&quot;lib&quot;</span>]</span>

<span class="kw">use</span> <span class="ident">std::path</span>::{<span class="ident">Path</span>, <span class="ident">PathBuf</span>};
<span class="kw">use </span>std::path::{Path, PathBuf};

<span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">target_os</span> = <span class="string">&quot;linux&quot;</span>)]</span>
<span class="kw">fn</span> <span class="ident">main</span>() -&gt; () {
<span class="kw">let</span> <span class="ident">foo</span> = <span class="bool-val">true</span> &amp;&amp; <span class="bool-val">false</span> || <span class="bool-val">true</span>;
<span class="kw">let</span> <span class="kw">_</span>: <span class="kw-2">*const</span> () = <span class="number">0</span>;
<span class="kw">let</span> <span class="kw">_</span> = <span class="kw-2">&amp;</span><span class="ident">foo</span>;
<span class="kw">let</span> <span class="kw">_</span> = &amp;&amp;<span class="ident">foo</span>;
<span class="kw">let</span> <span class="kw">_</span> = <span class="kw-2">*</span><span class="ident">foo</span>;
<span class="macro">mac!</span>(<span class="ident">foo</span>, <span class="kw-2">&amp;mut</span> <span class="ident">bar</span>);
<span class="macro">assert!</span>(<span class="self">self</span>.<span class="ident">length</span> &lt; <span class="ident">N</span> &amp;&amp; <span class="ident">index</span> &lt;= <span class="self">self</span>.<span class="ident">length</span>);
<span class="ident">::std::env::var</span>(<span class="string">&quot;gateau&quot;</span>).<span class="ident">is_ok</span>();
<span class="attribute">#[<span class="ident">rustfmt::skip</span>]</span>
<span class="kw">let</span> <span class="ident">s</span>:<span class="ident">std::path::PathBuf</span> = <span class="ident">std::path::PathBuf::new</span>();
<span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">s</span> = <span class="ident">String::new</span>();
<span class="attribute">#[cfg(target_os = <span class="string">&quot;linux&quot;</span>)]</span>
<span class="kw">fn </span>main() -&gt; () {
<span class="kw">let </span>foo = <span class="bool-val">true </span>&amp;&amp; <span class="bool-val">false </span>|| <span class="bool-val">true</span>;
<span class="kw">let _</span>: <span class="kw-2">*const </span>() = <span class="number">0</span>;
<span class="kw">let _ </span>= <span class="kw-2">&amp;</span>foo;
<span class="kw">let _ </span>= &amp;&amp;foo;
<span class="kw">let _ </span>= <span class="kw-2">*</span>foo;
<span class="macro">mac!</span>(foo, <span class="kw-2">&amp;mut </span>bar);
<span class="macro">assert!</span>(<span class="self">self</span>.length &lt; N &amp;&amp; index &lt;= <span class="self">self</span>.length);
::std::env::var(<span class="string">&quot;gateau&quot;</span>).is_ok();
<span class="attribute">#[rustfmt::skip]</span>
<span class="kw">let </span>s:std::path::PathBuf = std::path::PathBuf::new();
<span class="kw">let </span><span class="kw-2">mut </span>s = String::new();

<span class="kw">match</span> <span class="kw-2">&amp;</span><span class="ident">s</span> {
<span class="kw-2">ref</span> <span class="kw-2">mut</span> <span class="ident">x</span> =&gt; {}
<span class="kw">match </span><span class="kw-2">&amp;</span>s {
<span class="kw-2">ref mut </span>x =&gt; {}
}
}

<span class="macro">macro_rules!</span> <span class="ident">bar</span> {
(<span class="macro-nonterminal">$</span><span class="macro-nonterminal">foo</span>:<span class="ident">tt</span>) =&gt; {};
<span class="macro">macro_rules!</span> bar {
(<span class="macro-nonterminal">$foo</span>:tt) =&gt; {};
}
</code></pre>
10 changes: 5 additions & 5 deletions src/librustdoc/html/highlight/fixtures/union.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<span class="kw">union</span> <span class="ident">Foo</span> {
<span class="ident">i</span>: <span class="ident">i8</span>,
<span class="ident">u</span>: <span class="ident">i8</span>,
<span class="kw">union </span>Foo {
i: i8,
u: i8,
}
GuillaumeGomez marked this conversation as resolved.
Show resolved Hide resolved

<span class="kw">fn</span> <span class="ident">main</span>() {
<span class="kw">let</span> <span class="ident">union</span> = <span class="number">0</span>;
<span class="kw">fn </span>main() {
<span class="kw">let </span>union = <span class="number">0</span>;
}
6 changes: 1 addition & 5 deletions src/librustdoc/html/static/css/themes/ayu.css
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,6 @@ pre.rust .self {
pre.rust .attribute {
color: #e6e1cf;
}
pre.rust .attribute .ident {
color: #e6e1cf;
}

.example-wrap > pre.line-number {
color: #5c67736e;
Expand Down Expand Up @@ -398,8 +395,7 @@ pre.rust .comment {}
.block a.current.method,.content span.tymethod,.content a.tymethod,.block a.current.tymethod,
.content .fnname {}
pre.rust .kw {}
pre.rust .self,pre.rust .bool-val,pre.rust .prelude-val,pre.rust .attribute,
pre.rust .attribute .ident {}
pre.rust .self,pre.rust .bool-val,pre.rust .prelude-val,pre.rust .attribute {}
.content span.foreigntype,.content a.foreigntype,.block a.current.foreigntype {}
pre.rust .doccomment {}
.stab.deprecated {}
Expand Down
Loading