Skip to content

Commit 4c89c28

Browse files
Clean up highlight <span> merge code
1 parent f5b5d86 commit 4c89c28

File tree

1 file changed

+118
-121
lines changed

1 file changed

+118
-121
lines changed

src/librustdoc/html/highlight.rs

+118-121
Original file line numberDiff line numberDiff line change
@@ -111,65 +111,6 @@ fn write_header(out: &mut Buffer, class: &str, extra_content: Option<Buffer>) {
111111
write!(out, "<code>");
112112
}
113113

114-
/// Write all the pending elements sharing a same (or at mergeable) `Class`.
115-
///
116-
/// If there is a "parent" (if a `EnterSpan` event was encountered) and the parent can be merged
117-
/// with the elements' class, then we simply write the elements since the `ExitSpan` event will
118-
/// close the tag.
119-
///
120-
/// Otherwise, if there is only one pending element, we let the `string` function handle both
121-
/// opening and closing the tag, otherwise we do it into this function.
122-
fn write_pending_elems(
123-
out: &mut Buffer,
124-
href_context: &Option<HrefContext<'_, '_, '_>>,
125-
pending_elems: &mut Vec<(&str, Option<Class>)>,
126-
current_class: &mut Option<Class>,
127-
closing_tags: &[(&str, Class)],
128-
) {
129-
if pending_elems.is_empty() {
130-
return;
131-
}
132-
let mut done = false;
133-
if let Some((_, parent_class)) = closing_tags.last() {
134-
if can_merge(*current_class, Some(*parent_class), "") {
135-
for (text, class) in pending_elems.iter() {
136-
string(out, Escape(text), *class, &href_context, false);
137-
}
138-
done = true;
139-
}
140-
}
141-
if !done {
142-
// We only want to "open" the tag ourselves if we have more than one pending and if the current
143-
// parent tag is not the same as our pending content.
144-
let open_tag_ourselves = pending_elems.len() > 1 && current_class.is_some();
145-
let close_tag = if open_tag_ourselves {
146-
enter_span(out, current_class.unwrap(), &href_context)
147-
} else {
148-
""
149-
};
150-
for (text, class) in pending_elems.iter() {
151-
string(out, Escape(text), *class, &href_context, !open_tag_ourselves);
152-
}
153-
if open_tag_ourselves {
154-
exit_span(out, close_tag);
155-
}
156-
}
157-
pending_elems.clear();
158-
*current_class = None;
159-
}
160-
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-
173114
/// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
174115
/// basically (since it's `Option<Class>`). The following rules apply:
175116
///
@@ -187,6 +128,87 @@ fn can_merge(class1: Option<Class>, class2: Option<Class>, text: &str) -> bool {
187128
}
188129
}
189130

131+
/// This type is used as a conveniency to prevent having to pass all its fields as arguments into
132+
/// the various functions (which became its methods).
133+
struct TokenHandler<'a, 'b, 'c, 'd, 'e> {
134+
out: &'a mut Buffer,
135+
/// It contains the closing tag and the associated `Class`.
136+
closing_tags: Vec<(&'static str, Class)>,
137+
/// This is used because we don't automatically generate the closing tag on `ExitSpan` in
138+
/// case an `EnterSpan` event with the same class follows.
139+
pending_exit_span: Option<Class>,
140+
/// `current_class` and `pending_elems` are used to group HTML elements with same `class`
141+
/// attributes to reduce the DOM size.
142+
current_class: Option<Class>,
143+
/// We need to keep the `Class` for each element because it could contain a `Span` which is
144+
/// used to generate links.
145+
pending_elems: Vec<(&'b str, Option<Class>)>,
146+
href_context: Option<HrefContext<'c, 'd, 'e>>,
147+
}
148+
149+
impl<'a, 'b, 'c, 'd, 'e> TokenHandler<'a, 'b, 'c, 'd, 'e> {
150+
fn handle_exit_span(&mut self) {
151+
// We can't get the last `closing_tags` element using `pop()` because `closing_tags` is
152+
// being used in `write_pending_elems`.
153+
let class = self.closing_tags.last().expect("ExitSpan without EnterSpan").1;
154+
// We flush everything just in case...
155+
self.write_pending_elems(Some(class));
156+
157+
exit_span(self.out, self.closing_tags.pop().expect("ExitSpan without EnterSpan").0);
158+
self.pending_exit_span = None;
159+
}
160+
161+
/// Write all the pending elements sharing a same (or at mergeable) `Class`.
162+
///
163+
/// If there is a "parent" (if a `EnterSpan` event was encountered) and the parent can be merged
164+
/// with the elements' class, then we simply write the elements since the `ExitSpan` event will
165+
/// close the tag.
166+
///
167+
/// Otherwise, if there is only one pending element, we let the `string` function handle both
168+
/// opening and closing the tag, otherwise we do it into this function.
169+
///
170+
/// It returns `true` if `current_class` must be set to `None` afterwards.
171+
fn write_pending_elems(&mut self, current_class: Option<Class>) -> bool {
172+
if self.pending_elems.is_empty() {
173+
return false;
174+
}
175+
if let Some((_, parent_class)) = self.closing_tags.last() &&
176+
can_merge(current_class, Some(*parent_class), "")
177+
{
178+
for (text, class) in self.pending_elems.iter() {
179+
string(self.out, Escape(text), *class, &self.href_context, false);
180+
}
181+
} else {
182+
// We only want to "open" the tag ourselves if we have more than one pending and if the
183+
// current parent tag is not the same as our pending content.
184+
let close_tag = if self.pending_elems.len() > 1 && current_class.is_some() {
185+
Some(enter_span(self.out, current_class.unwrap(), &self.href_context))
186+
} else {
187+
None
188+
};
189+
for (text, class) in self.pending_elems.iter() {
190+
string(self.out, Escape(text), *class, &self.href_context, close_tag.is_none());
191+
}
192+
if let Some(close_tag) = close_tag {
193+
exit_span(self.out, close_tag);
194+
}
195+
}
196+
self.pending_elems.clear();
197+
true
198+
}
199+
}
200+
201+
impl<'a, 'b, 'c, 'd, 'e> Drop for TokenHandler<'a, 'b, 'c, 'd, 'e> {
202+
/// When leaving, we need to flush all pending data to not have missing content.
203+
fn drop(&mut self) {
204+
if self.pending_exit_span.is_some() {
205+
self.handle_exit_span();
206+
} else {
207+
self.write_pending_elems(self.current_class);
208+
}
209+
}
210+
}
211+
190212
/// Convert the given `src` source code into HTML by adding classes for highlighting.
191213
///
192214
/// This code is used to render code blocks (in the documentation) as well as the source code pages.
@@ -206,97 +228,72 @@ fn write_code(
206228
) {
207229
// This replace allows to fix how the code source with DOS backline characters is displayed.
208230
let src = src.replace("\r\n", "\n");
209-
// It contains the closing tag and the associated `Class`.
210-
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;
214-
// The following two variables are used to group HTML elements with same `class` attributes
215-
// to reduce the DOM size.
216-
let mut current_class: Option<Class> = None;
217-
// We need to keep the `Class` for each element because it could contain a `Span` which is
218-
// used to generate links.
219-
let mut pending_elems: Vec<(&str, Option<Class>)> = Vec::new();
231+
let mut token_handler = TokenHandler {
232+
out,
233+
closing_tags: Vec::new(),
234+
pending_exit_span: None,
235+
current_class: None,
236+
pending_elems: Vec::new(),
237+
href_context,
238+
};
220239

221240
Classifier::new(
222241
&src,
223-
href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
242+
token_handler.href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
224243
decoration_info,
225244
)
226245
.highlight(&mut |highlight| {
227246
match highlight {
228247
Highlight::Token { text, class } => {
229248
// If we received a `ExitSpan` event and then have a non-compatible `Class`, we
230249
// need to close the `<span>`.
231-
if let Some(pending) = pending_exit_span &&
250+
let need_current_class_update = if let Some(pending) = token_handler.pending_exit_span &&
232251
!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);
252+
token_handler.handle_exit_span();
253+
true
241254
// If the two `Class` are different, time to flush the current content and start
242255
// a new one.
243-
} else if !can_merge(current_class, class, text) {
244-
write_pending_elems(
245-
out,
246-
&href_context,
247-
&mut pending_elems,
248-
&mut current_class,
249-
&closing_tags,
250-
);
251-
current_class = class.map(Class::dummy);
252-
} else if current_class.is_none() {
253-
current_class = class.map(Class::dummy);
256+
} else if !can_merge(token_handler.current_class, class, text) {
257+
token_handler.write_pending_elems(token_handler.current_class);
258+
true
259+
} else {
260+
token_handler.current_class.is_none()
261+
};
262+
263+
if need_current_class_update {
264+
token_handler.current_class = class.map(Class::dummy);
254265
}
255-
pending_elems.push((text, class));
266+
token_handler.pending_elems.push((text, class));
256267
}
257268
Highlight::EnterSpan { class } => {
258269
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 {
270+
if let Some(pending_exit_span) = token_handler.pending_exit_span {
271+
if class.is_equal_to(pending_exit_span) {
263272
should_add = false;
273+
} else {
274+
token_handler.handle_exit_span();
264275
}
265276
} else {
266277
// 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-
);
278+
if token_handler.write_pending_elems(token_handler.current_class) {
279+
token_handler.current_class = None;
280+
}
274281
}
275-
current_class = None;
276-
pending_exit_span = None;
277282
if should_add {
278-
let closing_tag = enter_span(out, class, &href_context);
279-
closing_tags.push((closing_tag, class));
283+
let closing_tag = enter_span(token_handler.out, class, &token_handler.href_context);
284+
token_handler.closing_tags.push((closing_tag, class));
280285
}
286+
287+
token_handler.current_class = None;
288+
token_handler.pending_exit_span = None;
281289
}
282290
Highlight::ExitSpan => {
283-
current_class = None;
284-
pending_exit_span =
285-
Some(closing_tags.last().as_ref().expect("ExitSpan without EnterSpan").1);
291+
token_handler.current_class = None;
292+
token_handler.pending_exit_span =
293+
Some(token_handler.closing_tags.last().as_ref().expect("ExitSpan without EnterSpan").1);
286294
}
287295
};
288296
});
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-
}
300297
}
301298

302299
fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {

0 commit comments

Comments
 (0)