@@ -176,11 +176,17 @@ impl Element {
176
176
other. content . iter ( ) . all ( |c| can_merge ( self . class , other. class , & c. text ) )
177
177
}
178
178
179
- fn write_elem_to < W : Write > ( & self , out : & mut W , href_context : & Option < HrefContext < ' _ , ' _ > > , parent_class : Option < Class > ) {
179
+ fn write_elem_to < W : Write > (
180
+ & self ,
181
+ out : & mut W ,
182
+ href_context : & Option < HrefContext < ' _ , ' _ > > ,
183
+ parent_class : Option < Class > ,
184
+ ) {
180
185
let mut prev = parent_class;
181
186
let mut closing_tag = None ;
182
187
for part in & self . content {
183
- let text: & dyn Display = if part. needs_escape { & EscapeBodyText ( & part. text ) } else { & part. text } ;
188
+ let text: & dyn Display =
189
+ if part. needs_escape { & EscapeBodyText ( & part. text ) } else { & part. text } ;
184
190
if part. class . is_some ( ) {
185
191
// We only try to generate links as the `<span>` should have already be generated
186
192
// by the caller of `write_elem_to`.
@@ -215,11 +221,18 @@ enum ElementOrStack {
215
221
Stack ( ElementStack ) ,
216
222
}
217
223
224
+ /// This represents the stack of HTML elements. For example a macro expansion
225
+ /// will contain other elements which might themselves contain other elements
226
+ /// (like macros).
227
+ ///
228
+ /// This allows to easily handle HTML tags instead of having a more complicated
229
+ /// state machine to keep track of which tags are open.
218
230
#[ derive( Debug ) ]
219
231
struct ElementStack {
220
232
elements : Vec < ElementOrStack > ,
221
233
parent : Option < Box < ElementStack > > ,
222
234
class : Option < Class > ,
235
+ pending_exit : bool ,
223
236
}
224
237
225
238
impl ElementStack {
@@ -228,10 +241,15 @@ impl ElementStack {
228
241
}
229
242
230
243
fn new_with_class ( class : Option < Class > ) -> Self {
231
- Self { elements : Vec :: new ( ) , parent : None , class }
244
+ Self { elements : Vec :: new ( ) , parent : None , class, pending_exit : false }
232
245
}
233
246
234
247
fn push_element ( & mut self , mut elem : Element ) {
248
+ if self . pending_exit
249
+ && !can_merge ( self . class , elem. class , elem. content . first ( ) . map_or ( "" , |c| & c. text ) )
250
+ {
251
+ self . exit_current_stack ( ) ;
252
+ }
235
253
if let Some ( ElementOrStack :: Element ( last) ) = self . elements . last_mut ( )
236
254
&& last. can_merge ( & elem)
237
255
{
@@ -254,41 +272,61 @@ impl ElementStack {
254
272
}
255
273
}
256
274
257
- fn enter_stack ( & mut self , ElementStack { elements, parent, class } : ElementStack ) {
275
+ fn enter_stack (
276
+ & mut self ,
277
+ ElementStack { elements, parent, class, pending_exit } : ElementStack ,
278
+ ) {
279
+ if self . pending_exit {
280
+ if can_merge ( self . class , class, "" ) {
281
+ self . pending_exit = false ;
282
+ for elem in elements {
283
+ self . elements . push ( elem) ;
284
+ }
285
+ // Compatible stacks, nothing to be done here!
286
+ return ;
287
+ }
288
+ self . exit_current_stack ( ) ;
289
+ }
258
290
assert ! ( parent. is_none( ) , "`enter_stack` used with a non empty parent" ) ;
259
291
let parent_elements = std:: mem:: take ( & mut self . elements ) ;
260
292
let parent_parent = std:: mem:: take ( & mut self . parent ) ;
261
293
self . parent = Some ( Box :: new ( ElementStack {
262
294
elements : parent_elements,
263
295
parent : parent_parent,
264
296
class : self . class ,
297
+ pending_exit : self . pending_exit ,
265
298
} ) ) ;
266
299
self . class = class;
267
300
self . elements = elements;
301
+ self . pending_exit = pending_exit;
268
302
}
269
303
270
- fn enter_elem ( & mut self , class : Class ) {
271
- let elements = std :: mem :: take ( & mut self . elements ) ;
272
- let parent = std :: mem :: take ( & mut self . parent ) ;
273
- self . parent = Some ( Box :: new ( ElementStack { elements , parent , class : self . class } ) ) ;
274
- self . class = Some ( class ) ;
304
+ /// This sets the `pending_exit` field to `true`. Meaning that if we try to push another stack
305
+ /// which is not compatible with this one, it will exit the current one before adding the new
306
+ /// one.
307
+ fn exit_elem ( & mut self ) {
308
+ self . pending_exit = true ;
275
309
}
276
310
277
- fn exit_elem ( & mut self ) {
311
+ /// Unlike `exit_elem`, this method directly exits the current stack. It is called when the
312
+ /// current stack is not compatible with a new one pushed or if an expansion was ended.
313
+ fn exit_current_stack ( & mut self ) {
278
314
let Some ( element) = std:: mem:: take ( & mut self . parent ) else {
279
315
panic ! ( "exiting an element where there is no parent" ) ;
280
316
} ;
281
- let ElementStack { elements, parent, class } = Box :: into_inner ( element) ;
317
+ let ElementStack { elements, parent, class, pending_exit } = Box :: into_inner ( element) ;
282
318
283
319
let old_elements = std:: mem:: take ( & mut self . elements ) ;
284
320
self . elements = elements;
285
321
self . elements . push ( ElementOrStack :: Stack ( ElementStack {
286
322
elements : old_elements,
287
323
class : self . class ,
288
324
parent : None ,
325
+ pending_exit : false ,
289
326
} ) ) ;
290
327
self . parent = parent;
291
328
self . class = class;
329
+ self . pending_exit = pending_exit;
292
330
}
293
331
294
332
fn write_content < W : Write > ( & self , out : & mut W , href_context : & Option < HrefContext < ' _ , ' _ > > ) {
@@ -323,16 +361,18 @@ impl ElementStack {
323
361
// we generate the `<a>` directly here.
324
362
//
325
363
// For other elements, the links will be generated in `write_elem_to`.
326
- let href_context = if matches ! ( class, Class :: Macro ( _) ) {
327
- href_context
328
- } else {
329
- & None
330
- } ;
331
- string_without_closing_tag ( out, "" , Some ( class) , href_context, self . class != parent_class)
332
- . expect (
333
- "internal error: enter_span was called with Some(class) but did not \
364
+ let href_context = if matches ! ( class, Class :: Macro ( _) ) { href_context } else { & None } ;
365
+ string_without_closing_tag (
366
+ out,
367
+ "" ,
368
+ Some ( class) ,
369
+ href_context,
370
+ self . class != parent_class,
371
+ )
372
+ . expect (
373
+ "internal error: enter_span was called with Some(class) but did not \
334
374
return a closing HTML tag",
335
- )
375
+ )
336
376
} else {
337
377
""
338
378
} ;
@@ -444,7 +484,7 @@ impl<F: Write> TokenHandler<'_, '_, F> {
444
484
445
485
// We inline everything into the top-most element.
446
486
while self . element_stack . parent . is_some ( ) {
447
- self . element_stack . exit_elem ( ) ;
487
+ self . element_stack . exit_current_stack ( ) ;
448
488
if let Some ( ElementOrStack :: Stack ( stack) ) = self . element_stack . elements . last ( )
449
489
&& let Some ( class) = stack. class
450
490
&& class != Class :: Original
@@ -467,6 +507,11 @@ impl<F: Write> TokenHandler<'_, '_, F> {
467
507
impl < F : Write > Drop for TokenHandler < ' _ , ' _ , F > {
468
508
/// When leaving, we need to flush all pending data to not have missing content.
469
509
fn drop ( & mut self ) {
510
+ // We need to clean the hierarchy before displaying it, otherwise the parents won't see
511
+ // the last child.
512
+ while self . element_stack . parent . is_some ( ) {
513
+ self . element_stack . exit_current_stack ( ) ;
514
+ }
470
515
self . element_stack . write_content ( self . out , & self . href_context ) ;
471
516
}
472
517
}
@@ -510,7 +555,7 @@ fn end_expansion<'a, W: Write>(
510
555
expanded_codes : & ' a [ ExpandedCode ] ,
511
556
span : Span ,
512
557
) -> Option < & ' a ExpandedCode > {
513
- token_handler. element_stack . exit_elem ( ) ;
558
+ token_handler. element_stack . exit_current_stack ( ) ;
514
559
let expansion = get_next_expansion ( expanded_codes, token_handler. line , span) ;
515
560
if expansion. is_none ( ) {
516
561
token_handler. close_expansion ( ) ;
@@ -628,7 +673,9 @@ pub(super) fn write_code(
628
673
}
629
674
}
630
675
}
631
- Highlight :: EnterSpan { class } => token_handler. element_stack . enter_elem ( class) ,
676
+ Highlight :: EnterSpan { class } => {
677
+ token_handler. element_stack . enter_stack ( ElementStack :: new_with_class ( Some ( class) ) )
678
+ }
632
679
Highlight :: ExitSpan => token_handler. element_stack . exit_elem ( ) ,
633
680
} ) ;
634
681
}
0 commit comments