@@ -111,53 +111,6 @@ fn write_header(out: &mut Buffer, class: &str, extra_content: Option<Buffer>) {
111
111
write!(out, "<code>");
112
112
}
113
113
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;
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
114
/// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
162
115
/// basically (since it's `Option<Class>`). The following rules apply:
163
116
///
@@ -171,7 +124,88 @@ fn can_merge(class1: Option<Class>, class2: Option<Class>, text: &str) -> bool {
171
124
(Some(c1), Some(c2)) => c1.is_equal_to(c2),
172
125
(Some(Class::Ident(_)), None) | (None, Some(Class::Ident(_))) => true,
173
126
(Some(_), None) | (None, Some(_)) => text.trim().is_empty(),
174
- _ => false,
127
+ (None, None) => true,
128
+ }
129
+ }
130
+
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
+ }
175
209
}
176
210
}
177
211
@@ -194,64 +228,72 @@ fn write_code(
194
228
) {
195
229
// This replace allows to fix how the code source with DOS backline characters is displayed.
196
230
let src = src.replace("\r\n", "\n");
197
- // It contains the closing tag and the associated `Class`.
198
- let mut closing_tags: Vec<(&'static str, Class)> = Vec::new();
199
- // The following two variables are used to group HTML elements with same `class` attributes
200
- // to reduce the DOM size.
201
- let mut current_class: Option<Class> = None;
202
- // We need to keep the `Class` for each element because it could contain a `Span` which is
203
- // used to generate links.
204
- 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
+ } ;
205
239
206
240
Classifier::new(
207
241
&src,
208
- 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),
209
243
decoration_info,
210
244
)
211
245
.highlight(&mut |highlight| {
212
246
match highlight {
213
247
Highlight::Token { text, class } => {
248
+ // If we received a `ExitSpan` event and then have a non-compatible `Class`, we
249
+ // need to close the `<span>`.
250
+ let need_current_class_update = if let Some(pending) = token_handler.pending_exit_span &&
251
+ !can_merge(Some(pending), class, text) {
252
+ token_handler.handle_exit_span();
253
+ true
214
254
// If the two `Class` are different, time to flush the current content and start
215
255
// a new one.
216
- if !can_merge(current_class, class, text) {
217
- write_pending_elems(
218
- out,
219
- &href_context,
220
- &mut pending_elems,
221
- &mut current_class,
222
- &closing_tags,
223
- );
224
- current_class = class.map(Class::dummy);
225
- } else if current_class.is_none() {
226
- 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);
227
265
}
228
- pending_elems.push((text, class));
266
+ token_handler. pending_elems.push((text, class));
229
267
}
230
268
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))
269
+ let mut should_add = true;
270
+ if let Some(pending_exit_span) = token_handler.pending_exit_span {
271
+ if class.is_equal_to(pending_exit_span) {
272
+ should_add = false;
273
+ } else {
274
+ token_handler.handle_exit_span();
275
+ }
276
+ } else {
277
+ // We flush everything just in case...
278
+ if token_handler.write_pending_elems(token_handler.current_class) {
279
+ token_handler.current_class = None;
280
+ }
281
+ }
282
+ if should_add {
283
+ let closing_tag = enter_span(token_handler.out, class, &token_handler.href_context);
284
+ token_handler.closing_tags.push((closing_tag, class));
285
+ }
286
+
287
+ token_handler.current_class = None;
288
+ token_handler.pending_exit_span = None;
240
289
}
241
290
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)
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);
251
294
}
252
295
};
253
296
});
254
- write_pending_elems(out, &href_context, &mut pending_elems, &mut current_class, &closing_tags);
255
297
}
256
298
257
299
fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
@@ -291,8 +333,8 @@ impl Class {
291
333
match (self, other) {
292
334
(Self::Self_(_), Self::Self_(_))
293
335
| (Self::Macro(_), Self::Macro(_))
294
- | (Self::Ident(_), Self::Ident(_))
295
- | (Self::Decoration(_ ), Self::Decoration(_ )) => true ,
336
+ | (Self::Ident(_), Self::Ident(_)) => true,
337
+ (Self::Decoration(c1 ), Self::Decoration(c2 )) => c1 == c2 ,
296
338
(x, y) => x == y,
297
339
}
298
340
}
@@ -761,7 +803,7 @@ impl<'a> Classifier<'a> {
761
803
TokenKind::CloseBracket => {
762
804
if self.in_attribute {
763
805
self.in_attribute = false;
764
- sink(Highlight::Token { text: "]", class: Some(Class::Attribute) });
806
+ sink(Highlight::Token { text: "]", class: None });
765
807
sink(Highlight::ExitSpan);
766
808
return;
767
809
}
0 commit comments