Skip to content

Commit 042e0d0

Browse files
Merge "EnterSpan" events to reduce code blocks DOM size
1 parent 934d259 commit 042e0d0

File tree

1 file changed

+68
-23
lines changed

1 file changed

+68
-23
lines changed

src/librustdoc/html/highlight.rs

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ fn write_pending_elems(
141141
if !done {
142142
// We only want to "open" the tag ourselves if we have more than one pending and if the current
143143
// parent tag is not the same as our pending content.
144-
let open_tag_ourselves = pending_elems.len() > 1;
144+
let open_tag_ourselves = pending_elems.len() > 1 && current_class.is_some();
145145
let close_tag = if open_tag_ourselves {
146146
enter_span(out, current_class.unwrap(), &href_context)
147147
} else {
@@ -158,6 +158,18 @@ fn write_pending_elems(
158158
*current_class = None;
159159
}
160160

161+
fn handle_exit_span(
162+
out: &mut Buffer,
163+
href_context: &Option<HrefContext<'_, '_, '_>>,
164+
pending_elems: &mut Vec<(&str, Option<Class>)>,
165+
closing_tags: &mut Vec<(&str, Class)>,
166+
) {
167+
let class = closing_tags.last().expect("ExitSpan without EnterSpan").1;
168+
// We flush everything just in case...
169+
write_pending_elems(out, href_context, pending_elems, &mut Some(class), closing_tags);
170+
exit_span(out, closing_tags.pop().expect("ExitSpan without EnterSpan").0);
171+
}
172+
161173
/// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
162174
/// basically (since it's `Option<Class>`). The following rules apply:
163175
///
@@ -171,7 +183,7 @@ fn can_merge(class1: Option<Class>, class2: Option<Class>, text: &str) -> bool {
171183
(Some(c1), Some(c2)) => c1.is_equal_to(c2),
172184
(Some(Class::Ident(_)), None) | (None, Some(Class::Ident(_))) => true,
173185
(Some(_), None) | (None, Some(_)) => text.trim().is_empty(),
174-
_ => false,
186+
(None, None) => true,
175187
}
176188
}
177189

@@ -196,6 +208,9 @@ fn write_code(
196208
let src = src.replace("\r\n", "\n");
197209
// It contains the closing tag and the associated `Class`.
198210
let mut closing_tags: Vec<(&'static str, Class)> = Vec::new();
211+
// This is used because we don't automatically generate the closing tag on `ExitSpan` in
212+
// case an `EnterSpan` event with the same class follows.
213+
let mut pending_exit_span: Option<Class> = None;
199214
// The following two variables are used to group HTML elements with same `class` attributes
200215
// to reduce the DOM size.
201216
let mut current_class: Option<Class> = None;
@@ -211,9 +226,21 @@ fn write_code(
211226
.highlight(&mut |highlight| {
212227
match highlight {
213228
Highlight::Token { text, class } => {
229+
// If we received a `ExitSpan` event and then have a non-compatible `Class`, we
230+
// need to close the `<span>`.
231+
if let Some(pending) = pending_exit_span &&
232+
!can_merge(Some(pending), class, text) {
233+
handle_exit_span(
234+
out,
235+
&href_context,
236+
&mut pending_elems,
237+
&mut closing_tags,
238+
);
239+
pending_exit_span = None;
240+
current_class = class.map(Class::dummy);
214241
// If the two `Class` are different, time to flush the current content and start
215242
// a new one.
216-
if !can_merge(current_class, class, text) {
243+
} else if !can_merge(current_class, class, text) {
217244
write_pending_elems(
218245
out,
219246
&href_context,
@@ -228,30 +255,48 @@ fn write_code(
228255
pending_elems.push((text, class));
229256
}
230257
Highlight::EnterSpan { class } => {
231-
// We flush everything just in case...
232-
write_pending_elems(
233-
out,
234-
&href_context,
235-
&mut pending_elems,
236-
&mut current_class,
237-
&closing_tags,
238-
);
239-
closing_tags.push((enter_span(out, class, &href_context), class))
258+
let mut should_add = true;
259+
if pending_exit_span.is_some() {
260+
if !can_merge(Some(class), pending_exit_span, "") {
261+
handle_exit_span(out, &href_context, &mut pending_elems, &mut closing_tags);
262+
} else {
263+
should_add = false;
264+
}
265+
} else {
266+
// We flush everything just in case...
267+
write_pending_elems(
268+
out,
269+
&href_context,
270+
&mut pending_elems,
271+
&mut current_class,
272+
&closing_tags,
273+
);
274+
}
275+
current_class = None;
276+
pending_exit_span = None;
277+
if should_add {
278+
let closing_tag = enter_span(out, class, &href_context);
279+
closing_tags.push((closing_tag, class));
280+
}
240281
}
241282
Highlight::ExitSpan => {
242-
// We flush everything just in case...
243-
write_pending_elems(
244-
out,
245-
&href_context,
246-
&mut pending_elems,
247-
&mut current_class,
248-
&closing_tags,
249-
);
250-
exit_span(out, closing_tags.pop().expect("ExitSpan without EnterSpan").0)
283+
current_class = None;
284+
pending_exit_span =
285+
Some(closing_tags.last().as_ref().expect("ExitSpan without EnterSpan").1);
251286
}
252287
};
253288
});
254-
write_pending_elems(out, &href_context, &mut pending_elems, &mut current_class, &closing_tags);
289+
if pending_exit_span.is_some() {
290+
handle_exit_span(out, &href_context, &mut pending_elems, &mut closing_tags);
291+
} else {
292+
write_pending_elems(
293+
out,
294+
&href_context,
295+
&mut pending_elems,
296+
&mut current_class,
297+
&closing_tags,
298+
);
299+
}
255300
}
256301

257302
fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
@@ -761,7 +806,7 @@ impl<'a> Classifier<'a> {
761806
TokenKind::CloseBracket => {
762807
if self.in_attribute {
763808
self.in_attribute = false;
764-
sink(Highlight::Token { text: "]", class: Some(Class::Attribute) });
809+
sink(Highlight::Token { text: "]", class: None });
765810
sink(Highlight::ExitSpan);
766811
return;
767812
}

0 commit comments

Comments
 (0)