@@ -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